in objectModel/TypeScript/Cdm/CdmEntityDefinition.ts [561:1061]
public async createResolvedEntityAsync(
newEntName: string,
resOpt?: resolveOptions,
folder?: CdmFolderDefinition,
newDocName?: string): Promise<CdmEntityDefinition> {
// let bodyCode = () =>
{
return await using(enterScope(CdmEntityDefinition.name, this.ctx, this.createResolvedEntityAsync.name), async _ => {
if (!resOpt) {
resOpt = new resolveOptions(this, this.ctx.corpus.defaultResolutionDirectives);
}
// if the wrtDoc needs to be indexed (like it was just modified) then do that first
if (!resOpt.wrtDoc) {
Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, cdmLogCode.ErrDocWrtDocNotfound);
return undefined;
}
if (!newEntName || newEntName === '') {
Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, cdmLogCode.ErrResolveNewEntityNameNotSet);
return undefined;
}
// if the wrtDoc needs to be indexed (like it was just modified) then do that first
if (!await resOpt.wrtDoc.indexIfNeeded(resOpt, true)) {
Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, cdmLogCode.ErrIndexFailed);
return undefined;
}
if (!folder) {
folder = this.inDocument.folder;
}
const fileName: string = (newDocName === undefined || newDocName === '') ? `${newEntName}.cdm.json` : newDocName;
let origDoc: string = this.inDocument.atCorpusPath;
// Don't overwite the source document
const targetAtCorpusPath: string =
`${this.ctx.corpus.storage.createAbsoluteCorpusPath(folder.atCorpusPath, folder)}${fileName}`;
if (StringUtils.equalsWithIgnoreCase(targetAtCorpusPath, origDoc)) {
Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, cdmLogCode.ErrDocEntityReplacementFailure, targetAtCorpusPath);
return undefined;
}
// make sure the corpus has a set of default artifact attributes
await this.ctx.corpus.prepareArtifactAttributesAsync();
// make the top level attribute context for this entity
// for this whole section where we generate the attribute context tree and get resolved attributes
// set the flag that keeps all of the parent changes and document dirty from from happening
const wasResolving: boolean = this.ctx.corpus.isCurrentlyResolving;
this.ctx.corpus.isCurrentlyResolving = true;
const entName: string = newEntName;
const ctx: resolveContext = this.ctx as resolveContext;
let attCtxEnt: CdmAttributeContext = ctx.corpus.MakeObject(cdmObjectType.attributeContextDef, entName, true);
attCtxEnt.ctx = ctx;
attCtxEnt.inDocument = this.inDocument;
// cheating a bit to put the paths in the right place
const acp: AttributeContextParameters = {
under: attCtxEnt,
type: cdmAttributeContextType.attributeGroup,
name: 'attributeContext'
};
const attCtxAC: CdmAttributeContext = CdmAttributeContext.createChildUnder(resOpt, acp);
// this is the node that actually is first in the context we save. all definition refs should take the new perspective that they
// can only be understood through the resolvedFrom moniker
const entRefThis: CdmEntityReference = ctx.corpus.MakeObject<CdmEntityReference>(cdmObjectType.entityRef, this.getName(), true);
entRefThis.owner = this;
entRefThis.inDocument = this.inDocument; // need to set owner and inDocument to this starting entity so the ref will be portable to the new document
const prevOwner: CdmObject = this.owner;
entRefThis.explicitReference = this;
// we don't want to change the owner of this entity to the entity reference
// re-assign whatever was there before
this.owner = prevOwner;
const acpEnt: AttributeContextParameters = {
under: attCtxAC,
type: cdmAttributeContextType.entity,
name: entName,
regarding: entRefThis
};
// reset previous depth information in case there are left overs
resOpt.depthInfo.reset();
// use this whenever we need to keep references pointing at things that were already found.
// used when 'fixing' references by localizing to a new document
const resOptCopy: resolveOptions = CdmAttributeContext.prepareOptionsForResolveAttributes(resOpt);
// resolve attributes with this context. the end result is that each resolved attribute
// points to the level of the context where it was last modified, merged, created
const ras: ResolvedAttributeSet = this.fetchResolvedAttributes(resOptCopy, acpEnt);
if (resOptCopy.usedResolutionGuidance) {
Logger.warning(ctx, this.TAG, this.createResolvedEntityAsync.name, this.atCorpusPath, cdmLogCode.WarnDeprecatedResolutionGuidance);
}
if (ras === undefined) {
return undefined;
}
this.ctx.corpus.isCurrentlyResolving = wasResolving;
// make a new document in given folder if provided or the same folder as the source entity
folder.documents.remove(fileName);
const docRes: CdmDocumentDefinition = folder.documents.push(fileName);
// add a import of the source document
origDoc = this.ctx.corpus.storage.createRelativeCorpusPath(origDoc, docRes); // just in case we missed the prefix
docRes.imports.push(origDoc, 'resolvedFrom');
docRes.documentVersion = this.inDocument.documentVersion;
// make the empty entity
let entResolved: CdmEntityDefinition = docRes.definitions.push(entName) as CdmEntityDefinition;
// grab that context that comes from fetchResolvedAttributes. We are sure this tree is a copy that we can own, so no need to copy it again
let attCtx: CdmAttributeContext;
if (attCtxAC && attCtxAC.contents && attCtxAC.contents.length === 1) {
attCtx = attCtxAC.contents.allItems[0] as CdmAttributeContext;
}
entResolved.attributeContext = attCtx;
if (attCtx) {
// fix all of the definition references, parent and lineage references, owner documents, etc. in the context tree
attCtx.finalizeAttributeContext(resOptCopy, `${entName}/attributeContext/`, docRes, this.inDocument, 'resolvedFrom', true);
// TEST CODE
// run over the resolved attributes and make sure none have the dummy context
//testResolveAttributeCtx = (rasSub) =>
// const testResolveAttributeCtx: (rasSub: ResolvedAttributeSet) => void
// = (rasSub: ResolvedAttributeSet): void => {
// if (rasSub.set.size !== 0 && rasSub.attributeContext.atCoprusPath.startsWith('cacheHolder')) {
// console.log('Bad');
// }
// for (const ra of rasSub.set) {
// if (ra.AttCtx.AtCoprusPath.StartsWith("cacheHolder")) {
// console.log('Bad');
// }
// // the target for a resolved att can be a typeAttribute OR it can be another resolvedAttributeSet (meaning a group)
// if (ra.target instanceof ResolvedAttributeSet)
// {
// testResolveAttributeCtx(ra.target as ResolvedAttributeSet);
// }
// }
//}
//testResolveAttributeCtx(ras);
}
// add the traits of the entit, also add to attribute context top node
const rtsEnt: ResolvedTraitSet = this.fetchResolvedTraits(resOpt);
rtsEnt.set.forEach((rt: ResolvedTrait) => {
let traitRef: CdmTraitReference = CdmObjectBase.resolvedTraitToTraitRef(resOptCopy, rt);
(entResolved as CdmObjectDefinition).exhibitsTraits.push(traitRef);
traitRef = CdmObjectBase.resolvedTraitToTraitRef(resOptCopy, rt); // fresh copy
if (entResolved.attributeContext) {
entResolved.attributeContext.exhibitsTraits.push(traitRef);
}
});
// special trait to explain this is a resolved entity
entResolved.indicateAbstractionLevel('resolved', resOpt);
if (entResolved.attributeContext) {
// the attributes have been named, shaped, etc for this entity so now it is safe to go and
// make each attribute context level point at these final versions of attributes
// what we have is a resolved attribute set (maybe with structure) where each ra points at the best tree node
// we also have the tree of context, we need to flip this around so that the right tree nodes point at the paths to the
// right attributes. so run over the resolved atts and then add a path reference to each one into the context contents where is last lived
const attPath2Order: Map<string, number> = new Map<string, number>();
const finishedGroups: Set<string> = new Set<string>();
const allPrimaryCtx: Set<CdmAttributeContext> = new Set<CdmAttributeContext>(); // keep a list so it is easier to think about these later
const pointContextAtResolvedAtts: (rasSub: ResolvedAttributeSet, path: string) => void
= (rasSub: ResolvedAttributeSet, path: string): void => {
for (const ra of rasSub.set) {
const raCtx: CdmAttributeContext = ra.attCtx;
const refs = raCtx.contents;
allPrimaryCtx.add(raCtx);
let attRefPath: string = `${path}${ra.resolvedName}`;
// the target for a resolved att can be a typeAttribute OR it can be another resolvedAttributeSet (meaning a group)
const target: CdmAttribute = ra.target as CdmAttribute;
if (target && target.objectType) {
// it was an attribute, add to the content of the context, also, keep track of the ordering for all of the att paths we make up
// as we go through the resolved attributes, this is the order of atts in the final resolved entity
if (!attPath2Order.has(attRefPath)) {
var attRef = this.ctx.corpus.MakeObject<CdmObjectReferenceBase>(cdmObjectType.attributeRef, attRefPath, true);
// only need one explanation for this path to the insert order
attPath2Order.set(attRef.namedReference, ra.insertOrder);
raCtx.contents.push(attRef);
}
}
else {
// a group, so we know an attribute group will get created later with the name of the group and the things it contains will be in
// the members of that group
attRefPath = `${attRefPath}/members/`;
if (!finishedGroups.has(attRefPath)) {
pointContextAtResolvedAtts(ra.target as ResolvedAttributeSet, attRefPath);
finishedGroups.add(attRefPath);
}
}
}
};
pointContextAtResolvedAtts(ras, `${entName}/hasAttributes/`);
// the generated attribute structures sometimes has a LOT of extra nodes that don't say anything or explain anything
// our goal now is to prune that tree to just have the stuff one may need
// do this by keeping the leafs and all of the lineage nodes for the attributes that end up in the resolved entity
// along with some special nodes that explain entity structure and inherit
if (!attCtx.pruneToScope(allPrimaryCtx)) {
// TODO: log error
return undefined;
}
// create an all-up ordering of attributes at the leaves of this tree based on insert order
// sort the attributes in each context by their creation order and mix that with the other sub-contexts that have been sorted
let getOrderNum: (item: CdmObject) => number;
const orderContents: (under: CdmAttributeContext) => number
= (under: CdmAttributeContext): number => {
if (under.lowestOrder === undefined) {
under.lowestOrder = -1; // used for group with nothing but traits
if (under.contents.length === 1) {
under.lowestOrder = getOrderNum(under.contents.allItems[0]);
} else {
under.contents.allItems = under.contents.allItems.sort((l: CdmObject, r: CdmObject): number => {
const lNum: number = getOrderNum(l);
const rNum: number = getOrderNum(r);
if (lNum !== -1 && (under.lowestOrder === -1 || lNum < under.lowestOrder)) {
under.lowestOrder = lNum;
}
if (rNum !== -1 && (under.lowestOrder === -1 || rNum < under.lowestOrder)) {
under.lowestOrder = rNum;
}
return lNum - rNum;
});
}
}
return under.lowestOrder;
};
getOrderNum = (item: CdmObject): number => {
if (item.getObjectType() === cdmObjectType.attributeContextDef) {
return orderContents(item as CdmAttributeContext);
} else if (isAttributeReference(item)) {
return attPath2Order.get(item.namedReference);
} else {
return -1; // put the mystery item on top.
}
};
orderContents(attCtx);
// resolved attributes can gain traits that are applied to an entity when referenced
// since these traits are described in the context, it is redundant and messy to list them in the attribute
// so, remove them. create and cache a set of names to look for per context
// there is actually a hierarchy to all attributes from the base entity should have all traits applied independently of the
// sub-context they come from. Same is true of attribute entities. so do this recursively top down
const ctx2traitNames: Map<CdmAttributeContext, Set<string>> = new Map<CdmAttributeContext, Set<string>>();
const collectContextTraits: (subAttCtx: CdmAttributeContext, inheritedTraitNames: Set<string>) => void
= (subAttCtx: CdmAttributeContext, inheritedTraitNames: Set<string>): void => {
const traitNamesHere: Set<string> = new Set<string>(inheritedTraitNames);
const traitsHere: CdmTraitCollection = subAttCtx.exhibitsTraits;
if (traitsHere) {
traitsHere.allItems.forEach((tat: CdmTraitReference) => { traitNamesHere.add(tat.namedReference); });
}
ctx2traitNames.set(subAttCtx, traitNamesHere);
for (const cr of subAttCtx.contents.allItems) {
if (cr.getObjectType() === cdmObjectType.attributeContextDef) {
// do this for all types?
collectContextTraits(cr as CdmAttributeContext, traitNamesHere);
}
}
};
collectContextTraits(attCtx, new Set<string>());
// add the attributes, put them in attribute groups if structure needed
const resAtt2RefPath: Map<ResolvedAttribute, string> = new Map<ResolvedAttribute, string>();
const addAttributes: (rasSub: ResolvedAttributeSet, container: CdmEntityDefinition | CdmAttributeGroupDefinition, path: string) => void
= (rasSub: ResolvedAttributeSet, container: CdmEntityDefinition | CdmAttributeGroupDefinition, path: string): void => {
for (const ra of rasSub.set) {
const attPath: string = `${path}${ra.resolvedName}`;
// use the path of the context associated with this attribute to find the new context that matches on path
const raCtx: CdmAttributeContext = ra.attCtx;
if (ra.target instanceof ResolvedAttributeSet) {
// this is a set of attributes.
// make an attribute group to hold them
const attGrp: CdmAttributeGroupDefinition = this.ctx.corpus.MakeObject<CdmAttributeGroupDefinition>(cdmObjectType.attributeGroupDef, ra.resolvedName, false);
attGrp.attributeContext = this.ctx.corpus.MakeObject<CdmAttributeContextReference>(cdmObjectType.attributeContextRef, raCtx.atCorpusPath, true);
// debugLineage
//attGrp.AttributeContext.NamedReference = $"{ raCtx.AtCoprusPath}({raCtx.Id})";
// take any traits from the set and make them look like traits exhibited by the group
const avoidSet: Set<string> = ctx2traitNames.get(raCtx);
// traits with the same name can show up on entities and attributes AND have different meanings.
avoidSet.clear();
const rtsAtt: ResolvedTraitSet = ra.resolvedTraits;
for (const rt of rtsAtt.set) {
if (!rt.trait.ugly) {
// don't mention your ugly traits
if (avoidSet && !avoidSet.has(rt.traitName)) {
// avoid the ones from the context
const traitRef = CdmObjectBase.resolvedTraitToTraitRef(resOptCopy, rt);
(attGrp as CdmObjectDefinitionBase).exhibitsTraits.push(traitRef);
}
}
}
// wrap it in a reference and then recurse with this as the new container
const attGrpRef: CdmAttributeGroupReference = this.ctx.corpus.MakeObject<CdmAttributeGroupReference>(cdmObjectType.attributeGroupRef, undefined, false);
attGrpRef.explicitReference = attGrp;
container.addAttributeDef(attGrpRef);
// isn't this where ...
addAttributes(ra.target as ResolvedAttributeSet, attGrp, `${attPath}/members/`);
}
else {
const att: CdmTypeAttributeDefinition = this.ctx.corpus.MakeObject<CdmTypeAttributeDefinition>(cdmObjectType.typeAttributeDef, ra.resolvedName, false);
att.attributeContext = this.ctx.corpus.MakeObject<CdmAttributeContextReference>(cdmObjectType.attributeContextRef, raCtx.atCorpusPath, true);
// debugLineage
//att.attributeContext.namedReference = `${raCtx.atCorpusPath}(${raCtx.id})";
const avoidSet: Set<string> = ctx2traitNames.get(raCtx);
// i don't know why i thought this was the right thing to do,
// traits with the same name can show up on entities and attributes AND have different meanings.
avoidSet.clear();
// i really need to figure out the effects of this before making this change
// without it, some traits don't make it to the resolved entity
// with it, too many traits might make it there
const rtsAtt: ResolvedTraitSet = ra.resolvedTraits;
for (const rt of rtsAtt.set) {
if (!rt.trait.ugly) { // don't mention your ugly traits
if (avoidSet && !avoidSet.has(rt.traitName)) { // avoid the ones from the context
const traitRef = CdmObjectBase.resolvedTraitToTraitRef(resOptCopy, rt);
(att as CdmTypeAttributeDefinition).appliedTraits.push(traitRef);
// the trait that points at other entities for foreign keys, that is trouble
// the string value in the table needs to be a relative path from the document of this entity
// to the document of the related entity. but, right now it is a relative path to the source entity
// so find those traits, and adjust the paths in the tables they hold
if (rt.traitName === 'is.linkedEntity.identifier') {
// grab the content of the table from the new ref (should be a copy of orig)
let linkTable: string[][];
if (traitRef.arguments && traitRef.arguments.length > 0) {
if (traitRef.arguments) {
const argValue: CdmEntityReference = traitRef.arguments.allItems[0].value as CdmEntityReference;
if (argValue) {
const expRef: CdmConstantEntityDefinition = argValue.explicitReference as CdmConstantEntityDefinition;
if (expRef) {
linkTable = expRef.constantValues;
}
}
}
}
if (linkTable && linkTable.length > 0) {
for (const row of linkTable) {
if (row.length === 2 || row.length === 3) // either the old table or the new one with relationship name can be there
{
// entity path an attribute name
let fixedPath: string = row[0];
fixedPath = this.ctx.corpus.storage.createAbsoluteCorpusPath(fixedPath, this.inDocument); // absolute from relative to this
fixedPath = this.ctx.corpus.storage.createRelativeCorpusPath(fixedPath, docRes); // realtive to new entity
row[0] = fixedPath;
}
}
}
}
}
}
}
// none of the dataformat traits have the bit set that will make them turn into a property
// this is intentional so that the format traits make it into the resolved object
// but, we still want a guess as the data format, so get it and set it.
const impliedDataFormat: cdmDataFormat = att.dataFormat;
if (impliedDataFormat !== cdmDataFormat.unknown) {
att.dataFormat = impliedDataFormat;
}
container.addAttributeDef(att);
resAtt2RefPath.set(ra, attPath);
}
}
}
addAttributes(ras, entResolved, `${entName}/hasAttributes/`);
// any resolved traits that hold arguments with attribute refs should get 'fixed' here
const replaceTraitAttRef: (tr: CdmTraitReference, entityHint: string, isAttributeContext: boolean) => void
= (tr: CdmTraitReference, entityHint: string): void => {
if (tr.arguments) {
for (const arg of tr.arguments.allItems) {
const v: ArgumentValue =
arg.unresolvedValue ? arg.unresolvedValue : arg.getValue();
// is this an attribute reference?
if (v
&& (v as CdmObject).getObjectType
&& (v as CdmObject).getObjectType() === cdmObjectType.attributeRef) {
// only try this if the reference has no path to it (only happens with intra-entity att refs)
const attRef: CdmAttributeReference = v as CdmAttributeReference;
if (attRef.namedReference && attRef.namedReference.indexOf('/') === -1) {
if (!arg.unresolvedValue) {
arg.unresolvedValue = arg.value;
}
// give a promise that can be worked out later.
// assumption is that the attribute must come from this entity.
const newAttRef: CdmAttributeReference = this.ctx.corpus.MakeObject(
cdmObjectType.attributeRef,
`${entityHint}/(resolvedAttributes)/${attRef.namedReference}`,
true);
// inDocument is not propagated during resolution, so set it here
newAttRef.inDocument = arg.inDocument;
arg.setValue(newAttRef);
}
}
}
}
};
// fix entity traits
if (entResolved.exhibitsTraits) {
entResolved.exhibitsTraits.allItems
.forEach((et: CdmTraitReferenceBase) => {
if (et instanceof CdmTraitReference) {
replaceTraitAttRef(et, newEntName, false);
}
});
}
// fix context traits
const fixContextTraits: (subAttCtx: CdmAttributeContext, entityHint: string) => void
= (subAttCtx: CdmAttributeContext, entityHint: string): void => {
const traitsHere: CdmTraitCollection = subAttCtx.exhibitsTraits;
if (traitsHere) {
traitsHere.allItems.forEach((tr: CdmTraitReferenceBase) => {
if (tr instanceof CdmTraitReference) {
replaceTraitAttRef(tr, entityHint, true);
}
});
}
for (const cr of subAttCtx.contents.allItems) {
if (cr.getObjectType() === cdmObjectType.attributeContextDef) {
// if this is a new entity context, get the name to pass along
const subSubAttCtx: CdmAttributeContext = cr as CdmAttributeContext;
let subEntityHint: string = entityHint;
if (subSubAttCtx.type === cdmAttributeContextType.entity && subSubAttCtx.definition) {
subEntityHint = subSubAttCtx.definition.namedReference;
}
// do this for all types
fixContextTraits(subSubAttCtx, subEntityHint);
}
}
};
fixContextTraits(attCtx, newEntName);
// and the attribute traits
const entAtts: CdmCollection<CdmAttributeItem> = entResolved.attributes;
if (entAtts) {
for (const attribute of entAtts.allItems) {
const attTraits: CdmTraitCollection = attribute.appliedTraits;
if (attTraits) {
attTraits.allItems.forEach((tr: CdmTraitReference) => {
if (tr instanceof CdmTraitReference) {
replaceTraitAttRef(tr, newEntName, false);
}
});
}
}
}
}
// we are about to put this content created in the context of various documents (like references to attributes from base entities, etc.)
// into one specific document. all of the borrowed refs need to work. so, re-write all string references to work from this new document
// the catch-22 is that the new document needs these fixes done before it can be used to make these fixes.
// the fix needs to happen in the middle of the refresh
// trigger the document to refresh current content into the resolved OM
if (attCtx) {
attCtx.parent = undefined; // remove the fake parent that made the paths work
}
const resOptNew: resolveOptions = resOpt.copy();
resOptNew.localizeReferencesFor = docRes;
resOptNew.wrtDoc = docRes;
if (!await docRes.refreshAsync(resOptNew)) {
Logger.error(this.ctx, this.TAG, this.createResolvedEntityAsync.name, null, cdmLogCode.ErrIndexFailed);
return undefined;
}
// get a fresh ref
entResolved = docRes.fetchObjectFromDocumentPath(entName, resOptNew) as CdmEntityDefinition;
this.ctx.corpus.resEntMap.set(this.atCorpusPath, entResolved.atCorpusPath);
return entResolved;
});
}
// return p.measure(bodyCode);
}