/// <summary> /// Loads a folder or document given its obejct path. /// </summary> /// <param name="objectPath"></param> /// <param name="forceReload"></param> /// <param name="resOpt"></param> /// <returns></returns> private async Task <CdmContainerDefinition> _LoadFolderOrDocument(string objectPath, bool forceReload = false, ResolveOptions resOpt = null) { if (string.IsNullOrWhiteSpace(objectPath)) { return(null); } // first check for namespace Tuple <string, string> pathTuple = StorageUtils.SplitNamespacePath(objectPath); if (pathTuple == null) { Logger.Error(this.corpus.Ctx, TAG, nameof(LoadFolderOrDocument), objectPath, CdmLogCode.ErrPathNullObjectPath); return(null); } string nameSpace = !string.IsNullOrWhiteSpace(pathTuple.Item1) ? pathTuple.Item1 : this.corpus.Storage.DefaultNamespace; objectPath = pathTuple.Item2; if (!objectPath.StartsWith("/")) { return(null); } var namespaceFolder = this.corpus.Storage.FetchRootFolder(nameSpace); StorageAdapterBase namespaceAdapter = this.corpus.Storage.FetchAdapter(nameSpace); if (namespaceFolder == null || namespaceAdapter == null) { Logger.Error(this.corpus.Ctx, TAG, nameof(LoadFolderOrDocument), objectPath, CdmLogCode.ErrStorageNamespaceNotRegistered, nameSpace); return(null); } CdmFolderDefinition lastFolder = namespaceFolder.FetchChildFolderFromPath(objectPath, false); // don't create new folders, just go as far as possible if (lastFolder == null) { return(null); } // maybe the search is for a folder? string lastPath = lastFolder.FolderPath; if (lastPath == objectPath) { return(lastFolder); } // remove path to folder and then look in the folder objectPath = StringUtils.Slice(objectPath, lastPath.Length); this.concurrentReadLock.Acquire(); // During this step the document will be added to the pathLookup when it is added to a folder. var doc = await lastFolder.FetchDocumentFromFolderPathAsync(objectPath, forceReload, resOpt); this.concurrentReadLock.Release(); return(doc); }
/// <summary> /// Fetches the child folder from the corpus path. /// </summary> /// <param name="path">The path.</param> /// <param name="adapter">The storage adapter where the folder can be found.</param> /// <param name="makeFolder">Create the folder if it doesn't exist.</param> /// <returns>The <see cref="CdmFolderDefinition"/>.</returns> internal CdmFolderDefinition FetchChildFolderFromPath(string path, bool makeFolder = false) { string name; string remainingPath = path; CdmFolderDefinition childFolder = this; while (childFolder != null && remainingPath.IndexOf('/') != -1) { int first = remainingPath.IndexOf('/'); if (first < 0) { name = path; remainingPath = ""; } else { name = StringUtils.Slice(remainingPath, 0, first); remainingPath = StringUtils.Slice(remainingPath, first + 1); } if (name.ToLowerInvariant() != childFolder.Name.ToLowerInvariant()) { Logger.Error((ResolveContext)this.Ctx, Tag, nameof(FetchChildFolderFromPath), this.AtCorpusPath, CdmLogCode.ErrInvalidPath, path); return(null); } // the end? if (remainingPath.Length == 0) { return(childFolder); } first = remainingPath.IndexOf('/'); string childFolderName = remainingPath; if (first != -1) { childFolderName = StringUtils.Slice(remainingPath, 0, first); } else { // the last part of the path will be considered part of the part depending on the makeFolder flag. break; } // get next child folder. childFolder = childFolder.ChildFolders.GetOrCreate(childFolderName); } if (makeFolder) { childFolder = childFolder.ChildFolders.Add(remainingPath); } return(childFolder); }
/// <summary> /// Adds a folder and document to the list of all documents in the corpus. Also adds the document path to the path lookup. /// </summary> /// <param name="path">The document path.</param> /// <param name="folder">The folder.</param> /// <param name="doc">The document.</param> internal void AddDocumentPath(string path, CdmFolderDefinition folder, CdmDocumentDefinition doc) { lock (this.allDocuments) { if (!this.pathLookup.ContainsKey(path)) { this.allDocuments.Add(Tuple.Create(folder, doc)); this.pathLookup.Add(path, Tuple.Create(folder, doc)); } } }
/// <summary> /// Removes a folder and document from the list of all documents in the corpus. Also removes the document path from the path lookup. /// </summary> /// <param name="path">The document path.</param> /// <param name="folder">The folder.</param> /// <param name="doc">The document.</param> internal void RemoveDocumentPath(string path, CdmFolderDefinition folder, CdmDocumentDefinition doc) { lock (this.allDocuments) { if (this.pathLookup.ContainsKey(path)) { this.pathLookup.Remove(path); int index = this.allDocuments.IndexOf(Tuple.Create(folder, doc)); this.allDocuments.RemoveAt(index); } } }
/// <summary> /// changes a relative corpus path to be relative to the new folder /// </summary> private bool LocalizeCorpusPath(ref string path, CdmFolderDefinition newFolder) { // if this isn't a local path, then don't do anything to it if (string.IsNullOrWhiteSpace(path)) { return(true); } // but first, if there was no previous folder (odd) then just localize as best we can CdmFolderDefinition oldFolder = this.Owner as CdmFolderDefinition; string newPath; if (oldFolder == null) { newPath = this.Ctx.Corpus.Storage.CreateRelativeCorpusPath(path, newFolder); } else { // if the current value != the absolute path, then assume it is a relative path string absPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(path, oldFolder); if (absPath == path) { newPath = absPath; // leave it alone } else { // make it relative to the new folder then newPath = this.Ctx.Corpus.Storage.CreateRelativeCorpusPath(absPath, newFolder); } } if (newPath == null) { return(false); } path = newPath; return(true); }
/// <summary> /// finds any relative corpus paths that are held within this document and makes them relative to the new folder instead /// </summary> internal bool LocalizeCorpusPaths(CdmFolderDefinition newFolder) { bool allWentWell = true; bool wasBlocking = this.Ctx.Corpus.blockDeclaredPathChanges; this.Ctx.Corpus.blockDeclaredPathChanges = true; // shout into the void Logger.Info(nameof(CdmDocumentDefinition), (ResolveContext)this.Ctx, $"Localizing corpus paths in document '{this.Name}'", nameof(LocalizeCorpusPaths)); // find anything in the document that is a corpus path this.Visit("", new VisitCallback { Invoke = (iObject, path) => { // i don't like that document needs to know a little about these objects // in theory, we could create a virtual function on cdmObject that localizes properties // but then every object would need to know about the documents and paths and such ... // also, i already wrote this code. switch (iObject.ObjectType) { case CdmObjectType.Import: { CdmImport typeObj = iObject as CdmImport; typeObj.CorpusPath = LocalizeCorpusPath(typeObj.CorpusPath, newFolder, ref allWentWell) ?? typeObj.CorpusPath; break; } case CdmObjectType.LocalEntityDeclarationDef: case CdmObjectType.ReferencedEntityDeclarationDef: { CdmEntityDeclarationDefinition typeObj = iObject as CdmEntityDeclarationDefinition; typeObj.EntityPath = LocalizeCorpusPath(typeObj.EntityPath, newFolder, ref allWentWell) ?? typeObj.EntityPath; break; } case CdmObjectType.DataPartitionDef: { CdmDataPartitionDefinition typeObj = iObject as CdmDataPartitionDefinition; typeObj.Location = LocalizeCorpusPath(typeObj.Location, newFolder, ref allWentWell) ?? typeObj.Location; typeObj.SpecializedSchema = LocalizeCorpusPath(typeObj.SpecializedSchema, newFolder, ref allWentWell) ?? typeObj.SpecializedSchema; break; } case CdmObjectType.DataPartitionPatternDef: { CdmDataPartitionPatternDefinition typeObj = iObject as CdmDataPartitionPatternDefinition; typeObj.RootLocation = LocalizeCorpusPath(typeObj.RootLocation, newFolder, ref allWentWell) ?? typeObj.RootLocation; typeObj.SpecializedSchema = LocalizeCorpusPath(typeObj.SpecializedSchema, newFolder, ref allWentWell) ?? typeObj.SpecializedSchema; break; } case CdmObjectType.E2ERelationshipDef: { CdmE2ERelationship typeObj = iObject as CdmE2ERelationship; typeObj.ToEntity = LocalizeCorpusPath(typeObj.ToEntity, newFolder, ref allWentWell) ?? typeObj.ToEntity; typeObj.FromEntity = LocalizeCorpusPath(typeObj.FromEntity, newFolder, ref allWentWell) ?? typeObj.FromEntity; break; } case CdmObjectType.ManifestDeclarationDef: { CdmManifestDeclarationDefinition typeObj = iObject as CdmManifestDeclarationDefinition; typeObj.Definition = LocalizeCorpusPath(typeObj.Definition, newFolder, ref allWentWell) ?? typeObj.Definition; break; } } return(false); } }, null); this.Ctx.Corpus.blockDeclaredPathChanges = wasBlocking; return(allWentWell); }
/// <summary> /// Constructs a CdmDocumentCollection by using the parent constructor and DocumentDef as the default type. /// </summary> /// <param name="ctx">The context.</param> /// <param name="owner">The folder that contains this collection.</param> public CdmDocumentCollection(CdmCorpusContext ctx, CdmFolderDefinition owner) : base(ctx, owner, CdmObjectType.DocumentDef) { }
/// <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); }
/// <summary> /// Fetches the child folder from the corpus path. /// </summary> /// <param name="path">The path.</param> /// <param name="adapter">The storage adapter where the folder can be found.</param> /// <param name="makeFolder">Create the folder if it doesn't exist.</param> /// <returns>The <see cref="CdmFolderDefinition"/>.</returns> internal async Task <CdmFolderDefinition> FetchChildFolderFromPathAsync(string path, bool makeFolder = false) { string name; string remainingPath; int first = path.IndexOf('/', 0); if (first < 0) { name = path; remainingPath = ""; } else { name = StringUtils.Slice(path, 0, first); remainingPath = StringUtils.Slice(path, first + 1); } if (name.ToLowerInvariant() == this.Name.ToLowerInvariant()) { // the end? if (remainingPath.Length == 0) { return(this); } // check children folders CdmFolderDefinition result = null; if (this.ChildFolders != null) { foreach (var f in this.ChildFolders) { result = await f.FetchChildFolderFromPathAsync(remainingPath, makeFolder); if (result != null) { break; } } } if (result != null) { return(result); } // get the next folder first = remainingPath.IndexOf("/"); name = first > 0 ? remainingPath.Slice(0, first) : remainingPath; if (first != -1) { var childPath = await this.ChildFolders.Add(name).FetchChildFolderFromPathAsync(remainingPath, makeFolder); if (childPath == null) { Logger.Error(nameof(CdmFolderDefinition), (ResolveContext)this.Ctx, $"Invalid path '{path}'", nameof(FetchChildFolderFromPathAsync)); } return(childPath); } if (makeFolder) { // huh, well need to make the fold here return(await this.ChildFolders.Add(name).FetchChildFolderFromPathAsync(remainingPath, makeFolder)); } else { // good enough, return where we got to return(this); } } return(null); }
/// <summary> /// Fetches the child folder from the corpus path. /// </summary> /// <param name="path">The path.</param> /// <param name="adapter">The storage adapter where the folder can be found.</param> /// <param name="makeFolder">Create the folder if it doesn't exist.</param> /// <returns>The <see cref="CdmFolderDefinition"/>.</returns> internal CdmFolderDefinition FetchChildFolderFromPath(string path, bool makeFolder = false) { string name; string remainingPath = path; CdmFolderDefinition childFolder = this; while (childFolder != null && remainingPath.IndexOf('/') != -1) { int first = remainingPath.IndexOf('/'); if (first < 0) { name = path; remainingPath = ""; } else { name = StringUtils.Slice(remainingPath, 0, first); remainingPath = StringUtils.Slice(remainingPath, first + 1); } if (name.ToLowerInvariant() != childFolder.Name.ToLowerInvariant()) { Logger.Error(nameof(CdmFolderDefinition), (ResolveContext)this.Ctx, $"Invalid path '{path}'", nameof(FetchChildFolderFromPath)); return(null); } // the end? if (remainingPath.Length == 0) { return(childFolder); } first = remainingPath.IndexOf('/'); string childFolderName = remainingPath; if (first != -1) { childFolderName = StringUtils.Slice(remainingPath, 0, first); } else { // the last part of the path will be considered part of the part depending on the makeFolder flag. break; } // check children folders CdmFolderDefinition result = null; if (this.ChildFolders != null) { foreach (var folder in childFolder.ChildFolders) { if (childFolderName.ToLowerInvariant() == folder.Name.ToLowerInvariant()) { // found our folder. result = folder; break; } } } if (result == null) { result = childFolder.ChildFolders.Add(childFolderName); } childFolder = result; } if (makeFolder) { childFolder = childFolder.ChildFolders.Add(remainingPath); } return(childFolder); }