/// <summary>
        /// Re-index one or more specific artifact file(s).
        /// This method is NOT thread-safe!
        /// <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">An array of artifact file path(s).</param>
        /// <returns>
        /// <c>true</c> if any summary information was updated, or <c>false</c> otherwise.
        /// </returns>
        public bool Refresh(params string[] filePaths)
        {
            if (filePaths == null || filePaths.Length == 0)
            {
                // throw Error.ArgumentNullOrEmpty(nameof(filePaths));
                return(true); // NOP
            }

            bool result = false;

            // [WMR 20180814] Possible protection:
            // - Save current thread id in ctor
            // - In this method, compare current thread id with saved id; throw if mismatch
            // However this won't detect Refresh on main tread while bg threads are reading

            var summaries = GetSummaries();

            foreach (var filePath in filePaths)
            {
                bool exists = File.Exists(filePath);
                if (!exists)
                {
                    // File was deleted; remove associated summaries
                    result |= summaries.RemoveAll(s => PathComparer.Equals(filePath, s.Origin)) > 0;
                }
                else if (!summaries.Any(s => PathComparer.Equals(filePath, s.Origin)))
                {
                    // File was added; generate and add new summary
                    var newSummaries = _summaryGenerator.Generate(filePath, _settings.SummaryDetailsHarvesters);
                    summaries.AddRange(newSummaries);
                    result |= newSummaries.Count > 0;
                }
            }
            return(result);
        }
Beispiel #2
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);
            }
        }
Beispiel #3
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);
                        }
                    }
            }
        }
        /// <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);
        }
        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);
        }
Beispiel #9
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);
        }