/// <inheritdoc /> public override CdmObject Copy(ResolveOptions resOpt = null, CdmObject host = null) { // since we need to call the base copy which will only return a document when there is no host, make a fake host here CdmManifestDefinition tempHost = host as CdmManifestDefinition; if (tempHost == null) { tempHost = new CdmManifestDefinition(this.Ctx, this.ManifestName); } CdmManifestDefinition copy = base.Copy(resOpt, tempHost) as CdmManifestDefinition; copy.ManifestName = this.ManifestName; copy.Explanation = this.Explanation; copy.LastFileStatusCheckTime = this.LastFileStatusCheckTime; copy.LastFileModifiedTime = this.LastFileModifiedTime; copy.LastChildFileModifiedTime = this.LastChildFileModifiedTime; copy.Entities.Clear(); foreach (var ent in this.Entities) { copy.Entities.Add(ent.Copy(resOpt) as CdmEntityDeclarationDefinition); } copy.Relationships.Clear(); foreach (var rel in this.Relationships) { copy.Relationships.Add(rel.Copy(resOpt) as CdmE2ERelationship); } copy.SubManifests.Clear(); foreach (var man in this.SubManifests) { copy.SubManifests.Add(man.Copy(resOpt) as CdmManifestDeclarationDefinition); } copy.ExhibitsTraits.Clear(); foreach (var et in this.ExhibitsTraits) { copy.ExhibitsTraits.Add(et.Copy() as CdmTraitReferenceBase); } return(copy); }
/// <summary> /// finds and returns an entity object from an EntityDeclaration object that probably comes from a manifest /// </summary> /// <param name="entity"></param> /// <param name="manifest"></param> /// <returns></returns> internal async Task <CdmEntityDefinition> GetEntityFromReference(CdmEntityDeclarationDefinition entity, CdmManifestDefinition manifest) { string entityPath = await this.GetEntityPathFromDeclaration(entity, manifest); CdmEntityDefinition result = await this.Ctx.Corpus.FetchObjectAsync <CdmEntityDefinition>(entityPath); if (result == null) { Logger.Error(this.Ctx, Tag, nameof(GetEntityFromReference), this.AtCorpusPath, CdmLogCode.ErrResolveEntityFailure, entityPath); } return(result); }
/// <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.Folder == 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.Folder.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; } 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) { var entDef = await this.GetEntityFromReference(entity, this); if (entDef == null) { Logger.Error(this.Ctx as ResolveContext, Tag, nameof(CreateResolvedManifestAsync), this.AtCorpusPath, CdmLogCode.ErrResolveEntityRefError); return(null); } if (entDef.InDocument.Folder == 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.Folder.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) as CdmFolderDefinition; 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); } }
// finds and returns an entity object from an EntityDeclaration object that probably comes from a manifest internal async Task <CdmEntityDefinition> GetEntityFromReference(CdmEntityDeclarationDefinition entity, CdmManifestDefinition manifest) { string entityPath = await this.GetEntityPathFromDeclaration(entity, manifest); CdmEntityDefinition result = await this.Ctx.Corpus.FetchObjectAsync <CdmEntityDefinition>(entityPath); if (result == null) { Logger.Error(nameof(CdmManifestDefinition), this.Ctx, $"failed to resolve entity {entityPath}", "GetEntityFromReference"); } return(result); }
/// <summary> /// Populates the relationships that the entities in the current manifest are involved in. /// </summary> public async Task PopulateManifestRelationshipsAsync(CdmRelationshipDiscoveryStyle option = CdmRelationshipDiscoveryStyle.All) { 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) { string corpusPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(subManifestDef.Definition, this); CdmManifestDefinition subManifest = await this.Ctx.Corpus.FetchObjectAsync <CdmManifestDefinition>(corpusPath); await(subManifest as CdmManifestDefinition).PopulateManifestRelationshipsAsync(option); } } } }
/// 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). public async Task <CdmManifestDefinition> CreateResolvedManifestAsync(string newManifestName, string newEntityDocumentNameFormat) { if (this.Entities == null) { 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.Folder.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(nameof(CdmManifestDefinition), this.Ctx as ResolveContext, $"New folder for manifest not found {newFolderPath}", "CreateResolvedManifestAsync"); return(null); } newManifestName = newManifestName.Substring(resolvedManifestPathSplit); } else { resolvedManifestFolder = this.Owner as CdmFolderDefinition; } Logger.Debug(nameof(CdmManifestDefinition), this.Ctx as ResolveContext, $"resolving manifest {sourceManifestPath}", "CreateResolvedManifestAsync"); // 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 var resolvedManifest = new CdmManifestDefinition(this.Ctx, newManifestName); // add the new document to the folder if (resolvedManifestFolder.Documents.Add(resolvedManifest) == null) { // when would this happen? return(null); } // mapping from entity path to resolved entity path for translating relationhsip paths Dictionary <string, string> resEntMap = new Dictionary <string, string>(); foreach (var entity in this.Entities) { var entDef = await this.GetEntityFromReference(entity, this); if (entDef == null) { Logger.Error(nameof(CdmManifestDefinition), this.Ctx as ResolveContext, $"Unable to get entity from reference", "CreateResolvedManifestAsync"); 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.Folder.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) as CdmFolderDefinition; if (folder == null) { Logger.Error(nameof(CdmManifestDefinition), this.Ctx as ResolveContext, $"New folder not found {newDocumentPath}", "CreateResolvedManifestAsync"); return(null); } // Next create the resolved entity var resOpt = new ResolveOptions { WrtDoc = entDef.InDocument, Directives = new AttributeResolutionDirectiveSet(new HashSet <string> { "normalized", "referenceOnly" }) }; Logger.Debug(nameof(CdmManifestDefinition), this.Ctx as ResolveContext, $" resolving entity {sourceEntityFullPath} to document {newDocumentFullPath}", "CreateResolvedManifestAsync"); 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); // absolute path is needed for generating relationships var absoluteEntPath = this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(result.EntityPath, resolvedManifest); resEntMap.Add(this.Ctx.Corpus.Storage.CreateAbsoluteCorpusPath(entDef.AtCorpusPath, entDef.InDocument), absoluteEntPath); } Logger.Debug(nameof(CdmManifestDefinition), this.Ctx as ResolveContext, $" calculating relationships", "CreateResolvedManifestAsync"); // calculate the entity graph for just this manifest and any submanifests await(this.Ctx.Corpus as CdmCorpusDefinition)._CalculateEntityGraphAsync(resolvedManifest, resEntMap); // 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); }
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); }