Example #1
0
        public void TestProfilesTypesJson()
        {
            const string path = @"TestData\profiles-types.json";

            var summaries = ArtifactSummaryGenerator.Generate(path);

            Assert.IsNotNull(summaries);
            Assert.AreNotEqual(0, summaries.Count);
            for (int i = 0; i < summaries.Count; i++)
            {
                var summary = summaries[i];
                Assert.IsFalse(summary.IsFaulted);

                // Common properties
                Assert.AreEqual(path, summary.Origin);
                Assert.AreEqual(ResourceType.StructureDefinition.GetLiteral(), summary.ResourceTypeName);
                Assert.IsTrue(summary.ResourceType == ResourceType.StructureDefinition);

                // Conformance resource properties
                Assert.IsNotNull(summary.GetConformanceCanonicalUrl());
                Assert.IsTrue(summary.GetConformanceCanonicalUrl().ToString().StartsWith("http://hl7.org/fhir/StructureDefinition/"));
                Assert.IsNotNull(summary.GetConformanceName());
                Assert.AreEqual(ConformanceResourceStatus.Draft.GetLiteral(), summary.GetConformanceStatus());

                //Debug.WriteLine($"{summary.ResourceType} | {summary.Canonical()} | {summary.Name()}");

                // StructureDefinition properties
                Assert.IsNotNull(summary.GetStructureDefinitionFhirVersion());
                Assert.AreEqual(ModelInfo.Version, summary.GetStructureDefinitionFhirVersion());

                Assert.AreEqual(StructureDefinition.StructureDefinitionKind.Datatype.GetLiteral(), summary.GetStructureDefinitionKind());
                // If this is a constraining StructDef, then Base should also be specified
                Assert.IsTrue(summary.GetStructureDefinitionConstrainedType() == null || summary.GetStructureDefinitionBase() != null);
            }
        }
Example #2
0
 /// <summary>
 /// Create a new <see cref="DirectorySource"/> instance to browse and resolve resources
 /// from the specified <paramref name="contentDirectory"/>
 /// and using the default <see cref="DirectorySourceSettings"/>.
 /// </summary>
 /// <para>
 /// Initialization is thread-safe. The source ensures that only a single thread will
 /// collect the artifact summaries, while any other threads will block.
 /// </para>
 /// <param name="contentDirectory">The file path of the target directory.</param>
 /// <exception cref="ArgumentNullException">The specified argument is <c>null</c>.</exception>
 public DirectorySource(string contentDirectory)
 {
     ContentDirectory  = contentDirectory ?? throw Error.ArgumentNull(nameof(contentDirectory));
     _settings         = new DirectorySourceSettings();
     _summaryGenerator = new ArtifactSummaryGenerator(_settings.ExcludeSummariesForUnknownArtifacts);
     // Initialize Lazy
     Refresh();
 }
Example #3
0
 /// <summary>
 /// Create a new <see cref="DirectorySource"/> instance to browse and resolve resources
 /// from the specified <paramref name="contentDirectory"/>
 /// and using the specified <see cref="DirectorySourceSettings"/>.
 /// <para>
 /// Initialization is thread-safe. The source ensures that only a single thread will
 /// collect the artifact summaries, while any other threads will block.
 /// </para>
 /// </summary>
 /// <param name="contentDirectory">The file path of the target directory.</param>
 /// <param name="settings">Configuration settings that control the behavior of the <see cref="DirectorySource"/>.</param>
 /// <exception cref="ArgumentNullException">One of the specified arguments is <c>null</c>.</exception>
 public DirectorySource(string contentDirectory, DirectorySourceSettings settings)
 {
     ContentDirectory = contentDirectory ?? throw Error.ArgumentNull(nameof(contentDirectory));
     // [WMR 20171023] Always copy the specified settings, to prevent shared state
     _settings         = new DirectorySourceSettings(settings);
     _summaryGenerator = new ArtifactSummaryGenerator(_settings.ExcludeSummariesForUnknownArtifacts);
     // Initialize Lazy
     Refresh();
 }
