in grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/config/GormMappingConfigurationStrategy.java [569:698]
private ToOne establishDomainClassRelationship(PersistentEntity entity, PropertyDescriptor property, MappingContext context, Map hasOneMap, boolean embedded) {
ToOne association = null;
Class propType = property.getPropertyType();
if (embedded && !isPersistentEntity(propType)) {
// uni-directional to embedded non-entity
PersistentEntity associatedEntity = getOrCreateEmbeddedEntity(entity, context, propType);
association = propertyFactory.createEmbedded(entity, context, property);
association.setAssociatedEntity(associatedEntity);
return association;
}
ClassPropertyFetcher relatedCpf = ClassPropertyFetcher.forClass(propType);
// establish relationship to type
Map relatedClassRelationships = getAllAssociationMap(relatedCpf);
Map mappedBy = getMapStaticProperty(relatedCpf, MAPPED_BY);
Class<?> relatedClassPropertyType = null;
// if there is a relationships map use that to find out
// whether it is mapped to a Set
String relatedClassPropertyName = null;
if (!forceUnidirectional(property, mappedBy)) {
if (relatedClassRelationships != null && !relatedClassRelationships.isEmpty()) {
PropertyDescriptor[] descriptors = ReflectionUtils.getPropertiesOfType(entity.getJavaClass(), propType);
relatedClassPropertyName = findOneToManyThatMatchesType(entity, property, relatedClassRelationships, mappedBy, relatedCpf);
Object mappedByValue = relatedClassPropertyName != null ? mappedBy.get(relatedClassPropertyName) : null;
if(mappedByValue != null && property.getName().equals(mappedByValue)) {
relatedClassPropertyType = relatedCpf.getPropertyType(relatedClassPropertyName, true);
}
else {
// if there is only one property on many-to-one side of the relationship then
// try to establish if it is bidirectional
if (descriptors.length == 1 && isNotMappedToDifferentProperty(property,relatedClassPropertyName, mappedBy)) {
if (StringUtils.hasText(relatedClassPropertyName)) {
// get the type of the property
PropertyDescriptor potentialProperty = relatedCpf.getPropertyDescriptor(relatedClassPropertyName);
// ensure circular links are not possible between one-to-one associations
if(!potentialProperty.equals(property)) {
relatedClassPropertyType = potentialProperty.getPropertyType();
}
}
}
// if there is more than one property on the many-to-one side then we need to either
// find out if there is a mappedBy property or whether a convention is used to decide
// on the mapping property
else if (descriptors.length > 1) {
if (mappedBy.containsValue(property.getName())) {
for (Object o : mappedBy.keySet()) {
String mappedByPropertyName = (String) o;
if (property.getName().equals(mappedBy.get(mappedByPropertyName))) {
Class<?> mappedByRelatedType = (Class<?>) relatedClassRelationships.get(mappedByPropertyName);
if (mappedByRelatedType != null && propType.isAssignableFrom(mappedByRelatedType))
relatedClassPropertyType = relatedCpf.getPropertyType(mappedByPropertyName);
}
}
}
else if(relatedClassPropertyName != null) {
// in this case no mappedBy is found so check if the the property name is the same as the class name (eg. 'Foo' would be come 'foo')
// using this convention we consider this the default property to map to
String classNameAsProperty = Introspector.decapitalize(propType.getSimpleName());
if (property.getName().equals(classNameAsProperty) && !mappedBy.containsKey(relatedClassPropertyName)) {
relatedClassPropertyType = relatedCpf.getPropertyType(relatedClassPropertyName);
}
}
}
}
}
// otherwise retrieve all the properties of the type from the associated class
if (relatedClassPropertyType == null) {
List<PropertyDescriptor> descriptors = getPropertiesAssignableFromType(entity.getJavaClass(), relatedCpf);
// if there is only one then the association is established
if (descriptors.size() == 1) {
PropertyDescriptor first = descriptors.get(0);
// ensure circular links are not possible between one-to-one associations
if(!first.equals(property)) {
String otherSidePropertyName = first.getName();
if(mappedBy.containsKey(otherSidePropertyName)) {
Object mapping = mappedBy.get(otherSidePropertyName);
if(mapping != null && mapping.equals(property.getName())) {
relatedClassPropertyType = first.getPropertyType();
relatedClassPropertyName = otherSidePropertyName;
}
}
else {
relatedClassPropertyType = first.getPropertyType();
relatedClassPropertyName = otherSidePropertyName;
}
}
}
}
}
// establish relationship based on this type
final boolean isAssociationEntity = isPersistentEntity(relatedClassPropertyType);
// one-to-one
if (relatedClassPropertyType == null || isAssociationEntity) {
association = embedded ? propertyFactory.createEmbedded(entity, context, property) :
propertyFactory.createOneToOne(entity, context, property);
if (hasOneMap.containsKey(property.getName()) && !embedded) {
association.setForeignKeyInChild(true);
}
}
// bi-directional many-to-one
else if (!embedded && Collection.class.isAssignableFrom(relatedClassPropertyType)||Map.class.isAssignableFrom(relatedClassPropertyType)) {
association = propertyFactory.createManyToOne(entity, context, property);
}
// bi-directional
if (association != null) {
PersistentEntity associatedEntity = getOrCreateAssociatedEntity(entity, context, propType);
association.setAssociatedEntity(associatedEntity);
if (relatedClassPropertyName != null && relatedClassPropertyType != null) {
association.setReferencedPropertyName(relatedClassPropertyName);
}
}
return association;
}