public override CdmObject Copy(ResolveOptions resOpt = null) { if (resOpt == null) { resOpt = new ResolveOptions(this); } CdmEntityDefinition copy = new CdmEntityDefinition(this.Ctx, this.EntityName, null); copy.ExtendsEntity = copy.ExtendsEntity != null ? (CdmEntityReference)this.ExtendsEntity.Copy(resOpt) : null; copy.ExtendsEntityResolutionGuidance = this.ExtendsEntityResolutionGuidance != null ? (CdmAttributeResolutionGuidance)this.ExtendsEntityResolutionGuidance.Copy(resOpt) : null; copy.AttributeContext = copy.AttributeContext != null ? (CdmAttributeContext)this.AttributeContext.Copy(resOpt) : null; foreach (var att in this.Attributes) { copy.Attributes.Add(att); } this.CopyDef(resOpt, copy); return(copy); }
/// <summary> /// Creates an Entity Declaration based on an Entity Definition and adds it to the list. /// </summary> /// <param name="entity">The entity definition used to create the entity declaration.</param> /// <param name="simpleRef">Parameter is unused for this collection. Kept for consistency with other CdmCollections.</param> /// <returns>The entity declaration that was added to the collection.</returns> public CdmEntityDeclarationDefinition Add(CdmEntityDefinition entity, bool simpleRef = false) { var cdmCorpus = this.Ctx.Corpus; if (entity.Owner == null) { entity.Owner = this.Owner; if (entity.Owner == null) { throw new System.ArgumentException("Expected entity to have an \"Owner\" document set. Cannot create entity declaration to add to manifest."); } } var entityDeclaration = (this.Ctx.Corpus as CdmCorpusDefinition).MakeObject <CdmEntityDeclarationDefinition>(this.DefaultType, entity.EntityName, simpleRef); entityDeclaration.Owner = this.Owner; entityDeclaration.EntityPath = this.Ctx.Corpus.Storage.CreateRelativeCorpusPath($"{entity.Owner.AtCorpusPath}/{entity.EntityName}", this.Owner.InDocument); entityDeclaration.Explanation = entity.Explanation; return(this.Add(entityDeclaration)); }
/// <summary> /// Populates the relationships that the entities in the current manifest are involved in. /// </summary> public async Task PopulateManifestRelationshipsAsync(CdmRelationshipDiscoveryStyle option = CdmRelationshipDiscoveryStyle.All) { using (Logger.EnterScope(nameof(CdmManifestDefinition), Ctx, nameof(PopulateManifestRelationshipsAsync))) { this.Relationships.Clear(); HashSet <string> relCache = new HashSet <string>(); if (this.Entities != null) { foreach (CdmEntityDeclarationDefinition entDec in this.Entities) { string entPath = await this.GetEntityPathFromDeclaration(entDec, this); CdmEntityDefinition currEntity = await this.Ctx.Corpus.FetchObjectAsync <CdmEntityDefinition>(entPath); if (currEntity == null) { continue; } // handle the outgoing relationships List <CdmE2ERelationship> outgoingRels = this.Ctx.Corpus.FetchOutgoingRelationships(currEntity); if (outgoingRels != null) { foreach (CdmE2ERelationship rel in outgoingRels) { string cacheKey = rel2CacheKey(rel); if (!relCache.Contains(cacheKey) && this.IsRelAllowed(rel, option)) { this.Relationships.Add(this.LocalizeRelToManifest(rel)); relCache.Add(cacheKey); } } } List <CdmE2ERelationship> incomingRels = this.Ctx.Corpus.FetchIncomingRelationships(currEntity); if (incomingRels != null) { foreach (CdmE2ERelationship inRel in incomingRels) { // get entity object for current toEntity CdmEntityDefinition currentInBase = await this.Ctx.Corpus.FetchObjectAsync <CdmEntityDefinition>(inRel.ToEntity, this); if (currentInBase == null) { continue; } // create graph of inheritance for to currentInBase // graph represented by an array where entity at i extends entity at i+1 List <CdmEntityDefinition> toInheritanceGraph = new List <CdmEntityDefinition>(); while (currentInBase != null) { var resOpt = new ResolveOptions { WrtDoc = currentInBase.InDocument }; currentInBase = currentInBase.ExtendsEntity?.FetchObjectDefinition <CdmEntityDefinition>(resOpt); if (currentInBase != null) { toInheritanceGraph.Add(currentInBase); } } // add current incoming relationship string cacheKey = rel2CacheKey(inRel); if (!relCache.Contains(cacheKey) && this.IsRelAllowed(inRel, option)) { this.Relationships.Add(this.LocalizeRelToManifest(inRel)); relCache.Add(cacheKey); } // if A points at B, A's base classes must point at B as well foreach (CdmEntityDefinition baseEntity in toInheritanceGraph) { List <CdmE2ERelationship> incomingRelsForBase = this.Ctx.Corpus.FetchIncomingRelationships(baseEntity); if (incomingRelsForBase != null) { foreach (CdmE2ERelationship inRelBase in incomingRelsForBase) { CdmE2ERelationship newRel = new CdmE2ERelationship(this.Ctx, "") { FromEntity = inRelBase.FromEntity, FromEntityAttribute = inRelBase.FromEntityAttribute, ToEntity = inRel.ToEntity, ToEntityAttribute = inRel.ToEntityAttribute }; string baseRelCacheKey = rel2CacheKey(newRel); if (!relCache.Contains(baseRelCacheKey) && this.IsRelAllowed(newRel, option)) { this.Relationships.Add(this.LocalizeRelToManifest(newRel)); relCache.Add(baseRelCacheKey); } } } } } } } if (this.SubManifests != null) { foreach (CdmManifestDeclarationDefinition subManifestDef in this.SubManifests) { var corpusPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(subManifestDef.Definition, this); var subManifest = await this.Ctx.Corpus.FetchObjectAsync <CdmManifestDefinition>(corpusPath); await subManifest.PopulateManifestRelationshipsAsync(option); } } } } }
/// <summary> /// Creates a resolved copy of the entity. /// </summary> public async Task <CdmEntityDefinition> CreateResolvedEntityAsync(string newEntName, ResolveOptions resOpt = null, CdmFolderDefinition folder = null, string newDocName = null) { if (resOpt == null) { resOpt = new ResolveOptions(this, this.Ctx.Corpus.DefaultResolutionDirectives); } if (resOpt.WrtDoc == null) { Logger.Error(nameof(CdmEntityDefinition), this.Ctx as ResolveContext, $"No WRT document was supplied.", nameof(CreateResolvedEntityAsync)); return(null); } if (string.IsNullOrEmpty(newEntName)) { Logger.Error(nameof(CdmEntityDefinition), this.Ctx as ResolveContext, $"No Entity Name provided.", nameof(CreateResolvedEntityAsync)); return(null); } // 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(nameof(CdmEntityDefinition), this.Ctx as ResolveContext, $"Couldn't index source document.", nameof(CreateResolvedEntityAsync)); return(null); } if (folder == null) { folder = this.InDocument.Folder; } string fileName = (string.IsNullOrEmpty(newDocName)) ? $"{newEntName}.cdm.json" : newDocName; string origDoc = this.InDocument.AtCorpusPath; // Don't overwite the source document string targetAtCorpusPath = $"{this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(folder.AtCorpusPath, folder)}{fileName}"; if (StringUtils.EqualsWithIgnoreCase(targetAtCorpusPath, origDoc)) { Logger.Error(nameof(CdmEntityDefinition), this.Ctx as ResolveContext, $"Attempting to replace source entity's document '{targetAtCorpusPath}'", nameof(CreateResolvedEntityAsync)); return(null); } // 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 bool wasResolving = this.Ctx.Corpus.isCurrentlyResolving; this.Ctx.Corpus.isCurrentlyResolving = true; string entName = newEntName; ResolveContext ctx = this.Ctx as ResolveContext; CdmAttributeContext attCtxEnt = ctx.Corpus.MakeObject <CdmAttributeContext>(CdmObjectType.AttributeContextDef, entName, true); attCtxEnt.Ctx = ctx; attCtxEnt.InDocument = this.InDocument; // cheating a bit to put the paths in the right place AttributeContextParameters acp = new AttributeContextParameters { under = attCtxEnt, type = CdmAttributeContextType.AttributeGroup, Name = "attributeContext" }; CdmAttributeContext attCtxAC = CdmAttributeContext.CreateChildUnder(resOpt, acp); AttributeContextParameters acpEnt = new AttributeContextParameters { under = attCtxAC, type = CdmAttributeContextType.Entity, Name = entName, Regarding = ctx.Corpus.MakeObject <CdmObject>(CdmObjectType.EntityRef, this.GetName(), true) }; // 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 ResolveOptions resOptCopy = CdmObjectBase.CopyResolveOptions(resOpt); resOptCopy.SaveResolutionsOnCopy = true; // resolve attributes with this context. the end result is that each resolved attribute // points to the level of the context where it was created var ras = this.FetchResolvedAttributes(resOptCopy, acpEnt); if (ras == null) { this.resolvingAttributes = false; return(null); } // create a new copy of the attribute context for this entity HashSet <CdmAttributeContext> allAttCtx = new HashSet <CdmAttributeContext>(); CdmAttributeContext newNode = attCtxEnt.CopyNode(resOpt) as CdmAttributeContext; attCtxEnt = attCtxEnt.CopyAttributeContextTree(resOpt, newNode, ras, allAttCtx, "resolvedFrom"); CdmAttributeContext attCtx = (attCtxEnt.Contents[0] as CdmAttributeContext).Contents[0] as CdmAttributeContext; 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); CdmDocumentDefinition docRes = folder.Documents.Add(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.Add(origDoc, "resolvedFrom"); // make the empty entity CdmEntityDefinition entResolved = docRes.Definitions.Add(entName); // set the context to the copy of the tree. fix the docs on the context nodes entResolved.AttributeContext = attCtx; attCtx.Visit($"{entName}/attributeContext/", new VisitCallback { Invoke = (obj, path) => { obj.InDocument = docRes; return(false); } }, null); // add the traits of the entity ResolvedTraitSet rtsEnt = this.FetchResolvedTraits(resOpt); rtsEnt.Set.ForEach(rt => { var traitRef = CdmObjectBase.ResolvedTraitToTraitRef(resOptCopy, rt); (entResolved as CdmObjectDefinition).ExhibitsTraits.Add(traitRef); }); // 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 IDictionary <string, int> attPath2Order = new Dictionary <string, int>(); Action <ResolvedAttributeSet, string> pointContextAtResolvedAtts = null; pointContextAtResolvedAtts = (rasSub, path) => { rasSub.Set.ForEach(ra => { List <CdmAttributeContext> raCtxInEnt = new List <CdmAttributeContext>(); HashSet <CdmAttributeContext> raCtxSet = null; rasSub.Ra2attCtxSet.TryGetValue(ra, out raCtxSet); // find the correct attCtx for this copy, intersect these two sets // (iterate over the shortest list) if (allAttCtx.Count < raCtxSet.Count) { foreach (CdmAttributeContext currAttCtx in allAttCtx) { if (raCtxSet.Contains(currAttCtx)) { raCtxInEnt.Add(currAttCtx); } } } else { foreach (CdmAttributeContext currAttCtx in raCtxSet) { if (allAttCtx.Contains(currAttCtx)) { raCtxInEnt.Add(currAttCtx); } } } foreach (CdmAttributeContext raCtx in raCtxInEnt) { var refs = raCtx.Contents; // there might be more than one explanation for where and attribute came from when things get merges as they do // this won't work when I add the structured attributes to avoid name collisions string attRefPath = path + ra.ResolvedName; if ((ra.Target as CdmAttribute)?.GetType().GetMethod("GetObjectType") != null) { if (!attPath2Order.ContainsKey(attRefPath)) { var attRef = this.Ctx.Corpus.MakeObject <CdmObjectReferenceBase>(CdmObjectType.AttributeRef, attRefPath, true); // only need one explanation for this path to the insert order attPath2Order.Add(attRef.NamedReference, ra.InsertOrder); refs.Add(attRef); } } else { attRefPath += "/members/"; pointContextAtResolvedAtts(ra.Target as ResolvedAttributeSet, attRefPath); } } }); }; pointContextAtResolvedAtts(ras, entName + "/hasAttributes/"); // generated attribute structures may end up with 0 attributes after that. prune them Func <CdmObject, bool, bool> CleanSubGroup = null; CleanSubGroup = (subItem, underGenerated) => { if (subItem.ObjectType == CdmObjectType.AttributeRef) { return(true); // not empty } CdmAttributeContext ac = subItem as CdmAttributeContext; if (ac.Type == CdmAttributeContextType.GeneratedSet) { underGenerated = true; } if (ac.Contents == null || ac.Contents.Count == 0) { return(false); // empty } // look at all children, make a set to remove List <CdmAttributeContext> toRemove = new List <CdmAttributeContext>(); foreach (var subSub in ac.Contents) { if (CleanSubGroup(subSub, underGenerated) == false) { bool potentialTarget = underGenerated; if (potentialTarget == false) { // cast is safe because we returned false meaning empty and not a attribute ref // so is this the set holder itself? potentialTarget = (subSub as CdmAttributeContext).Type == CdmAttributeContextType.GeneratedSet; } if (potentialTarget) { toRemove.Add(subSub as CdmAttributeContext); } } } foreach (var toDie in toRemove) { ac.Contents.Remove(toDie); } return(ac.Contents.Count != 0); }; CleanSubGroup(attCtx, false); Func <CdmAttributeContext, int?> orderContents = null; // 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 Func <CdmObject, int?> getOrderNum = (item) => { if (item.ObjectType == CdmObjectType.AttributeContextDef && orderContents != null) { return(orderContents(item as CdmAttributeContext)); } else if (item.ObjectType == CdmObjectType.AttributeRef) { string attName = (item as CdmAttributeReference).NamedReference; int o = attPath2Order[attName]; return(o); } else { return(-1); // put the mystery item on top. } }; orderContents = (CdmAttributeContext under) => { if (under.LowestOrder == null) { under.LowestOrder = -1; // used for group with nothing but traits if (under.Contents.Count == 1) { under.LowestOrder = getOrderNum(under.Contents[0]); } else { under.Contents.AllItems.Sort((l, r) => { int lNum = -1; int rNum = -1; int?aux; aux = getOrderNum(l); if (aux != null) { lNum = (int)aux; } aux = getOrderNum(r); if (aux != null) { rNum = (int)aux; } 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); }; orderContents((CdmAttributeContext)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 var ctx2traitNames = new Dictionary <CdmAttributeContext, HashSet <string> >(); Action <CdmAttributeContext, HashSet <string> > collectContextTraits = null; collectContextTraits = (subAttCtx, inheritedTraitNames) => { var traitNamesHere = new HashSet <string>(inheritedTraitNames); var traitsHere = subAttCtx.ExhibitsTraits; if (traitsHere != null) { foreach (var tat in traitsHere) { traitNamesHere.Add(tat.NamedReference); } } ctx2traitNames.Add(subAttCtx, traitNamesHere); subAttCtx.Contents.AllItems.ForEach((cr) => { if (cr.ObjectType == CdmObjectType.AttributeContextDef) { // do this for all types? collectContextTraits(cr as CdmAttributeContext, traitNamesHere); } }); }; collectContextTraits(attCtx, new HashSet <string>()); // add the attributes, put them in attribute groups if structure needed IDictionary <ResolvedAttribute, string> resAtt2RefPath = new Dictionary <ResolvedAttribute, string>(); Action <ResolvedAttributeSet, dynamic, string> addAttributes = null; addAttributes = (rasSub, container, path) => { rasSub.Set.ForEach(ra => { string attPath = path + ra.ResolvedName; // use the path of the context associated with this attribute to find the new context that matches on path HashSet <CdmAttributeContext> raCtxSet = null; rasSub.Ra2attCtxSet.TryGetValue(ra, out raCtxSet); CdmAttributeContext raCtx = null; // find the correct attCtx for this copy // (interate over the shortest list) if (allAttCtx.Count < raCtxSet.Count) { foreach (CdmAttributeContext currAttCtx in allAttCtx) { if (raCtxSet.Contains(currAttCtx)) { raCtx = currAttCtx; break; } } } else { foreach (CdmAttributeContext currAttCtx in raCtxSet) { if (allAttCtx.Contains(currAttCtx)) { raCtx = currAttCtx; break; } } } if (ra.Target is ResolvedAttributeSet) { // this is a set of attributes. // make an attribute group to hold them CdmAttributeGroupDefinition attGrp = this.Ctx.Corpus.MakeObject <CdmAttributeGroupDefinition>(CdmObjectType.AttributeGroupDef, ra.ResolvedName, false); attGrp.AttributeContext = this.Ctx.Corpus.MakeObject <CdmAttributeContextReference>(CdmObjectType.AttributeContextRef, raCtx.AtCorpusPath, true); // take any traits from the set and make them look like traits exhibited by the group HashSet <string> avoidSet = ctx2traitNames[raCtx]; ResolvedTraitSet rtsAtt = ra.ResolvedTraits; rtsAtt.Set.ForEach(rt => { if (rt.Trait.Ugly != true) { // don't mention your ugly traits if (avoidSet?.Contains(rt.TraitName) != true) { // avoid the ones from the context var traitRef = CdmObjectBase.ResolvedTraitToTraitRef(resOptCopy, rt); (attGrp as CdmObjectDefinitionBase).ExhibitsTraits.Add(traitRef); } } }); // wrap it in a reference and then recurse with this as the new container CdmAttributeGroupReference attGrpRef = this.Ctx.Corpus.MakeObject <CdmAttributeGroupReference>(CdmObjectType.AttributeGroupRef, null, false); attGrpRef.ExplicitReference = attGrp; container.AddAttributeDef(attGrpRef); // isn't this where ... addAttributes(ra.Target as ResolvedAttributeSet, attGrp, attPath + "/members/"); } else { CdmTypeAttributeDefinition att = this.Ctx.Corpus.MakeObject <CdmTypeAttributeDefinition>(CdmObjectType.TypeAttributeDef, ra.ResolvedName, false); att.AttributeContext = this.Ctx.Corpus.MakeObject <CdmAttributeContextReference>(CdmObjectType.AttributeContextRef, raCtx.AtCorpusPath, true); HashSet <string> avoidSet = ctx2traitNames[raCtx]; ResolvedTraitSet rtsAtt = ra.ResolvedTraits; rtsAtt.Set.ForEach(rt => { if (rt.Trait.Ugly != true) { // don't mention your ugly traits if (avoidSet?.Contains(rt.TraitName) != true) { // avoid the ones from the context var traitRef = CdmObjectBase.ResolvedTraitToTraitRef(resOptCopy, rt); ((CdmTypeAttributeDefinition)att).AppliedTraits.Add(traitRef); } } }); // 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. var impliedDataFormat = att.DataFormat; if (impliedDataFormat != CdmDataFormat.Unknown) { att.DataFormat = impliedDataFormat; } container.AddAttributeDef(att); resAtt2RefPath[ra] = attPath; } }); }; addAttributes(ras, entResolved, entName + "/hasAttributes/"); // any resolved traits that hold arguments with attribute refs should get 'fixed' here Action <CdmTraitReference, string, bool> replaceTraitAttRef = (tr, entityHint, isAttributeContext) => { if (tr.Arguments != null) { foreach (CdmArgumentDefinition arg in tr.Arguments.AllItems) { dynamic v = arg.UnResolvedValue != null ? arg.UnResolvedValue : arg.Value; // is this an attribute reference? if (v != null && (v as CdmObject)?.ObjectType == CdmObjectType.AttributeRef) { // only try this if the reference has no path to it (only happens with intra-entity att refs) var attRef = v as CdmAttributeReference; if (!string.IsNullOrEmpty(attRef.NamedReference) && attRef.NamedReference.IndexOf('/') == -1) { if (arg.UnResolvedValue == null) { arg.UnResolvedValue = arg.Value; } // give a promise that can be worked out later. assumption is that the attribute must come from this entity. var newAttRef = this.Ctx.Corpus.MakeRef <CdmAttributeReference>(CdmObjectType.AttributeRef, entityHint + "/(resolvedAttributes)/" + attRef.NamedReference, true); // inDocument is not propagated during resolution, so set it here newAttRef.InDocument = arg.InDocument; arg.Value = newAttRef; } } } } }; // fix entity traits if (entResolved.ExhibitsTraits != null) { foreach (var et in entResolved.ExhibitsTraits) { replaceTraitAttRef(et, newEntName, false); } } // fix context traits Action <CdmAttributeContext, string> fixContextTraits = null; fixContextTraits = (subAttCtx, entityHint) => { var traitsHere = subAttCtx.ExhibitsTraits; if (traitsHere != null) { foreach (var tr in traitsHere) { replaceTraitAttRef(tr, entityHint, true); } } subAttCtx.Contents.AllItems.ForEach((cr) => { if (cr.ObjectType == CdmObjectType.AttributeContextDef) { // if this is a new entity context, get the name to pass along CdmAttributeContext subSubAttCtx = (CdmAttributeContext)cr; string subEntityHint = entityHint; if (subSubAttCtx.Type == CdmAttributeContextType.Entity) { subEntityHint = subSubAttCtx.Definition.NamedReference; } // do this for all types fixContextTraits(subSubAttCtx, subEntityHint); } }); }; fixContextTraits(attCtx, newEntName); // and the attribute traits var entAttributes = entResolved.Attributes; if (entAttributes != null) { foreach (var entAtt in entAttributes) { CdmTraitCollection attTraits = entAtt.AppliedTraits; if (attTraits != null) { foreach (var tr in attTraits) { 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 attCtx.Parent = null; // remove the fake parent that made the paths work ResolveOptions resOptNew = CdmObjectBase.CopyResolveOptions(resOpt); resOptNew.LocalizeReferencesFor = docRes; resOptNew.WrtDoc = docRes; if (!await docRes.RefreshAsync(resOptNew)) { Logger.Error(nameof(CdmEntityDefinition), this.Ctx as ResolveContext, $"Failed to index the resolved document.", nameof(CreateResolvedEntityAsync)); return(null); } // get a fresh ref entResolved = docRes.FetchObjectFromDocumentPath(entName, resOptNew) as CdmEntityDefinition; this.Ctx.Corpus.resEntMap[this.AtCorpusPath] = entResolved.AtCorpusPath; return(entResolved); }
// the only thing we need this code for is testing!!! public override ResolvedEntityReferenceSet FetchResolvedEntityReferences(ResolveOptions resOpt = null) { if (resOpt == null) { resOpt = new ResolveOptions(this); } ResolvedTraitSet rtsThisAtt = this.FetchResolvedTraits(resOpt); CdmAttributeResolutionGuidance resGuide = (CdmAttributeResolutionGuidance)this.ResolutionGuidance; // this context object holds all of the info about what needs to happen to resolve these attributes AttributeResolutionContext arc = new AttributeResolutionContext(resOpt, resGuide, rtsThisAtt); RelationshipInfo relInfo = this.GetRelationshipInfo(resOpt, arc); if (relInfo.IsByRef && !relInfo.IsArray) { { // only place this is used, so logic here instead of encapsulated. // make a set and the one ref it will hold ResolvedEntityReferenceSet rers = new ResolvedEntityReferenceSet(resOpt); ResolvedEntityReference rer = new ResolvedEntityReference(); // referencing attribute(s) come from this attribute rer.Referencing.ResolvedAttributeSetBuilder.MergeAttributes(this.FetchResolvedAttributes(resOpt, null)); Func<CdmEntityReference, ResolvedEntityReferenceSide> resolveSide = entRef => { ResolvedEntityReferenceSide sideOther = new ResolvedEntityReferenceSide(null, null); if (entRef != null) { // reference to the other entity, hard part is the attribue name. // by convention, this is held in a trait that identifies the key sideOther.Entity = entRef.FetchObjectDefinition<CdmEntityDefinition>(resOpt); if (sideOther.Entity != null) { CdmAttribute otherAttribute; ResolveOptions otherOpts = new ResolveOptions { WrtDoc = resOpt.WrtDoc, Directives = resOpt.Directives }; ResolvedTrait t = entRef.FetchResolvedTraits(otherOpts).Find(otherOpts, "is.identifiedBy"); if (t?.ParameterValues?.Length > 0) { dynamic otherRef = (t.ParameterValues.FetchParameterValueByName("attribute").Value); if (typeof(CdmObject).IsAssignableFrom(otherRef?.GetType())) { otherAttribute = (otherRef as CdmObject).FetchObjectDefinition<CdmObjectDefinition>(otherOpts) as CdmAttribute; if (otherAttribute != null) { sideOther.ResolvedAttributeSetBuilder.OwnOne(sideOther.Entity.FetchResolvedAttributes(otherOpts).Get(otherAttribute.GetName()).Copy()); } } } } } return sideOther; }; // either several or one entity // for now, a sub for the 'select one' idea if ((this.Entity as CdmEntityReference).ExplicitReference != null) { CdmEntityDefinition entPickFrom = (this.Entity as CdmEntityReference).FetchObjectDefinition<CdmEntityDefinition>(resOpt); CdmCollection<CdmAttributeItem> attsPick = entPickFrom.GetAttributeDefinitions(); if (attsPick != null && attsPick != null) { for (int i = 0; i < attsPick.Count; i++) { if (attsPick.AllItems[i].ObjectType == CdmObjectType.EntityAttributeDef) { CdmEntityReference er = (attsPick.AllItems[i] as CdmEntityAttributeDefinition).Entity; rer.Referenced.Add(resolveSide(er)); } } } } else { rer.Referenced.Add(resolveSide(this.Entity as CdmEntityReference)); } rers.Set.Add(rer); return rers; } } return null; }
internal override ResolvedAttributeSetBuilder ConstructResolvedAttributes(ResolveOptions resOpt, CdmAttributeContext under = null) { // 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. ResolvedAttributeSetBuilder rasb = new ResolvedAttributeSetBuilder(); CdmEntityReference ctxEnt = this.Entity as CdmEntityReference; CdmAttributeContext underAtt = (CdmAttributeContext)under; AttributeContextParameters acpEnt = null; if (underAtt != null) { // make a context for this attreibute that holds the attributes that come up from the entity acpEnt = new AttributeContextParameters { under = underAtt, type = CdmAttributeContextType.Entity, Name = ctxEnt.FetchObjectDefinitionName(), Regarding = ctxEnt, IncludeTraits = true }; } ResolvedTraitSet rtsThisAtt = this.FetchResolvedTraits(resOpt); // this context object holds all of the info about what needs to happen to resolve these attributes. // make a copy and add defaults if missing CdmAttributeResolutionGuidance resGuideWithDefault; if (this.ResolutionGuidance != null) resGuideWithDefault = (CdmAttributeResolutionGuidance)this.ResolutionGuidance.Copy(resOpt); else resGuideWithDefault = new CdmAttributeResolutionGuidance(this.Ctx); resGuideWithDefault.UpdateAttributeDefaults(this.Name); AttributeResolutionContext arc = new AttributeResolutionContext(resOpt, resGuideWithDefault, rtsThisAtt); // complete cheating but is faster. // this purpose will remove all of the attributes that get collected here, so dumb and slow to go get them RelationshipInfo relInfo = this.GetRelationshipInfo(arc.ResOpt, arc); if (relInfo.IsByRef) { // make the entity context that a real recursion would have give us if (under != null) under = rasb.ResolvedAttributeSet.CreateAttributeContext(resOpt, acpEnt); // if selecting from one of many attributes, then make a context for each one if (under != null && 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 CdmEntityDefinition entPickFrom = (this.Entity as CdmEntityReference).FetchObjectDefinition<CdmEntityDefinition>(resOpt) as CdmEntityDefinition; CdmCollection<CdmAttributeItem> attsPick = entPickFrom?.GetAttributeDefinitions(); if (entPickFrom != null && attsPick != null) { for (int i = 0; i < attsPick.Count; i++) { if (attsPick.AllItems[i].ObjectType == 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 AttributeContextParameters acpEntAtt = new AttributeContextParameters { under = under, type = CdmAttributeContextType.AttributeDefinition, Name = attsPick.AllItems[i].FetchObjectDefinitionName(), Regarding = attsPick.AllItems[i], IncludeTraits = true }; CdmAttributeContext pickUnder = rasb.ResolvedAttributeSet.CreateAttributeContext(resOpt, acpEntAtt); CdmEntityReference pickEnt = (attsPick.AllItems[i] as CdmEntityAttributeDefinition).Entity as CdmEntityReference; AttributeContextParameters acpEntAttEnt = new AttributeContextParameters { under = pickUnder, type = CdmAttributeContextType.Entity, Name = pickEnt.FetchObjectDefinitionName(), Regarding = pickEnt, IncludeTraits = true }; rasb.ResolvedAttributeSet.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 (relInfo.MaxDepthExceeded) { if (arc.ResOpt.Directives == null) arc.ResOpt.Directives = new AttributeResolutionDirectiveSet(); arc.ResOpt.Directives.Add("referenceOnly"); } } else { ResolveOptions resLink = CopyResolveOptions(resOpt); resLink.SymbolRefSet = resOpt.SymbolRefSet; resLink.RelationshipDepth = relInfo.NextDepth; rasb.MergeAttributes((this.Entity as CdmEntityReference).FetchResolvedAttributes(resLink, acpEnt)); } // from the traits of purpose and applied here, see if new attributes get generated rasb.ResolvedAttributeSet.AttributeContext = 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.ResolvedAttributeSet?.Set != null && relInfo.IsByRef) { foreach (var att in rasb.ResolvedAttributeSet.Set) { var reqdTrait = att.ResolvedTraits.Find(resOpt, "is.linkedEntity.identifier"); if (reqdTrait == null) { continue; } if (reqdTrait.ParameterValues == null || reqdTrait.ParameterValues.Length == 0) { Logger.Warning(nameof(CdmEntityAttributeDefinition), this.Ctx as ResolveContext, "is.linkedEntity.identifier does not support arguments"); continue; } var entReferences = new List<string>(); var attReferences = new List<string>(); Action<CdmEntityReference, string> addEntityReference = (CdmEntityReference entRef, string nameSpace) => { var entDef = entRef.FetchObjectDefinition<CdmEntityDefinition>(resOpt); var identifyingTrait = entRef.FetchResolvedTraits(resOpt).Find(resOpt, "is.identifiedBy"); if (identifyingTrait != null && entDef != null) { var attRef = identifyingTrait.ParameterValues.FetchParameterValueByName("attribute").Value; string attNamePath = ((CdmObjectReferenceBase)attRef).NamedReference; string attName = attNamePath.Split('/').Last(); // path should be absolute and without a namespace string relativeEntPath = Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(entDef.AtCorpusPath, entDef.InDocument); if (relativeEntPath.StartsWith($"{nameSpace}:")) { relativeEntPath = relativeEntPath.Substring(nameSpace.Length + 1); } entReferences.Add(relativeEntPath); attReferences.Add(attName); } }; if (relInfo.SelectsOne) { var entPickFrom = (this.Entity as CdmEntityReference).FetchObjectDefinition<CdmEntityDefinition>(resOpt) as CdmEntityDefinition; var attsPick = entPickFrom?.GetAttributeDefinitions()?.Cast<CdmObject>().ToList(); if (entPickFrom != null && attsPick != null) { for (int i = 0; i < attsPick.Count; i++) { if (attsPick[i].ObjectType == CdmObjectType.EntityAttributeDef) { var entAtt = attsPick[i] as CdmEntityAttributeDefinition; addEntityReference(entAtt.Entity, this.InDocument.Namespace); } } } } else { addEntityReference(this.Entity, this.InDocument.Namespace); } var constantEntity = this.Ctx.Corpus.MakeObject<CdmConstantEntityDefinition>(CdmObjectType.ConstantEntityDef); constantEntity.EntityShape = this.Ctx.Corpus.MakeRef<CdmEntityReference>(CdmObjectType.EntityRef, "entityGroupSet", true); constantEntity.ConstantValues = entReferences.Select((entRef, idx) => new List<string> { entRef, attReferences[idx] }).ToList(); var traitParam = this.Ctx.Corpus.MakeRef<CdmEntityReference>(CdmObjectType.EntityRef, constantEntity, false); reqdTrait.ParameterValues.SetParameterValue(resOpt, "entityReferences", traitParam); } } // a 'structured' directive wants to keep all entity attributes together in a group if (arc.ResOpt.Directives?.Has("structured") == true) { ResolvedAttribute raSub = new ResolvedAttribute(rtsThisAtt.ResOpt, rasb.ResolvedAttributeSet, this.Name, (CdmAttributeContext)rasb.ResolvedAttributeSet.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 CdmTraitReference tr = this.Ctx.Corpus.MakeObject<CdmTraitReference>(CdmObjectType.TraitRef, "is.linkedEntity.array", true); var t = tr.FetchObjectDefinition<CdmTraitDefinition>(resOpt); ResolvedTrait rt = new ResolvedTrait(t, null, new List<dynamic>(), new List<bool>()); raSub.ResolvedTraits = raSub.ResolvedTraits.Merge(rt, true); } rasb = new ResolvedAttributeSetBuilder(); rasb.OwnOne(raSub); } return rasb; }
/// <summary> /// Creates a resolved copy of the manifest. /// newEntityDocumentNameFormat specifies a pattern to use when creating documents for resolved entites. /// The default is "{f}resolved/{n}.cdm.json" to avoid a document name conflict with documents in the same folder as the manifest. /// Every instance of the string {n} is replaced with the entity name from the source manifest. /// Every instance of the string {f} is replaced with the folder path from the source manifest to the source entity /// (if there is one that is possible as a relative location, else nothing). /// </summary> /// <param name="newManifestName"></param> /// <param name="newEntityDocumentNameFormat"></param> /// <param name="Directives"></param> /// <returns></returns> public async Task <CdmManifestDefinition> CreateResolvedManifestAsync(string newManifestName, string newEntityDocumentNameFormat, AttributeResolutionDirectiveSet Directives = null) { using (Logger.EnterScope(nameof(CdmManifestDefinition), Ctx, nameof(CreateResolvedManifestAsync))) { if (this.Entities == null) { return(null); } if (this.Owner == null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, CdmLogCode.ErrResolveManifestFailed, this.ManifestName); return(null); } if (newEntityDocumentNameFormat == null) { newEntityDocumentNameFormat = "{f}resolved/{n}.cdm.json"; } else if (newEntityDocumentNameFormat == "") // for back compat { newEntityDocumentNameFormat = "{n}.cdm.json"; } else if (!newEntityDocumentNameFormat.Contains("{n}")) // for back compat { newEntityDocumentNameFormat = newEntityDocumentNameFormat + "/{n}.cdm.json"; } string sourceManifestPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(this.AtCorpusPath, this); string sourceManifestFolderPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(this.Owner.AtCorpusPath, this); int resolvedManifestPathSplit = newManifestName.LastIndexOf("/") + 1; CdmFolderDefinition resolvedManifestFolder; if (resolvedManifestPathSplit > 0) { var resolvedManifestPath = newManifestName.Substring(0, resolvedManifestPathSplit); var newFolderPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(resolvedManifestPath, this); resolvedManifestFolder = await this.Ctx.Corpus.FetchObjectAsync <CdmFolderDefinition>(newFolderPath); if (resolvedManifestFolder == null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, CdmLogCode.ErrResolveFolderNotFound, newFolderPath); return(null); } newManifestName = newManifestName.Substring(resolvedManifestPathSplit); } else { resolvedManifestFolder = this.Owner as CdmFolderDefinition; } if (resolvedManifestFolder.Documents.Item(newManifestName) != null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, CdmLogCode.ErrResolveManifestExists, newManifestName, resolvedManifestFolder.AtCorpusPath); return(null); } Logger.Debug(this.Ctx as ResolveContext, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, $"resolving manifest {sourceManifestPath}"); // Using the references present in the resolved entities, get an entity // create an imports doc with all the necessary resolved entity references and then resolve it // sometimes they might send the docname, that makes sense a bit, don't include the suffix in the name if (newManifestName.ToLowerInvariant().EndsWith(".manifest.cdm.json")) { newManifestName = newManifestName.Substring(0, newManifestName.Length - ".manifest.cdm.json".Length); } var resolvedManifest = new CdmManifestDefinition(this.Ctx, newManifestName); // bring over any imports in this document or other bobbles resolvedManifest.Schema = this.Schema; resolvedManifest.Explanation = this.Explanation; resolvedManifest.DocumentVersion = this.DocumentVersion; foreach (CdmImport imp in this.Imports) { resolvedManifest.Imports.Add((CdmImport)imp.Copy()); } // add the new document to the folder if (resolvedManifestFolder.Documents.Add(resolvedManifest) == null) { // when would this happen? return(null); } foreach (var entity in this.Entities) { string entityPath = await this.GetEntityPathFromDeclaration(entity, this); CdmEntityDefinition entDef = await this.Ctx.Corpus.FetchObjectAsync <CdmEntityDefinition>(entityPath); if (entDef == null) { Logger.Error(this.Ctx, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, CdmLogCode.ErrResolveEntityFailure, entityPath); } if (entDef.InDocument.Owner == null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, CdmLogCode.ErrDocIsNotFolder, entDef.EntityName); return(null); } // get the path from this manifest to the source entity. this will be the {f} replacement value string sourceEntityFullPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(entDef.InDocument.Owner.AtCorpusPath, this); string f = ""; if (sourceEntityFullPath.StartsWith(sourceManifestFolderPath)) { f = sourceEntityFullPath.Substring(sourceManifestFolderPath.Length); } string newDocumentFullPath = newEntityDocumentNameFormat.Replace("{n}", entDef.EntityName); newDocumentFullPath = newDocumentFullPath.Replace("{f}", f); newDocumentFullPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(newDocumentFullPath, this); int newDocumentPathSplit = newDocumentFullPath.LastIndexOf("/") + 1; string newDocumentPath = newDocumentFullPath.Substring(0, newDocumentPathSplit); string newDocumentName = newDocumentFullPath.Substring(newDocumentPathSplit); // make sure the new folder exists var folder = await this.Ctx.Corpus.FetchObjectAsync <CdmFolderDefinition>(newDocumentPath); if (folder == null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, CdmLogCode.ErrResolveFolderNotFound, newDocumentPath); return(null); } // Next create the resolved entity AttributeResolutionDirectiveSet withDirectives = Directives != null ? Directives : this.Ctx.Corpus.DefaultResolutionDirectives; var resOpt = new ResolveOptions { WrtDoc = entDef.InDocument, Directives = withDirectives?.Copy() }; Logger.Debug(this.Ctx as ResolveContext, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, $"resolving entity {sourceEntityFullPath} to document {newDocumentFullPath}"); var resolvedEntity = await entDef.CreateResolvedEntityAsync(entDef.EntityName, resOpt, folder, newDocumentName); if (resolvedEntity == null) { // Fail all resolution, if any one entity resolution fails return(null); } var result = entity.Copy(resOpt) as CdmEntityDeclarationDefinition; if (result.ObjectType == CdmObjectType.LocalEntityDeclarationDef) { result.EntityPath = this.Ctx.Corpus.Storage.CreateRelativeCorpusPath(resolvedEntity.AtCorpusPath, resolvedManifest) ?? result.AtCorpusPath; } resolvedManifest.Entities.Add(result); } Logger.Debug(this.Ctx as ResolveContext, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, "calculating relationships"); // calculate the entity graph for just this manifest and any submanifests await this.Ctx.Corpus.CalculateEntityGraphAsync(resolvedManifest); // stick results into the relationships list for the manifest // only put in relationships that are between the entities that are used in the manifest await resolvedManifest.PopulateManifestRelationshipsAsync(CdmRelationshipDiscoveryStyle.Exclusive); // needed until Matt's changes with collections where I can propigate resolvedManifest.IsDirty = true; return(resolvedManifest); } }