Example #4
0
        ArtifactSummary assertSummary(string path, params ArtifactSummaryHarvester[] harvesters)
        {
            var summaries = ArtifactSummaryGenerator.Generate(path, harvesters);

            Assert.IsNotNull(summaries);
            Assert.AreEqual(1, summaries.Count);
            var summary = summaries[0];

            Assert.IsFalse(summary.IsFaulted);
            Assert.AreEqual(path, summary.Origin);
            return(summary);
        }
        public void TestLoadResourceFromZipStream()
        {
            // Harvest summaries and load artifact straight from core ZIP archive

            // Use XmlNavigatorStream to navigate resources stored inside a zip file
            // ZipDeflateStream does not support seeking (forward-only stream)
            // Therefore this only works for the XmlNavigatorStream, as the ctor does NOT (need to) call Reset()
            // JsonNavigatorStream cannot support zip streams; ctor needs to call Reset after scanning resourceType

            ArtifactSummary corePatientSummary;
            var             corePatientUrl = ModelInfo.CanonicalUriForFhirCoreType(FHIRDefinedType.Patient);
            string          zipEntryName   = "profiles-resources.xml";

            // Generate summaries from core ZIP resource definitions (extract in memory)
            using (var archive = ZipFile.Open(ZipSource.SpecificationZipFileName, ZipArchiveMode.Read))
            {
                var entry = archive.Entries.FirstOrDefault(e => e.Name == zipEntryName);
                Assert.IsNotNull(entry);

                using (var entryStream = entry.Open())
                    using (var navStream = new XmlNavigatorStream(entryStream))
                    {
                        var summaries = ArtifactSummaryGenerator.Generate(navStream);
                        Assert.IsNotNull(summaries);
                        corePatientSummary = summaries.FindConformanceResources(corePatientUrl).FirstOrDefault();
                    }
            }

            Assert.IsNotNull(corePatientSummary);
            Assert.AreEqual(ResourceType.StructureDefinition, corePatientSummary.ResourceType);
            Assert.AreEqual(corePatientUrl, corePatientSummary.GetConformanceCanonicalUrl());

            // Load core Patient resource from ZIP (extract in memory)
            using (var archive = ZipFile.Open(ZipSource.SpecificationZipFileName, ZipArchiveMode.Read))
            {
                var entry = archive.Entries.FirstOrDefault(e => e.Name == zipEntryName);
                using (var entryStream = entry.Open())
                    using (var navStream = new XmlNavigatorStream(entryStream))
                    {
                        var nav = navStream.Current;
                        if (nav != null)
                        {
                            // Parse target resource from navigator
                            var parser      = new BaseFhirParser();
                            var corePatient = parser.Parse <StructureDefinition>(nav);
                            Assert.IsNotNull(corePatient);
                            Assert.AreEqual(corePatientUrl, corePatient.Url);
                        }
                    }
            }
        }
 // Internal ctor
 DirectorySource(string contentDirectory, DirectorySourceSettings settings, bool cloneSettings)
 {
     ContentDirectory = contentDirectory ?? throw Error.ArgumentNull(nameof(contentDirectory));
     // [WMR 20171023] Clone specified settings to prevent shared state
     _settings = settings != null
         ? (cloneSettings ? new DirectorySourceSettings(settings) : settings)
         : DirectorySourceSettings.CreateDefault();
     _summaryGenerator = new ArtifactSummaryGenerator(_settings.ExcludeSummariesForUnknownArtifacts);
     _navigatorFactory = new ConfigurableNavigatorStreamFactory(_settings.XmlParserSettings, _settings.JsonParserSettings)
     {
         ThrowOnUnsupportedFormat = false
     };
     // Initialize Lazy
     Refresh();
 }
