/**
   * Returns the effective association metadata for the attribute checking with the parent mappings
   * and entities.
   *
   * @return the column metadata
   * @since 2.0.0
   */
  @SuppressWarnings("unchecked")
  protected AssociationMetadata getAssociationMetadata() {
    AssociationMetadata metadata = null;

    final String path = this.getParent().getRootPath(this.getName());
    final AttributeImpl<?, ?> rootAttribute =
        this.getParent().getRootAttribute(this.getAttribute());

    /**
     * The priorities are like below:
     *
     * <p>1. If the root attribute is defined in the root type (thus the entity) then locate the
     * association override on the attribute chain<br>
     * 2. If the root attribute is defined in a parent mapped super class then locate the
     * association on the entity<br>
     * 3. If the parent is an embeddable mapping then locate the attribute override again on the
     * association chain<br>
     * 4. return the association metadata from the attribute<br>
     */

    // Clause 1
    if ((rootAttribute.getDeclaringType() == this.getRoot().getType())
        && (this.getParent() instanceof EmbeddedMappingImpl)) {
      metadata = ((EmbeddedMappingImpl<?, ?>) this.getParent()).getAssociationOverride(path);
      if (metadata != null) {
        return metadata;
      }
    }

    // Clause 2
    if (rootAttribute.getDeclaringType() instanceof MappedSuperclassTypeImpl) {
      metadata = ((EntityTypeImpl<Y>) this.getRoot().getType()).getAssociationOverride(path);
      if (metadata != null) {
        return metadata;
      }
    }

    // Clause 3
    if (this.getParent() instanceof EmbeddedMappingImpl) {
      metadata = ((EmbeddedMappingImpl<?, ?>) this.getParent()).getAssociationOverride(path);
      if (metadata != null) {
        return metadata;
      }
    }

    // Clause 4: fall back to attribute's column metadata
    return (AssociationMetadata) this.getAttribute().getMetadata();
  }
  /**
   * @param parent the parent mapping
   * @param metadata the metadata
   * @param attribute the attribute
   * @since 2.0.0
   */
  public AssociationMappingImpl(
      AbstractParentMapping<?, Z> parent,
      AssociationAttributeMetadata metadata,
      AttributeImpl<? super Z, X> attribute) {
    super(parent, attribute, attribute.getJavaType(), attribute.getName());

    if ((metadata instanceof MappableAssociationAttributeMetadata)
        && StringUtils.isNotBlank(
            ((MappableAssociationAttributeMetadata) metadata).getMappedBy())) {
      this.mappedBy = ((MappableAssociationAttributeMetadata) metadata).getMappedBy();
    } else {
      this.mappedBy = null;
    }

    this.eager =
        attribute.isCollection() || (this.mappedBy == null)
            ? metadata.getFetchType() == FetchType.EAGER
            : true;

    this.maxFetchDepth = metadata.getMaxFetchDepth();
    this.fetchStrategy = metadata.getFetchStrategy();

    if (metadata instanceof OrphanableAssociationAttributeMetadata) {
      this.removesOrphans = ((OrphanableAssociationAttributeMetadata) metadata).removesOrphans();
    } else {
      this.removesOrphans = false;
    }

    this.cascadesDetach =
        metadata.getCascades().contains(CascadeType.ALL)
            || metadata.getCascades().contains(CascadeType.DETACH);
    this.cascadesMerge =
        metadata.getCascades().contains(CascadeType.ALL)
            || metadata.getCascades().contains(CascadeType.MERGE);
    this.cascadesPersist =
        metadata.getCascades().contains(CascadeType.ALL)
            || metadata.getCascades().contains(CascadeType.PERSIST);
    this.cascadesRefresh =
        metadata.getCascades().contains(CascadeType.ALL)
            || metadata.getCascades().contains(CascadeType.REFRESH);
    this.cascadesRemove =
        metadata.getCascades().contains(CascadeType.ALL)
            || metadata.getCascades().contains(CascadeType.REMOVE);
  }