From 70c75fde6273084fc5144e15f92b8f40ce733f5c Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 27 May 2020 07:08:36 +1000 Subject: [PATCH 01/13] fix problem with package cache corruption when different processes install the same package at the same time --- .../utilities/cache/PackageCacheManager.java | 190 +++++++++++------- 1 file changed, 121 insertions(+), 69 deletions(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java index c35172733..c2a99edd3 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java @@ -35,11 +35,15 @@ import java.io.BufferedOutputStream; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; +import java.io.RandomAccessFile; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; +import java.nio.channels.FileChannel; +import java.nio.channels.FileLock; import java.sql.Timestamp; import java.text.ParseException; import java.text.SimpleDateFormat; @@ -186,6 +190,42 @@ public class PackageCacheManager { } } + public interface CacheLockFunction { + T get() throws IOException; + } + public class CacheLock { + + private final File lockFile; + + public CacheLock(String name) throws IOException { + this.lockFile = new File(cacheFolder, name + ".lock"); + if (!lockFile.isFile()) { + TextFile.stringToFile("", lockFile); + } + } + + public T doWithLock(CacheLockFunction f) throws FileNotFoundException, IOException { + try (FileChannel channel = new RandomAccessFile(lockFile, "rw").getChannel()) { + final FileLock fileLock = channel.lock(); + T result = null; + try { + result = f.get(); + } finally { + fileLock.release(); + } + try { + if (!lockFile.delete()) { + lockFile.deleteOnExit(); + } + } catch (Throwable t) { + System.out.println("unable to clean up lock file for "+lockFile.getName()+": "+t.getMessage()); + // nothing + } + return result; + } + } + } + public static final String PRIMARY_SERVER = "http://packages.fhir.org"; public static final String SECONDARY_SERVER = "http://packages2.fhir.org/packages"; // private static final String SECONDARY_SERVER = "http://local.fhir.org:960/packages"; @@ -256,16 +296,19 @@ public class PackageCacheManager { private void clearCache() throws IOException { for (File f : new File(cacheFolder).listFiles()) { if (f.isDirectory()) { - Utilities.clearDirectory(f.getAbsolutePath()); - try { - FileUtils.deleteDirectory(f); - } catch (Exception e1) { + new CacheLock(f.getName()).doWithLock(() -> { + Utilities.clearDirectory(f.getAbsolutePath()); try { FileUtils.deleteDirectory(f); + } catch (Exception e1) { + try { + FileUtils.deleteDirectory(f); } catch (Exception e2) { // just give up } - } + } + return null; // must return something + }); } else if (!f.getName().equals("packages.ini")) FileUtils.forceDelete(f); @@ -413,15 +456,18 @@ public class PackageCacheManager { * @throws IOException */ public void removePackage(String id, String ver) throws IOException { - String f = Utilities.path(cacheFolder, id+"#"+ver); - File ff = new File(f); - if (ff.exists()) { - Utilities.clearDirectory(f); - IniFile ini = new IniFile(Utilities.path(cacheFolder, "packages.ini")); - ini.removeProperty("packages", id+"#"+ver); - ini.save(); - ff.delete(); - } + new CacheLock(id+"#"+ver).doWithLock(() -> { + String f = Utilities.path(cacheFolder, id+"#"+ver); + File ff = new File(f); + if (ff.exists()) { + Utilities.clearDirectory(f); + IniFile ini = new IniFile(Utilities.path(cacheFolder, "packages.ini")); + ini.removeProperty("packages", id+"#"+ver); + ini.save(); + ff.delete(); + } + return null; + }); } /** @@ -494,70 +540,76 @@ public class PackageCacheManager { if (version == null) version = npm.version(); - String packRoot = Utilities.path(cacheFolder, id+"#"+version); - try { - Utilities.createDirectory(packRoot); - Utilities.clearDirectory(packRoot); + String v = version; + return new CacheLock(id+"#"+version).doWithLock(() -> { + NpmPackage pck = null; + String packRoot = Utilities.path(cacheFolder, id+"#"+v); + try { + Utilities.createDirectory(packRoot); + Utilities.clearDirectory(packRoot); - int i = 0; - int c = 0; - int size = 0; - for (Entry e : npm.getFolders().entrySet()) { - String dir = e.getKey().equals("package") ? Utilities.path(packRoot, "package") : Utilities.path(packRoot, "package", e.getKey());; - if (!(new File(dir).exists())) - Utilities.createDirectory(dir); - for (Entry fe : e.getValue().getContent().entrySet()) { - String fn = Utilities.path(dir, fe.getKey()); - byte[] cnt = fe.getValue(); - TextFile.bytesToFile(cnt, fn); - size = size + cnt.length; - i++; - if (progress && i % 50 == 0) { - c++; - System.out.print("."); - if (c == 120) { - System.out.println(""); - System.out.print(" "); - c = 2; - } - } + int i = 0; + int c = 0; + int size = 0; + for (Entry e : npm.getFolders().entrySet()) { + String dir = e.getKey().equals("package") ? Utilities.path(packRoot, "package") : Utilities.path(packRoot, "package", e.getKey());; + if (!(new File(dir).exists())) + Utilities.createDirectory(dir); + for (Entry fe : e.getValue().getContent().entrySet()) { + String fn = Utilities.path(dir, fe.getKey()); + byte[] cnt = fe.getValue(); + TextFile.bytesToFile(cnt, fn); + size = size + cnt.length; + i++; + if (progress && i % 50 == 0) { + c++; + System.out.print("."); + if (c == 120) { + System.out.println(""); + System.out.print(" "); + c = 2; + } + } + } } - } - IniFile ini = new IniFile(Utilities.path(cacheFolder, "packages.ini")); - ini.setTimeStampFormat("yyyyMMddhhmmss"); - ini.setTimestampProperty("packages", id+"#"+version, Timestamp.from(Instant.now()), null); - ini.setIntegerProperty("package-sizes", id+"#"+version, size, null); - ini.save(); - if (progress) - System.out.println(" done."); + IniFile ini = new IniFile(Utilities.path(cacheFolder, "packages.ini")); + ini.setTimeStampFormat("yyyyMMddhhmmss"); + ini.setTimestampProperty("packages", id+"#"+v, Timestamp.from(Instant.now()), null); + ini.setIntegerProperty("package-sizes", id+"#"+v, size, null); + ini.save(); + if (progress) + System.out.println(" done."); - NpmPackage pck = loadPackageInfo(packRoot); - if (!id.equals(JSONUtil.str(npm.getNpm(), "name")) || !version.equals(JSONUtil.str(npm.getNpm(), "version"))) { - if (!id.equals(JSONUtil.str(npm.getNpm(), "name"))) { - npm.getNpm().addProperty("original-name", JSONUtil.str(npm.getNpm(), "name")); - npm.getNpm().remove("name"); - npm.getNpm().addProperty("name", id); + pck = loadPackageInfo(packRoot); + if (!id.equals(JSONUtil.str(npm.getNpm(), "name")) || !v.equals(JSONUtil.str(npm.getNpm(), "version"))) { + if (!id.equals(JSONUtil.str(npm.getNpm(), "name"))) { + npm.getNpm().addProperty("original-name", JSONUtil.str(npm.getNpm(), "name")); + npm.getNpm().remove("name"); + npm.getNpm().addProperty("name", id); + } + if (!v.equals(JSONUtil.str(npm.getNpm(), "version"))) { + npm.getNpm().addProperty("original-version", JSONUtil.str(npm.getNpm(), "version")); + npm.getNpm().remove("version"); + npm.getNpm().addProperty("version", v); + } + TextFile.stringToFile(new GsonBuilder().setPrettyPrinting().create().toJson(npm.getNpm()), Utilities.path(cacheFolder, id+"#"+v, "package", "package.json"), false); } - if (!version.equals(JSONUtil.str(npm.getNpm(), "version"))) { - npm.getNpm().addProperty("original-version", JSONUtil.str(npm.getNpm(), "version")); - npm.getNpm().remove("version"); - npm.getNpm().addProperty("version", version); + } catch (Exception e) { + try { + // don't leave a half extracted package behind + System.out.println("Clean up package "+packRoot+" because installation failed: "+e.getMessage()); + e.printStackTrace(); + Utilities.clearDirectory(packRoot); + new File(packRoot).delete(); + } catch (Exception ei) { + // nothing } - TextFile.stringToFile(new GsonBuilder().setPrettyPrinting().create().toJson(npm.getNpm()), Utilities.path(cacheFolder, id+"#"+version, "package", "package.json"), false); + throw e; } return pck; - } catch (Exception e) { - try { - // don't leave a half extracted package behind - Utilities.clearDirectory(packRoot); - new File(packRoot).delete(); - } catch (Exception ei) { - // nothing - } - throw e; - } + }); } public String getPackageId(String canonical) throws IOException { From d7788c79457a269c0055bb6b17982e85924bfd1a Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 27 May 2020 08:34:02 +1000 Subject: [PATCH 02/13] fix bug loading packages --- .../src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java index 8243a068e..de7ca2d2b 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/NpmPackage.java @@ -576,6 +576,9 @@ public class NpmPackage { public boolean hasFile(String folder, String file) throws IOException { NpmPackageFolder f = folders.get(folder); + if (f == null) { + f = folders.get(Utilities.path("package", folder)); + } return f != null && f.hasFile(file); } From 4ee25ee1d036ae826d087bb6517d26b61270a189 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 27 May 2020 08:34:28 +1000 Subject: [PATCH 03/13] remove spurious debugging code --- .../hl7/fhir/utilities/cache/PackageCacheManager.java | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java index c2a99edd3..8f4efe9f8 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java @@ -213,13 +213,8 @@ public class PackageCacheManager { } finally { fileLock.release(); } - try { - if (!lockFile.delete()) { - lockFile.deleteOnExit(); - } - } catch (Throwable t) { - System.out.println("unable to clean up lock file for "+lockFile.getName()+": "+t.getMessage()); - // nothing + if (!lockFile.delete()) { + lockFile.deleteOnExit(); } return result; } From c66bfec06b6e7da861ecfd3f8c2078a59f69c021 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 27 May 2020 09:28:55 +1000 Subject: [PATCH 04/13] update dependency on pubpack --- .../java/org/hl7/fhir/utilities/tests/PackageCacheTests.java | 2 +- .../java/org/hl7/fhir/comparison/tests/ComparisonTests.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java index 46735d07f..504c2dd15 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java @@ -18,7 +18,7 @@ public class PackageCacheTests { PackageCacheManager cache = new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION); cache.clear(); Assertions.assertTrue(cache.listPackages().isEmpty()); - NpmPackage npm = cache.loadPackage("hl7.fhir.pubpack", "0.0.3"); + NpmPackage npm = cache.loadPackage("hl7.fhir.pubpack", "0.0.5"); npm.loadAllFiles(); Assertions.assertNotNull(npm); File dir = new File(Utilities.path("[tmp]", "cache")); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java index db786420a..5b51201e6 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/comparison/tests/ComparisonTests.java @@ -129,7 +129,7 @@ public class ComparisonTests { System.out.println("---- Set up Output ----------------------------------------------------------"); Utilities.createDirectory(Utilities.path("[tmp]", "comparison")); PackageCacheManager pcm = new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION); - NpmPackage npm = pcm.loadPackage("hl7.fhir.pubpack", "0.0.4"); + NpmPackage npm = pcm.loadPackage("hl7.fhir.pubpack", "0.0.5"); for (String f : npm.list("other")) { TextFile.streamToFile(npm.load("other", f), Utilities.path("[tmp]", "comparison", f)); } From 998d1e0afc574a8b4f899bebae6b99d062593802 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Wed, 27 May 2020 11:06:30 +1000 Subject: [PATCH 05/13] Release new version 5.0.4 --- org.hl7.fhir.convertors/pom.xml | 2 +- org.hl7.fhir.dstu2/pom.xml | 2 +- org.hl7.fhir.dstu2016may/pom.xml | 2 +- org.hl7.fhir.dstu3/pom.xml | 2 +- org.hl7.fhir.r4/pom.xml | 2 +- org.hl7.fhir.r5/pom.xml | 2 +- org.hl7.fhir.report/pom.xml | 2 +- org.hl7.fhir.utilities/pom.xml | 2 +- .../utilities/cache/PackageCacheManager.java | 70 ++++++++++--------- .../utilities/tests/PackageCacheTests.java | 2 +- org.hl7.fhir.validation.cli/pom.xml | 2 +- org.hl7.fhir.validation/pom.xml | 2 +- pom.xml | 2 +- release-notes-validator.md | 7 +- release.bat | 6 +- 15 files changed, 57 insertions(+), 50 deletions(-) diff --git a/org.hl7.fhir.convertors/pom.xml b/org.hl7.fhir.convertors/pom.xml index a5b23bb70..d3321c5e0 100644 --- a/org.hl7.fhir.convertors/pom.xml +++ b/org.hl7.fhir.convertors/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2/pom.xml b/org.hl7.fhir.dstu2/pom.xml index 6b2f3ed1d..5ff47d6b0 100644 --- a/org.hl7.fhir.dstu2/pom.xml +++ b/org.hl7.fhir.dstu2/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu2016may/pom.xml b/org.hl7.fhir.dstu2016may/pom.xml index bffe04ba9..f4fde8cb6 100644 --- a/org.hl7.fhir.dstu2016may/pom.xml +++ b/org.hl7.fhir.dstu2016may/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.dstu3/pom.xml b/org.hl7.fhir.dstu3/pom.xml index 4d8b2a292..70fa08d8a 100644 --- a/org.hl7.fhir.dstu3/pom.xml +++ b/org.hl7.fhir.dstu3/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r4/pom.xml b/org.hl7.fhir.r4/pom.xml index b525cb9ee..e3e11a080 100644 --- a/org.hl7.fhir.r4/pom.xml +++ b/org.hl7.fhir.r4/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.r5/pom.xml b/org.hl7.fhir.r5/pom.xml index 7460a2d6d..1796fae1c 100644 --- a/org.hl7.fhir.r5/pom.xml +++ b/org.hl7.fhir.r5/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.report/pom.xml b/org.hl7.fhir.report/pom.xml index b654ae126..6cca76a91 100644 --- a/org.hl7.fhir.report/pom.xml +++ b/org.hl7.fhir.report/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/pom.xml b/org.hl7.fhir.utilities/pom.xml index f83aca78f..2eb8a2bb0 100644 --- a/org.hl7.fhir.utilities/pom.xml +++ b/org.hl7.fhir.utilities/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java index 8f4efe9f8..a1721fa17 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/cache/PackageCacheManager.java @@ -540,43 +540,45 @@ public class PackageCacheManager { NpmPackage pck = null; String packRoot = Utilities.path(cacheFolder, id+"#"+v); try { - Utilities.createDirectory(packRoot); - Utilities.clearDirectory(packRoot); + // ok, now we have a lock on it... check if something created it while we were waiting + if (!new File(packRoot).exists()) { + Utilities.createDirectory(packRoot); + Utilities.clearDirectory(packRoot); - int i = 0; - int c = 0; - int size = 0; - for (Entry e : npm.getFolders().entrySet()) { - String dir = e.getKey().equals("package") ? Utilities.path(packRoot, "package") : Utilities.path(packRoot, "package", e.getKey());; - if (!(new File(dir).exists())) - Utilities.createDirectory(dir); - for (Entry fe : e.getValue().getContent().entrySet()) { - String fn = Utilities.path(dir, fe.getKey()); - byte[] cnt = fe.getValue(); - TextFile.bytesToFile(cnt, fn); - size = size + cnt.length; - i++; - if (progress && i % 50 == 0) { - c++; - System.out.print("."); - if (c == 120) { - System.out.println(""); - System.out.print(" "); - c = 2; - } - } + int i = 0; + int c = 0; + int size = 0; + for (Entry e : npm.getFolders().entrySet()) { + String dir = e.getKey().equals("package") ? Utilities.path(packRoot, "package") : Utilities.path(packRoot, "package", e.getKey());; + if (!(new File(dir).exists())) + Utilities.createDirectory(dir); + for (Entry fe : e.getValue().getContent().entrySet()) { + String fn = Utilities.path(dir, fe.getKey()); + byte[] cnt = fe.getValue(); + TextFile.bytesToFile(cnt, fn); + size = size + cnt.length; + i++; + if (progress && i % 50 == 0) { + c++; + System.out.print("."); + if (c == 120) { + System.out.println(""); + System.out.print(" "); + c = 2; + } + } + } } + + + IniFile ini = new IniFile(Utilities.path(cacheFolder, "packages.ini")); + ini.setTimeStampFormat("yyyyMMddhhmmss"); + ini.setTimestampProperty("packages", id+"#"+v, Timestamp.from(Instant.now()), null); + ini.setIntegerProperty("package-sizes", id+"#"+v, size, null); + ini.save(); + if (progress) + System.out.println(" done."); } - - - IniFile ini = new IniFile(Utilities.path(cacheFolder, "packages.ini")); - ini.setTimeStampFormat("yyyyMMddhhmmss"); - ini.setTimestampProperty("packages", id+"#"+v, Timestamp.from(Instant.now()), null); - ini.setIntegerProperty("package-sizes", id+"#"+v, size, null); - ini.save(); - if (progress) - System.out.println(" done."); - pck = loadPackageInfo(packRoot); if (!id.equals(JSONUtil.str(npm.getNpm(), "name")) || !v.equals(JSONUtil.str(npm.getNpm(), "version"))) { if (!id.equals(JSONUtil.str(npm.getNpm(), "name"))) { diff --git a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java index 504c2dd15..46735d07f 100644 --- a/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java +++ b/org.hl7.fhir.utilities/src/test/java/org/hl7/fhir/utilities/tests/PackageCacheTests.java @@ -18,7 +18,7 @@ public class PackageCacheTests { PackageCacheManager cache = new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION); cache.clear(); Assertions.assertTrue(cache.listPackages().isEmpty()); - NpmPackage npm = cache.loadPackage("hl7.fhir.pubpack", "0.0.5"); + NpmPackage npm = cache.loadPackage("hl7.fhir.pubpack", "0.0.3"); npm.loadAllFiles(); Assertions.assertNotNull(npm); File dir = new File(Utilities.path("[tmp]", "cache")); diff --git a/org.hl7.fhir.validation.cli/pom.xml b/org.hl7.fhir.validation.cli/pom.xml index 06ea416ff..d4ca46590 100644 --- a/org.hl7.fhir.validation.cli/pom.xml +++ b/org.hl7.fhir.validation.cli/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/org.hl7.fhir.validation/pom.xml b/org.hl7.fhir.validation/pom.xml index 99850f0db..0950c4859 100644 --- a/org.hl7.fhir.validation/pom.xml +++ b/org.hl7.fhir.validation/pom.xml @@ -5,7 +5,7 @@ ca.uhn.hapi.fhir org.hl7.fhir.core - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT ../pom.xml diff --git a/pom.xml b/pom.xml index 7aca1362f..0132d8afa 100644 --- a/pom.xml +++ b/pom.xml @@ -13,7 +13,7 @@ each other. It is fine to bump the point version of this POM without affecting HAPI FHIR. --> - 5.0.3-SNAPSHOT + 5.0.4-SNAPSHOT 5.0.0 diff --git a/release-notes-validator.md b/release-notes-validator.md index 11a2e4bc3..6da2750d5 100644 --- a/release-notes-validator.md +++ b/release-notes-validator.md @@ -6,6 +6,11 @@ title: FHIR Validator Release Notes ## Current (not released yet) +(no changes yet) + +## v5.0.4 (2020-05-27) + + (no changes yet) ## v5.0.3 (2020-05-26) @@ -199,4 +204,4 @@ title: FHIR Validator Release Notes ## v4.1.60 (2020-02-02) -* This \ No newline at end of file +* This \ No newline at end of file diff --git a/release.bat b/release.bat index 3ed927b6c..990dca588 100644 --- a/release.bat +++ b/release.bat @@ -1,7 +1,7 @@ @echo off -set oldver=5.0.2 -set newver=5.0.3 +set oldver=5.0.3 +set newver=5.0.4 echo .. echo ========================================================================= @@ -15,7 +15,7 @@ call "C:\tools\fnr.exe" -dir "C:\work\org.hl7.fhir\build" -fileMask "*.xml" -fin call "C:\tools\fnr.exe" -dir "C:\work\org.hl7.fhir\fhir-ig-publisher" -fileMask "*.xml" -find "%oldver%-SNAPSHOT" -replace "%newver%-SNAPSHOT" -count 2 call "C:\tools\fnr.exe" -dir "C:\work\org.hl7.fhir\latest-ig-publisher" -fileMask "*.html" -find "%oldver%" -replace "%newver%" -count 1 call "C:\tools\fnr.exe" -dir "C:\work\org.hl7.fhir\latest-ig-publisher" -fileMask "*.json" -find "%oldver%" -replace "%newver%" -count 1 -call mvn clean deploy -Dmaven.test.redirectTestOutputToFile=false -DdeployAtEnd=true +rem call mvn clean deploy -Dmaven.test.redirectTestOutputToFile=false -DdeployAtEnd=true IF %ERRORLEVEL% NEQ 0 ( GOTO DONE ) From af41297803f450bc2e9a7287bcc7c0d43d5787b8 Mon Sep 17 00:00:00 2001 From: Mark Iantorno Date: Wed, 27 May 2020 10:57:39 -0400 Subject: [PATCH 06/13] Adding Pipeline to publish SNAPSHOT for each module [skip ci] --- module-snapshot-publishing.yml | 107 +++++++++++++++++++++++++++++++++ 1 file changed, 107 insertions(+) create mode 100644 module-snapshot-publishing.yml diff --git a/module-snapshot-publishing.yml b/module-snapshot-publishing.yml new file mode 100644 index 000000000..b2b4ad8b1 --- /dev/null +++ b/module-snapshot-publishing.yml @@ -0,0 +1,107 @@ +# This pipeline produces a SNAPSHOT build for each of the sub modules in +# the core library, and publishes them to ossrh. +trigger: + - master + +strategy: + matrix: + dstu2: + module: "org.hl7.fhir.dstu2" + dstu3: + module: "org.hl7.fhir.dstu3" + dstu2016may: + module: "org.hl7.fhir.dstu2016may" + r4: + module: "org.hl7.fhir.r4" + r5: + module: "org.hl7.fhir.r5" + validator: + module: "org.hl7.fhir.validation" + maxParallel: 3 + +pool: + vmImage: "ubuntu-16.04" + +variables: + currentModule: $(module) + +steps: + # Debugging output to identify current module. + - bash: echo Publishing SNAPSHOT for $(module) + + # Signing, for now, occurs for all builds, SNAPSHOT or release. So we need a valid + # signing key. The next two steps download the public and private keys from the + # secure library files. + - task: DownloadSecureFile@1 + displayName: 'Download public key.' + inputs: + secureFile: public.key + + - task: DownloadSecureFile@1 + displayName: 'Download private key.' + inputs: + secureFile: private.key + + # Import both the private and public keys into gpg for signing. + - bash: | + gpg --import --no-tty --batch --yes $(Agent.TempDirectory)/public.key + gpg --import --no-tty --batch --yes $(Agent.TempDirectory)/private.key + gpg --list-keys --keyid-format LONG + gpg --list-secret-keys --keyid-format LONG + displayName: 'Import signing keys.' + + # For creating the snapshot release with maven, we need to build a fake settings.xml + # for it to read from. This is done for the master branch merges only. + - bash: | + cat >$(System.DefaultWorkingDirectory)/settings.xml < + + + ossrh + $(SONATYPE_USER) + $(SONATYPE_PASS) + + + sonatype-nexus-snapshots + $(SONATYPE_USER) + $(SONATYPE_PASS) + + + sonatype-nexus-staging + $(SONATYPE_USER) + $(SONATYPE_PASS) + + + $(PGP_KEYNAME) + $(PGP_PASSPHRASE) + + + + + release + + true + + + $(PGP_KEYNAME) + + + + + EOL + displayName: 'Create .mvn/settings.xml' + condition: and(eq(variables.currentImage, 'ubuntu-16.04'), eq(variables['Build.SourceBranch'], 'refs/heads/master')) + + # Deploy the SNAPSHOT artifact to sonatype nexus. + # This is done for the master branch merges only. + - task: Maven@3 + displayName: 'Deploy $(version) to Sonatype staging' + condition: and(eq(variables.currentImage, 'ubuntu-16.04'), eq(variables['Build.SourceBranch'], 'refs/heads/master')) + inputs: + mavenPomFile: '$(System.DefaultWorkingDirectory)/$(module)/pom.xml' + goals: deploy + options: '--settings $(System.DefaultWorkingDirectory)/settings.xml ' + publishJUnitResults: false \ No newline at end of file From 2f472a10e5e5d895855015b12aa8b2b548563074 Mon Sep 17 00:00:00 2001 From: markiantorno Date: Wed, 27 May 2020 11:01:13 -0400 Subject: [PATCH 07/13] test commit to trigger SNAPSHOT pipeline --- pom.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/pom.xml b/pom.xml index 0132d8afa..bf91bca9d 100644 --- a/pom.xml +++ b/pom.xml @@ -136,6 +136,7 @@ org.hl7.fhir.report + From 2096ee0cf530779d17008617d14fa9473ac9381b Mon Sep 17 00:00:00 2001 From: Mark Iantorno Date: Wed, 27 May 2020 11:09:34 -0400 Subject: [PATCH 08/13] Remove conditions for pipeline tasks --- module-snapshot-publishing.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/module-snapshot-publishing.yml b/module-snapshot-publishing.yml index b2b4ad8b1..3ae4261d2 100644 --- a/module-snapshot-publishing.yml +++ b/module-snapshot-publishing.yml @@ -93,13 +93,11 @@ steps: EOL displayName: 'Create .mvn/settings.xml' - condition: and(eq(variables.currentImage, 'ubuntu-16.04'), eq(variables['Build.SourceBranch'], 'refs/heads/master')) # Deploy the SNAPSHOT artifact to sonatype nexus. # This is done for the master branch merges only. - task: Maven@3 displayName: 'Deploy $(version) to Sonatype staging' - condition: and(eq(variables.currentImage, 'ubuntu-16.04'), eq(variables['Build.SourceBranch'], 'refs/heads/master')) inputs: mavenPomFile: '$(System.DefaultWorkingDirectory)/$(module)/pom.xml' goals: deploy From 613237382a6db2a8538e59a325930ef5ea9020e3 Mon Sep 17 00:00:00 2001 From: Mark Iantorno Date: Wed, 27 May 2020 16:07:36 -0400 Subject: [PATCH 09/13] Update module-snapshot-publishing.yml for Azure Pipelines --- module-snapshot-publishing.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/module-snapshot-publishing.yml b/module-snapshot-publishing.yml index 3ae4261d2..d6bd8728d 100644 --- a/module-snapshot-publishing.yml +++ b/module-snapshot-publishing.yml @@ -28,6 +28,7 @@ variables: steps: # Debugging output to identify current module. - bash: echo Publishing SNAPSHOT for $(module) + displayName: 'Print module name.' # Signing, for now, occurs for all builds, SNAPSHOT or release. So we need a valid # signing key. The next two steps download the public and private keys from the @@ -97,7 +98,7 @@ steps: # Deploy the SNAPSHOT artifact to sonatype nexus. # This is done for the master branch merges only. - task: Maven@3 - displayName: 'Deploy $(version) to Sonatype staging' + displayName: 'Deploy $(module) to Sonatype staging' inputs: mavenPomFile: '$(System.DefaultWorkingDirectory)/$(module)/pom.xml' goals: deploy From c7258dc7eb5a50c9ce4842a6d282a332d5eb985e Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 28 May 2020 14:07:26 +1000 Subject: [PATCH 10/13] fix bug constraining elements once they are sliced --- .../main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 4d9085919..1ca5325da 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -1447,7 +1447,7 @@ public class ProfileUtilities extends TranslatingUtilities { if (!diffMatches.get(0).hasSliceName()) { // it's not real content, just the slice diffpos++; } - if (hasInnerDiffMatches(differential, cpath, diffpos, diffLimit, base.getElement(), false)) { + if (hasInnerDiffMatches(differential, cpath, diffCursor, diffLimit, base.getElement(), false)) { int nbl = findEndOfElement(base, baseCursor); int ndx = differential.getElement().indexOf(diffMatches.get(0)); int ndc = ndx+(diffMatches.get(0).hasSlicing() ? 1 : 0); From f64d5c7fd5f063ff58dbb3fa1f9b6918e011f990 Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 28 May 2020 14:08:30 +1000 Subject: [PATCH 11/13] fix problem with rendering confusion between paths around local and base specifications --- .../r5/comparison/OldProfileComparer.java | 2 +- .../fhir/r5/conformance/ProfileComparer.java | 2 +- .../hl7/fhir/r5/renderers/BundleRenderer.java | 63 ++++++++++++++++++- .../CapabilityStatementRenderer.java | 2 +- .../fhir/r5/renderers/CodeSystemRenderer.java | 2 +- .../fhir/r5/renderers/ConceptMapRenderer.java | 2 +- .../hl7/fhir/r5/renderers/DataRenderer.java | 4 +- .../OperationDefinitionRenderer.java | 2 +- .../r5/renderers/QuestionnaireRenderer.java | 16 ++--- .../fhir/r5/renderers/ResourceRenderer.java | 6 +- .../StructureDefinitionRenderer.java | 2 +- .../r5/renderers/TerminologyRenderer.java | 26 ++++---- .../fhir/r5/renderers/ValueSetRenderer.java | 10 +-- .../fhir/r5/renderers/utils/BaseWrappers.java | 2 + .../fhir/r5/renderers/utils/DOMWrappers.java | 17 +++++ .../r5/renderers/utils/DirectWrappers.java | 18 ++++++ .../r5/renderers/utils/ElementWrappers.java | 24 ++++++- .../r5/renderers/utils/RenderingContext.java | 29 +++++++-- .../r5/test/NarrativeGenerationTests.java | 2 +- .../fhir/r5/test/NarrativeGeneratorTests.java | 2 +- .../fhir/r5/test/ResourceRoundTripTests.java | 2 +- .../fhir/r5/test/SnapShotGenerationTests.java | 2 +- .../hl7/fhir/utilities/xhtml/XhtmlNode.java | 7 +++ .../hl7/fhir/validation/ValidationEngine.java | 8 +-- .../tests/SnapShotGenerationTestsX.java | 2 +- 25 files changed, 194 insertions(+), 60 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/OldProfileComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/OldProfileComparer.java index c60774d2a..4902af3a8 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/OldProfileComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/comparison/OldProfileComparer.java @@ -1403,7 +1403,7 @@ public class OldProfileComparer implements ProfileKnowledgeProvider { } private void genValueSetFile(String filename, ValueSet vs) throws IOException, FHIRException, EOperationOutcome { - RenderingContext rc = new RenderingContext(context, null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); rc.setNoSlowLookup(true); RendererFactory.factory(vs, rc).render(vs); String s = new XhtmlComposer(XhtmlComposer.HTML).compose(vs.getText().getDiv()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileComparer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileComparer.java index 2d6933402..b2e099182 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileComparer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileComparer.java @@ -1391,7 +1391,7 @@ public class ProfileComparer implements ProfileKnowledgeProvider { } private void genValueSetFile(String filename, ValueSet vs) throws IOException, FHIRException, EOperationOutcome { - RenderingContext rc = new RenderingContext(context, null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); rc.setNoSlowLookup(true); RendererFactory.factory(vs, rc).render(vs); String s = new XhtmlComposer(XhtmlComposer.HTML).compose(vs.getText().getDiv()); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java index 197a16023..c8602e48e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/BundleRenderer.java @@ -15,7 +15,10 @@ import org.hl7.fhir.r5.model.Bundle.BundleEntrySearchComponent; import org.hl7.fhir.r5.model.Bundle.BundleType; import org.hl7.fhir.r5.model.Composition; import org.hl7.fhir.r5.model.DomainResource; +import org.hl7.fhir.r5.model.Provenance; import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.BaseWrapper; +import org.hl7.fhir.r5.renderers.utils.BaseWrappers.ResourceWrapper; import org.hl7.fhir.r5.renderers.utils.RenderingContext; import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.utils.EOperationOutcome; @@ -42,13 +45,54 @@ public class BundleRenderer extends ResourceRenderer { return null; } + @Override + public boolean render(XhtmlNode x, ResourceWrapper b) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { + List entries = b.children("entry"); + if ("document".equals(b.get("type").primitiveValue())) { + if (entries.isEmpty() || (entries.get(0).has("resource") && "Composition".equals(entries.get(0).get("resource").fhirType()))) + throw new FHIRException("Invalid document - first entry is not a Composition"); + ResourceWrapper r = (ResourceWrapper) entries.get(0).getChildByName("resource").getValues().get(0); + x.addChildren(r.getNarrative()); + } else if ("collection".equals(b.get("type").primitiveValue()) && allEntriesAreHistoryProvenance(entries)) { + // nothing + } else { + XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); + root.para().addText("Bundle "+b.getId()+" of type "+b.get("type").primitiveValue()); + int i = 0; + for (BaseWrapper be : entries) { + i++; + if (be.has("fullUrl")) { + root.an(makeInternalLink(be.get("fullUrl").primitiveValue())); + } + if (be.has("resource") && be.getChildByName("resource").getValues().get(0).has("id")) { + root.an(be.get("resource").fhirType().toLowerCase() + "_" + be.getChildByName("resource").getValues().get(0).get("id").primitiveValue()); + } + root.hr(); + root.para().addText("Entry "+Integer.toString(i)+(be.has("fullUrl") ? " - Full URL = " + be.get("fullUrl").primitiveValue() : "")); +// if (be.hasRequest()) +// renderRequest(root, be.getRequest()); +// if (be.hasSearch()) +// renderSearch(root, be.getSearch()); +// if (be.hasResponse()) +// renderResponse(root, be.getResponse()); + if (be.has("resource")) { + root.para().addText("Resource "+be.get("resource").fhirType()+":"); + ResourceWrapper rw = be.getChildByName("resource").getAsResource(); + root.blockquote().addChildren(rw.getNarrative()); + } + } + } + return false; + } + + public XhtmlNode render(Bundle b) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { if (b.getType() == BundleType.DOCUMENT) { if (!b.hasEntry() || !(b.getEntryFirstRep().hasResource() && b.getEntryFirstRep().getResource() instanceof Composition)) throw new FHIRException("Invalid document - first entry is not a Composition"); Composition dr = (Composition) b.getEntryFirstRep().getResource(); return dr.getText().getDiv(); - } else if ((b.getType() == BundleType.DOCUMENT && allEntresAreHistoryProvenance(b))) { + } else if ((b.getType() == BundleType.COLLECTION && allEntresAreHistoryProvenance(b))) { return null; } else { XhtmlNode root = new XhtmlNode(NodeType.Element, "div"); @@ -81,9 +125,22 @@ public class BundleRenderer extends ResourceRenderer { } } - + private boolean allEntriesAreHistoryProvenance(List entries) throws UnsupportedEncodingException, FHIRException, IOException { + for (BaseWrapper be : entries) { + if (!"Provenance".equals(be.get("resource").fhirType())) { + return false; + } + } + return !entries.isEmpty(); + } + private boolean allEntresAreHistoryProvenance(Bundle b) { - return false; + for (BundleEntryComponent be : b.getEntry()) { + if (!(be.getResource() instanceof Provenance)) { + return false; + } + } + return !b.getEntry().isEmpty(); } private List checkInternalLinks(Bundle b, List childNodes) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CapabilityStatementRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CapabilityStatementRenderer.java index 4dc18ac3d..8da8bfbbc 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CapabilityStatementRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CapabilityStatementRenderer.java @@ -82,7 +82,7 @@ public class CapabilityStatementRenderer extends ResourceRenderer { tr = t.tr(); tr.td().addText(r.getType()); if (r.hasProfile()) { - tr.td().ah(context.getSpecLink()+r.getProfile()).addText(r.getProfile()); + tr.td().ah(r.getProfile()).addText(r.getProfile()); } tr.td().addText(showOp(r, TypeRestfulInteraction.READ)); if (hasVRead) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java index 67c577975..7214219cf 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/CodeSystemRenderer.java @@ -414,7 +414,7 @@ public class CodeSystemRenderer extends TerminologyRenderer { first = false; XhtmlNode span = td.span(null, mapping.comp.hasRelationship() ? mapping.comp.getRelationship().toCode() : ""); span.addText(getCharForRelationship(mapping.comp)); - a = td.ah(getContext().getSpecLink()+m.getLink()+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode())); + a = td.ah(getContext().getSpecificationLink()+m.getLink()+"#"+makeAnchor(mapping.group.getTarget(), mapping.comp.getCode())); a.addText(mapping.comp.getCode()); if (!Utilities.noString(mapping.comp.getComment())) td.i().tx("("+mapping.comp.getComment()+")"); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java index 8887da001..a3221f056 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ConceptMapRenderer.java @@ -375,7 +375,7 @@ public class ConceptMapRenderer extends TerminologyRenderer { if (cs == null) td.tx(url); else - td.ah(cs.getUserString("path")).attribute("title", url).tx(cs.present()); + td.ah(context.fixReference(cs.getUserString("path"))).attribute("title", url).tx(cs.present()); } private void addUnmapped(XhtmlNode tbl, ConceptMapGroupComponent grp) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java index f1a101da2..32fa5d827 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/DataRenderer.java @@ -61,7 +61,7 @@ public class DataRenderer { public DataRenderer(IWorkerContext worker) { super(); - this.context = new RenderingContext(worker, new MarkDownProcessor(Dialect.COMMON_MARK), ValidationOptions.defaults(), "", null, ResourceRendererMode.RESOURCE); + this.context = new RenderingContext(worker, new MarkDownProcessor(Dialect.COMMON_MARK), ValidationOptions.defaults(), "http://hl7.org/fhir/R4", "", null, ResourceRendererMode.RESOURCE); } // -- 2. Markdown support ------------------------------------------------------- @@ -218,7 +218,7 @@ public class DataRenderer { // -- 5. Data type Rendering ---------------------------------------------- public static String display(IWorkerContext context, DataType type) { - return new DataRenderer(new RenderingContext(context, null, null, "", null, ResourceRendererMode.RESOURCE)).display(type); + return new DataRenderer(new RenderingContext(context, null, null, "http://hl7.org/fhir/R4", "", null, ResourceRendererMode.RESOURCE)).display(type); } public String displayBase(Base b) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationDefinitionRenderer.java index 07b914986..66b705b9c 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/OperationDefinitionRenderer.java @@ -105,7 +105,7 @@ public class OperationDefinitionRenderer extends TerminologyRenderer { if (p.hasSearchType()) { td.br(); td.tx("("); - td.ah( context.getSpecLink() == null ? "search.html#"+p.getSearchType().toCode() : Utilities.pathURL(context.getSpecLink(), "search.html#"+p.getSearchType().toCode())).tx(p.getSearchType().toCode()); + td.ah( context.getSpecificationLink() == null ? "search.html#"+p.getSearchType().toCode() : Utilities.pathURL(context.getSpecificationLink(), "search.html#"+p.getSearchType().toCode())).tx(p.getSearchType().toCode()); td.tx(")"); } td = tr.td(); diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java index e572b6443..0d7a73aa5 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/QuestionnaireRenderer.java @@ -59,8 +59,8 @@ public class QuestionnaireRenderer extends TerminologyRenderer { HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context.getDestDir(), context.isInlineGraphics(), true); TableModel model = gen.new TableModel("qtree="+q.getId(), true); model.setAlternating(true); - model.setDocoImg(context.getSpecLink() +"help16.png"); - model.setDocoRef(context.getSpecLink()+"formats.html#table"); + model.setDocoImg(context.getSpecificationLink() +"help16.png"); + model.setDocoRef(context.getSpecificationLink()+"formats.html#table"); model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "LinkId"), translate("sd.hint", "The linkId for the item"), null, 0)); model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Text"), translate("sd.hint", "Text for the item"), null, 0)); model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Cardinality"), translate("sd.hint", "Minimum and Maximum # of times the the itemcan appear in the instance"), null, 0)); @@ -87,19 +87,19 @@ public class QuestionnaireRenderer extends TerminologyRenderer { String txt = (i.hasPrefix() ? i.getPrefix() + ". " : "") + i.getText(); r.getCells().add(gen.new Cell(null, null, txt, null, null)); r.getCells().add(gen.new Cell(null, null, (i.getRequired() ? "1" : "0")+".."+(i.getRepeats() ? "*" : "1"), null, null)); - r.getCells().add(gen.new Cell(null, context.getSpecLink()+"codesystem-item-type.html#"+i.getType().toCode(), i.getType().toCode(), null, null)); + r.getCells().add(gen.new Cell(null, context.getSpecificationLink()+"codesystem-item-type.html#"+i.getType().toCode(), i.getType().toCode(), null, null)); // flags: Cell flags = gen.new Cell(); r.getCells().add(flags); if (i.getReadOnly()) { - flags.addPiece(gen.new Piece(Utilities.pathURL(context.getSpecLink(), "questionnaire-definitions.html#Questionnaire.item.readOnly"), null, "Is Readonly").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-readonly.png")))); + flags.addPiece(gen.new Piece(Utilities.pathURL(context.getSpecificationLink(), "questionnaire-definitions.html#Questionnaire.item.readOnly"), null, "Is Readonly").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-readonly.png")))); } if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject")) { flags.addPiece(gen.new Piece("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-isSubject", null, "Can change the subject of the questionnaire").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-subject.png")))); } if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden")) { - flags.addPiece(gen.new Piece(Utilities.pathURL(context.getSpecLink(), "extension-questionnaire-hidden.html"), null, "Is a hidden item").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-hidden.png")))); + flags.addPiece(gen.new Piece(Utilities.pathURL(context.getSpecificationLink(), "extension-questionnaire-hidden.html"), null, "Is a hidden item").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-hidden.png")))); } if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-optionalDisplay")) { flags.addPiece(gen.new Piece("http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-optionalDisplay", null, "Is optional to display").addHtml(new XhtmlNode(NodeType.Element, "img").attribute("src", Utilities.path(context.getDestDir(), "icon-qi-optional.png")))); @@ -225,8 +225,8 @@ public class QuestionnaireRenderer extends TerminologyRenderer { HierarchicalTableGenerator gen = new HierarchicalTableGenerator(context.getDestDir(), context.isInlineGraphics(), true); TableModel model = gen.new TableModel("qtree="+q.getId(), true); model.setAlternating(true); - model.setDocoImg(context.getSpecLink() +"help16.png"); - model.setDocoRef(context.getSpecLink()+"formats.html#table"); + model.setDocoImg(context.getSpecificationLink() +"help16.png"); + model.setDocoRef(context.getSpecificationLink()+"formats.html#table"); model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "LinkId"), translate("sd.hint", "The linkId for the item"), null, 0)); model.getTitles().add(gen.new Title(null, model.getDocoRef(), translate("sd.head", "Description & Constraints"), translate("sd.hint", "Additional information about the item"), null, 0)); @@ -474,7 +474,7 @@ public class QuestionnaireRenderer extends TerminologyRenderer { } if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/StructureDefinition/questionnaire-hidden")) { hasFlag = true; - flags.ah(Utilities.pathURL(context.getSpecLink(), "extension-questionnaire-hidden.html"), "Is a hidden item").img(Utilities.path(context.getDestDir(), "icon-qi-hidden.png")); + flags.ah(Utilities.pathURL(context.getSpecificationLink(), "extension-questionnaire-hidden.html"), "Is a hidden item").img(Utilities.path(context.getDestDir(), "icon-qi-hidden.png")); d.style("background-color: #eeeeee"); } if (ToolingExtensions.readBoolExtension(i, "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-optionalDisplay")) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java index 42cf98182..13c2666c4 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ResourceRenderer.java @@ -74,14 +74,16 @@ public abstract class ResourceRenderer extends DataRenderer { assert r.getContext() == context; XhtmlNode x = new XhtmlNode(NodeType.Element, "div"); boolean hasExtensions = render(x, r); - r.injectNarrative(x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); + if (r.hasNarrative()) { + r.injectNarrative(x, hasExtensions ? NarrativeStatus.EXTENSIONS : NarrativeStatus.GENERATED); + } return x; } public abstract boolean render(XhtmlNode x, DomainResource r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome; public boolean render(XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome { - throw new NotImplementedException("This is not implemented yet for resources of type "+r.getName()); + throw new NotImplementedException("Rendering using the wrapper is not implemented yet for resources of type "+r.getName()); } public void describe(XhtmlNode x, DomainResource r) throws UnsupportedEncodingException, IOException { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java index 31484ff16..6a7250399 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/StructureDefinitionRenderer.java @@ -27,7 +27,7 @@ public class StructureDefinitionRenderer extends ResourceRenderer { } public boolean render(XhtmlNode x, StructureDefinition sd) throws FHIRFormatError, DefinitionException, IOException { - x.getChildNodes().add(context.getProfileUtilities().generateTable(context.getDefinitionsTarget(), sd, true, context.getDestDir(), false, sd.getId(), false, context.getSpecLink(), "", false, false, null, false)); + x.getChildNodes().add(context.getProfileUtilities().generateTable(context.getDefinitionsTarget(), sd, true, context.getDestDir(), false, sd.getId(), false, context.getSpecificationLink(), "", false, false, null, false)); return true; } diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java index 575151772..599bfb78e 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/TerminologyRenderer.java @@ -103,7 +103,7 @@ public abstract class TerminologyRenderer extends ResourceRenderer { for (UsedConceptMap m : maps) { XhtmlNode td = tr.td(); XhtmlNode b = td.b(); - XhtmlNode a = b.ah(getContext().getSpecLink()+m.getLink()); + XhtmlNode a = b.ah(getContext().getSpecificationLink()+m.getLink()); a.addText(m.getDetails().getName()); if (m.getDetails().isDoDescription() && m.getMap().hasDescription()) addMarkdown(td, m.getMap().getDescription()); @@ -155,19 +155,22 @@ public abstract class TerminologyRenderer extends ResourceRenderer { ref = (String) cs.getUserData("filename"); else addHtml = false; - if (Utilities.noString(ref)) + if (Utilities.noString(ref)) { ref = (String) cs.getUserData("path"); + if (ref != null) { + addHtml = false; + } + } } String spec = getSpecialReference(inc.getSystem()); if (spec != null) { XhtmlNode a = li.ah(spec); a.code(inc.getSystem()); } else if (cs != null && ref != null) { - if (!Utilities.noString(getContext().getSpecLink()) && ref.startsWith("http://hl7.org/fhir/")) - ref = ref.substring(20)+"/index.html"; - else if (addHtml && !ref.contains(".html")) + if (addHtml && !ref.contains(".html")) ref = ref + ".html"; - XhtmlNode a = li.ah(getContext().getSpecLink()+ref.replace("\\", "/")); + ref = context.fixReference(ref); + XhtmlNode a = li.ah(ref.replace("\\", "/")); a.code(inc.getSystem()); } else { li.code(inc.getSystem()); @@ -260,14 +263,14 @@ public abstract class TerminologyRenderer extends ResourceRenderer { if (vs != null) { String ref = (String) vs.getUserData("path"); - ref = adjustForPath(ref); + ref = context.fixReference(ref); XhtmlNode a = li.ah(ref == null ? "?ngen-11?" : ref.replace("\\", "/")); a.addText(value); } else { CodeSystem cs = getContext().getWorker().fetchCodeSystem(value); if (cs != null) { String ref = (String) cs.getUserData("path"); - ref = adjustForPath(ref); + ref = context.fixReference(ref); XhtmlNode a = li.ah(ref == null ? "?ngen-12?" : ref.replace("\\", "/")); a.addText(value); } else if (value.equals("http://snomed.info/sct") || value.equals("http://snomed.info/id")) { @@ -282,13 +285,6 @@ public abstract class TerminologyRenderer extends ResourceRenderer { } } - private String adjustForPath(String ref) { - if (getContext().getSpecLink() == null) - return ref; - else - return getContext().getSpecLink()+ref; - } - protected String getDisplayForConcept(String system, String value) { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java index 3070591bd..09c4df084 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/ValueSetRenderer.java @@ -207,7 +207,7 @@ public class ValueSetRenderer extends TerminologyRenderer { if (ref == null) p.code(vs.getExpansion().getContains().get(0).getSystem()); else - p.ah(getContext().getSpecLink()+ref).code(vs.getExpansion().getContains().get(0).getSystem()); + p.ah(context.fixReference(ref)).code(vs.getExpansion().getContains().get(0).getSystem()); } XhtmlNode t = x.table( "codes"); XhtmlNode tr = t.tr(); @@ -638,7 +638,7 @@ public class ValueSetRenderer extends TerminologyRenderer { } else td.addText(code); } else { - String href = getContext().getSpecLink()+getCsRef(e); + String href = context.fixReference(getCsRef(e)); if (href.contains("#")) href = href + "-"+Utilities.nmtokenize(code); else @@ -662,9 +662,9 @@ public class ValueSetRenderer extends TerminologyRenderer { String cslink = getCsRef(cs); XhtmlNode a = null; if (cslink != null) - a = td.ah(getContext().getSpecLink()+cslink+"#"+cs.getId()+"-"+code); + a = td.ah(getContext().getSpecificationLink()+cslink+"#"+cs.getId()+"-"+code); else - a = td.ah(getContext().getSpecLink()+vslink+"#"+code); + a = td.ah(getContext().getSpecificationLink()+vslink+"#"+code); a.addText(code); } @@ -796,7 +796,7 @@ public class ValueSetRenderer extends TerminologyRenderer { } else { li.tx(f.getProperty()+" "+describe(f.getOp())+" "); if (e != null && codeExistsInValueSet(e, f.getValue())) { - String href = getContext().getSpecLink()+getCsRef(e); + String href = getContext().getSpecificationLink()+getCsRef(e); if (href.contains("#")) href = href + "-"+Utilities.nmtokenize(f.getValue()); else diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java index 067af416c..8ec2d07df 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/BaseWrappers.java @@ -29,6 +29,7 @@ public class BaseWrappers { public int getMaxCardinality(); public StructureDefinition getStructure(); public BaseWrapper value(); + public ResourceWrapper getAsResource(); } public interface WrapperBase extends RendererWrapper { @@ -48,6 +49,7 @@ public class BaseWrappers { public void injectNarrative(XhtmlNode x, NarrativeStatus status) throws IOException; public BaseWrapper root(); public StructureDefinition getDefinition(); + public boolean hasNarrative(); } public interface BaseWrapper extends WrapperBase { diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java index c6db74faf..d53cd178d 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DOMWrappers.java @@ -178,6 +178,11 @@ public class DOMWrappers { return getValues().get(0); } + @Override + public ResourceWrapper getAsResource() { + throw new Error("Not implemented yet"); + } + } public static class ResourceWrapperElement extends WrapperBaseImpl implements ResourceWrapper { @@ -312,6 +317,18 @@ public class DOMWrappers { public Base getBase() { throw new Error("Not Implemented yet"); } + + @Override + public boolean hasNarrative() { + StructureDefinition sd = definition; + while (sd != null) { + if ("DomainResource".equals(sd.getType())) { + return true; + } + sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return false; + } } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java index d111539b3..4a4e311bb 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/DirectWrappers.java @@ -96,6 +96,11 @@ public class DirectWrappers { public String toString() { return "#."+wrapped.toString(); } + + @Override + public ResourceWrapper getAsResource() { + throw new Error("Not implemented yet"); + } } public static class BaseWrapperDirect extends WrapperBaseImpl implements BaseWrapper { @@ -219,6 +224,19 @@ public class DirectWrappers { public Base getBase() { return wrapped; } + + @Override + public boolean hasNarrative() { + StructureDefinition sd = context.getWorker().fetchTypeDefinition(wrapped.fhirType()); + while (sd != null) { + if ("DomainResource".equals(sd.getType())) { + return true; + } + sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return false; + + } } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java index 6ebabda2a..cdcaf8acd 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/ElementWrappers.java @@ -48,8 +48,9 @@ public class ElementWrappers { if (type == null || type.equals("Resource") || type.equals("BackboneElement") || type.equals("Element")) return null; - if (element.hasElementProperty()) - return null; + if (element.hasElementProperty()) { + return element; + } ByteArrayOutputStream xml = new ByteArrayOutputStream(); try { new XmlParser(context.getWorker()).compose(element, xml, OutputStyle.PRETTY, null); @@ -57,7 +58,7 @@ public class ElementWrappers { throw new FHIRException(e.getMessage(), e); } if (context.getParser() == null) { - System.out.println("huh?"); + System.out.println("Noe version specific parser provided"); } return context.getParser().parseType(xml.toString(), type); } @@ -219,6 +220,18 @@ public class ElementWrappers { public Base getBase() { return wrapped; } + + @Override + public boolean hasNarrative() { + StructureDefinition sd = definition; + while (sd != null) { + if ("DomainResource".equals(sd.getType())) { + return true; + } + sd = context.getWorker().fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return false; + } } public static class PropertyWrapperMetaElement extends RendererWrapperImpl implements PropertyWrapper { @@ -287,6 +300,11 @@ public class ElementWrappers { return getValues().get(0); } + @Override + public ResourceWrapper getAsResource() { + return new ElementWrappers.ResourceWrapperMetaElement(context, values.get(0)); + } + } } \ No newline at end of file diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java index a3a5b2044..1d54e2386 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/renderers/utils/RenderingContext.java @@ -16,6 +16,7 @@ import org.hl7.fhir.r5.renderers.utils.Resolver.ResourceContext; import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; import org.hl7.fhir.utilities.MarkDownProcessor; import org.hl7.fhir.utilities.MarkDownProcessor.Dialect; +import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.validation.ValidationOptions; public class RenderingContext { @@ -71,7 +72,8 @@ public class RenderingContext { private ITypeParser parser; private String lang; - private String specLink; + private String localPrefix; // relative link within local context + private String specificationLink; private String selfLink; // absolute link to where the content is to be found (only used in a few circumstances when making external references to tools) private int headerLevelContext; private boolean canonicalUrlsAsLinks; @@ -101,12 +103,13 @@ public class RenderingContext { * @param specLink - path to FHIR specification * @param lang - langauage to render in */ - public RenderingContext(IWorkerContext worker, MarkDownProcessor markdown, ValidationOptions terminologyServiceOptions, String specLink, String lang, ResourceRendererMode mode) { + public RenderingContext(IWorkerContext worker, MarkDownProcessor markdown, ValidationOptions terminologyServiceOptions, String specLink, String localPrefix, String lang, ResourceRendererMode mode) { super(); this.worker = worker; this.markdown = markdown; this.lang = lang; - this.specLink = specLink; + this.specificationLink = specLink; + this.localPrefix = localPrefix; this.mode = mode; this.terminologyServiceOptions = terminologyServiceOptions; profileUtilities = new ProfileUtilities(worker, null, null); @@ -146,8 +149,12 @@ public class RenderingContext { return lang; } - public String getSpecLink() { - return specLink; + public String getSpecificationLink() { + return specificationLink; + } + + public String getLocalPrefix() { + return localPrefix; } public ValidationOptions getTerminologyServiceOptions() { @@ -293,7 +300,7 @@ public class RenderingContext { } public RenderingContext copy() { - RenderingContext res = new RenderingContext(worker, markdown, terminologyServiceOptions, specLink, lang, mode); + RenderingContext res = new RenderingContext(worker, markdown, terminologyServiceOptions, specificationLink, localPrefix, lang, mode); res.resolver = resolver; res.templateProvider = templateProvider; @@ -349,6 +356,16 @@ public class RenderingContext { public void setSelfLink(String selfLink) { this.selfLink = selfLink; } + + public String fixReference(String ref) { + if (!Utilities.isAbsoluteUrl(ref)) { + return (localPrefix == null ? "" : localPrefix)+ref; + } + if (ref.startsWith("http://hl7.org/fhir") && !ref.substring(20).contains("/")) { + return specificationLink+ref.substring(20); + } + return ref; + } diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java index cc926a7e7..025c1bc23 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGenerationTests.java @@ -90,7 +90,7 @@ public class NarrativeGenerationTests { @ParameterizedTest(name = "{index}: file {0}") @MethodSource("data") public void test(String id, TestDetails test) throws Exception { - RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", null, ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); rc.setDestDir("C:\\work\\org.hl7.fhir\\packages\\packages\\hl7.fhir.pubpack\\package\\other\\"); rc.setHeader(test.isHeader()); rc.setDefinitionsTarget("test.html"); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java index a71b6f748..e152d0b87 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/NarrativeGeneratorTests.java @@ -24,7 +24,7 @@ public class NarrativeGeneratorTests { @BeforeAll public static void setUp() throws FHIRException { - rc = new RenderingContext(TestingUtilities.context(), null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); } @Test diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java index 17242acbe..6406fb930 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/ResourceRoundTripTests.java @@ -27,7 +27,7 @@ public class ResourceRoundTripTests { @Test public void test() throws IOException, FHIRException, EOperationOutcome { DomainResource res = (DomainResource) new XmlParser().parse(TestingUtilities.loadTestResourceStream("r5", "unicode.xml")); - RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); RendererFactory.factory(res, rc).render(res); IOUtils.copy(TestingUtilities.loadTestResourceStream("r5", "unicode.xml"), new FileOutputStream(TestingUtilities.tempFile("gen", "unicode.xml"))); new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilities.tempFile("gen", "unicode.out.xml")), res); diff --git a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java index 5c95a28c6..dd137806c 100644 --- a/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java +++ b/org.hl7.fhir.r5/src/test/java/org/hl7/fhir/r5/test/SnapShotGenerationTests.java @@ -550,7 +550,7 @@ public class SnapShotGenerationTests { throw e; } if (output.getDifferential().hasElement()) { - RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); rc.setProfileUtilities(new ProfileUtilities(TestingUtilities.context(), null, new TestPKP())); RendererFactory.factory(output, rc).render(output); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java index 4c3dc81e2..a330521c1 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/xhtml/XhtmlNode.java @@ -681,6 +681,13 @@ public class XhtmlNode implements IBaseXhtml { return this; } + public XhtmlNode addChildren(XhtmlNode x) { + if (x != null) { + getChildNodes().addAll(x.getChildNodes()); + } + return this; + } + public XhtmlNode input(String name, String type, String placeholder, int size) { XhtmlNode p = new XhtmlNode(NodeType.Element, "input"); diff --git a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java index 4209ba440..901cf5615 100644 --- a/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java +++ b/org.hl7.fhir.validation/src/main/java/org/hl7/fhir/validation/ValidationEngine.java @@ -1171,7 +1171,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { private OperationOutcome exceptionToOutcome(Exception ex) throws IOException, FHIRException, EOperationOutcome { OperationOutcome op = new OperationOutcome(); op.addIssue().setCode(org.hl7.fhir.r5.model.OperationOutcome.IssueType.EXCEPTION).setSeverity(org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity.FATAL).getDetails().setText(ex.getMessage()); - RenderingContext rc = new RenderingContext(context, null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); RendererFactory.factory(op, rc).render(op); return op; } @@ -1187,7 +1187,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { } op.getIssue().add(OperationOutcomeUtilities.convertToIssue(vm, op)); } - RenderingContext rc = new RenderingContext(context, null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); RendererFactory.factory(op, rc).render(op); return op; } @@ -1245,7 +1245,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { public DomainResource generate(String source, String version) throws Exception { Content cnt = loadContent(source, "validate"); Resource res = loadResourceByVersion(version, cnt.focus, source); - RenderingContext rc = new RenderingContext(context, null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); RendererFactory.factory(res, rc).render((DomainResource) res); return (DomainResource) res; } @@ -1550,7 +1550,7 @@ public class ValidationEngine implements IValidatorResourceFetcher { } private void genScanOutputItem(ScanOutputItem item, String filename) throws IOException, FHIRException, EOperationOutcome { - RenderingContext rc = new RenderingContext(context, null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(context, null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); rc.setNoSlowLookup(true); RendererFactory.factory(item.outcome, rc).render(item.outcome); String s = new XhtmlComposer(XhtmlComposer.HTML).compose(item.outcome.getText().getDiv()); diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationTestsX.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationTestsX.java index 107d0f286..7ada692fe 100644 --- a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationTestsX.java +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationTestsX.java @@ -520,7 +520,7 @@ public class SnapShotGenerationTestsX { throw e; } if (output.getDifferential().hasElement()) { - RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "", "http://hl7.org/fhir", ResourceRendererMode.RESOURCE); + RenderingContext rc = new RenderingContext(TestingUtilities.context(), null, null, "http://hl7.org/fhir", "", null, ResourceRendererMode.RESOURCE); rc.setProfileUtilities(new ProfileUtilities(TestingUtilities.context(), null, new TestPKP())); RendererFactory.factory(output, rc).render(output); } From 0f0d19cd94d9c31eac779805e4874d29e747a0bc Mon Sep 17 00:00:00 2001 From: Grahame Grieve Date: Thu, 28 May 2020 22:55:35 +1000 Subject: [PATCH 12/13] Add checks for illegal constratins on elements --- .../fhir/r5/conformance/ProfileUtilities.java | 31 +++++++++++++++++++ .../fhir/utilities/i18n/I18nConstants.java | 24 ++------------ .../src/main/resources/Messages.properties | 3 ++ 3 files changed, 36 insertions(+), 22 deletions(-) diff --git a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java index 1ca5325da..94a4b26c1 100644 --- a/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java +++ b/org.hl7.fhir.r5/src/main/java/org/hl7/fhir/r5/conformance/ProfileUtilities.java @@ -898,6 +898,9 @@ public class ProfileUtilities extends TranslatingUtilities { baseCursor++; } else if (diffMatches.size() == 1 && (slicingDone || (!isImplicitSlicing(diffMatches.get(0), cpath) && !(diffMatches.get(0).hasSlicing() || (isExtension(diffMatches.get(0)) && diffMatches.get(0).hasSliceName()))))) {// one matching element in the differential ElementDefinition template = null; + if (diffMatches.get(0).hasType() && "Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode()) && !isValidType(diffMatches.get(0).getType().get(0), currentBase)) { + throw new DefinitionException(context.formatMessage(I18nConstants.VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT, url, diffMatches.get(0).getPath(), diffMatches.get(0).getType().get(0), currentBase.typeSummary())); + } if (diffMatches.get(0).hasType() && diffMatches.get(0).getType().size() == 1 && diffMatches.get(0).getType().get(0).hasProfile() && !"Reference".equals(diffMatches.get(0).getType().get(0).getWorkingCode())) { CanonicalType p = diffMatches.get(0).getType().get(0).getProfile().get(0); StructureDefinition sd = context.fetchResource(StructureDefinition.class, p.getValue()); @@ -912,6 +915,9 @@ public class ProfileUtilities extends TranslatingUtilities { } } if (sd != null) { + if (!isMatchingType(sd, diffMatches.get(0).getType())) { + throw new DefinitionException(context.formatMessage(I18nConstants.VALIDATION_VAL_PROFILE_WRONGTYPE2, sd.getUrl(), diffMatches.get(0).getPath(), sd.getType(), p.getValue(), diffMatches.get(0).getType().get(0).getWorkingCode())); + } if (isGenerating(sd)) { // this is a special case, because we're only going to access the first element, and we can rely on the fact that it's already populated. // but we check anyway @@ -1605,6 +1611,31 @@ public class ProfileUtilities extends TranslatingUtilities { return res; } + private boolean isMatchingType(StructureDefinition sd, List types) { + while (sd != null) { + for (TypeRefComponent tr : types) { + if (sd.getType().equals(tr.getCode())) { + return true; + } + } + sd = context.fetchResource(StructureDefinition.class, sd.getBaseDefinition()); + } + return false; + } + + private boolean isValidType(TypeRefComponent t, ElementDefinition base) { + for (TypeRefComponent tr : base.getType()) { + if (tr.getCode().equals(t.getCode())) { + return true; + } + if (tr.getWorkingCode().equals(t.getCode())) { + System.out.println("Type error: use of a simple type \""+t.getCode()+"\" wrongly constraining "+base.getPath()); + return true; + } + } + return false; + } + private boolean isGenerating(StructureDefinition sd) { return sd.hasUserData("profileutils.snapshot.generating"); } diff --git a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java index 11f2eb71b..2a2799d60 100644 --- a/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java +++ b/org.hl7.fhir.utilities/src/main/java/org/hl7/fhir/utilities/i18n/I18nConstants.java @@ -491,30 +491,10 @@ public class I18nConstants { public static final String MEASURE_M_CRITERIA_CQL_NO_ELM = "MEASURE_M_CRITERIA_CQL_NO_ELM"; public static final String MEASURE_M_CRITERIA_CQL_ELM_NOT_VALID = "MEASURE_M_CRITERIA_CQL_ELM_NOT_VALID"; public static final String MEASURE_M_CRITERIA_CQL_NOT_FOUND = "MEASURE_M_CRITERIA_CQL_NOT_FOUND"; -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String -//public static final String + public static final String VALIDATION_VAL_PROFILE_WRONGTYPE2 = "Validation_VAL_Profile_WrongType2"; public static final String XHTML_URL_EMPTY = "XHTML_URL_EMPTY"; public static final String XHTML_URL_INVALID_CHARS = "XHTML_URL_INVALID_CHARS"; public static final String TERMINOLOGY_TX_SYSTEM_HTTPS = "TERMINOLOGY_TX_SYSTEM_HTTPS"; public static final String CODESYSTEM_CS_NO_VS_NOTCOMPLETE = "CODESYSTEM_CS_NO_VS_NOTCOMPLETE"; + public static final String VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT = "VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT"; } \ No newline at end of file diff --git a/org.hl7.fhir.utilities/src/main/resources/Messages.properties b/org.hl7.fhir.utilities/src/main/resources/Messages.properties index 8c31c8dc0..5bce660b7 100644 --- a/org.hl7.fhir.utilities/src/main/resources/Messages.properties +++ b/org.hl7.fhir.utilities/src/main/resources/Messages.properties @@ -495,3 +495,6 @@ TYPE_SPECIFIC_CHECKS_DT_ATT_TOO_LONG = Attachment size is {0} bytes which exceed TYPE_SPECIFIC_CHECKS_DT_ATT_NO_CONTENT = Attachments have data and/or url, or else must have either contentType and/oor language TYPE_SPECIFIC_CHECKS_DT_BASE64_TOO_LONG = Base64 size is {0} bytes which exceeds the stated limit of {1} bytes TYPE_SPECIFIC_CHECKS_DT_DECIMAL_CHARS = Found {0} decimal places which exceeds the stated limit of {1} digits +Validation_VAL_Profile_WrongType = Specified profile type was "{0}", but found type "{1}" +Validation_VAL_Profile_WrongType2 = Type mismatch processing profile {0} at path {1}: The element type is {4}, but the profile {3} is for a different type {2} +VALIDATION_VAL_ILLEGAL_TYPE_CONSTRAINT = Illegal constraint in profile {0} at path {1} - cannot constrain to type {2} from base types {3} \ No newline at end of file From fb2a8c32bed64bc900b564e3c0087568aeccccb7 Mon Sep 17 00:00:00 2001 From: Mark Iantorno Date: Thu, 28 May 2020 09:31:52 -0400 Subject: [PATCH 13/13] Adding release branch pipeline, and fixing code coverage publishing --- azure-pipelines.yml | 71 ++++++++++++++++++++++++++++++++++++--------- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 00cf045b0..b3ded8145 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,16 +1,20 @@ trigger: - branches: - include: - - '*' + - master + - release +pr: + - master + - release + +# Different users have different machine setups, we run the build three times, on ubuntu, osx, and windows strategy: matrix: linux: imageName: "ubuntu-16.04" -# mac: -# imageName: "macos-10.14" -# windows: -# imageName: "vs2017-win2016" + mac: + imageName: "macos-10.14" + windows: + imageName: "vs2017-win2016" maxParallel: 3 pool: @@ -19,18 +23,47 @@ pool: variables: currentImage: $(imageName) codecov: $(CODECOV_TOKEN) + VERSION: steps: + - bash: ls -la + - bash: pwd + # This task pulls the value from the org.hl7.fhir.r5 project pom.xml file. All modules are released as + # the same version, at the same time, as defined in the root level pom.xml. - task: PowerShell@2 inputs: targetType: 'inline' script: | - [xml]$pomXml = Get-Content .\pom.xml + [xml]$pomXml = Get-Content -Path .\pom.xml # version Write-Host $pomXml.project.version $version=$pomXml.project.version Write-Host "##vso[task.setvariable variable=version]$version" + # Prints out the build version, for debugging purposes + - bash: echo Pulled version from pom.xml => $(version) + + # Azure pipelines cannot pass variables between pipelines, but it can pass files, so we + # pass the build id (ex: 1.1.13-SNAPSHOT) as a string in a file. + # This is used in the release pipeline, so we create it here. + # This is only done for the release branch. + - bash: | + echo $(version) + VERSION=$(version) + echo "$VERSION" > $(System.DefaultWorkingDirectory)/VERSION + condition: and(eq(variables.currentImage, 'ubuntu-16.04'), eq(variables['Build.SourceBranch'], 'refs/heads/release')) + + # Copies the VERSION file containing the build id (ex: 1.1.13-SNAPSHOT) to the staging directory + # This is done for release versions only. + - task: CopyFiles@2 + condition: and(eq(variables.currentImage, 'ubuntu-16.04'), eq(variables['Build.SourceBranch'], 'refs/heads/release')) + displayName: 'Copy Files to: $(build.artifactstagingdirectory)' + inputs: + SourceFolder: '$(System.Defaultworkingdirectory)' + Contents: "$(System.DefaultWorkingDirectory)/VERSION" + TargetFolder: '$(build.artifactstagingdirectory)' + + # Runs 'mvn clean package' - task: Maven@3 inputs: mavenPomFile: 'pom.xml' @@ -41,22 +74,34 @@ steps: publishJUnitResults: true testResultsFiles: '**/surefire-reports/TEST-*.xml' goals: 'clean package' - - - bash: echo Current version => $(version) - displayName: 'version' + # Upload test results to codecov - script: bash <(curl https://codecov.io/bash) -t $(codecov) - displayName: 'codecov' + displayName: 'codecov Bash Uploader' + + # Publishes the test results to build artifacts. - task: PublishCodeCoverageResults@1 + displayName: 'Publish JaCoCo test results ' inputs: codeCoverageTool: 'JaCoCo' summaryFileLocation: '$(System.DefaultWorkingDirectory)/org.hl7.fhir.report/target/site/jacoco-aggregate/jacoco.xml' reportDirectory: '$(System.DefaultWorkingDirectory)/org.hl7.fhir.report/target/site/jacoco-aggregate/' + # Publishes the built Validator jar to build artifacts. Primarily for testing and debugging builds. - task: PublishPipelineArtifact@1 - condition: eq(variables.currentImage, 'ubuntu-16.04') + displayName: 'Publish Validator jar' + condition: and(eq(variables.currentImage, 'ubuntu-16.04'), eq(variables['Build.SourceBranch'], 'refs/heads/release')) inputs: targetPath: "$(System.DefaultWorkingDirectory)/org.hl7.fhir.validation/target/org.hl7.fhir.validation-$(version).jar" artifactName: Validator + # Publishes the files we've moved into the staging directory, so they can be accessed by the + # release pipeline. You will notice that we only do this for the ubuntu build, as doing it + # for each of the three release pipelines will cause conflicts. + # This is done for release versions only. + - task: PublishBuildArtifacts@1 + condition: and(eq(variables.currentImage, 'ubuntu-16.04'), eq(variables['Build.SourceBranch'], 'refs/heads/release')) + displayName: 'Publish Build Artifacts' + inputs: + PathtoPublish: '$(build.artifactstagingdirectory)'