public constructResolvedAttributes()

in objectModel/TypeScript/Cdm/CdmEntityAttributeDefinition.ts [240:443]


    public constructResolvedAttributes(resOpt: resolveOptions, under?: CdmAttributeContext): ResolvedAttributeSetBuilder {
        // let bodyCode = () =>
        {
            // find and cache the complete set of attributes
            // attributes definitions originate from and then get modified by subsequent re-defintions from (in this order):
            // the entity used as an attribute, traits applied to that entity,
            // the purpose of the attribute, any traits applied to the attribute.
            let rasb: ResolvedAttributeSetBuilder = new ResolvedAttributeSetBuilder();
            const underAtt: CdmAttributeContext = under;
            let acpEnt: AttributeContextParameters;

            if (!resOpt.inCircularReference) {
                if (this.entity?.isProjection) {
                    // A Projection

                    // if the max depth is exceeded it should not try to execute the projection
                    if (!resOpt.depthInfo.maxDepthExceeded) {
                        const projDef: CdmProjection = this.entity.fetchObjectDefinition<CdmProjection>(resOpt);;
                        const projDirective: ProjectionDirective = new ProjectionDirective(resOpt, this, this.entity);
                        const projCtx: ProjectionContext = projDef.constructProjectionContext(projDirective, under);
                        rasb.ras = projDef.extractResolvedAttributes(projCtx, under);
                        // from the traits of purpose and applied here
                        rasb.ras.applyTraits(this.fetchResolvedTraits(resOpt));
                    }
                } else {
                    // Resolution guidance

                    resOpt.usedResolutionGuidance = true;

                    const arc: AttributeResolutionContext = this.fetchAttResContext(resOpt);
                    const relInfo: relationshipInfo = arc.getRelationshipInfo();

                    if (underAtt) {
                        // make a context for this attribute that holds the attributes that come up from the entity
                        acpEnt = {
                            under: underAtt,
                            type: cdmAttributeContextType.entity,
                            name: this.entity.fetchObjectDefinitionName(),
                            regarding: this.entity,
                            includeTraits: true
                        };
                    }

                    if (relInfo.isByRef) {
                        // make the entity context that a real recursion would have give us
                        if (under) {
                            under = rasb.ras.createAttributeContext(resOpt, acpEnt);
                        }
                        // if selecting from one of many attributes, then make a context for each one
                        if (under && relInfo.selectsOne) {
                            // the right way to do this is to get a resolved entity from the embedded entity and then
                            // look through the attribute context hierarchy for non-nested entityReferenceAsAttribute nodes
                            // that seems like a disaster waiting to happen given endless looping, etc.
                            // for now, just insist that only the top level entity attributes declared in the ref entity will work
                            const entPickFrom: CdmEntityDefinition = this.entity.fetchObjectDefinition(resOpt);
                            const attsPick: CdmCollection<CdmAttributeItem> = entPickFrom.attributes;
                            if (entPickFrom && attsPick) {
                                const l: number = attsPick.length;
                                for (let i: number = 0; i < l; i++) {
                                    if (attsPick.allItems[i].getObjectType() === cdmObjectType.entityAttributeDef) {
                                        // a table within a table. as expected with a selectsOne attribute
                                        // since this is by ref, we won't get the atts from the table, but we do need the traits that hold the key
                                        // these are the same contexts that would get created if we recursed
                                        // first this attribute
                                        const acpEntAtt: AttributeContextParameters = {
                                            under: under,
                                            type: cdmAttributeContextType.attributeDefinition,
                                            name: attsPick.allItems[i].fetchObjectDefinitionName(),
                                            regarding: attsPick.allItems[i],
                                            includeTraits: true
                                        };
                                        const pickUnder: CdmAttributeContext = rasb.ras.createAttributeContext(resOpt, acpEntAtt);
                                        // and the entity under that attribute
                                        const pickEnt: CdmEntityReference = (attsPick.allItems[i] as CdmEntityAttributeDefinition).entity;
                                        const pickEntType: cdmAttributeContextType = (pickEnt.fetchObjectDefinition<CdmObjectDefinition>(resOpt).objectType === cdmObjectType.projectionDef) ?
                                            cdmAttributeContextType.projection :
                                            cdmAttributeContextType.entity;

                                        const acpEntAttEnt: AttributeContextParameters = {
                                            under: pickUnder,
                                            type: pickEntType,
                                            name: pickEnt.fetchObjectDefinitionName(),
                                            regarding: pickEnt,
                                            includeTraits: true
                                        };
                                        rasb.ras.createAttributeContext(resOpt, acpEntAttEnt);
                                    }
                                }
                            }
                        }
                        // if we got here because of the max depth, need to impose the directives to make the trait work as expected
                        if (resOpt.depthInfo.maxDepthExceeded) {
                            if (!arc.resOpt.directives) {
                                arc.resOpt.directives = new AttributeResolutionDirectiveSet();
                            }
                            arc.resOpt.directives.add('referenceOnly');
                        }
                    } else {
                        const resLink: resolveOptions = resOpt.copy();
                        resLink.symbolRefSet = resOpt.symbolRefSet;
                        rasb.mergeAttributes(this.entity.fetchResolvedAttributes(resLink, acpEnt));

                        // need to pass up maxDepthExceeded if it was hit
                        if (resLink.depthInfo && resLink.depthInfo.maxDepthExceeded) {
                            resOpt.depthInfo = resLink.depthInfo.copy();
                        }
                    }

                    // from the traits of purpose and applied here, see if new attributes get generated
                    rasb.ras.setAttributeContext(underAtt);
                    rasb.applyTraits(arc);
                    rasb.generateApplierAttributes(arc, true); // true = apply the prepared traits to new atts
                    // this may have added symbols to the dependencies, so merge them
                    resOpt.symbolRefSet.merge(arc.resOpt.symbolRefSet);

                    // use the traits for linked entity identifiers to record the actual foreign key links
                    if (rasb.ras && rasb.ras.set && (relInfo.isByRef)) {
                        rasb.ras.set.forEach((att: ResolvedAttribute): void => {
                            if (att.resolvedTraits) {
                                const reqdTrait: ResolvedTrait = att.resolvedTraits.find(resOpt, 'is.linkedEntity.identifier');
                                if (!reqdTrait) {
                                    return;
                                }

                                if (reqdTrait.parameterValues === undefined || reqdTrait.parameterValues.length === 0) {
                                    Logger.warning(this.ctx, this.TAG, this.constructResolvedAttributes.name, this.atCorpusPath, cdmLogCode.WarnLinkEntIdentArgsNotSupported, att.displayName, this.entity.namedReference);
                                    return;
                                }
                                const entReferences: (string)[] = [];
                                const attReferences: (string)[] = [];
                                const addEntityReference: (entRef: CdmEntityReference, namespace: string) => void =
                                    (entityRef: CdmEntityReference, namespace: string): void => {
                                        const entDef: CdmObjectDefinition = entityRef.fetchObjectDefinition(resOpt);
                                        if (entDef) {
                                            const otherResTraits: ResolvedTraitSet = entityRef.fetchResolvedTraits(resOpt);
                                            const identifyingTrait: ResolvedTrait = otherResTraits.find(resOpt, 'is.identifiedBy');
                                            if (otherResTraits && identifyingTrait) {
                                                const attRef: CdmObjectReference = identifyingTrait.parameterValues
                                                    .fetchParameterValueByName('attribute').value as CdmObjectReference;
                                                const attNamePath: string = attRef.namedReference;
                                                const attName: string = attNamePath.split('/')
                                                    .pop();
                                                // path should be absolute and without a namespace
                                                let absoluteEntPath: string =
                                                    this.ctx.corpus.storage.createAbsoluteCorpusPath(entDef.atCorpusPath, entDef.inDocument);
                                                entReferences.push(absoluteEntPath);
                                                attReferences.push(attName);
                                            }
                                        }
                                    };
                                if (relInfo.selectsOne) {
                                    const entPickFrom: CdmEntityDefinition = this.entity.fetchObjectDefinition(resOpt);
                                    const attsPick: CdmCollection<CdmAttributeItem> = entPickFrom ? entPickFrom.attributes : undefined;
                                    if (entPickFrom && attsPick) {
                                        const l: number = attsPick.length;
                                        for (let i: number = 0; i < l; i++) {
                                            if (attsPick.allItems[i].getObjectType() === cdmObjectType.entityAttributeDef) {
                                                const entAtt: CdmEntityAttributeDefinition = attsPick.allItems[i] as CdmEntityAttributeDefinition;
                                                addEntityReference(entAtt.entity, this.inDocument.namespace);
                                            }
                                        }
                                    }
                                } else {
                                    addEntityReference(this.entity, this.inDocument.namespace);
                                }
                                const cEnt: CdmConstantEntityDefinition =
                                    this.ctx.corpus.MakeObject<CdmConstantEntityDefinition>(cdmObjectType.constantEntityDef);
                                cEnt.setEntityShape(this.ctx.corpus.MakeRef(cdmObjectType.entityRef, 'entityGroupSet', true));
                                cEnt.setConstantValues(entReferences.map((entityRef: string, idx: number) => [entityRef, attReferences[idx]]));
                                const param: CdmObjectReference = this.ctx.corpus.MakeRef(cdmObjectType.entityRef, cEnt, false);
                                reqdTrait.parameterValues.setParameterValue(resOpt, 'entityReferences', param);
                            }
                        });
                    }

                    // a 'structured' directive wants to keep all entity attributes together in a group
                    if (arc.resOpt.directives && arc.resOpt.directives.has('structured')) {
                        const raSub: ResolvedAttribute = new ResolvedAttribute(
                            arc.traitsToApply.resOpt, rasb.ras, this.name, rasb.ras.attributeContext);
                        if (relInfo.isArray) {
                            // put a resolved trait on this att group, yuck,
                            //  hope I never need to do this again and then need to make a function for this
                            const tr: CdmTraitReference =
                                this.ctx.corpus.MakeObject<CdmTraitReference>(cdmObjectType.traitRef, 'is.linkedEntity.array', true);
                            const t: CdmTraitDefinition = tr.fetchObjectDefinition(resOpt);
                            const rt: ResolvedTrait = new ResolvedTrait(t, undefined, [], []);
                            raSub.resolvedTraits = raSub.resolvedTraits.merge(rt, true);
                        }
                        const depth: number = rasb.ras.depthTraveled;
                        rasb = new ResolvedAttributeSetBuilder();
                        rasb.ras.attributeContext = raSub.attCtx; // this got set to null with the new builder
                        rasb.ownOne(raSub);
                        rasb.ras.depthTraveled = depth;
                    }
                }
            }
            // how ever they got here, mark every attribute from this entity attribute as now being 'owned' by this entityAtt
            rasb.ras.setAttributeOwnership(this.name);
            rasb.ras.depthTraveled += 1;

            return rasb;
        }
        // return p.measure(bodyCode);
    }