/// <summary> /// Adds a document to the list of documents that are not indexed to mark it for indexing. /// </summary> /// <param name="doc">The document.</param> internal void MarkDocumentForIndexing(CdmDocumentDefinition doc) { lock (this.allDocuments) { this.docsNotIndexed[doc] = 1; } }
/// <summary> /// Removes a document from the list of documents that are not indexed to mark it as indexed. /// </summary> /// <param name="doc">The document.</param> internal void MarkDocumentAsIndexed(CdmDocumentDefinition doc) { lock (this.allDocuments) { this.docsNotIndexed.Remove(doc); } }
/// <summary> /// Returns a list of all the documents that are not indexed. /// </summary> internal List <CdmDocumentDefinition> ListDocsNotIndexed(CdmDocumentDefinition rootDoc, ISet <string> docsLoaded) { var docsNotIndexed = new List <CdmDocumentDefinition>(); lock (this.allDocuments) { // gets all the documents that needs indexing and set the currentlyIndexing flag to true. foreach (var docPath in docsLoaded) { var doc = this.FetchDocument(docPath); if (doc == null) { continue; } // The root document that started this indexing process is already masked for indexing, don't mark it again. if (doc != rootDoc) { if (this.MarkDocumentForIndexing(doc)) { docsNotIndexed.Add(doc); } } else { docsNotIndexed.Add(rootDoc); } } } return(docsNotIndexed); }
// Helper that fixes a path from local to absolute. // Gets the object from that path then looks at the document where the object is found. // If dirty, the document is saved with the original name. private async Task <bool> SaveDirtyLink(string relative, CopyOptions options) { // get the document object from the import string docPath = Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(relative, this); if (docPath == null) { Logger.Error(nameof(CdmManifestDefinition), this.Ctx as ResolveContext, $"Invalid corpus path {relative}`, `saveDirtyLink", "saveDirtyLink"); return(false); } CdmObject objAt = await Ctx.Corpus.FetchObjectAsync <CdmObject>(docPath); if (objAt == null) { Logger.Error(nameof(CdmManifestDefinition), this.Ctx as ResolveContext, $"Couldn't get object from path {docPath}", "saveDirtyLink"); return(false); } CdmDocumentDefinition docImp = objAt.InDocument as CdmDocumentDefinition; if (docImp != null) { if (docImp.IsDirty) { // save it with the same name if (await docImp.SaveAsAsync(docImp.Name, true, options) == false) { Logger.Error(nameof(CdmManifestDefinition), this.Ctx as ResolveContext, $"failed saving document {docImp.Name}", "saveDirtyLink"); return(false); } } } return(true); }
/// <summary> /// Sets a document's status to loading if the document needs to be loaded. /// </summary> /// <param name="docName">The document name.</param> /// <returns>Whether a document needs to be loaded.</returns> internal bool NeedToLoadDocument(string docName, ConcurrentDictionary <CdmDocumentDefinition, byte> docsNowLoaded) { bool needToLoad = false; CdmDocumentDefinition doc = null; lock (this.allDocuments) { if (this.docsNotLoaded.ContainsKey(docName) && !this.docsNotFound.ContainsKey(docName) && !this.docsCurrentlyLoading.ContainsKey(docName)) { // Set status to loading. this.docsNotLoaded.Remove(docName); // The document was loaded already, skip it. if (this.pathLookup.TryGetValue(docName.ToLower(), out var lookup)) { doc = lookup.Item2; } else { this.docsCurrentlyLoading.Add(docName, 1); needToLoad = true; } } } if (doc != null) { MarkDocumentAsLoadedOrFailed(doc, docName, docsNowLoaded); } return(needToLoad); }
/// <summary> /// Helper that fixes a path from local to absolute. /// Gets the object from that path then looks at the document where the object is found. /// If dirty, the document is saved with the original name. /// </summary> /// <param name="relative"></param> /// <param name="options"></param> /// <returns></returns> private async Task <bool> SaveDirtyLink(string relative, CopyOptions options) { // get the document object from the import string docPath = Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(relative, this); if (docPath == null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(SaveDirtyLink), this.AtCorpusPath, CdmLogCode.ErrValdnInvalidCorpusPath, relative); return(false); } CdmObject objAt = await Ctx.Corpus.FetchObjectAsync <CdmObject>(docPath); if (objAt == null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(SaveDirtyLink), this.AtCorpusPath, CdmLogCode.ErrPersistObjectNotFound, docPath); return(false); } CdmDocumentDefinition docImp = objAt.InDocument; if (docImp != null && docImp.IsDirty) { // save it with the same name if (await docImp.SaveAsAsync(docImp.Name, true, options) == false) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(SaveDirtyLink), this.AtCorpusPath, CdmLogCode.ErrDocEntityDocSavingFailure, docImp.Name); return(false); } } return(true); }
/// <summary> /// Helper that fixes a path from local to absolute.Gets the object from that path. /// Created from SaveDirtyLink in order to be able to save docs in parallel. /// Represents the part of SaveDirtyLink that could not be parallelized. /// </summary> /// <param name="relativePath"></param> /// <returns></returns> private async Task <Tuple <CdmDocumentDefinition, bool> > FetchDocumentDefinition(string relativePath) { // get the document object from the import string docPath = Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(relativePath, this); if (docPath == null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(FetchDocumentDefinition), this.AtCorpusPath, CdmLogCode.ErrValdnInvalidCorpusPath, relativePath); return(Tuple.Create((CdmDocumentDefinition)null, false)); } ResolveOptions resOpt = new ResolveOptions { ImportsLoadStrategy = ImportsLoadStrategy.Load }; CdmObject objAt = await Ctx.Corpus.FetchObjectAsync <CdmObject>(docPath, null, resOpt); if (objAt == null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(FetchDocumentDefinition), this.AtCorpusPath, CdmLogCode.ErrPersistObjectNotFound, docPath); return(Tuple.Create((CdmDocumentDefinition)null, false)); } CdmDocumentDefinition docImp = objAt.InDocument; return(Tuple.Create(docImp, true)); }
/// <summary> /// Marks a document for indexing if it has loaded successfully, or adds it to the list of documents not found if it failed to load. /// </summary> /// <param name="doc">The document that was loaded.</param> /// <param name="docName">The document name.</param> /// <param name="docsNowLoaded">The dictionary of documents that are now loaded.</param> /// <returns>Returns true if the document has loaded, false if it failed to load.</returns> internal bool MarkDocumentAsLoadedOrFailed(CdmDocumentDefinition doc, string docName, ConcurrentDictionary <CdmDocumentDefinition, byte> docsNowLoaded) { lock (this.allDocuments) { // Doc is no longer loading. this.docsCurrentlyLoading.Remove(docName); if (doc != null) { // Doc is now loaded. docsNowLoaded.TryAdd(doc, 1); // Doc needs to be indexed. this.docsNotIndexed.Add(doc, 1); doc.CurrentlyIndexing = true; return(true); } else { // The doc failed to load, so set doc as not found. this.docsNotFound.Add(docName, 1); return(false); } } }
public override CdmObject Copy(ResolveOptions resOpt = null) { if (resOpt == null) { resOpt = new ResolveOptions(this); } CdmDocumentDefinition c = new CdmDocumentDefinition(this.Ctx, this.Name) { Ctx = this.Ctx, IsDirty = true, FolderPath = this.FolderPath, Schema = this.Schema, JsonSchemaSemanticVersion = this.JsonSchemaSemanticVersion, }; foreach (var def in this.Definitions) { c.Definitions.Add(def); } foreach (var imp in this.Imports) { c.Imports.Add(imp); } return(c); }
/// <summary> /// Removes a document from the list of documents that are not indexed to mark it as indexed. /// </summary> /// <param name="doc">The document.</param> internal void MarkDocumentAsIndexed(CdmDocumentDefinition doc) { lock (this.allDocuments) { doc.CurrentlyIndexing = false; this.docsCurrentlyIndexing.Remove(doc); } }
/// <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); } } }
virtual internal async Task <bool> SaveLinkedDocuments(CopyOptions options = null) { List <CdmDocumentDefinition> docs = new List <CdmDocumentDefinition>(); if (options == null) { options = new CopyOptions(); } if (this.Imports != null) { // the only linked documents would be the imports foreach (CdmImport imp in this.Imports) { // get the document object from the import string docPath = Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(imp.CorpusPath, this); if (docPath == null) { Logger.Error((ResolveContext)this.Ctx, Tag, nameof(SaveLinkedDocuments), this.AtCorpusPath, CdmLogCode.ErrValdnInvalidCorpusPath, imp.CorpusPath); return(false); } try { CdmObject objAt = await Ctx.Corpus.FetchObjectAsync <CdmObject>(docPath); if (objAt == null) { Logger.Error((ResolveContext)this.Ctx, Tag, nameof(SaveLinkedDocuments), this.AtCorpusPath, CdmLogCode.ErrPersistObjectNotFound, imp.CorpusPath); return(false); } CdmDocumentDefinition docImp = objAt.InDocument; if (docImp != null && docImp.IsDirty) { docs.Add(docImp); } } catch (Exception e) { Logger.Error((ResolveContext)this.Ctx, Tag, nameof(SaveLinkedDocuments), this.AtCorpusPath, CdmLogCode.ErrPersistObjectNotFound, imp.CorpusPath + " " + e.Message); return(false); } } foreach (var docImp in docs) { if (await docImp.SaveAsAsync(docImp.Name, true, options) == false) { Logger.Error((ResolveContext)this.Ctx, Tag, nameof(SaveLinkedDocuments), this.AtCorpusPath, CdmLogCode.ErrDocImportSavingFailure, docImp.Name); return(false); } } } return(true); }
/// <summary> /// Saves CdmDocumentDefinition if dirty. /// Was created from SaveDirtyLink in order to be able to save docs in parallel. /// Represents the part of SaveDirtyLink that could be parallelized. /// </summary> /// <param name="docImp"></param> /// <param name="options"></param> /// <returns></returns> private async Task <bool> SaveDocumentIfDirty(CdmDocumentDefinition docImp, CopyOptions options) { if (docImp != null && docImp.IsDirty) { // save it with the same name if (await docImp.SaveAsAsync(docImp.Name, true, options) == false) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(SaveDocumentIfDirty), this.AtCorpusPath, CdmLogCode.ErrDocEntityDocSavingFailure, docImp.Name); return(false); } } return(true); }
/// <summary> /// Adds a document to the list of documents that are not indexed to mark it for indexing. /// </summary> /// <param name="doc">The document.</param> /// <returns>If the document needs indexing or not.</returns> internal bool MarkDocumentForIndexing(CdmDocumentDefinition doc) { lock (this.allDocuments) { if (doc.NeedsIndexing && !doc.CurrentlyIndexing) { // If the document was not indexed before and it's not currently being indexed. this.docsCurrentlyIndexing.Add(doc); doc.CurrentlyIndexing = true; } return(doc.NeedsIndexing); } }
/// <inheritdoc /> public override CdmObject Copy(ResolveOptions resOpt = null, CdmObject host = null) { if (resOpt == null) { resOpt = new ResolveOptions(this, this.Ctx.Corpus.DefaultResolutionDirectives); } CdmDocumentDefinition copy; if (host == null) { copy = new CdmDocumentDefinition(this.Ctx, this.Name); } else { copy = host as CdmDocumentDefinition; copy.Ctx = this.Ctx; copy.Name = this.Name; copy.Definitions.Clear(); copy.DeclarationsIndexed = false; copy.InternalDeclarations = new ConcurrentDictionary <string, CdmObjectBase>(); copy.NeedsIndexing = true; copy.Imports.Clear(); copy.ImportsIndexed = false; copy.ImportPriorities = null; } copy.InDocument = copy; copy.IsDirty = true; copy.FolderPath = this.FolderPath; copy.Schema = this.Schema; copy.JsonSchemaSemanticVersion = this.JsonSchemaSemanticVersion; copy.DocumentVersion = this.DocumentVersion; foreach (var def in this.Definitions) { copy.Definitions.Add(def); } foreach (var imp in this.Imports) { copy.Imports.Add(imp); } return(copy); }
/// <summary> /// Fetches the document from the folder path. /// </summary> /// <param name="path">The path.</param> /// <param name="adapter">The storage adapter where the document can be found.</param> /// <param name="forceReload">If true, reload the object from file and replace the current object with it.</param> /// <returns>The <see cref="CdmDocumentDefinition"/>.</returns> internal async Task <CdmDocumentDefinition> FetchDocumentFromFolderPathAsync(string objectPath, StorageAdapter adapter, bool forceReload = false) { string docName; string remainingPath; int first = objectPath.IndexOf('/', 0); if (first < 0) { remainingPath = ""; docName = objectPath; } else { remainingPath = objectPath.Slice(first + 1); docName = objectPath.Substring(0, first); } // got that doc? CdmDocumentDefinition doc = null; if (this.DocumentLookup.ContainsKey(docName)) { doc = this.DocumentLookup[docName]; if (!forceReload) { return(doc); } // remove them from the caches since they will be back in a moment if ((doc as CdmDocumentDefinition).IsDirty) { Logger.Warning(nameof(CdmFolderDefinition), this.Ctx, $"discarding changes in document: {doc.Name}"); } this.Documents.Remove(docName); } // go get the doc doc = await PersistenceLayer.LoadDocumentFromPathAsync(this, docName, doc); return(doc); }
/// <summary> /// Fetches the document from the folder path. /// </summary> /// <param name="path">The path.</param> /// <param name="adapter">The storage adapter where the document can be found.</param> /// <param name="forceReload">If true, reload the object from file and replace the current object with it.</param> /// <returns>The <see cref="CdmDocumentDefinition"/>.</returns> internal async Task <CdmDocumentDefinition> FetchDocumentFromFolderPathAsync(string objectPath, StorageAdapter adapter, bool forceReload = false, ResolveOptions resOpt = null) { string docName; string remainingPath; int first = objectPath.IndexOf('/', 0); if (first < 0) { remainingPath = ""; docName = objectPath; } else { remainingPath = objectPath.Slice(first + 1); docName = objectPath.Substring(0, first); } // got that doc? CdmDocumentDefinition doc = null; if (this.DocumentLookup.ContainsKey(docName)) { doc = this.DocumentLookup[docName]; if (!forceReload) { return(doc); } // remove them from the caches since they will be back in a moment if ((doc as CdmDocumentDefinition).IsDirty) { Logger.Warning(this.Ctx, Tag, nameof(FetchDocumentFromFolderPathAsync), this.AtCorpusPath, CdmLogCode.WarnDocChangesDiscarded, doc.Name); } this.Documents.Remove(docName); } // go get the doc doc = await this.Corpus.Persistence.LoadDocumentFromPathAsync(this, docName, doc, resOpt); return(doc); }
internal async Task <bool> IndexIfNeeded(ResolveOptions resOpt) { if (this.NeedsIndexing) { // make the corpus internal machinery pay attention to this document for this call CdmCorpusDefinition corpus = (this.Folder as CdmFolderDefinition).Corpus; CdmDocumentDefinition oldDoc = this; ConcurrentDictionary <CdmDocumentDefinition, byte> docsJustAdded = new ConcurrentDictionary <CdmDocumentDefinition, byte>(); ConcurrentDictionary <string, byte> docsNotFound = new ConcurrentDictionary <string, byte>(); await corpus.ResolveImportsAsync(this, docsJustAdded, docsNotFound); // maintain actual current doc (corpus.Ctx as ResolveContext).CurrentDoc = oldDoc; (this.Ctx.Corpus.Ctx as ResolveContext).CurrentDoc = oldDoc; docsJustAdded[this] = 1; return(corpus.IndexDocuments(resOpt, docsJustAdded)); } return(true); }
/// <inheritdoc /> public override CdmObject Copy(ResolveOptions resOpt = null, CdmObject host = null) { if (resOpt == null) { resOpt = new ResolveOptions(this); } CdmDocumentDefinition copy; if (host == null) { copy = new CdmDocumentDefinition(this.Ctx, this.Name); } else { copy = host as CdmDocumentDefinition; copy.Ctx = this.Ctx; copy.Name = this.Name; copy.Definitions.Clear(); copy.Imports.Clear(); } copy.InDocument = copy; copy.IsDirty = true; copy.FolderPath = this.FolderPath; copy.Schema = this.Schema; copy.JsonSchemaSemanticVersion = this.JsonSchemaSemanticVersion; foreach (var def in this.Definitions) { copy.Definitions.Add(def); } foreach (var imp in this.Imports) { copy.Imports.Add(imp); } return(copy); }
internal bool FinalizeAttributeContext(ResolveOptions resOpt, string pathStart, CdmDocumentDefinition docHome, CdmDocumentDefinition docFrom, string monikerForDocFrom, bool finished = false) { // run over the attCtx tree again and 'fix it' fix means replace the parent and lineage reference path strings with // final values from new home and set the inDocument and fix any references to definitions // keep track of the paths to documents for fixing symbol refs. expensive to compute Dictionary <CdmDocumentDefinition, string> foundDocPaths = new Dictionary <CdmDocumentDefinition, string>(); if (!string.IsNullOrWhiteSpace(monikerForDocFrom)) { monikerForDocFrom = $"{monikerForDocFrom}/"; } // first step makes sure every node in the tree has a good path for itself and a good document // second pass uses the paths from nodes to fix references to other nodes Action <CdmObject, string> FixAttCtxNodePaths = null; FixAttCtxNodePaths = (subItem, pathFrom) => { CdmAttributeContext ac = subItem as CdmAttributeContext; if (ac == null) { return; } ac.InDocument = docHome; // fix up the reference to defintion. need to get path from this document to the // add moniker if this is a reference if (ac.Definition != null) { ac.Definition.InDocument = docHome; if (ac.Definition?.NamedReference != null) { // need the real path to this thing from the explicitRef held in the portable reference // the real path is {monikerFrom/}{path from 'from' document to document holding the explicit ref/{declaredPath of explicitRef}} // if we have never looked up the path between docs, do that now CdmDocumentDefinition docFromDef = ac.Definition.ExplicitReference.InDocument; // if all parts not set, this is a broken portal ref! string pathBetweenDocs; if (foundDocPaths.TryGetValue(docFromDef, out pathBetweenDocs) == false) { pathBetweenDocs = docFrom.ImportPathToDoc(docFromDef); if (pathBetweenDocs == null) { // hmm. hmm. pathBetweenDocs = ""; } foundDocPaths[docFrom] = pathBetweenDocs; } (ac.Definition as CdmObjectReferenceBase).LocalizePortableReference(resOpt, $"{monikerForDocFrom}{pathBetweenDocs}"); } } // doc of parent ref if (ac.Parent != null) { ac.Parent.InDocument = docHome; } // doc of lineage refs if (ac.Lineage != null && ac.Lineage.Count > 0) { foreach (var lin in ac.Lineage) { lin.InDocument = docHome; } } string divider = (string.IsNullOrEmpty(ac.AtCorpusPath) || !pathFrom.EndsWith("/")) ? "/" : ""; ac.AtCorpusPath = $"{pathFrom}{divider}{ac.Name}"; if (ac.Contents == null || ac.Contents.Count == 0) { return; } // look at all children foreach (var subSub in ac.Contents) { if (subSub.ObjectType == CdmObjectType.AttributeContextDef) { FixAttCtxNodePaths(subSub, ac.AtCorpusPath); } } }; FixAttCtxNodePaths(this, pathStart); // now fix any lineage and parent references Action <CdmObject> FixAttCtxNodeLineage = null; FixAttCtxNodeLineage = (subItem) => { CdmAttributeContext ac = subItem as CdmAttributeContext; if (ac == null) { return; } // for debugLineage, write id //ac.Name = $"{ac.Name}({ac.Id})"; // parent ref if (ac.Parent != null && ac.Parent.ExplicitReference != null) { ac.Parent.NamedReference = (ac.Parent.ExplicitReference as CdmAttributeContext).AtCorpusPath; // for debugLineage, write id //ac.Parent.NamedReference = $"{ (ac.Parent.ExplicitReference as CdmAttributeContext).AtCorpusPath}({ac.Parent.ExplicitReference.Id})"; } // fix lineage if (ac.Lineage != null && ac.Lineage.Count > 0) { foreach (var lin in ac.Lineage) { if (lin.ExplicitReference != null) { // use the new path as the ref lin.NamedReference = (lin.ExplicitReference as CdmAttributeContext).AtCorpusPath; // for debugLineage, write id //lin.NamedReference = $"{ (lin.ExplicitReference as CdmAttributeContext).AtCorpusPath}({lin.ExplicitReference.Id})"; } } } if (ac.Contents == null || ac.Contents.Count == 0) { return; } // look at all children foreach (var subSub in ac.Contents) { FixAttCtxNodeLineage(subSub); } }; FixAttCtxNodeLineage(this); if (finished) { resOpt.SaveResolutionsOnCopy = false; resOpt.MapOldCtxToNewCtx = null; } return(true); }
/// <summary> /// Constructs a CdmImportCollection. /// </summary> /// <param name="ctx">The context.</param> /// <param name="owner">The owner of the collection. This class is customized for <see cref="CdmDocumentDefinition"/>.</param> public CdmImportCollection(CdmCorpusContext ctx, CdmDocumentDefinition owner) : base(ctx, owner, Enums.CdmObjectType.Import) { }
private int PrioritizeImports(HashSet <CdmDocumentDefinition> processedSet, IDictionary <CdmDocumentDefinition, int> priorityMap, int sequence, IDictionary <string, CdmDocumentDefinition> monikerMap, bool skipMonikered = false) { // goal is to make a map from the reverse order of imports (breadth first) to the first (aka last) sequence number in that list. // This gives the semantic that the 'last/shallowest' definition for a duplicate symbol wins, // the lower in this list a document shows up, the higher priority its definitions are for resolving conflicts. // for 'moniker' imports, keep track of the 'last/shallowest' use of each moniker tag. // if already in list, don't do this again if (processedSet.Contains(this)) { return(sequence); } processedSet.Add(this); if (this.Imports != null) { // first add the imports done at this level only int l = this.Imports.Count; // reverse order for (int i = l - 1; i >= 0; i--) { CdmImport imp = this.Imports.AllItems[i]; CdmDocumentDefinition impDoc = imp.ResolvedDocument as CdmDocumentDefinition; // don't add the moniker imports to the priority list bool isMoniker = !string.IsNullOrWhiteSpace(imp.Moniker); if (imp.ResolvedDocument != null && !isMoniker) { if (priorityMap.ContainsKey(impDoc) == false) { // add doc priorityMap.Add(impDoc, sequence); sequence++; } } } // now add the imports of the imports for (int i = l - 1; i >= 0; i--) { CdmImport imp = this.Imports.AllItems[i]; CdmDocumentDefinition impDoc = imp.ResolvedDocument as CdmDocumentDefinition; // don't add the moniker imports to the priority list bool isMoniker = !string.IsNullOrWhiteSpace(imp.Moniker); if (impDoc?.ImportPriorities != null) { // lucky, already done so avoid recursion and copy ImportPriorities impPriSub = impDoc.GetImportPriorities(); impPriSub.ImportPriority.Remove(impDoc); // because already added above foreach (var ip in impPriSub.ImportPriority) { if (priorityMap.ContainsKey(ip.Key) == false) { // add doc priorityMap.Add(ip.Key, sequence); sequence++; } } if (!isMoniker) { foreach (var mp in impPriSub.MonikerPriorityMap) { monikerMap[mp.Key] = mp.Value; } } } else if (impDoc != null) { // skip the monikered imports from here if this is a monikered import itself and we are only collecting the dependencies sequence = impDoc.PrioritizeImports(processedSet, priorityMap, sequence, monikerMap, isMoniker); } } // skip the monikered imports from here if this is a monikered import itself and we are only collecting the dependencies if (!skipMonikered) { // moniker imports are prioritized by the 'closest' use of the moniker to the starting doc. so last one found in this recursion for (int i = 0; i < l; i++) { CdmImport imp = this.Imports.AllItems[i]; if (imp.ResolvedDocument != null && imp.Moniker != null) { monikerMap[imp.Moniker] = imp.ResolvedDocument as CdmDocumentDefinition; } } } } return(sequence); }
internal string ImportPathToDoc(CdmDocumentDefinition docDest) { HashSet <CdmDocumentDefinition> avoidLoop = new HashSet <CdmDocumentDefinition>(); Func <CdmDocumentDefinition, string, string> InternalImportPathToDoc = null; InternalImportPathToDoc = (docCheck, path) => { if (docCheck == docDest) { return(""); } if (avoidLoop.Contains(docCheck)) { return(null); } avoidLoop.Add(docCheck); // if the docDest is one of the monikered imports of docCheck, then add the moniker and we are cool if (docCheck.ImportPriorities?.MonikerPriorityMap?.Count > 0) { foreach (var monPair in docCheck.ImportPriorities?.MonikerPriorityMap) { if (monPair.Value == docDest) { return($"{path}{monPair.Key}/"); } } } // ok, what if the document can be reached directly from the imports here ImportInfo impInfo = null; if (docCheck.ImportPriorities?.ImportPriority?.TryGetValue(docDest, out impInfo) == false) { impInfo = null; } if (impInfo != null && impInfo.IsMoniker == false) { // good enough return(path); } // still nothing, now we need to check those docs deeper if (docCheck.ImportPriorities?.MonikerPriorityMap?.Count > 0) { foreach (var monPair in docCheck.ImportPriorities?.MonikerPriorityMap) { string pathFound = InternalImportPathToDoc(monPair.Value, $"{path}{monPair.Key}/"); if (pathFound != null) { return(pathFound); } } } if (docCheck.ImportPriorities?.ImportPriority?.Count > 0) { foreach (var impInfoPair in docCheck.ImportPriorities.ImportPriority) { if (!impInfoPair.Value.IsMoniker) { string pathFound = InternalImportPathToDoc(impInfoPair.Key, path); if (pathFound != null) { return(pathFound); } } } } return(null); }; return(InternalImportPathToDoc(this, "")); }
override internal async Task <bool> SaveLinkedDocuments(CopyOptions options = null) { HashSet <string> links = new HashSet <string>(); if (options == null) { options = new CopyOptions(); } if (this.Imports != null) { this.Imports.ToList().ForEach(x => links.Add(x.CorpusPath)); } if (this.Entities != null) { // only the local entity declarations please foreach (CdmEntityDeclarationDefinition def in this.Entities) { if (def.ObjectType == CdmObjectType.LocalEntityDeclarationDef) { CdmLocalEntityDeclarationDefinition defImp = def as CdmLocalEntityDeclarationDefinition; links.Add(defImp.EntityPath); // also, partitions can have their own schemas if (defImp.DataPartitions != null) { foreach (CdmDataPartitionDefinition part in defImp.DataPartitions) { if (part.SpecializedSchema != null) { links.Add(part.SpecializedSchema); } } } // so can patterns if (defImp.DataPartitionPatterns != null) { foreach (CdmDataPartitionPatternDefinition part in defImp.DataPartitionPatterns) { if (part.SpecializedSchema != null) { links.Add(part.SpecializedSchema); } } } } } } // Get all Cdm documents sequentially List <CdmDocumentDefinition> docs = new List <CdmDocumentDefinition>(); foreach (var link in links) { CdmDocumentDefinition document = await FetchDocumentDefinition(link); if (document == null) { return(false); } docs.Add(document); } // Save all dirty Cdm documents in parallel IEnumerable <Task <bool> > tasks = docs.Select(doc => SaveDocumentIfDirty(doc, options)); var results = await Task.WhenAll(tasks); if (this.SubManifests != null) { foreach (var subDeclaration in this.SubManifests) { CdmManifestDefinition subManifest = await FetchDocumentDefinition(subDeclaration.Definition) as CdmManifestDefinition; if (subManifest == null || !await SaveDocumentIfDirty(subManifest, options)) { return(false); } } } return(true); }
/// <summary> /// Checks if the trait argumnet value matchs the data type defined on the trait parameter /// </summary> /// <param name="resOpt"></param> /// <param name="wrtDoc"></param> /// <param name="argumentValue"></param> /// <returns></returns> internal dynamic ConstTypeCheck(ResolveOptions resOpt, CdmDocumentDefinition wrtDoc, dynamic argumentValue) { ResolveContext ctx = this.Ctx as ResolveContext; dynamic replacement = argumentValue; // if parameter type is entity, then the value should be an entity or ref to one // same is true of 'dataType' dataType if (this.DataTypeRef == null) { return(replacement); } CdmDataTypeDefinition dt = this.DataTypeRef.FetchObjectDefinition <CdmDataTypeDefinition>(resOpt); if (dt == null) { Logger.Error(ctx, Tag, nameof(ConstTypeCheck), this.AtCorpusPath, CdmLogCode.ErrUnrecognizedDataType, this.Name); return(null); } // compare with passed in value or default for parameter dynamic pValue = argumentValue; if (pValue == null) { pValue = this.DefaultValue; replacement = pValue; } if (pValue != null) { if (dt.IsDerivedFrom("cdmObject", resOpt)) { List <CdmObjectType> expectedTypes = new List <CdmObjectType>(); string expected = null; if (dt.IsDerivedFrom("entity", resOpt)) { expectedTypes.Add(CdmObjectType.ConstantEntityDef); expectedTypes.Add(CdmObjectType.EntityRef); expectedTypes.Add(CdmObjectType.EntityDef); expectedTypes.Add(CdmObjectType.ProjectionDef); expected = "entity"; } else if (dt.IsDerivedFrom("attribute", resOpt)) { expectedTypes.Add(CdmObjectType.AttributeRef); expectedTypes.Add(CdmObjectType.TypeAttributeDef); expectedTypes.Add(CdmObjectType.EntityAttributeDef); expected = "attribute"; } else if (dt.IsDerivedFrom("dataType", resOpt)) { expectedTypes.Add(CdmObjectType.DataTypeRef); expectedTypes.Add(CdmObjectType.DataTypeDef); expected = "dataType"; } else if (dt.IsDerivedFrom("purpose", resOpt)) { expectedTypes.Add(CdmObjectType.PurposeRef); expectedTypes.Add(CdmObjectType.PurposeDef); expected = "purpose"; } else if (dt.IsDerivedFrom("traitGroup", resOpt)) { expectedTypes.Add(CdmObjectType.TraitGroupRef); expectedTypes.Add(CdmObjectType.TraitGroupDef); expected = "traitGroup"; } else if (dt.IsDerivedFrom("trait", resOpt)) { expectedTypes.Add(CdmObjectType.TraitRef); expectedTypes.Add(CdmObjectType.TraitDef); expected = "trait"; } else if (dt.IsDerivedFrom("attributeGroup", resOpt)) { expectedTypes.Add(CdmObjectType.AttributeGroupRef); expectedTypes.Add(CdmObjectType.AttributeGroupDef); expected = "attributeGroup"; } if (expectedTypes.Count == 0) { Logger.Error(ctx, Tag, nameof(ConstTypeCheck), wrtDoc.FolderPath + wrtDoc.Name, CdmLogCode.ErrUnexpectedDataType, this.Name); } // if a string constant, resolve to an object ref. CdmObjectType foundType = CdmObjectType.Error; Type pValueType = pValue.GetType(); if (typeof(CdmObject).IsAssignableFrom(pValueType)) { foundType = (pValue as CdmObject).ObjectType; } string foundDesc = ctx.RelativePath; if (!(pValue is CdmObject)) { // pValue is a string or JValue pValue = (string)pValue; if (pValue == "this.attribute" && expected == "attribute") { // will get sorted out later when resolving traits foundType = CdmObjectType.AttributeRef; } else { foundDesc = pValue; int seekResAtt = CdmObjectReferenceBase.offsetAttributePromise(pValue); if (seekResAtt >= 0) { // get an object there that will get resolved later after resolved attributes replacement = new CdmAttributeReference(ctx, pValue, true); (replacement as CdmAttributeReference).Ctx = ctx; (replacement as CdmAttributeReference).InDocument = wrtDoc; foundType = CdmObjectType.AttributeRef; } else { CdmObjectBase lu = ctx.Corpus.ResolveSymbolReference(resOpt, wrtDoc, pValue, CdmObjectType.Error, retry: true); if (lu != null) { if (expected == "attribute") { replacement = new CdmAttributeReference(ctx, pValue, true); (replacement as CdmAttributeReference).Ctx = ctx; (replacement as CdmAttributeReference).InDocument = wrtDoc; foundType = CdmObjectType.AttributeRef; } else { replacement = lu; foundType = (replacement as CdmObject).ObjectType; } } } } } if (expectedTypes.IndexOf(foundType) == -1) { Logger.Error(ctx, Tag, nameof(ConstTypeCheck), wrtDoc.AtCorpusPath, CdmLogCode.ErrResolutionFailure, this.Name, expected, foundDesc, expected); } else { Logger.Info(ctx, Tag, nameof(ConstTypeCheck), wrtDoc.AtCorpusPath, $"resolved '{foundDesc}'"); } } } return(replacement); }
private int PrioritizeImports(HashSet <CdmDocumentDefinition> processedSet, ImportPriorities importPriorities, int sequence, bool skipMonikered) { // goal is to make a map from the reverse order of imports (breadth first) to the first (aka last) sequence number in that list. // This gives the semantic that the 'last/shallowest' definition for a duplicate symbol wins, // the lower in this list a document shows up, the higher priority its definitions are for resolving conflicts. // for 'moniker' imports, keep track of the 'last/shallowest' use of each moniker tag. // maps document to priority. IDictionary <CdmDocumentDefinition, int> priorityMap = importPriorities.ImportPriority; // maps moniker to document. IDictionary <string, CdmDocumentDefinition> monikerMap = importPriorities.MonikerPriorityMap; // if already in list, don't do this again if (processedSet.Contains(this)) { // if the first document in the priority map is this then the document was the starting point of the recursion. // and if this document is present in the processedSet we know that there is a cicular list of imports. if (priorityMap.ContainsKey(this) && priorityMap[this] == 0) { importPriorities.hasCircularImport = true; } return(sequence); } processedSet.Add(this); if (this.Imports != null) { var reversedImports = this.Imports.Reverse(); // first add the imports done at this level only in reverse order. foreach (var imp in reversedImports) { var impDoc = imp.Document; bool isMoniker = !string.IsNullOrWhiteSpace(imp.Moniker); // don't add the moniker imports to the priority list. if (impDoc != null && !isMoniker && !priorityMap.ContainsKey(impDoc)) { // add doc. priorityMap.Add(impDoc, sequence); sequence++; } } // now add the imports of the imports. foreach (var imp in reversedImports) { CdmDocumentDefinition impDoc = imp.Document; bool isMoniker = !string.IsNullOrWhiteSpace(imp.Moniker); // if the document has circular imports its order on the impDoc.ImportPriorities list is not correct. // since the document itself will always be the first one on the list. if (impDoc?.ImportPriorities != null && impDoc?.ImportPriorities.hasCircularImport == false) { // lucky, already done so avoid recursion and copy. ImportPriorities impPriSub = impDoc.GetImportPriorities(); impPriSub.ImportPriority.Remove(impDoc); // because already added above. foreach (var ip in impPriSub.ImportPriority) { if (priorityMap.ContainsKey(ip.Key) == false) { // add doc. priorityMap.Add(ip.Key, sequence); sequence++; } } // if the import is not monikered then merge its monikerMap to this one. if (!isMoniker) { foreach (var mp in impPriSub.MonikerPriorityMap) { monikerMap[mp.Key] = mp.Value; } } } else if (impDoc != null) { // skip the monikered imports from here if this is a monikered import itself and we are only collecting the dependencies. sequence = impDoc.PrioritizeImports(processedSet, importPriorities, sequence, isMoniker); } } // skip the monikered imports from here if this is a monikered import itself and we are only collecting the dependencies. if (!skipMonikered) { // moniker imports are prioritized by the 'closest' use of the moniker to the starting doc. so last one found in this recursion. foreach (var imp in this.Imports) { bool isMoniker = !string.IsNullOrWhiteSpace(imp.Moniker); if (imp.Document != null && isMoniker) { monikerMap[imp.Moniker] = imp.Document; } } } } return(sequence); }
/// <summary> /// Constructs a CdmDefinitionCollection. /// </summary> /// <param name="ctx">The context.</param> /// <param name="owner">The owner of the collection. Has to be a <see cref="CdmDocumentDefinition"/>.</param> public CdmDefinitionCollection(CdmCorpusContext ctx, CdmDocumentDefinition owner) : base(ctx, owner, CdmObjectType.EntityDef) { }
/// <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); }