protected Association establishRelationshipForCollection()

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;
    }