diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/CrossVersionLibraryTests.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/CrossVersionLibraryTests.java new file mode 100644 index 000000000..e9a32df60 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/CrossVersionLibraryTests.java @@ -0,0 +1,12 @@ +package org.hl7.fhir.conversion.tests; + +import org.junit.runner.RunWith; +import org.junit.runners.Suite; +import org.junit.runners.Suite.SuiteClasses; + +@RunWith(Suite.class) +@SuiteClasses({ + SnapShotGenerationTestsX.class}) +public class CrossVersionLibraryTests { + +} 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 new file mode 100644 index 000000000..0af08409e --- /dev/null +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/SnapShotGenerationTestsX.java @@ -0,0 +1,544 @@ +package org.hl7.fhir.conversion.tests; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; + +import javax.xml.parsers.ParserConfigurationException; + +import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.NotImplementedException; +import org.hl7.fhir.exceptions.DefinitionException; +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.exceptions.PathEngineException; +import org.hl7.fhir.r5.conformance.ProfileUtilities; +import org.hl7.fhir.r5.conformance.ProfileUtilities.ProfileKnowledgeProvider; +import org.hl7.fhir.r5.formats.IParser.OutputStyle; +import org.hl7.fhir.r5.formats.JsonParser; +import org.hl7.fhir.r5.formats.XmlParser; +import org.hl7.fhir.r5.model.Base; +import org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent; +import org.hl7.fhir.r5.model.ExpressionNode.CollectionStatus; +import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.model.StructureDefinition; +import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind; +import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule; +import org.hl7.fhir.r5.model.TypeDetails; +import org.hl7.fhir.r5.model.ValueSet; +import org.hl7.fhir.r5.utils.FHIRPathEngine; +import org.hl7.fhir.r5.utils.FHIRPathEngine.IEvaluationContext; +import org.hl7.fhir.r5.utils.IResourceValidator; +import org.hl7.fhir.r5.utils.NarrativeGenerator; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.validation.ValidationMessage; +import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; +import org.hl7.fhir.utilities.xml.XMLUtil; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import org.junit.runners.Parameterized.Parameters; +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import junit.framework.Assert; + +@RunWith(Parameterized.class) +public class SnapShotGenerationTestsX { + + public enum TestFetchMode { + INPUT, + OUTPUT, + INCLUDE + } + + public static class Rule { + private String description; + private String expression; + public Rule(String description, String expression) { + super(); + this.description = description; + this.expression = expression; + } + public Rule(Element rule) { + super(); + this.description = rule.getAttribute("text"); + this.expression = rule.getAttribute("fhirpath"); + } + public String getDescription() { + return description; + } + public String getExpression() { + return expression; + } + + } + + public static class TestDetails { + private String id; + private String include; + private String register; + private String regex; + private boolean gen; + private boolean sort; + private boolean fail; + private boolean newSliceProcessing; + private boolean debug; + private String version; + + private List rules = new ArrayList<>(); + private StructureDefinition source; + private StructureDefinition included; + private StructureDefinition expected; + private StructureDefinition output; + + public TestDetails(Element test) { + super(); + gen = "true".equals(test.getAttribute("gen")); + sort = "true".equals(test.getAttribute("sort")); + fail = "true".equals(test.getAttribute("fail")); + newSliceProcessing = !"false".equals(test.getAttribute("new-slice-processing")); + debug = "true".equals(test.getAttribute("debug")); + + id = test.getAttribute("id"); + include = test.getAttribute("include"); + register = test.getAttribute("register"); + regex = test.getAttribute("regex"); + version = test.getAttribute("version"); + Element rule = XMLUtil.getFirstChild(test); + while (rule != null && rule.getNodeName().equals("rule")) { + rules.add(new Rule(rule)); + rule = XMLUtil.getNextSibling(rule); + } + } + public String getId() { + return id; + } + public boolean isSort() { + return sort; + } + public boolean isGen() { + return gen; + } + public String getInclude() { + return include; + } + public boolean isFail() { + return fail; + } + public StructureDefinition getIncluded() { + return included; + } + public List getRules() { + return rules; + } + public StructureDefinition getSource() { + return source; + } + public void setSource(StructureDefinition source) { + this.source = source; + } + public StructureDefinition getExpected() { + return expected; + } + public void setExpected(StructureDefinition expected) { + this.expected = expected; + } + public StructureDefinition getOutput() { + return output; + } + public void setOutput(StructureDefinition output) { + this.output = output; + } + public void load() throws FHIRFormatError, FileNotFoundException, IOException { + if (TestingUtilitiesX.findTestResource("rX", "snapshot-generation", id+"-input.json")) + source = (StructureDefinition) new JsonParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", id+"-input.json")); + else + source = (StructureDefinition) new XmlParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", id+"-input.xml")); + if (!fail) + expected = (StructureDefinition) new XmlParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", id+"-expected.xml")); + if (!Utilities.noString(include)) + included = (StructureDefinition) new XmlParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", include+".xml")); + if (!Utilities.noString(register)) { + if (TestingUtilitiesX.findTestResource("rX", "snapshot-generation", register+".xml")) { + included = (StructureDefinition) new XmlParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", register+".xml")); + } else { + included = (StructureDefinition) new JsonParser().parse(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", register+".json")); + } + } + } + public boolean isNewSliceProcessing() { + return newSliceProcessing; + } + public boolean isDebug() { + return debug; + } + } + + public class TestPKP implements ProfileKnowledgeProvider { + + @Override + public boolean isDatatype(String name) { + StructureDefinition sd = TestingUtilitiesX.context(version).fetchTypeDefinition(name); + return (sd != null) && (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) && (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE || sd.getKind() == StructureDefinitionKind.COMPLEXTYPE); + } + + @Override + public boolean isResource(String typeSimple) { + StructureDefinition sd = TestingUtilitiesX.context(version).fetchTypeDefinition(typeSimple); + return (sd != null) && (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) && (sd.getKind() == StructureDefinitionKind.RESOURCE); + } + + @Override + public boolean hasLinkFor(String typeSimple) { + return isDatatype(typeSimple); + } + + @Override + public String getLinkFor(String corePath, String typeSimple) { + return Utilities.pathURL(corePath, "datatypes.html#"+typeSimple); + } + + @Override + public BindingResolution resolveBinding(StructureDefinition def, ElementDefinitionBindingComponent binding, String path) throws FHIRException { + BindingResolution br = new BindingResolution(); + br.url = path+"/something.html"; + br.display = "something"; + return br; + } + + @Override + public BindingResolution resolveBinding(StructureDefinition def, String url, String path) throws FHIRException { + BindingResolution br = new BindingResolution(); + br.url = path+"/something.html"; + br.display = "something"; + return br; + } + + @Override + public String getLinkForProfile(StructureDefinition profile, String url) { + StructureDefinition sd = TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, url); + if (sd == null) + return url+"|"+url; + else + return sd.getId()+".html|"+sd.present(); + } + + @Override + public boolean prependLinks() { + return false; + } + + @Override + public String getLinkForUrl(String corePath, String s) { + // TODO Auto-generated method stub + return null; + } + + } + + private static class SnapShotGenerationTestsContext implements IEvaluationContext { + public List tests = new ArrayList<>(); + + public Resource fetchFixture(String id) { + TestFetchMode mode = TestFetchMode.INPUT; + if (id.equals("patient")) + return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Patient"); + if (id.equals("valueset")) + return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/ValueSet"); + if (id.equals("organization")) + return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Organization"); + if (id.equals("operationoutcome")) + return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/OperationOutcome"); + if (id.equals("parameters")) + return TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/Parameters"); + + if (id.contains("-")) { + String[] p = id.split("\\-"); + id = p[0]; + if (p[1].equals("output")) + mode = TestFetchMode.OUTPUT; + else if (p[1].equals("include")) + mode = TestFetchMode.INCLUDE; + } + for (TestDetails td : tests) { + if (td.getId().equals(id)) + switch (mode) { + case INPUT: return td.getSource(); + case OUTPUT: if (td.getOutput() == null) + throw new FHIRException("Not generated yet"); + else + return td.getOutput(); + case INCLUDE: + return td.getIncluded(); + default: + throw new FHIRException("Not done yet"); + } + } + return null; + } + + // FHIRPath methods + @Override + public Base resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException { + throw new Error("Not implemented yet"); + } + + @Override + public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException { + throw new Error("Not implemented yet"); + } + + @Override + public boolean log(String argument, List focus) { + System.out.println(argument+": "+fp.convertToString(focus)); + return true; + } + + @Override + public FunctionDetails resolveFunction(String functionName) { + if ("fixture".equals(functionName)) + return new FunctionDetails("Access a fixture defined in the testing context", 0, 1); + return null; + } + + @Override + public TypeDetails checkFunction(Object appContext, String functionName, List parameters) throws PathEngineException { + if ("fixture".equals(functionName)) + return new TypeDetails(CollectionStatus.SINGLETON, TestingUtilitiesX.context(version).getResourceNamesAsSet()); + return null; + } + + @Override + public List executeFunction(Object appContext, String functionName, List> parameters) { + if ("fixture".equals(functionName)) { + String id = fp.convertToString(parameters.get(0)); + Resource res = fetchFixture(id); + if (res != null) { + List list = new ArrayList(); + list.add(res); + return list; + } + throw new Error("Could not resolve "+id); + } + throw new Error("Not implemented yet"); + } + + @Override + public Base resolveReference(Object appContext, String url, Base refContext) { + // TODO Auto-generated method stub + return null; + } + + @Override + public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException { + IResourceValidator val = TestingUtilitiesX.context(version).newValidator(); + List valerrors = new ArrayList(); + if (item instanceof Resource) { + val.validate(appContext, valerrors, (Resource) item, url); + boolean ok = true; + for (ValidationMessage v : valerrors) + ok = ok && v.getLevel().isError(); + return ok; + } + throw new NotImplementedException("Not done yet (IGPublisherHostServices.SnapShotGenerationTestsContext), when item is element"); + } + + public StructureDefinition getByUrl(String url) { + if (url == null) + return null; + for (TestDetails t : tests) { + if (t.expected != null && url.equals(t.expected.getUrl())) + return t.expected; + if (t.included != null && url.equals(t.included.getUrl())) + return t.included; + } + return null; + } + + @Override + public ValueSet resolveValueSet(Object appContext, String url) { + throw new Error("Not implemented yet"); + } + + } + + private static FHIRPathEngine fp; + + @Parameters(name = "{index}: file {0}") + public static Iterable data() throws ParserConfigurationException, IOException, FHIRFormatError, SAXException { + + SnapShotGenerationTestsContext context = new SnapShotGenerationTestsContext(); + Document tests = XMLUtil.parseToDom(TestingUtilitiesX.loadTestResource("rX", "snapshot-generation", "manifest.xml")); + Element test = XMLUtil.getFirstChild(tests.getDocumentElement()); + List objects = new ArrayList(); + while (test != null && test.getNodeName().equals("test")) { + TestDetails t = new TestDetails(test); + context.tests.add(t); + t.load(); + objects.add(new Object[] {t.getId(), t, context }); + test = XMLUtil.getNextSibling(test); + } + return objects; + + } + + + private final TestDetails test; + private SnapShotGenerationTestsContext context; + private List messages; + private static String version; + + public SnapShotGenerationTestsX(String id, TestDetails test, SnapShotGenerationTestsContext context) { + this.test = test; + this.context = context; + } + + @SuppressWarnings("deprecation") + @Test + public void test() throws Exception { + version = test.version; + if (fp == null) + fp = new FHIRPathEngine(TestingUtilitiesX.context(version)); + fp.setHostServices(context); + messages = new ArrayList(); + + if (test.isFail()) { + try { + if (test.isGen()) + testGen(true); + else + testSort(); + Assert.assertTrue("Should have failed", false); + } catch (Throwable e) { + System.out.println("Error running test: "+e.getMessage()); + if (!Utilities.noString(test.regex)) { + Assert.assertTrue("correct error message", e.getMessage().matches(test.regex)); + } else if ("Should have failed".equals(e.getMessage())) { + throw e; + } else { + Assert.assertTrue("all ok", true); + } + + } + } else if (test.isGen()) + testGen(false); + else + testSort(); + for (Rule r : test.getRules()) { + StructureDefinition sdn = new StructureDefinition(); + boolean ok = fp.evaluateToBoolean(sdn, sdn, sdn, r.expression); + Assert.assertTrue(r.description, ok); + } + } + + + private void testSort() throws DefinitionException, FHIRException, IOException { + StructureDefinition base = getSD(test.getSource().getBaseDefinition()); + test.setOutput(test.getSource().copy()); + ProfileUtilities pu = new ProfileUtilities(TestingUtilitiesX.context(version), null, null); + pu.setIds(test.getSource(), false); + List errors = new ArrayList(); + pu.sortDifferential(base, test.getOutput(), test.getOutput().getUrl(), errors, false); + if (!errors.isEmpty()) + throw new FHIRException(errors.get(0)); + IOUtils.copy(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", test.getId()+"-expected.xml"), new FileOutputStream(TestingUtilitiesX.tempFile("snapshot", test.getId()+"-expected.xml"))); + new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilitiesX.tempFile("snapshot", test.getId()+"-actual.xml")), test.getOutput()); + Assert.assertTrue("Output does not match expected", test.expected.equalsDeep(test.output)); + } + + private void testGen(boolean fail) throws Exception { + if (!Utilities.noString(test.register)) { + List messages = new ArrayList(); + ProfileUtilities pu = new ProfileUtilities(TestingUtilitiesX.context(version), messages, null); + pu.setNewSlicingProcessing(true); + pu.setIds(test.included, false); + StructureDefinition base = TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, test.included.getBaseDefinition()); + if (base != null) { + pu.generateSnapshot(base, test.included, test.included.getUrl(), "http://test.org/profile", test.included.getName()); + } + if (!TestingUtilitiesX.context(version).hasResource(StructureDefinition.class, test.included.getUrl())) + TestingUtilitiesX.context(version).cacheResource(test.included); + int ec = 0; + for (ValidationMessage vm : messages) { + if (vm.getLevel() == IssueSeverity.ERROR) { + System.out.println(vm.summary()); + ec++; + } + } + if (ec > 0) + throw new FHIRException("register gen failed: "+messages.toString()); + } + StructureDefinition base = getSD(test.getSource().getBaseDefinition()); + if (!base.getUrl().equals(test.getSource().getBaseDefinition())) + throw new Exception("URL mismatch on base: "+base.getUrl()+" wanting "+test.getSource().getBaseDefinition()); + + StructureDefinition output = test.getSource().copy(); + ProfileUtilities pu = new ProfileUtilities(TestingUtilitiesX.context(version), messages , new TestPKP()); + pu.setNewSlicingProcessing(test.isNewSliceProcessing()); + pu.setThrowException(false); + pu.setDebug(test.isDebug()); + pu.setIds(test.getSource(), false); + if (test.isSort()) { + List errors = new ArrayList(); + int lastCount = output.getDifferential().getElement().size(); + pu.sortDifferential(base, output, test.getSource().getName(), errors, false); + if (errors.size() > 0) + throw new FHIRException("Sort failed: "+errors.toString()); + } + try { + messages.clear(); + pu.generateSnapshot(base, output, test.getSource().getUrl(), "http://test.org/profile", test.getSource().getName()); + List ml = new ArrayList<>(); + for (ValidationMessage vm : messages) { + if (vm.getLevel() == IssueSeverity.ERROR) { + ml.add(vm); + } + } + if (ml.size() > 0) { + throw new FHIRException("Snapshot Generation failed: "+ml.toString()); + } + } catch (Throwable e) { + System.out.println("\r\nException: "+e.getMessage()); + throw e; + } + if (output.getDifferential().hasElement()) + new NarrativeGenerator("", "http://hl7.org/fhir", TestingUtilitiesX.context(version)).setPkp(new TestPKP()).generate(output, null); + if (!fail) { + test.output = output; + TestingUtilitiesX.context(version).cacheResource(output); + File dst = new File(TestingUtilitiesX.tempFile("snapshot", test.getId()+"-expected.xml")); + if (dst.exists()) + dst.delete(); + IOUtils.copy(TestingUtilitiesX.loadTestResourceStream("rX", "snapshot-generation", test.getId()+"-expected.xml"), new FileOutputStream(dst)); + new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(TestingUtilitiesX.tempFile("snapshot", test.getId()+"-actual.xml")), output); + StructureDefinition t1 = test.expected.copy(); + t1.setText(null); + StructureDefinition t2 = test.output.copy(); + t2.setText(null); + Assert.assertTrue("Output does not match expected", t1.equalsDeep(t2)); + } + } + + private StructureDefinition getSD(String url) throws DefinitionException, FHIRException, IOException { + StructureDefinition sd = context.getByUrl(url); + if (sd == null) + sd = TestingUtilitiesX.context(version).fetchResource(StructureDefinition.class, url); + if (!sd.hasSnapshot()) { + StructureDefinition base = getSD(sd.getBaseDefinition()); + ProfileUtilities pu = new ProfileUtilities(TestingUtilitiesX.context(version), messages , new TestPKP()); + pu.setNewSlicingProcessing(true); + List errors = new ArrayList(); + pu.sortDifferential(base, sd, url, errors, false); + if (!errors.isEmpty()) + throw new FHIRException(errors.get(0)); + pu.setIds(sd, false); + pu.generateSnapshot(base, sd, sd.getUrl(), "http://test.org/profile", sd.getName()); + } + return sd; + } +} diff --git a/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/TestingUtilitiesX.java b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/TestingUtilitiesX.java new file mode 100644 index 000000000..185446742 --- /dev/null +++ b/org.hl7.fhir.validation/src/test/java/org/hl7/fhir/conversion/tests/TestingUtilitiesX.java @@ -0,0 +1,556 @@ +package org.hl7.fhir.conversion.tests; + +/*- + * #%L + * org.hl7.fhir.r5 + * %% + * Copyright (C) 2014 - 2019 Health Level 7 + * %% + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * #L% + */ + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.DocumentBuilder; +import javax.xml.parsers.DocumentBuilderFactory; + +import org.apache.commons.codec.binary.Base64; +import org.apache.commons.io.IOUtils; +import org.fhir.ucum.UcumEssenceService; +import org.hl7.fhir.convertors.R2016MayToR5Loader; +import org.hl7.fhir.convertors.R2ToR5Loader; +import org.hl7.fhir.convertors.R3ToR5Loader; +import org.hl7.fhir.convertors.R4ToR5Loader; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.context.SimpleWorkerContext; +import org.hl7.fhir.r5.context.SimpleWorkerContext.IContextResourceLoader; +import org.hl7.fhir.r5.model.Parameters; +import org.hl7.fhir.utilities.CSFile; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.VersionUtilities; +import org.hl7.fhir.utilities.cache.PackageCacheManager; +import org.hl7.fhir.utilities.cache.ToolsVersion; + +import org.w3c.dom.Document; +import org.w3c.dom.Element; +import org.w3c.dom.NamedNodeMap; +import org.w3c.dom.Node; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonNull; +import com.google.gson.JsonObject; +import com.google.gson.JsonPrimitive; +import com.google.gson.JsonSyntaxException; + +public class TestingUtilitiesX { + private static final boolean SHOW_DIFF = true; + + static public Map fcontexts; + + public static IWorkerContext context(String version) { + if (fcontexts == null) { + fcontexts = new HashMap<>(); + } + if (!fcontexts.containsKey(version)) { + PackageCacheManager pcm; + try { + pcm = new PackageCacheManager(true, ToolsVersion.TOOLS_VERSION); + IWorkerContext fcontext = SimpleWorkerContext.fromPackage(pcm.loadPackage(VersionUtilities.packageForVersion(version), version), loaderForVersion(version)); + fcontext.setUcumService(new UcumEssenceService(TestingUtilitiesX.loadTestResourceStream("ucum", "ucum-essence.xml"))); + fcontext.setExpansionProfile(new Parameters()); + fcontexts.put(version, fcontext); + } catch (Exception e) { + throw new Error(e); + } + } + return fcontexts.get(version); + } + + private static IContextResourceLoader loaderForVersion(String version) { + if (Utilities.noString(version)) + return null; + if (version.startsWith("1.0")) + return new R2ToR5Loader(new String[] { "Conformance", "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}); + if (version.startsWith("1.4")) + return new R2016MayToR5Loader(new String[] { "Conformance", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}); // special case + if (version.startsWith("3.0")) + return new R3ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}); + if (version.startsWith("4.0")) + return new R4ToR5Loader(new String[] { "CapabilityStatement", "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire","ConceptMap","StructureMap", "NamingSystem"}); + return null; + } + + static public boolean silent; + + static public String fixedpath; + static public String contentpath; + + public static String home() { + if (fixedpath != null) + return fixedpath; + String s = System.getenv("FHIR_HOME"); + if (!Utilities.noString(s)) + return s; + s = "C:\\work\\org.hl7.fhir\\build"; + // FIXME: change this back + s = "/Users/jamesagnew/git/fhir"; + if (new File(s).exists()) + return s; + throw new Error("FHIR Home directory not configured"); + } + + + public static String content() throws IOException { + if (contentpath != null) + return contentpath; + String s = "R:\\fhir\\publish"; + if (new File(s).exists()) + return s; + return Utilities.path(home(), "publish"); + } + + // diretory that contains all the US implementation guides + public static String us() { + if (fixedpath != null) + return fixedpath; + String s = System.getenv("FHIR_HOME"); + if (!Utilities.noString(s)) + return s; + s = "C:\\work\\org.hl7.fhir.us"; + if (new File(s).exists()) + return s; + throw new Error("FHIR US directory not configured"); + } + + public static String checkXMLIsSame(InputStream f1, InputStream f2) throws Exception { + String result = compareXml(f1, f2); + return result; + } + + public static String checkXMLIsSame(String f1, String f2) throws Exception { + String result = compareXml(f1, f2); + if (result != null && SHOW_DIFF) { + String diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe"); + List command = new ArrayList(); + command.add("\"" + diff + "\" \"" + f1 + "\" \"" + f2 + "\""); + + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(new CSFile("c:\\temp")); + builder.start(); + + } + return result; + } + + private static String compareXml(InputStream f1, InputStream f2) throws Exception { + return compareElements("", loadXml(f1).getDocumentElement(), loadXml(f2).getDocumentElement()); + } + + private static String compareXml(String f1, String f2) throws Exception { + return compareElements("", loadXml(f1).getDocumentElement(), loadXml(f2).getDocumentElement()); + } + + private static String compareElements(String path, Element e1, Element e2) { + if (!e1.getNamespaceURI().equals(e2.getNamespaceURI())) + return "Namespaces differ at "+path+": "+e1.getNamespaceURI()+"/"+e2.getNamespaceURI(); + if (!e1.getLocalName().equals(e2.getLocalName())) + return "Names differ at "+path+": "+e1.getLocalName()+"/"+e2.getLocalName(); + path = path + "/"+e1.getLocalName(); + String s = compareAttributes(path, e1.getAttributes(), e2.getAttributes()); + if (!Utilities.noString(s)) + return s; + s = compareAttributes(path, e2.getAttributes(), e1.getAttributes()); + if (!Utilities.noString(s)) + return s; + + Node c1 = e1.getFirstChild(); + Node c2 = e2.getFirstChild(); + c1 = skipBlankText(c1); + c2 = skipBlankText(c2); + while (c1 != null && c2 != null) { + if (c1.getNodeType() != c2.getNodeType()) + return "node type mismatch in children of "+path+": "+Integer.toString(e1.getNodeType())+"/"+Integer.toString(e2.getNodeType()); + if (c1.getNodeType() == Node.TEXT_NODE) { + if (!normalise(c1.getTextContent()).equals(normalise(c2.getTextContent()))) + return "Text differs at "+path+": "+normalise(c1.getTextContent()) +"/"+ normalise(c2.getTextContent()); + } + else if (c1.getNodeType() == Node.ELEMENT_NODE) { + s = compareElements(path, (Element) c1, (Element) c2); + if (!Utilities.noString(s)) + return s; + } + + c1 = skipBlankText(c1.getNextSibling()); + c2 = skipBlankText(c2.getNextSibling()); + } + if (c1 != null) + return "node mismatch - more nodes in source in children of "+path; + if (c2 != null) + return "node mismatch - more nodes in target in children of "+path; + return null; + } + + private static Object normalise(String text) { + String result = text.trim().replace('\r', ' ').replace('\n', ' ').replace('\t', ' '); + while (result.contains(" ")) + result = result.replace(" ", " "); + return result; + } + + private static String compareAttributes(String path, NamedNodeMap src, NamedNodeMap tgt) { + for (int i = 0; i < src.getLength(); i++) { + + Node sa = src.item(i); + String sn = sa.getNodeName(); + if (! (sn.equals("xmlns") || sn.startsWith("xmlns:"))) { + Node ta = tgt.getNamedItem(sn); + if (ta == null) + return "Attributes differ at "+path+": missing attribute "+sn; + if (!normalise(sa.getTextContent()).equals(normalise(ta.getTextContent()))) { + byte[] b1 = unBase64(sa.getTextContent()); + byte[] b2 = unBase64(ta.getTextContent()); + if (!sameBytes(b1, b2)) + return "Attributes differ at "+path+": value "+normalise(sa.getTextContent()) +"/"+ normalise(ta.getTextContent()); + } + } + } + return null; + } + + private static boolean sameBytes(byte[] b1, byte[] b2) { + if (b1.length == 0 || b2.length == 0) + return false; + if (b1.length != b2.length) + return false; + for (int i = 0; i < b1.length; i++) + if (b1[i] != b2[i]) + return false; + return true; + } + + private static byte[] unBase64(String text) { + return Base64.decodeBase64(text); + } + + private static Node skipBlankText(Node node) { + while (node != null && (((node.getNodeType() == Node.TEXT_NODE) && Utilities.isWhitespace(node.getTextContent())) || (node.getNodeType() == Node.COMMENT_NODE))) + node = node.getNextSibling(); + return node; + } + + private static Document loadXml(String fn) throws Exception { + return loadXml(new FileInputStream(fn)); + } + + private static Document loadXml(InputStream fn) throws Exception { + DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance(); + factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true); + factory.setFeature("http://xml.org/sax/features/external-general-entities", false); + factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false); + factory.setFeature("http://apache.org/xml/features/nonvalidating/load-external-dtd", false); + factory.setXIncludeAware(false); + factory.setExpandEntityReferences(false); + + factory.setNamespaceAware(true); + DocumentBuilder builder = factory.newDocumentBuilder(); + return builder.parse(fn); + } + + public static String checkJsonSrcIsSame(String s1, String s2) throws JsonSyntaxException, FileNotFoundException, IOException { + return checkJsonSrcIsSame(s1,s2,true); + } + + public static String checkJsonSrcIsSame(String s1, String s2, boolean showDiff) throws JsonSyntaxException, FileNotFoundException, IOException { + String result = compareJsonSrc(s1, s2); + if (result != null && SHOW_DIFF && showDiff) { + String diff = null; + if (System.getProperty("os.name").contains("Linux")) + diff = Utilities.path("/", "usr", "bin", "meld"); + else { + if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge"), "\\WinMergeU.exe", null)) + diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe"); + else if (Utilities.checkFile("WinMerge", Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld"), "\\Meld.exe", null)) + diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "Meld", "Meld.exe"); + } + if (diff == null || diff.isEmpty()) + return result; + + List command = new ArrayList(); + String f1 = Utilities.path("[tmp]", "input" + s1.hashCode() + ".json"); + String f2 = Utilities.path("[tmp]", "output" + s2.hashCode() + ".json"); + TextFile.stringToFile(s1, f1); + TextFile.stringToFile(s2, f2); + command.add(diff); + if (diff.toLowerCase().contains("meld")) + command.add("--newtab"); + command.add(f1); + command.add(f2); + + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(new CSFile(Utilities.path("[tmp]"))); + builder.start(); + + } + return result; + } + public static String checkJsonIsSame(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException { + String result = compareJson(f1, f2); + if (result != null && SHOW_DIFF) { + String diff = Utilities.path(System.getenv("ProgramFiles(X86)"), "WinMerge", "WinMergeU.exe"); + List command = new ArrayList(); + command.add("\"" + diff + "\" \"" + f1 + "\" \"" + f2 + "\""); + + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(new CSFile("c:\\temp")); + builder.start(); + + } + return result; + } + + private static String compareJsonSrc(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException { + JsonObject o1 = (JsonObject) new com.google.gson.JsonParser().parse(f1); + JsonObject o2 = (JsonObject) new com.google.gson.JsonParser().parse(f2); + return compareObjects("", o1, o2); + } + + private static String compareJson(String f1, String f2) throws JsonSyntaxException, FileNotFoundException, IOException { + JsonObject o1 = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(f1)); + JsonObject o2 = (JsonObject) new com.google.gson.JsonParser().parse(TextFile.fileToString(f2)); + return compareObjects("", o1, o2); + } + + private static String compareObjects(String path, JsonObject o1, JsonObject o2) { + for (Map.Entry en : o1.entrySet()) { + String n = en.getKey(); + if (!n.equals("fhir_comments")) { + if (o2.has(n)) { + String s = compareNodes(path+'.'+n, en.getValue(), o2.get(n)); + if (!Utilities.noString(s)) + return s; + } + else + return "properties differ at "+path+": missing property "+n; + } + } + for (Map.Entry en : o2.entrySet()) { + String n = en.getKey(); + if (!n.equals("fhir_comments")) { + if (!o1.has(n)) + return "properties differ at "+path+": missing property "+n; + } + } + return null; + } + + private static String compareNodes(String path, JsonElement n1, JsonElement n2) { + if (n1.getClass() != n2.getClass()) + return "properties differ at "+path+": type "+n1.getClass().getName()+"/"+n2.getClass().getName(); + else if (n1 instanceof JsonPrimitive) { + JsonPrimitive p1 = (JsonPrimitive) n1; + JsonPrimitive p2 = (JsonPrimitive) n2; + if (p1.isBoolean() && p2.isBoolean()) { + if (p1.getAsBoolean() != p2.getAsBoolean()) + return "boolean property values differ at "+path+": type "+p1.getAsString()+"/"+p2.getAsString(); + } else if (p1.isString() && p2.isString()) { + String s1 = p1.getAsString(); + String s2 = p2.getAsString(); + if (!(s1.contains(" command = new ArrayList(); + String f1 = Utilities.path("[tmp]", "input" + s1.hashCode() + ".json"); + String f2 = Utilities.path("[tmp]", "output" + s2.hashCode() + ".json"); + TextFile.stringToFile(s1, f1); + TextFile.stringToFile(s2, f2); + command.add(diff); + if (diff.toLowerCase().contains("meld")) + command.add("--newtab"); + command.add(f1); + command.add(f2); + + ProcessBuilder builder = new ProcessBuilder(command); + builder.directory(new CSFile(Utilities.path("[tmp]"))); + builder.start(); + + } + return result; + } + + + private static String compareText(String s1, String s2) { + for (int i = 0; i < Integer.min(s1.length(), s2.length()); i++) { + if (s1.charAt(i) != s2.charAt(i)) + return "Strings differ at character "+Integer.toString(i)+": '"+s1.charAt(i) +"' vs '"+s2.charAt(i)+"'"; + } + if (s1.length() != s2.length()) + return "Strings differ in length: "+Integer.toString(s1.length())+" vs "+Integer.toString(s2.length())+" but match to the end of the shortest"; + return null; + } + + public static boolean findTestResource(String... paths) throws IOException { + if (new File("../../fhir-test-cases").exists() && isTryToLoadFromFileSystem()) { + String n = Utilities.path(System.getProperty("user.dir"), "..", "..", "fhir-test-cases", Utilities.path(paths)); + return new File(n).exists(); + } else { + String classpath = ("/org/hl7/fhir/testcases/"+ Utilities.pathURL(paths)); + try { + InputStream inputStream = TestingUtilitiesX.class.getResourceAsStream(classpath); + return inputStream != null; + } catch (Throwable t) { + return false; + } + } + } + + // TODO: JA need to figure out how to detect that we're running in maven + private static boolean isTryToLoadFromFileSystem() { + return !"true".equals(System.getProperty("dont_load_from_filesystem")); + } + + public static String loadTestResource(String... paths) throws IOException { + if (new File("../../fhir-test-cases").exists() && isTryToLoadFromFileSystem()) { + String n = Utilities.path(System.getProperty("user.dir"), "..", "..", "fhir-test-cases", Utilities.path(paths)); + // ok, we'll resolve this locally + return TextFile.fileToString(new File(n)); + } else { + // resolve from the package + String contents; + String classpath = ("/org/hl7/fhir/testcases/"+ Utilities.pathURL(paths)); + try (InputStream inputStream = TestingUtilitiesX.class.getResourceAsStream(classpath)) { + if (inputStream == null) { + throw new IOException("Can't find file on classpath: " + classpath); + } + contents = IOUtils.toString(inputStream, java.nio.charset.StandardCharsets.UTF_8); + } + return contents; + } + } + + public static InputStream loadTestResourceStream(String... paths) throws IOException { + if (new File("../../fhir-test-cases").exists() && isTryToLoadFromFileSystem()) { + String n = Utilities.path(System.getProperty("user.dir"), "..", "..", "fhir-test-cases", Utilities.path(paths)); + return new FileInputStream(n); + } else { + String classpath = ("/org/hl7/fhir/testcases/"+ Utilities.pathURL(paths)); + InputStream s = TestingUtilitiesX.class.getResourceAsStream(classpath); + if (s == null) { + throw new Error("unable to find resource "+classpath); + } + return s; + } + } + + public static byte[] loadTestResourceBytes(String... paths) throws IOException { + if (new File("../../fhir-test-cases").exists() && isTryToLoadFromFileSystem()) { + String n = Utilities.path(System.getProperty("user.dir"), "..", "..", "fhir-test-cases", Utilities.path(paths)); + return TextFile.fileToBytes(n); + } else { + String classpath = ("/org/hl7/fhir/testcases/"+ Utilities.pathURL(paths)); + InputStream s = TestingUtilitiesX.class.getResourceAsStream(classpath); + if (s == null) { + throw new Error("unable to find resource "+classpath); + } + return TextFile.streamToBytes(s); + } + } + + + public static String tempFile(String folder, String name) throws IOException { + String tmp = tempFolder(folder); + return Utilities.path(tmp, name); + } + + public static String tempFolder(String name) throws IOException { + File tmp = new File("C:\\temp"); + if (tmp.exists() && tmp.isDirectory()) { + String path = Utilities.path("C:\\temp", name); + Utilities.createDirectory(path); + return path; + } else if (new File("/tmp").exists()) { + String path = Utilities.path("/tmp", name); + Utilities.createDirectory(path); + return path; + } else { + String path = Utilities.path(System.getProperty("java.io.tmpdir"), name); + Utilities.createDirectory(path); + return path; + } + } +}