Example #7
0
        /// <summary>Request a re-scan of one or more specific  artifact file(s).</summary>
        /// <param name="filePaths">One or more artifact file path(s).</param>
        /// <remarks>
        /// Notify the <see cref="DirectorySource"/> that specific files in the current
        /// <see cref="ContentDirectory"/> have been created, updated or deleted.
        /// The <paramref name="filePaths"/> argument should specify an array of artifact
        /// file paths that (may) have been deleted, modified or created.
        /// The source will:
        /// <list type="number">
        /// <item>
        /// <description>
        /// Remove any existing summary information for the specified artifacts, if available.
        /// </description>
        /// </item>
        /// <item>
        /// <description>
        /// Try to harvest new summary information from the specified artifacts, if they still exist.
        /// </description>
        /// </item>
        /// </list>
        /// </remarks>
        public void Refresh(params string[] filePaths)
        {
            if (filePaths == null || filePaths.Length == 0)
            {
                throw Error.ArgumentNullOrEmpty(nameof(filePaths));
            }
#if THREADSAFE
            lock (_syncRoot)
#endif
            {
                if (_artifactFilePaths == null)
                {
                    // Cache is empty, perform full scan on demand
                    return;
                }
                // Update file paths
                foreach (var filePath in filePaths)
                {
                    // Update file paths
                    bool exists = File.Exists(filePath);
                    if (!exists)
                    {
                        _artifactFilePaths.Remove(filePath);
                    }
                    else if (!_artifactFilePaths.Contains(filePath))
                    {
                        _artifactFilePaths.Add(filePath);
                    }

                    // Update summaries (if cached)
                    if (_artifactSummaries != null)
                    {
                        _artifactSummaries.RemoveAll(s => StringComparer.OrdinalIgnoreCase.Equals(filePath, s.Origin));
                        if (exists)
                        {
                            // May fail, e.g. if another thread/process has deleted the target file
                            // Generate will catch exceptions and return empty list
                            var summaries = ArtifactSummaryGenerator.Generate(filePath, _settings.SummaryDetailsHarvesters);
                            _artifactSummaries.AddRange(summaries);
                        }
                        // [WMR 20180409] No need to recreate r/o wrapper, automatically synchronized
                        // _roArtifactSummaries = _artifactSummaries.AsReadOnly();
                    }
                }
            }
        }
        public void TestProfilesResourcesXml()
        {
            const string path = @"TestData\profiles-resources.xml";

            var summaries = ArtifactSummaryGenerator.Generate(path);

            Assert.IsNotNull(summaries);
            Assert.AreNotEqual(0, summaries.Count);
            for (int i = 0; i < summaries.Count; i++)
            {
                var summary = summaries[i];
                Assert.IsFalse(summary.IsFaulted);

                // Common properties
                Assert.AreEqual(path, summary.Origin);

                var fi = new FileInfo(path);
                Assert.AreEqual(fi.Length, summary.FileSize);
                Assert.AreEqual(fi.LastWriteTimeUtc, summary.LastModified);

                if (StringComparer.Ordinal.Equals(ResourceType.StructureDefinition.GetLiteral(), summary.ResourceTypeName))
                {
                    Assert.IsTrue(summary.ResourceType == ResourceType.StructureDefinition);

                    // Conformance resource properties
                    Assert.IsNotNull(summary.GetConformanceCanonicalUrl());
                    Assert.IsTrue(summary.GetConformanceCanonicalUrl().ToString().StartsWith("http://hl7.org/fhir/StructureDefinition/"));
                    Assert.IsNotNull(summary.GetConformanceName());
                    Assert.AreEqual(ConformanceResourceStatus.Draft.GetLiteral(), summary.GetConformanceStatus());

                    //Debug.WriteLine($"{summary.ResourceType} | {summary.Canonical()} | {summary.Name()}");

                    // StructureDefinition properties
                    Assert.IsNotNull(summary.GetStructureDefinitionFhirVersion());
                    Assert.AreEqual(ModelInfo.Version, summary.GetStructureDefinitionFhirVersion());

                    Assert.AreEqual(StructureDefinition.StructureDefinitionKind.Resource.GetLiteral(), summary.GetStructureDefinitionKind());
                    // If this is a constraining StructDef, then Base should also be specified
                    Assert.IsTrue(summary.GetStructureDefinitionConstrainedType() == null || summary.GetStructureDefinitionBase() != null);

                    // [WMR 20171218] Maturity Level extension
                    Assert.IsNotNull(summary.GetStructureDefinitionMaturityLevel());
                }
            }
        }
        ArtifactSummary assertSummary(string path, params ArtifactSummaryHarvester[] harvesters)
        {
            var summaries = ArtifactSummaryGenerator.Generate(path, harvesters);

            Assert.IsNotNull(summaries);
            Assert.AreEqual(1, summaries.Count);
            var summary = summaries[0];

            Assert.IsFalse(summary.IsFaulted);
            Assert.AreEqual(path, summary.Origin);

            var fi = new FileInfo(path);

            Assert.AreEqual(fi.Length, summary.FileSize);
            Assert.AreEqual(fi.LastWriteTimeUtc, summary.LastModified);

            return(summary);
        }
