@Override
  public ClassDescriptor getDescriptor() {
    if (isAttribute()) {
      return null;
    }
    if (descriptor == null) {
      // Look first for query keys, then mappings. Ultimately we should have query keys
      // for everything and can dispense with the mapping part.
      ForeignReferenceQueryKey queryKey = (ForeignReferenceQueryKey) getQueryKeyOrNull();
      if (queryKey != null) {
        descriptor =
            convertToCastDescriptor(
                getSession().getDescriptor(queryKey.getReferenceClass()), getSession());
        return descriptor;
      }
      if (getMapping() == null) {
        throw QueryException.invalidQueryKeyInExpression(this);
      }

      // We assume this is either a foreign reference or an aggregate mapping
      descriptor = getMapping().getReferenceDescriptor();
      if (getMapping().isVariableOneToOneMapping()) {
        throw QueryException.cannotQueryAcrossAVariableOneToOneMapping(getMapping(), descriptor);
      }
      descriptor = convertToCastDescriptor(descriptor, getSession());
    }
    return descriptor;
  }
  /** Do any required validation for this node. Throw an exception if it's incorrect. */
  public void validateNode() {
    if ((getQueryKeyOrNull() == null) && (getMapping() == null)) {
      throw QueryException.invalidQueryKeyInExpression(getName());
    }

    QueryKey queryKey = getQueryKeyOrNull();
    DatabaseMapping mapping = getMapping();

    Object theOneThatsNotNull = null;
    boolean qkIsToMany = false;
    if (queryKey != null) {
      theOneThatsNotNull = queryKey;
      qkIsToMany = queryKey.isManyToManyQueryKey() || queryKey.isOneToManyQueryKey();
    }
    boolean isNestedMapping = false;
    if (mapping != null) {
      // Bug 2847621 - Add Aggregate Collection to the list of valid items for outer join.
      if (shouldUseOuterJoin
          && (!(mapping.isOneToOneMapping()
              || mapping.isOneToManyMapping()
              || mapping.isManyToManyMapping()
              || mapping.isAggregateCollectionMapping()
              || mapping.isDirectCollectionMapping()))) {
        throw QueryException.outerJoinIsOnlyValidForOneToOneMappings(getMapping());
      }
      qkIsToMany = mapping.isCollectionMapping();
      if (index != null) {
        if (qkIsToMany) {
          CollectionMapping collectionMapping = (CollectionMapping) getMapping();
          if (collectionMapping.getListOrderField() != null) {
            index.setField(collectionMapping.getListOrderField());
            if (collectionMapping.shouldUseListOrderFieldTableExpression()) {
              Expression newBase = getTable(collectionMapping.getListOrderField().getTable());
              index.setBaseExpression(newBase);
            } else {
              addDerivedField(index);
            }
          } else {
            throw QueryException.indexRequiresCollectionMappingWithListOrderField(
                this, collectionMapping);
          }
        } else {
          throw QueryException.indexRequiresCollectionMappingWithListOrderField(this, mapping);
        }
      }
      isNestedMapping = mapping.isNestedTableMapping();
      theOneThatsNotNull = mapping;
    } else {
      if (index != null) {
        throw QueryException.indexRequiresCollectionMappingWithListOrderField(this, null);
      }
    }
    if ((!shouldQueryToManyRelationship()) && qkIsToMany && (!isNestedMapping)) {
      throw QueryException.invalidUseOfToManyQueryKeyInExpression(theOneThatsNotNull);
    }
    if (shouldQueryToManyRelationship() && !qkIsToMany) {
      throw QueryException.invalidUseOfAnyOfInExpression(theOneThatsNotNull);
    }
  }
  public QueryKey getQueryKeyOrNull() {
    if (!hasQueryKey) {
      return null;
    }

    // Oct 19, 2000 JED
    // Added try/catch. This was throwing a NPE in the following case
    // expresssionBuilder.get("firstName").get("bob")
    // moved by Gordon Yorke to cover validate and normalize
    if (getContainingDescriptor() == null) {
      throw QueryException.invalidQueryKeyInExpression(getName());
    }
    if (queryKey == null) {
      queryKey = getContainingDescriptor().getQueryKeyNamed(getName());
      if (queryKey == null) {
        hasQueryKey = false;
      }
    }
    return queryKey;
  }