View Javadoc
1   /**
2    * Copyright 2005-2014 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.eclipselink;
17  
18  import org.apache.commons.lang.StringUtils;
19  import org.eclipse.persistence.descriptors.ClassDescriptor;
20  import org.eclipse.persistence.expressions.Expression;
21  import org.eclipse.persistence.internal.expressions.FunctionExpression;
22  import org.eclipse.persistence.internal.helper.DatabaseField;
23  import org.eclipse.persistence.internal.jpa.metamodel.EmbeddableTypeImpl;
24  import org.eclipse.persistence.internal.jpa.metamodel.EntityTypeImpl;
25  import org.eclipse.persistence.internal.jpa.metamodel.ManagedTypeImpl;
26  import org.eclipse.persistence.internal.jpa.metamodel.PluralAttributeImpl;
27  import org.eclipse.persistence.internal.jpa.metamodel.SingularAttributeImpl;
28  import org.eclipse.persistence.jpa.JpaEntityManager;
29  import org.eclipse.persistence.mappings.AggregateObjectMapping;
30  import org.eclipse.persistence.mappings.CollectionMapping;
31  import org.eclipse.persistence.mappings.DatabaseMapping;
32  import org.eclipse.persistence.mappings.DirectToFieldMapping;
33  import org.eclipse.persistence.mappings.ForeignReferenceMapping;
34  import org.eclipse.persistence.mappings.ManyToOneMapping;
35  import org.eclipse.persistence.mappings.OneToManyMapping;
36  import org.eclipse.persistence.mappings.OneToOneMapping;
37  import org.eclipse.persistence.mappings.converters.Converter;
38  import org.eclipse.persistence.mappings.converters.ConverterClass;
39  import org.eclipse.persistence.queries.ObjectLevelReadQuery;
40  import org.kuali.rice.krad.data.jpa.JpaMetadataProviderImpl;
41  import org.kuali.rice.krad.data.metadata.DataObjectAttributeRelationship;
42  import org.kuali.rice.krad.data.metadata.DataObjectCollectionSortAttribute;
43  import org.kuali.rice.krad.data.metadata.DataObjectMetadata;
44  import org.kuali.rice.krad.data.metadata.DataObjectRelationship;
45  import org.kuali.rice.krad.data.metadata.MetadataConfigurationException;
46  import org.kuali.rice.krad.data.metadata.SortDirection;
47  import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeImpl;
48  import org.kuali.rice.krad.data.metadata.impl.DataObjectAttributeRelationshipImpl;
49  import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionImpl;
50  import org.kuali.rice.krad.data.metadata.impl.DataObjectCollectionSortAttributeImpl;
51  import org.kuali.rice.krad.data.metadata.impl.DataObjectMetadataImpl;
52  import org.kuali.rice.krad.data.metadata.impl.DataObjectRelationshipImpl;
53  import org.kuali.rice.krad.data.metadata.impl.MetadataChildBase;
54  
55  import javax.persistence.metamodel.Attribute.PersistentAttributeType;
56  import javax.persistence.metamodel.EntityType;
57  import javax.persistence.metamodel.ManagedType;
58  import javax.persistence.metamodel.PluralAttribute;
59  import javax.persistence.metamodel.SingularAttribute;
60  import java.lang.reflect.Field;
61  import java.util.ArrayList;
62  import java.util.Collection;
63  import java.util.List;
64  import java.util.Map;
65  import java.util.Set;
66  
67  /**
68   * Provides an EclipseLink-specific implementation for the {@link JpaMetadataProviderImpl}.
69   */
70  public class EclipseLinkJpaMetadataProviderImpl extends JpaMetadataProviderImpl {
71  	private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger
72  			.getLogger(EclipseLinkJpaMetadataProviderImpl.class);
73  
74      /**
75       * {@inheritDoc}
76       */
77  	@Override
78  	protected void populateImplementationSpecificEntityLevelMetadata(DataObjectMetadataImpl metadata,
79  			EntityType<?> entityType) {
80  		if ( entityType instanceof EntityTypeImpl ) {
81  			metadata.setBackingObjectName(((EntityTypeImpl<?>) entityType).getDescriptor().getTableName());
82  		}
83  	}
84  
85      /**
86       * {@inheritDoc}
87       */
88  	@Override
89  	protected void populateImplementationSpecificAttributeLevelMetadata(DataObjectAttributeImpl attribute,
90  			SingularAttribute<?, ?> attr) {
91  
92  		if (attr instanceof SingularAttributeImpl) {
93  			DatabaseMapping mapping = ((SingularAttributeImpl<?, ?>) attr).getMapping();
94  			if (mapping != null && mapping.getField() != null) {
95                  attribute.setReadOnly(mapping.isReadOnly());
96  				attribute.setBackingObjectName(mapping.getField().getName());
97  				if (mapping.getField().getLength() != 0) {
98  					attribute.setMaxLength((long) mapping.getField().getLength());
99  				}
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 			for (DatabaseField parentField : relationshipMapping.getForeignKeyFields()) {
269 				String parentFieldName = getPropertyNameFromDatabaseColumnName(rd.getDeclaringType(),
270 						parentField.getName());
271                 if (parentFieldName != null) {
272 				    DatabaseField childField = relationshipMapping.getSourceToTargetKeyFields().get(parentField);
273 				    if (childField != null) {
274 					    // the target field is always done by column name. So, we need to get into the target entity and
275 					    // find the associated field :-(
276 					    // If the lookup fails, we will at least have the column name
277 					    String childFieldName = getPropertyNameFromDatabaseColumnName(referencedEntityType,
278                                 childField.getName());
279                         if (childFieldName != null) {
280 					        attributeRelationships
281                                     .add(new DataObjectAttributeRelationshipImpl(parentFieldName, childFieldName));
282                         }
283 				    } else {
284 					    LOG.warn("Unable to find child field reference.  There may be a JPA mapping problem on "
285 						    	+ rd.getDeclaringType().getJavaType() + ": " + relationship);
286 				    }
287                 }
288 			}
289 			relationship.setAttributeRelationships(attributeRelationships);
290 
291             populateInverseRelationship(relationshipMapping, relationship);
292 
293 		} else {
294 			// get what we can based on JPA values (note that we just set some to have values here)
295 			relationship.setReadOnly(persistentAttributeType == PersistentAttributeType.MANY_TO_ONE);
296 			relationship.setSavedWithParent(persistentAttributeType == PersistentAttributeType.ONE_TO_ONE);
297 			relationship.setDeletedWithParent(persistentAttributeType == PersistentAttributeType.ONE_TO_ONE);
298 			relationship.setLoadedAtParentLoadTime(true);
299 			relationship.setLoadedDynamicallyUponUse(false);
300 		}
301 	}
302 
303     /**
304      * Populates the inverse relationship for a given relationship.
305      *
306      * @param mapping the {@link DatabaseMapping} that defines the relationship.
307      * @param relationship the relationship of which to populate the other side.
308      */
309     protected void populateInverseRelationship(DatabaseMapping mapping, MetadataChildBase relationship) {
310         DatabaseMapping relationshipPartner = findRelationshipPartner(mapping);
311         if (relationshipPartner != null) {
312             Class<?> partnerType = relationshipPartner.getDescriptor().getJavaClass();
313             DataObjectMetadata partnerMetadata = masterMetadataMap.get(partnerType);
314             // if the target metadata is not null, it means that entity has already been processed,
315             // so we can go ahead and establish the inverse relationship
316             if (partnerMetadata != null) {
317                 // first check if it's a relationship
318                 MetadataChildBase relationshipPartnerMetadata =
319                         (MetadataChildBase)partnerMetadata.getRelationship(relationshipPartner.getAttributeName());
320                 if (relationshipPartnerMetadata == null) {
321                     relationshipPartnerMetadata =
322                             (MetadataChildBase)partnerMetadata.getCollection(relationshipPartner.getAttributeName());
323                 }
324                 if (relationshipPartnerMetadata != null) {
325                     relationshipPartnerMetadata.setInverseRelationship(relationship);
326                     relationship.setInverseRelationship(relationshipPartnerMetadata);
327                 }
328 
329             }
330         }
331     }
332 
333     /**
334      * Gets the inverse mapping of the given {@link DatabaseMapping}.
335      *
336      * @param databaseMapping the {@link DatabaseMapping} of which to get the inverse.
337      * @return the inverse mapping of the given {@link DatabaseMapping}.
338      */
339     protected DatabaseMapping findRelationshipPartner(DatabaseMapping databaseMapping) {
340         if (databaseMapping instanceof OneToManyMapping) {
341             OneToManyMapping mapping = (OneToManyMapping)databaseMapping;
342             if (mapping.getMappedBy() != null) {
343                 Class<?> referenceClass = mapping.getReferenceClass();
344                 ClassDescriptor referenceClassDescriptor = getClassDescriptor(referenceClass);
345                 return referenceClassDescriptor.getMappingForAttributeName(mapping.getMappedBy());
346             }
347         } else if (databaseMapping instanceof ManyToOneMapping) {
348             // one odd thing just to note here, for ManyToOne mappings with an inverse OneToMany, for some reason the
349             // getMappedBy method still returns the mappedBy from the OneToMany side, so we can't use nullness of
350             // mappedBy to infer which side of the relationship we are on, oddly enough, that's not the way it works
351             // for OneToOne mappings (see below)...go figure
352             //
353             // I have to assume this is some sort of bug in EclipseLink metadata
354             ManyToOneMapping mapping = (ManyToOneMapping)databaseMapping;
355             Class<?> referenceClass = mapping.getReferenceClass();
356             ClassDescriptor referenceClassDescriptor = getClassDescriptor(referenceClass);
357             // find the OneToMany mapping which points back to this ManyToOne
358             for (DatabaseMapping referenceMapping : referenceClassDescriptor.getMappings()) {
359                 if (referenceMapping instanceof OneToManyMapping) {
360                     OneToManyMapping oneToManyMapping = (OneToManyMapping)referenceMapping;
361                     if (mapping.getAttributeName().equals(oneToManyMapping.getMappedBy())) {
362                         return oneToManyMapping;
363                     }
364                 }
365             }
366         } else if (databaseMapping instanceof OneToOneMapping) {
367             OneToOneMapping mapping = (OneToOneMapping)databaseMapping;
368             // well for reasons I can't quite fathom, mappedBy is always null on OneToOne relationships,
369             // thankfully it's OneToOne so it's pretty easy to figure out the inverse
370             ClassDescriptor referenceClassDescriptor = getClassDescriptor(mapping.getReferenceClass());
371             // let's check if theres a OneToOne pointing back to us
372             for (DatabaseMapping referenceMapping : referenceClassDescriptor.getMappings()) {
373                 if (referenceMapping instanceof OneToOneMapping) {
374                     OneToOneMapping oneToOneMapping = (OneToOneMapping)referenceMapping;
375                     if (oneToOneMapping.getReferenceClass().equals(mapping.getDescriptor().getJavaClass())) {
376                         return oneToOneMapping;
377                     }
378                 }
379             }
380         }
381         // TODO need to implement for bi-directional OneToOne and ManyToMany
382         return null;
383     }
384 
385     /**
386      * {@inheritDoc}
387      */
388 	@Override
389 	public DataObjectRelationship addExtensionRelationship(Class<?> entityClass, String extensionPropertyName,
390 			Class<?> extensionEntityClass) {
391 		ClassDescriptor entityDescriptor = getClassDescriptor(entityClass);
392 		ClassDescriptor extensionEntityDescriptor = getClassDescriptor(extensionEntityClass);
393 
394 		if (LOG.isDebugEnabled()) {
395 			LOG.debug("About to attempt to inject a 1:1 relationship on PKs between " + entityDescriptor + " and "
396 					+ extensionEntityDescriptor);
397 		}
398 		OneToOneMapping dm = (OneToOneMapping) entityDescriptor.newOneToOneMapping();
399 		dm.setAttributeName(extensionPropertyName);
400 		dm.setReferenceClass(extensionEntityClass);
401 		dm.setDescriptor(entityDescriptor);
402 		dm.setIsPrivateOwned(true);
403 		dm.setJoinFetch(ForeignReferenceMapping.OUTER_JOIN);
404 		dm.setCascadeAll(true);
405 		dm.setIsLazy(false);
406 		dm.dontUseIndirection();
407 		dm.setIsOneToOneRelationship(true);
408 		dm.setRequiresTransientWeavedFields(false);
409 
410         OneToOneMapping inverse = findExtensionInverse(extensionEntityDescriptor, entityClass);
411         dm.setMappedBy(inverse.getAttributeName());
412         for (DatabaseField sourceField : inverse.getSourceToTargetKeyFields().keySet()) {
413             DatabaseField targetField = inverse.getSourceToTargetKeyFields().get(sourceField);
414             // reverse them, pass the source from the inverse as our target and the target from the inverse as our source
415             dm.addTargetForeignKeyField(sourceField, targetField);
416         }
417 
418         dm.preInitialize(getEclipseLinkEntityManager().getDatabaseSession());
419 		dm.initialize(getEclipseLinkEntityManager().getDatabaseSession());
420 		entityDescriptor.addMapping(dm);
421 		entityDescriptor.getObjectBuilder().initialize(getEclipseLinkEntityManager().getDatabaseSession());
422 
423         // build the data object relationship
424         ManagedTypeImpl<?> managedType = (ManagedTypeImpl<?>)getEntityManager().getMetamodel().managedType(entityClass);
425         SingularAttributeImpl<?, ?> singularAttribute = new SingularAttributeLocal(managedType, dm);
426         return getRelationshipMetadata(singularAttribute);
427 	}
428 
429     /**
430      * Provides a local implementation of {@link SingularAttributeImpl}.
431      */
432     class SingularAttributeLocal extends SingularAttributeImpl {
433 
434         /**
435          * Creates a local implementation of {@link SingularAttributeImpl}.
436          *
437          * @param managedType the {@link ManagedType}.
438          * @param mapping the {@link DatabaseMapping}.
439          */
440         SingularAttributeLocal(ManagedTypeImpl managedType, DatabaseMapping mapping) {
441             super(managedType, mapping);
442         }
443     }
444 
445     /**
446      * Gets the inverse extension of the given {@link ClassDescriptor}.
447      *
448      * @param extensionEntityDescriptor the {@link ClassDescriptor} of which to get the inverse.
449      * @param entityType the type of the entity.
450      * @return the inverse extension of the given {@link ClassDescriptor}.
451      */
452     protected OneToOneMapping findExtensionInverse(ClassDescriptor extensionEntityDescriptor, Class<?> entityType) {
453         Collection<DatabaseMapping> derivedIdMappings = extensionEntityDescriptor.getDerivesIdMappinps();
454         String extensionInfo = "(" + extensionEntityDescriptor.getJavaClass().getName() + " -> " + entityType.getName()
455                 + ")";
456         if (derivedIdMappings == null || derivedIdMappings.isEmpty()) {
457             throw new MetadataConfigurationException("Attempting to use extension framework, but extension "
458                     + extensionInfo + " does not have a valid inverse OneToOne Id mapping back to the extended data "
459                     + "object. Please ensure it is annotated property for use of the extension framework with JPA.");
460         } else if (derivedIdMappings.size() > 1) {
461             throw new MetadataConfigurationException("When attempting to determine the inverse relationship for use "
462                     + "with extension framework " + extensionInfo + " encountered more than one 'derived id' mapping, "
463                     + "there should be only one!");
464         }
465         DatabaseMapping inverseMapping = derivedIdMappings.iterator().next();
466         if (!(inverseMapping instanceof OneToOneMapping)) {
467             throw new MetadataConfigurationException("Identified an inverse derived id mapping for extension "
468                     + "relationship " + extensionInfo + " but it was not a one-to-one mapping: " + inverseMapping);
469         }
470         return (OneToOneMapping)inverseMapping;
471     }
472 
473     /**
474      * Gets the descriptor for the entity type.
475      *
476      * @param entityClass the type of the enty.
477      * @return the descriptor for the entity type.
478      */
479     protected ClassDescriptor getClassDescriptor(Class<?> entityClass) {
480 		return getEclipseLinkEntityManager().getDatabaseSession().getDescriptor(entityClass);
481 	}
482 
483     /**
484      * The entity manager for interacting with the database.
485      * @return the entity manager for interacting with the database.
486      */
487 	protected JpaEntityManager getEclipseLinkEntityManager() {
488 		return (JpaEntityManager) entityManager;
489 	}
490 }