Example #10
0
        private static List <ArtifactSummary> harvestSummaries(List <string> paths, ArtifactSummaryHarvester[] harvesters, bool singleThreaded)
        {
            // [WMR 20171023] Note: some files may no longer exist

            var cnt        = paths.Count;
            var scanResult = new List <ArtifactSummary>(cnt);

            if (singleThreaded)
            {
                foreach (var filePath in paths)
                {
                    var summaries = ArtifactSummaryGenerator.Generate(filePath, harvesters);
                    scanResult.AddRange(summaries);
                }
            }
            else
            {
                // Optimization: use Task.Parallel.ForEach to process files in parallel
                // More efficient then creating task per file (esp. if many files)
                //
                // For netstandard13, add NuGet package System.Threading.Tasks.Parallel
                //
                //   <ItemGroup Condition=" '$(TargetFramework)' != 'net45' ">
                //    <PackageReference Include="System.Threading.Tasks.Parallel" Version="4.3.0" />
                //   </ItemGroup>
                //
                // TODO:
                // - Support TimeOut
                // - Support CancellationToken (how to inject?)

                // Pre-allocate results array, one entry per file
                // Each entry receives a list with summaries harvested from a single file (Bundles return 0..*)
                var results = new List <ArtifactSummary> [cnt];
                try
                {
                    // Process files in parallel
                    var loopResult = Parallel.For(0, cnt,
                                                  // new ParallelOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount },
                                                  i =>
                    {
                        // Harvest summaries from single file
                        // Save each result to a separate array entry (no locking required)
                        results[i] = ArtifactSummaryGenerator.Generate(paths[i], harvesters);
                    });
                }
                catch (AggregateException aex)
                {
                    // ArtifactSummaryHarvester.HarvestAll catches and returns exceptions using ArtifactSummary.FromException
                    // However Parallel.For may still throw, e.g. due to time out or cancel

                    // var isCanceled = ex.InnerExceptions.OfType<TaskCanceledException>().Any();
                    Debug.WriteLine($"[{nameof(DirectorySource)}.{nameof(harvestSummaries)}] {aex.GetType().Name}: {aex.Message}"
                                    + aex.InnerExceptions?.Select(ix => $"\r\n\t{ix.GetType().Name}: {ix.Message}"));

                    // [WMR 20171023] Return exceptions via ArtifactSummary.FromException
                    // Or unwrap all inner exceptions?
                    // scanResult.Add(ArtifactSummary.FromException(aex));
                    scanResult.AddRange(aex.InnerExceptions.Select(ArtifactSummary.FromException));
                }
                // Aggregate completed results into single list
                scanResult.AddRange(results.SelectMany(r => r ?? Enumerable.Empty <ArtifactSummary>()));
            }

            return(scanResult);
        }
Example #11
0
        /// <summary>
        /// Re-index one or more specific  artifact file(s).
        /// <para>
        /// Notifies the <see cref="DirectorySource"/> that specific files in the current
        /// <see cref="ContentDirectory"/> have been created, updated or deleted.
        /// The <paramref name="filePaths"/> argument should specify an array of artifact
        /// file paths that (may) have been deleted, modified or created.
        /// </para>
        /// <para>
        /// The source will:
        /// <list type="number">
        /// <item>remove any existing summary information for the specified artifacts, if available;</item>
        /// <item>try to harvest updated summary information from the specified artifacts, if they still exist.</item>
        /// </list>
        /// </para>
        /// </summary>
        /// <param name="filePaths">One or more artifact file path(s).</param>
        /// <returns>
        /// <c>true</c> if succesful, i.e. if matching cache items have been evicted, or <c>false</c> otherwise.
        /// </returns>
        public bool Refresh(params string[] filePaths)
        {
            if (filePaths == null || filePaths.Length == 0)
            {
                throw Error.ArgumentNullOrEmpty(nameof(filePaths));
            }

            bool result = false;

#if THREADSAFE
            lock (_syncRoot)
#endif
            {
                var artifactFilePaths = _artifactFilePaths;
                if (artifactFilePaths == null)
                {
                    // Cache is empty, perform full scan on demand
                    return(false);
                }
                // Update file paths
                foreach (var filePath in filePaths)
                {
                    // Update file paths
                    bool exists = File.Exists(filePath);
                    if (!exists)
                    {
                        // Return true if existing file path was evicted from cache
                        result = artifactFilePaths.Remove(filePath);
                    }
                    else if (!artifactFilePaths.Contains(filePath))
                    {
                        // Discovered new artifact; cache file path and return true
                        artifactFilePaths.Add(filePath);
                        result = true;
                    }

                    // Update summaries (if cached)
                    var artifactSummaries = _artifactSummaries;
                    if (artifactSummaries != null)
                    {
                        if (artifactSummaries.RemoveAll(s => StringComparer.OrdinalIgnoreCase.Equals(filePath, s.Origin)) > 0)
                        {
                            // Evicted some existing summaries from the cache; return true
                            result = true;
                        }
                        if (exists)
                        {
                            // May fail, e.g. if another thread/process has deleted the target file
                            // Generate will catch exceptions and return empty list
                            var summaries = ArtifactSummaryGenerator.Generate(filePath, _settings.SummaryDetailsHarvesters);
                            artifactSummaries.AddRange(summaries);
                            if (summaries.Count > 0)
                            {
                                // Added some new/updated summaries to the cache; return true
                                result = true;
                            }
                        }
                    }
                }
            }

            return(result);
        }