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}