in grails-datastore-core/src/main/groovy/org/grails/datastore/mapping/model/config/GormMappingConfigurationStrategy.java [303:500]
protected Association establishRelationshipForCollection(PropertyDescriptor property, PersistentEntity entity, MappingContext context, Map<String, Class> hasManyMap, Map mappedByMap, boolean embedded) {
// is it a relationship
Class relatedClassType = hasManyMap.get(property.getName());
// try a bit harder for embedded collections (could make this the default, rendering 'hasMany' optional
// if generics are used)
if (relatedClassType == null && entity != null) {
Class javaClass = entity.getJavaClass();
Class genericClass = MappingUtils.getGenericTypeForProperty(javaClass, property.getName());
if (genericClass != null) {
relatedClassType = genericClass;
}
}
if (relatedClassType == null) {
return propertyFactory.createBasicCollection(entity, context, property);
}
if (embedded) {
if (propertyFactory.isSimpleType(relatedClassType)) {
return propertyFactory.createBasicCollection(entity, context, property, relatedClassType);
}
else if (!isPersistentEntity(relatedClassType)) {
// no point in setting up bidirectional link here, since target isn't an entity.
EmbeddedCollection association = propertyFactory.createEmbeddedCollection(entity, context, property);
PersistentEntity associatedEntity = getOrCreateEmbeddedEntity(entity, context, relatedClassType);
association.setAssociatedEntity(associatedEntity);
return association;
}
}
else if (!isPersistentEntity(relatedClassType) && !relatedClassType.equals(entity.getJavaClass())) {
// otherwise set it to not persistent as you can't persist
// relationships to non-domain classes
return propertyFactory.createBasicCollection(entity, context, property, relatedClassType);
}
// set the referenced type in the property
ClassPropertyFetcher referencedCpf = ClassPropertyFetcher.forClass(relatedClassType);
String referencedPropertyName = null;
// if the related type is a domain class
// then figure out what kind of relationship it is
// check the relationship defined in the referenced type
// if it is also a Set/domain class etc.
Map relatedClassRelationships = getAssociationMap(referencedCpf, HAS_MANY);
Class<?> relatedClassPropertyType = null;
String relatedClassPropertyName = null;
ClassPropertyFetcher cpf = ClassPropertyFetcher.forClass(entity.getJavaClass());
// First check whether there is an explicit relationship
// mapping for this property (as provided by "mappedBy").
String mappingProperty = (String)mappedByMap.get(property.getName());
if (StringUtils.hasText(mappingProperty)) {
// First find the specified property on the related class, if it exists.
PropertyDescriptor pd = findProperty(getPropertiesAssignableFromType(entity.getJavaClass(), referencedCpf),
mappingProperty);
// If a property of the required type does not exist, search
// for any collection properties on the related class.
if (pd == null) {
pd = findProperty(referencedCpf.getPropertiesAssignableToType(Collection.class), mappingProperty);
}
// We've run out of options. The given "mappedBy" setting is invalid.
if (pd == null && !MAPPED_BY_NONE.equals(mappingProperty)) {
if (entity.isExternal()) {
return null;
}
throw new IllegalMappingException("Non-existent mapping property [" + mappingProperty +
"] specified for property [" + property.getName() +
"] in class [" + entity.getJavaClass().getName() + "]");
}
else if(pd != null) {
// Tie the properties together.
relatedClassPropertyType = pd.getPropertyType();
referencedPropertyName = pd.getName();
relatedClassPropertyName = referencedPropertyName;
}
}
else {
if (!forceUnidirectional(property, mappedByMap)) {
// if the related type has a relationships map it may be a many-to-many
// figure out if there is a many-to-many relationship defined
if (isRelationshipToMany(entity, relatedClassType, relatedClassRelationships)) {
Map relatedClassMappedBy = getMapStaticProperty(referencedCpf, MAPPED_BY);
// retrieve the relationship property
for (Object o : relatedClassRelationships.keySet()) {
String currentKey = (String) o;
String mappedByProperty = (String) relatedClassMappedBy.get(currentKey);
if (mappedByProperty != null && !mappedByProperty.equals(property.getName())) continue;
Class<?> currentClass = (Class<?>)relatedClassRelationships.get(currentKey);
if (currentClass.isAssignableFrom(entity.getJavaClass())) {
relatedClassPropertyName = currentKey;
break;
}
}
// Map classRelationships = cpf.getPropertyValue(HAS_MANY, Map.class);
//
// if (isRelationshipToMany(entity, relatedClassType, classRelationships)) {
// String relatedClassPropertyName = findManyRelatedClassPropertyName(
// property.getName(), referencedCpf, classRelationships, relatedClassType);
// if there is one defined get the type
if (relatedClassPropertyName != null) {
relatedClassPropertyType = referencedCpf.getPropertyType(relatedClassPropertyName);
}
}
// otherwise figure out if there is a one-to-many relationship by retrieving any properties that are of the related type
// if there is more than one property then (for the moment) ignore the relationship
if (relatedClassPropertyType == null || Collection.class.isAssignableFrom(relatedClassPropertyType)) {
List<PropertyDescriptor> descriptors = getPropertiesAssignableFromType(entity.getJavaClass(), referencedCpf);
Map relatedMappedBy = referencedCpf.getStaticPropertyValue(GormProperties.MAPPED_BY, Map.class);
List referencedTransients = referencedCpf.getStaticPropertyValue(GormProperties.TRANSIENT, List.class);
List referencedEmbedded = referencedCpf.getStaticPropertyValue(GormProperties.EMBEDDED, List.class);
if(referencedTransients == null) {
referencedTransients = Collections.emptyList();
}
if(referencedEmbedded == null) {
referencedEmbedded = Collections.emptyList();
}
if(relatedMappedBy == null) {
relatedMappedBy = Collections.emptyMap();
}
if (descriptors.size() == 1) {
final PropertyDescriptor pd = descriptors.get(0);
if(!referencedTransients.contains(pd.getName()) && !referencedEmbedded.contains(pd.getName()) && isNotMappedToDifferentProperty(property, pd.getName(), relatedMappedBy)) {
relatedClassPropertyType = pd.getPropertyType();
referencedPropertyName = pd.getName();
}
}
else if (descriptors.size() > 1) {
// try now to use the class name by convention
String classPropertyName = entity.getDecapitalizedName();
PropertyDescriptor pd = findProperty(descriptors, classPropertyName);
if (pd == null) {
if (entity.isExternal()) {
return null;
}
pd = descriptors.get(0);
}
if (pd != null) {
if(isNotMappedToDifferentProperty(property, pd.getName(), relatedMappedBy)) {
relatedClassPropertyType = pd.getPropertyType();
referencedPropertyName = pd.getName();
}
}
}
}
}
}
// if its a many-to-many figure out the owning side of the relationship
final boolean isInverseSideEntity = isPersistentEntity(relatedClassPropertyType);
Association association = null;
boolean many = false;
if (embedded) {
association = propertyFactory.createEmbeddedCollection(entity, context, property);
}
else if (relatedClassPropertyType == null || isInverseSideEntity) {
// uni or bi-directional one-to-many
association = propertyFactory.createOneToMany(entity, context, property);
}
else if (Collection.class.isAssignableFrom(relatedClassPropertyType) ||
Map.class.isAssignableFrom(relatedClassPropertyType)) {
// many-to-many
association = propertyFactory.createManyToMany(entity, context, property);
((ManyToMany)association).setInversePropertyName(relatedClassPropertyName);
many = true;
}
else {
// uni-directional one-to-many
association = propertyFactory.createOneToMany(entity, context, property);
}
PersistentEntity associatedEntity = getOrCreateAssociatedEntity(entity, context, relatedClassType);
if (many) {
Map classRelationships = referencedCpf.getPropertyValue(HAS_MANY, Map.class);
referencedPropertyName = findManyRelatedClassPropertyName(null,
referencedCpf, classRelationships, entity.getJavaClass());
}
if (association != null) {
association.setAssociatedEntity(associatedEntity);
if (referencedPropertyName != null) {
// bidirectional
association.setReferencedPropertyName(referencedPropertyName);
}
}
return association;
}