diff --git a/SECURITY.md b/SECURITY.md index 6c182091a..9f1e5a50f 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,5 +1,9 @@ # Security Policy +## Supported Versions + +Only new releases of this project will contain security updates. All clients should use the latest version of this project in their dependencies. There is no process in place to backport security fixes to previous releases. If you require a backport, please [create an issue](../../issues/new/choose) containing an explanation of why the latest version cannot be used. + ## Reporting a Vulnerability Please report all potential security vulnerabilities using the [Report a vulnerability](../../security/advisories/new) button in the [Security](../../security) section of this repository. diff --git a/org.hl7.fhir.publisher.cli/pom.xml b/org.hl7.fhir.publisher.cli/pom.xml index d2bce759b..df0b87b45 100644 --- a/org.hl7.fhir.publisher.cli/pom.xml +++ b/org.hl7.fhir.publisher.cli/pom.xml @@ -5,15 +5,13 @@ org.hl7.fhir.publisher org.hl7.fhir.publisher - 1.8.5-SNAPSHOT + 1.8.7-SNAPSHOT ../pom.xml 4.0.0 org.hl7.fhir.publisher.cli - - org.hl7.fhir.publisher @@ -124,6 +122,31 @@ + + + + empty-javadoc-jar + package + + jar + + + javadoc + ${basedir}/javadoc + + + + + + maven-javadoc-plugin + + true + org.codehaus.mojo diff --git a/org.hl7.fhir.publisher.cli/src/main/java/Placeholder.java b/org.hl7.fhir.publisher.cli/src/main/java/Placeholder.java deleted file mode 100644 index a29de2bc1..000000000 --- a/org.hl7.fhir.publisher.cli/src/main/java/Placeholder.java +++ /dev/null @@ -1,8 +0,0 @@ -import org.hl7.fhir.utilities.Utilities; - -/** - * This class is just a placeholder to force the source JAR and javadoc JAR - * to be created so that Maven Central doesn't complain. - */ -public class Placeholder { -} diff --git a/org.hl7.fhir.publisher.cli/src/main/resources/logback.xml b/org.hl7.fhir.publisher.cli/src/main/resources/logback.xml index 1c8de9da3..83e50b7fe 100644 --- a/org.hl7.fhir.publisher.cli/src/main/resources/logback.xml +++ b/org.hl7.fhir.publisher.cli/src/main/resources/logback.xml @@ -1,5 +1,6 @@ - - + + + INFO @@ -13,4 +14,6 @@ + + \ No newline at end of file diff --git a/org.hl7.fhir.publisher.core/pom.xml b/org.hl7.fhir.publisher.core/pom.xml index 086af15ce..c83b76231 100644 --- a/org.hl7.fhir.publisher.core/pom.xml +++ b/org.hl7.fhir.publisher.core/pom.xml @@ -5,7 +5,7 @@ org.hl7.fhir.publisher org.hl7.fhir.publisher - 1.8.5-SNAPSHOT + 1.8.7-SNAPSHOT ../pom.xml 4.0.0 diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/openehr/ArchetypeImporter.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/openehr/ArchetypeImporter.java new file mode 100644 index 000000000..d5b252e23 --- /dev/null +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/openehr/ArchetypeImporter.java @@ -0,0 +1,517 @@ +package org.hl7.fhir.igtools.openehr; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.time.temporal.TemporalAmount; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.xml.parsers.ParserConfigurationException; + +import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.r5.context.IWorkerContext; +import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.r5.model.Bundle.BundleType; +import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.CodeSystem; +import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.r5.model.Coding; +import org.hl7.fhir.r5.model.ContactDetail; +import org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem; +import org.hl7.fhir.r5.model.DecimalType; +import org.hl7.fhir.r5.model.ElementDefinition; +import org.hl7.fhir.r5.model.ElementDefinition.DiscriminatorType; +import org.hl7.fhir.r5.model.ElementDefinition.SlicingRules; +import org.hl7.fhir.r5.model.Enumerations.BindingStrength; +import org.hl7.fhir.r5.model.Enumerations.CodeSystemContentMode; +import org.hl7.fhir.r5.model.Enumerations.FHIRVersion; +import org.hl7.fhir.r5.model.Enumerations.PublicationStatus; +import org.hl7.fhir.r5.model.IntegerType; +import org.hl7.fhir.r5.model.StringType; +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.ValueSet; +import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent; +import org.hl7.fhir.r5.utils.ToolingExtensions; +import org.hl7.fhir.utilities.TextFile; +import org.hl7.fhir.utilities.Utilities; +import org.hl7.fhir.utilities.xml.XMLUtil; +import org.openehr.referencemodels.BuiltinReferenceModels; +import org.w3c.dom.Element; +import org.xml.sax.SAXException; + +import com.nedap.archie.adl14.ADL14ConversionConfiguration; +import com.nedap.archie.adl14.ADL14Parser; +import com.nedap.archie.adlparser.ADLParseException; +import com.nedap.archie.adlparser.ADLParser; +import com.nedap.archie.aom.Archetype; +import com.nedap.archie.aom.ArchetypeSlot; +import com.nedap.archie.aom.CAttribute; +import com.nedap.archie.aom.CComplexObject; +import com.nedap.archie.aom.CComplexObjectProxy; +import com.nedap.archie.aom.CObject; +import com.nedap.archie.aom.ResourceDescriptionItem; +import com.nedap.archie.aom.primitives.CDuration; +import com.nedap.archie.aom.primitives.CInteger; +import com.nedap.archie.aom.primitives.CReal; +import com.nedap.archie.aom.primitives.CString; +import com.nedap.archie.aom.primitives.CTerminologyCode; +import com.nedap.archie.aom.terminology.ArchetypeTerm; +import com.nedap.archie.base.Interval; + +public class ArchetypeImporter { + + private IWorkerContext context; + private String canonicalBase; + private Element terminology; + private Map atMap = new HashMap<>(); + + private Archetype archetype; + private StructureDefinition sd; + private Bundle bnd; + + public static class ProcessedArchetype { + private Archetype archetype; + private StructureDefinition sd; + private Bundle bnd; + private String source; + private String sourceName; + + protected ProcessedArchetype(String source,String sourceName, Archetype archetype, Bundle bnd, StructureDefinition sd) { + super(); + this.source = source; + this.sourceName = sourceName; + this.archetype = archetype; + this.sd = sd; + this.bnd = bnd; + } + public String getSource() { + return source; + } + public String getSourceName() { + return sourceName; + } + public Archetype getArchetype() { + return archetype; + } + public StructureDefinition getSd() { + return sd; + } + public Bundle getBnd() { + return bnd; + } + + } + public ArchetypeImporter(IWorkerContext context, String canonicalBase) throws ParserConfigurationException, SAXException, IOException { + super(); + this.context = context; + this.canonicalBase = canonicalBase; + // todo: check openehr-base is loaded, and if it's not, load it + // todo: load this from the context.binaries + byte[] tf = context.getBinaryForKey("openehr_terminology.xml"); + if (tf == null) { + // hack temp workaround + tf = TextFile.fileToBytes("/Users/grahamegrieve/Downloads/openehr_terminology.xml"); + } + terminology = XMLUtil.parseToDom(tf).getDocumentElement(); + } + + + public void checkArchetype(byte[] content, String name) throws ParserConfigurationException, SAXException, IOException, ADLParseException { + checkArchetype(new ByteArrayInputStream(content), name); + } + + public void checkArchetype(InputStream stream, String name) throws ParserConfigurationException, SAXException, IOException, ADLParseException { + byte[] cnt = TextFile.streamToBytes(stream); + try { + archetype = load20(new ByteArrayInputStream(cnt)); + } catch (Exception e20) { + try { + archetype = load14(new ByteArrayInputStream(cnt)); + } catch (Exception e14) { + if (e20.getMessage().equals(e14.getMessage())) { + throw new FHIRException("Error reading "+name+": "+e20.getMessage(), e20); + } else { + throw new FHIRException("Error reading "+name+". ADL 1.4: "+e14.getMessage()+"; ADL 2: "+e20.getMessage(), e20); + } + } + } + } + + public ProcessedArchetype importArchetype(byte[] content, String name) throws ParserConfigurationException, SAXException, IOException, ADLParseException { + return importArchetype(new ByteArrayInputStream(content), name); + } + + public ProcessedArchetype importArchetype(InputStream stream, String name) throws ParserConfigurationException, SAXException, IOException, ADLParseException { + atMap.clear(); + + byte[] cnt = TextFile.streamToBytes(stream); + try { + archetype = load20(new ByteArrayInputStream(cnt)); + } catch (Exception e20) { + try { + archetype = load14(new ByteArrayInputStream(cnt)); + } catch (Exception e14) { + if (e20.getMessage().equals(e14.getMessage())) { + throw new FHIRException("Error reading "+name+": "+e20.getMessage(), e20); + } else { + throw new FHIRException("Error reading "+name+". ADL 1.4: "+e14.getMessage()+"; ADL 2: "+e20.getMessage(), e20); + } + } + } + + bnd = new Bundle(); + bnd.setType(BundleType.COLLECTION); + bnd.setId(archetype.getArchetypeId().toString().replace("_", "-")); + + + sd = new StructureDefinition(); + sd.setId(archetype.getArchetypeId().toString().replace("_", "-")); + sd.setUrl(canonicalBase +"/StructureDefinition/"+sd.getId()); + sd.setVersion(archetype.getDescription().getOtherDetails().get("revision")); + sd.setTitle(archetype.getArchetypeId().toString()); + sd.setName(Utilities.javaTokenize(sd.getId().substring(sd.getId().indexOf(".")+1), true)); + if ("published".equals(archetype.getDescription().getLifecycleState().getCodeString())) { + sd.setStatus(PublicationStatus.ACTIVE); + } else { + System.out.println(archetype.getDescription().getLifecycleState().getCodeString()); + sd.setStatus(PublicationStatus.DRAFT); + } + sd.setExperimental(false); + sd.setDate(new Date()); + sd.setPublisher(archetype.getDescription().getOtherDetails().get("custodian_organisation")); + if (archetype.getDescription().getOtherDetails().get("current_contact") != null) { + String contact = archetype.getDescription().getOtherDetails().get("current_contact"); + if (contact.contains("<")) { + ContactDetail c = sd.addContact(); + c.setName(contact.substring(0, contact.indexOf("<"))); + c.addTelecom().setSystem(ContactPointSystem.EMAIL).setValue(contact.substring(contact.indexOf("<")+1).replace(">", "")); + + } else { + sd.addContact().setName(contact); + } + } + ResourceDescriptionItem lang = archetype.getDescription().getDetails().get("en"); + if (lang != null) { // todo: handle translations + sd.setDescription(lang.getUse()); + if (lang.getMisuse() != null) { + sd.setPurpose(lang.getPurpose()+" Misuse: "+lang.getMisuse()); + } else { + sd.setPurpose(lang.getPurpose()); + } + sd.setCopyright(getCopyright(lang)); + for (String s : lang.getKeywords()) { + sd.addKeyword().setDisplay(s); + } + } + addResource(sd); + + CComplexObject defn = archetype.getDefinition(); + String baseType = defn.getRmTypeName(); + sd.setFhirVersion(FHIRVersion._5_0_0); + sd.setKind(StructureDefinitionKind.LOGICAL); + sd.setDerivation(TypeDerivationRule.CONSTRAINT); + sd.setAbstract(false); + sd.setType("http://openehr.org/fhir/StructureDefinition/"+baseType); + sd.setBaseDefinition("http://openehr.org/fhir/StructureDefinition/"+baseType); + + List defns = sd.getDifferential().getElement(); + processDefinition(defns, null, defn, baseType, null, baseType, defn.getNodeId()); + + return new ProcessedArchetype(new String(cnt), name, archetype, bnd, sd); + } + + private Archetype load20(InputStream stream) throws ADLParseException, IOException { + ADLParser parser = new ADLParser(); + Archetype archetype = parser.parse(stream); + return archetype; + } + + private Archetype load14(InputStream stream) throws ParserConfigurationException, SAXException, IOException, ADLParseException { + ADL14ConversionConfiguration conversionConfiguration = new ADL14ConversionConfiguration(); + ADL14Parser parser = new ADL14Parser(BuiltinReferenceModels.getMetaModels()); + Archetype archetype = parser.parse(stream, conversionConfiguration); + return archetype; + } + + + private void addResource(CanonicalResource cr) { + bnd.addEntry().setResource(cr).setFullUrl(cr.getUrl()); + } + + private String getCopyright(ResourceDescriptionItem lang) { + String copyright = lang.getCopyright(); + String licence = archetype.getDescription().getOtherDetails().get("licence"); + String ip = archetype.getDescription().getOtherDetails().get("ip_acknowledgements"); + if (ip != null) { + return copyright+". "+licence+" "+ip; + } else { + return copyright+". "+licence; + } + } + + private void processDefinition(List defns, ElementDefinition parent, CComplexObject source, String path, String sliceName, String id, String label) { + if (source.getNodeId() != null) { + atMap.put(source.getNodeId(), id); + } + ElementDefinition defn = new ElementDefinition(path); + defn.setId(id); + defns.add(defn); + defn.setSliceName(sliceName); + buildDefinition(source, defn); + + for (CAttribute a : source.getAttributes()) { + String name = a.getRmAttributeName(); + if (a.getChildren().size() == 1) { + CObject o = a.getChildren().get(0); + if (o instanceof CComplexObject) { + processDefinition(defns, defn, (CComplexObject) o, path+"."+name, null, id+"."+name, defn.hasLabel() ? defn.getLabel() : label); + } else if (o instanceof CTerminologyCode) { + CTerminologyCode c = (CTerminologyCode) o; + if ("property".equals(name)) { + if (c.getConstraint().size() == 1 && c.getConstraint().get(0).startsWith("[openehr::")) { + Coding cc = new Coding("https://specifications.openehr.org/fhir/codesystem-property", c.getConstraint().get(0).substring(10).replace("]", ""), null); + defn.addExtension("http://openehr.org/fhir/StructureDefinition/property", cc); + } + } else if (bindingGoesOnParent(source.getRmTypeName(), name)) { + defn.getBinding().setStrength(BindingStrength.REQUIRED); + defn.getBinding().setValueSet(makeValueSet(label, parent.getShort(), parent.getDefinition(), c)); + } else { + ElementDefinition ed = new ElementDefinition(path+"."+name); + ed.setId(id+"."+name); + defns.add(ed); + ed.getBinding().setStrength(BindingStrength.REQUIRED); + ed.getBinding().setValueSet(makeValueSet(label, defn.getShort(), defn.getDefinition(), c)); + } + } else if (o instanceof CReal) { + CReal c = (CReal) o; + if (c.getConstraint().size() == 1) { + Interval dbl = c.getConstraint().get(0); + ElementDefinition ed = new ElementDefinition(path+"."+name); + ed.setId(id+"."+name); + defns.add(ed); + ed.setMinValue(new DecimalType(dbl.getLower())); + ed.setMaxValue(new DecimalType(dbl.getUpper())); + } else { + System.out.println("not done yet: "+path+"."+name+": "+o.getClass().getName()); + } + } else if (o instanceof CString) { + CString c = (CString) o; + if (c.getConstraint().size() == 1) { + String v = c.getConstraint().get(0); + ElementDefinition ed = new ElementDefinition(path+"."+name); + ed.setId(id+"."+name); + defns.add(ed); + ed.setFixed(new StringType(v)); + } else { + System.out.println("not done yet: "+path+"."+name+": "+o.getClass().getName()); + } + } else if (o instanceof CDuration) { + CDuration c = (CDuration) o; + if (c.getConstraint().size() == 1) { + Interval v = c.getConstraint().get(0); + ElementDefinition ed = new ElementDefinition(path+"."+name); + ed.setId(id+"."+name); + defns.add(ed); + if (v.getLower().equals(v.getUpper())) { + ed.setFixed(new StringType(v.getLower().toString())); + } else { + ed.setMinValue(new StringType(v.getLower().toString())); + ed.setMaxValue(new StringType(v.getUpper().toString())); + } + } else { + System.out.println("not done yet: "+path+"."+name+": "+o.getClass().getName()); + } + } else if (o instanceof CInteger) { + CInteger c = (CInteger) o; + if (c.getConstraint().size() == 1) { + Interval v = c.getConstraint().get(0); + ElementDefinition ed = new ElementDefinition(path+"."+name); + ed.setId(id+"."+name); + defns.add(ed); + if (v.getLower() == v.getUpper()) { + ed.setFixed(new IntegerType(v.getLower())); + } else { + ed.setMinValue(new IntegerType(v.getLower())); + ed.setMaxValue(new IntegerType(v.getUpper())); + } + } else { + System.out.println("not done yet: "+path+"."+name+": "+o.getClass().getName()); + } + } else if (o instanceof CComplexObjectProxy) { + CComplexObjectProxy c = (CComplexObjectProxy) o; + ElementDefinition ed = new ElementDefinition(path+"."+name); + ed.setId(id+"."+name); + defns.add(ed); + String[] tp = c.getTargetPath().split("\\/"); + String at = tp[tp.length-1]; + at = at.substring(at.indexOf("[")+1).replace("]", ""); + var ct = ed.addType().setCode(c.getRmTypeName()).addProfileElement(); + ct.setValue(sd.getUrl()); + ct.addExtension(ToolingExtensions.EXT_PROFILE_ELEMENT, new StringType(atMap.get(at))); + } else { + System.out.println("not done yet: "+path+"."+name+": "+o.getClass().getName()); + } + } else { + ElementDefinition slicer = new ElementDefinition(path+"."+name); + slicer.getSlicing().setRules(SlicingRules.CLOSED); + boolean typeSlicing = false; + if (isSingleton(source.getRmTypeName(), name)) { + slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.TYPE).setPath("$this"); + typeSlicing = true; + } else { + slicer.getSlicing().addDiscriminator().setType(DiscriminatorType.PROFILE).setPath("$this"); + } + defns.add(slicer); + for (CObject o : a.getChildren()) { + String sn = typeSlicing ? o.getRmTypeName(): o.getNodeId(); + if (o instanceof CComplexObject) { + processDefinition(defns, defn, (CComplexObject) o, path+"."+name, sn, id+"."+name+":"+sn, defn.hasLabel() ? defn.getLabel() : label); + } else if (o instanceof ArchetypeSlot) { + ArchetypeSlot c = (ArchetypeSlot) o; + ElementDefinition ed = new ElementDefinition(path+"."+name); + ed.setId(id+"."+name+":"+sn); + ed.setSliceName(sn); + defns.add(ed); + buildDefinition(c, ed); + // ed.getTypeFirstRep().addProfile(canonical+"/StructureDefinition/"+c); + } else { + System.out.println("not done yet: "+path+"."+name+": "+o.getClass().getName()); + } + } + } + } + } + + private boolean bindingGoesOnParent(String rmTypeName, String name) { + switch (rmTypeName+"."+name) { + case "DV_CODED_TEXT.defining_code": return true; + default: + throw new Error("unknown element "+rmTypeName+"."+name); + } + } + + private boolean isSingleton(String rmTypeName, String name) { + switch (rmTypeName+"."+name) { + case "HISTORY.events": return false; + case "ITEM_TREE.items": return false; + case "ELEMENT.value": return true; + default: + throw new Error("unknown element "+rmTypeName+"."+name); + } + } + + public void buildDefinition(CObject source, ElementDefinition defn) { + defn.setLabel(source.getNodeId()); + defn.setShort(lookup(source.getNodeId())); + defn.setDefinition(lookupDesc(source.getNodeId())); + defn.addType().setCode("http://openehr.org/fhir/StructureDefinition/"+source.getRmTypeName()); + if (source.getOccurrences() != null) { + if (source.getOccurrences().isLowerIncluded()) { + defn.setMin(source.getOccurrences().getLower()); + } + if (source.getOccurrences().isUpperIncluded()) { + if (source.getOccurrences().isUpperUnbounded()) { + defn.setMax("*"); + } else { + defn.setMax(source.getOccurrences().getUpper().toString()); + } + } + } + } + + private String makeValueSet(String label, String text, String desc, CTerminologyCode c) { + String id = sd.getId()+"."+label; + String url = canonicalBase+"/ValueSet/"+id; + + ValueSet vs = new ValueSet(); + vs.setIdBase(id); + vs.setUrl(url); + vs.setName(Utilities.javaTokenize(text, true)+"VS"); + vs.setTitle(text+" ValueSet"); + vs.setStatus(sd.getStatus()); + vs.setDate(sd.getDate()); + vs.setVersion(sd.getVersion()); + vs.setDescription("ValueSet for "+desc); + addResource(vs); + + CodeSystem cs = null; + + Map incs = new HashMap(); + for (String s : c.getConstraint()) { + String code = s; + ConceptSetComponent inc; + if (s.contains("::")) { + String sys = s.substring(0, s.indexOf("::")).replace("[", ""); + code = s.substring(s.indexOf("::")+2).replace("]", ""); + if ("openehr".equals(sys)) { + String system = getSystemForConcept(code); + inc = incs.get(system); + if (inc == null) { + inc = vs.getCompose().addInclude(); + inc.setSystem(system); + incs.put(system, inc); + } + } else { + throw new Error("Not handled yet"); + } + } else { + if (cs == null) { + cs = new CodeSystem(); + cs.setIdBase(id); + cs.setUrl(canonicalBase+"/CodeSystem/"+id); + cs.setStatus(sd.getStatus()); + cs.setName(Utilities.javaTokenize(text, true)+"CS"); + cs.setTitle(text); + + cs.setDate(sd.getDate()); + cs.setVersion(sd.getVersion()); + cs.setDescription("CodeSystem for "+desc); + cs.setContent(CodeSystemContentMode.COMPLETE); + cs.setCaseSensitive(true); + + addResource(cs); + } + inc = incs.get(""); + if (inc == null) { + inc = vs.getCompose().addInclude(); + inc.setSystem(cs.getUrl()); + incs.put("", inc); + } + ConceptDefinitionComponent cd = cs.addConcept(); + cd.setCode(s); + cd.setDisplay(lookup(s)); + cd.setDefinition(lookupDesc(s)); + } + inc.addConcept().setCode(code); + } + return vs.getUrl(); + } + + private String getSystemForConcept(String code) { + for (Element group : XMLUtil.getNamedChildren(terminology, "group")) { + for (Element concept : XMLUtil.getNamedChildren(group, "concept")) { + if (code.equals(concept.getAttribute("id"))) { + return "https://specifications.openehr.org/fhir/codesystem-"+group.getAttribute("openehr_id"); + } + } + } + throw new Error("unknown openEHR code "+code); + } + + + private String lookup(String nodeId) { + ArchetypeTerm td = archetype.getTerminology().getTermDefinition("en", nodeId); + return td == null ? null : td.getText(); + } + + private String lookupDesc(String nodeId) { + ArchetypeTerm td = archetype.getTerminology().getTermDefinition("en", nodeId); + return td == null ? null : td.getDescription(); + } + +} diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/openehr/OpenEHRTest.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/openehr/OpenEHRTest.java new file mode 100644 index 000000000..927c10bb4 --- /dev/null +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/openehr/OpenEHRTest.java @@ -0,0 +1,33 @@ +package org.hl7.fhir.igtools.openehr; + +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; + +import javax.xml.parsers.ParserConfigurationException; + +import org.hl7.fhir.igtools.openehr.ArchetypeImporter.ProcessedArchetype; +import org.hl7.fhir.r5.formats.IParser.OutputStyle; +import org.hl7.fhir.r5.formats.JsonParser; +import org.hl7.fhir.r5.model.Bundle; +import org.hl7.fhir.utilities.TextFile; +import org.xml.sax.SAXException; + +import com.nedap.archie.adlparser.ADLParseException; + +public class OpenEHRTest { + + public static void main(String[] args) throws Exception { + new OpenEHRTest().test(); + } + + public void test() throws FileNotFoundException, ADLParseException, IOException, ParserConfigurationException, SAXException { + ArchetypeImporter ai = new ArchetypeImporter(null, "http://openehr.org/fhir/uv/test"); + ProcessedArchetype pa = ai.importArchetype(new FileInputStream("/Users/grahamegrieve/Downloads/openEHR-EHR-OBSERVATION.blood_pressure.v2.adl"), "openEHR-EHR-OBSERVATION.blood_pressure.v2.adl"); + System.out.println(); + String json = new JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(pa.getBnd()); + TextFile.stringToFile(json, "/Users/grahamegrieve/temp/igs/FHIR-sample-ig#master/input/resources/Bundle-openEHR-EHR-OBSERVATION.blood-pressure.v2.json"); + System.out.println("Done"); + } + +} diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/GitUtilities.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/GitUtilities.java index 2d32671dc..1fe1aecc2 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/GitUtilities.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/GitUtilities.java @@ -34,14 +34,28 @@ private static String toString(InputStream inputStream) throws InterruptedExcept public static String getGitStatus(File gitDir) { if (!gitDir.exists()) { - return ""; + return ""; } try { - String[] cmd = { "git", "branch", "--show-current" }; - return execAndReturnString(cmd, new String[]{}, gitDir); + String[] cmd = { "git", "branch", "--show-current" }; + return execAndReturnString(cmd, new String[]{}, gitDir); } catch (Exception e) { - System.out.println("Warning @ Unable to read the git branch: " + e.getMessage().replace("fatal: ", "") ); - return ""; + System.out.println("Warning @ Unable to read the git branch: " + e.getMessage().replace("fatal: ", "") ); + return ""; } } + + public static String getGitSource(File gitDir) { + if (!gitDir.exists()) { + return ""; + } + try { + String[] cmd = { "git", "remote", "get-url", "origin" }; + return execAndReturnString(cmd, new String[]{}, gitDir); + } catch (Exception e) { + System.out.println("Warning @ Unable to read the git source: " + e.getMessage().replace("fatal: ", "") ); + return ""; + } + } + } diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/IGPack2NpmConvertor.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/IGPack2NpmConvertor.java index 5b4b807d9..91732f336 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/IGPack2NpmConvertor.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/IGPack2NpmConvertor.java @@ -216,9 +216,7 @@ private void processValidatorPack(File f) throws IOException { String destFile = dest != null ? dest : Utilities.path(Utilities.getDirectoryForFile(f.getAbsolutePath()), "package.tgz"); String url = Utilities.noString(website) ? canonical : website; NPMPackageGenerator npm = new NPMPackageGenerator(destFile, canonical, url, PackageType.IG, ig, new Date(), false); - ByteArrayOutputStream bs = new ByteArrayOutputStream(); - new JsonParser().setOutputStyle(OutputStyle.NORMAL).compose(bs, ig); - npm.addFile(Category.OTHER, "ig-r4.jsonX", bs.toByteArray()); + npm.addFile(Category.RESOURCE, "ImplementationGuide-"+ig.getId()+".json", compose(ig, version)); diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/Publisher.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/Publisher.java index b87fb9efb..c4f41a28c 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/Publisher.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/Publisher.java @@ -91,6 +91,8 @@ import org.hl7.fhir.exceptions.DefinitionException; import org.hl7.fhir.exceptions.FHIRException; import org.hl7.fhir.exceptions.FHIRFormatError; +import org.hl7.fhir.igtools.openehr.ArchetypeImporter; +import org.hl7.fhir.igtools.openehr.ArchetypeImporter.ProcessedArchetype; import org.hl7.fhir.igtools.publisher.FetchedFile.FetchedBundleType; import org.hl7.fhir.igtools.publisher.IFetchFile.FetchState; import org.hl7.fhir.igtools.publisher.comparators.IpaComparator; @@ -704,6 +706,7 @@ public enum CacheOption { private List pagesDirs = new ArrayList(); private List testDirs = new ArrayList(); private List dataDirs = new ArrayList(); + private List otherDirs = new ArrayList(); private String tempDir; private String tempLangDir; private String outputDir; @@ -2783,8 +2786,7 @@ private void initializeFromIg(IniFile ini) throws Exception { } catch (Exception e) { throw new Exception("Error Parsing File "+igName+": "+e.getMessage(), e); } - template = templateManager.loadTemplate(templateName, rootDir, sourceIg.getPackageId(), mode == IGBuildMode.AUTOBUILD); - + template = templateManager.loadTemplate(templateName, rootDir, sourceIg.getPackageId(), mode == IGBuildMode.AUTOBUILD, logOptions.contains("template")); if (template.hasExtraTemplates()) { processExtraTemplates(template.getExtraTemplates()); } @@ -2878,6 +2880,9 @@ private void initializeFromIg(IniFile ini) throws Exception { case "path-data": dataDirs.add(Utilities.path(rootDir, p.getValue())); break; + case "path-other": + otherDirs.add(Utilities.path(rootDir, p.getValue())); + break; case "copyrightyear": copyrightYear = p.getValue(); break; @@ -3355,6 +3360,7 @@ else if (vsCache == null) { igpkp.setAutoPath(true); } fetcher.setPkp(igpkp); + fetcher.setContext(context); template.loadSummaryRows(igpkp.summaryRows()); if (VersionUtilities.isR4Plus(version) && !dependsOnExtensions(sourceIg.getDependsOn()) && !sourceIg.getPackageId().contains("hl7.fhir.uv.extensions")) { @@ -5410,7 +5416,7 @@ private void templateBeforeGenerate() throws IOException, FHIRException { if (debug) { waitForInput("before OnGenerate"); } - logMessage("Run Template "); + logMessage("Run Template"); Session tts = tt.start("template"); List newFileList = new ArrayList(); checkOutcomes(template.beforeGenerateEvent(publishedIg, tempDir, otherFilesRun, newFileList)); @@ -5438,7 +5444,7 @@ private void templateBeforeGenerate() throws IOException, FHIRException { loadIgPages(publishedIg.getDefinition().getPage(), igPages); } tts.end(); - logMessage("Template Done"); + logDebugMessage(LogCategory.PROGRESS, "Template Done"); if (debug) { waitForInput("after OnGenerate"); } @@ -5690,16 +5696,74 @@ private boolean loadBundle(String name, boolean needToBuild, FetchedFile igf, St return changed || needToBuild; } + private boolean loadArchetype(FetchedFile f, String cause) throws Exception { + ProcessedArchetype pa = new ArchetypeImporter(context, igpkp.getCanonical()).importArchetype(f.getSource(), new File(f.getStatedPath()).getName()); + Bundle bnd = pa.getBnd(); + pa.getSd().setUserData(UserDataNames.archetypeSource, pa.getSource()); + pa.getSd().setUserData(UserDataNames.archetypeName, pa.getSourceName()); + + f.setBundle(new FetchedResource(f.getName()+" (bundle)")); + f.setBundleType(FetchedBundleType.NATIVE); + + boolean changed = noteFile("Bundle/"+bnd.getIdBase(), f); + int i = -1; + for (BundleEntryComponent be : bnd.getEntry()) { + i++; + Resource res = be.getResource(); + Element e = new ObjectConverter(context).convert(res); + checkResourceUnique(res.fhirType()+"/"+res.getIdBase(), f.getName(), cause); + FetchedResource r = f.addResource(f.getName()+"["+i+"]"); + r.setElement(e); + r.setResource(res); + r.setId(res.getIdBase()); + + r.setTitle(r.getElement().getChildValue("name")); + igpkp.findConfiguration(f, r); + } + for (FetchedResource r : f.getResources()) { + bndIds.add(r.fhirType()+"/"+r.getId()); + ImplementationGuideDefinitionResourceComponent res = findIGReference(r.fhirType(), r.getId()); + if (res == null) { + res = publishedIg.getDefinition().addResource(); + if (!res.hasName()) + if (r.hasTitle()) + res.setName(r.getTitle()); + else + res.setName(r.getId()); + if (!res.hasDescription() && r.getElement().hasChild("description")) { + res.setDescription(r.getElement().getChildValue("description").trim()); + } + res.setReference(new Reference().setReference(r.fhirType()+"/"+r.getId())); + } + res.setUserData(UserDataNames.pub_loaded_resource, r); + r.setResEntry(res); + if (r.getResource() instanceof CanonicalResource) { + CanonicalResource cr = (CanonicalResource)r.getResource(); + if (!canonicalResources.containsKey(cr.getUrl())) { + canonicalResources.put(cr.getUrl(), r); + if (cr.hasVersion()) + canonicalResources.put(cr.getUrl()+"#"+cr.getVersion(), r); + } + } + } + return changed; + } + private boolean loadResources(boolean needToBuild, FetchedFile igf) throws Exception { // igf is not currently used, but it was about relative references? List resources = fetcher.scan(sourceDir, context, igpkp.isAutoPath()); for (FetchedFile ff : resources) { ff.start("loadResources"); - try { - if (!ff.matches(igf) && !isBundle(ff)) { - needToBuild = loadResource(needToBuild, ff, "scan folder "+Utilities.getDirectoryForFile(ff.getStatedPath())); + if (ff.getContentType().equals("adl")) { + loadArchetype(ff, "scan folder "+Utilities.getDirectoryForFile(ff.getStatedPath())); + needToBuild = true; + } else { + try { + if (!ff.matches(igf) && !isBundle(ff)) { + needToBuild = loadResource(needToBuild, ff, "scan folder "+Utilities.getDirectoryForFile(ff.getStatedPath())); + } + } finally { + ff.finish("loadResources"); } - } finally { - ff.finish("loadResources"); } } return needToBuild; @@ -5708,11 +5772,11 @@ private boolean loadResources(boolean needToBuild, FetchedFile igf) throws Excep private boolean isBundle(FetchedFile ff) { File f = new File(ff.getName()); String n = f.getName(); - if (n.contains(".")) { - n = n.substring(0, n.indexOf(".")); + if (n.endsWith(".json") || n.endsWith(".xml")) { + n = n.substring(0, n.lastIndexOf(".")); } for (String s : bundles) { - if (n.equals("bundle-"+s)) { + if (n.equals("bundle-"+s) || n.equals("Bundle-"+s) ) { return true; } } @@ -6617,10 +6681,10 @@ public void checkResourceUnique(String tid, String source, String cause) throws System.out.println("id: "+tid+", file: "+source+", from "+cause); } if (loadedIds.containsKey(tid)) { - System.out.println("Duplicate Resource in IG: "+tid+". first found in "+loadedIds.get(tid)+", now in "+source); + System.out.println("Duplicate Resource in IG: "+tid+". first found in "+loadedIds.get(tid)+", now in "+source+" ("+cause+")"); duplicateInputResourcesDetected = true; } - loadedIds.put(tid, source); + loadedIds.put(tid, source+" ("+cause+")"); } private ImplementationGuideDefinitionResourceComponent findIGReference(String type, String id) { @@ -6631,7 +6695,7 @@ private ImplementationGuideDefinitionResourceComponent findIGReference(String ty } return null; } - + private Element loadFromMap(FetchedFile file) throws Exception { if (!VersionUtilities.isR4Plus(context.getVersion())) { throw new Error("Loading Map Files is not supported for version "+VersionUtilities.getNameForVersion(context.getVersion())); @@ -8084,6 +8148,18 @@ private void generate() throws Exception { for (String t : testDirs) { addTestDir(new File(t), t); } + for (String n : otherDirs) { + File f = new File(n); + if (f.exists()) { + for (File ff : f.listFiles()) { + if (!SimpleFetcher.isIgnoredFile(f.getName())) { + npm.addFile(Category.OTHER, ff.getName(), TextFile.fileToBytes(ff.getAbsolutePath())); + } + } + } else { + logMessage("Other Directory not found: "+n); + } + } npm.finish(); if (r4tor4b.canBeR4() && r4tor4b.canBeR4B()) { try { @@ -8340,11 +8416,7 @@ private void updateImplementationGuide() throws Exception { } r.setResource(publishedIg); r.setElement(convertToElement(r, publishedIg)); - - ByteArrayOutputStream bs = new ByteArrayOutputStream(); - new org.hl7.fhir.r4.formats.JsonParser().compose(bs, VersionConvertorFactory_40_50.convertResource(publishedIg)); - npm.addFile(Category.OTHER, "ig-r4.jsonX", bs.toByteArray()); - + for (ImplementationGuideDefinitionResourceComponent res : publishedIg.getDefinition().getResource()) { FetchedResource rt = null; for (FetchedFile tf : fileList) { @@ -10797,6 +10869,11 @@ private void generateDataFile(DBBuilder db) throws IOException, FHIRException, S if (repoSource != null) { data.add("repoSource", gh()); + } else { + String git= getGitSource(); + if (git != null) { + data.add("repoSource", git); + } } data.add("genDate", genTime()); data.add("genDay", genDate()); @@ -10926,6 +11003,11 @@ private String getGitStatus() throws IOException { File gitDir = new File(Utilities.getDirectoryForFile(configFile)); return GitUtilities.getGitStatus(gitDir); } + + private String getGitSource() throws IOException { + File gitDir = new File(Utilities.getDirectoryForFile(configFile)); + return GitUtilities.getGitSource(gitDir); + } private void generateResourceReferences() throws Exception { Set resourceTypes = new HashSet<>(); @@ -11299,7 +11381,6 @@ private void generateHtmlOutputs(FetchedFile f, boolean regen, DBBuilder db) thr saveFileOutputs(f); for (FetchedResource r : f.getResources()) { logDebugMessage(LogCategory.PROGRESS, "Produce outputs for "+r.fhirType()+"/"+r.getId()); - logMessage("Generate HTML Outputs for "+r.fhirType()+"/"+r.getId()); Map vars = makeVars(r); makeTemplates(f, r, vars); saveDirectResourceOutputs(f, r, r.getResource(), vars); @@ -12327,7 +12408,10 @@ private byte[] saveNativeResourceOutputs(FetchedFile f, FetchedResource r) throw Element eNN = element; jp.compose(element, bsj, OutputStyle.NORMAL, igpkp.getCanonical()); if (!r.isCustomResource()) { - npm.addFile(isExample(f,r ) ? Category.EXAMPLE : Category.RESOURCE, element.fhirType()+"-"+r.getId()+".json", bsj.toByteArray()); + npm.addFile(isExample(f,r ) ? Category.EXAMPLE : Category.RESOURCE, element.fhirType()+"-"+r.getId()+".json", bsj.toByteArray()); + if (r.getResource() != null && r.getResource().hasUserData(UserDataNames.archetypeSource)) { + npm.addFile(Category.ADL, r.getResource().getUserString(UserDataNames.archetypeName), r.getResource().getUserString(UserDataNames.archetypeSource).getBytes(StandardCharsets.UTF_8)); + } } else if ("StructureDefinition".equals(r.fhirType())) { npm.addFile(Category.RESOURCE, element.fhirType()+"-"+r.getId()+".json", bsj.toByteArray()); StructureDefinition sdt = (StructureDefinition) r.getResource().copy(); @@ -13159,6 +13243,15 @@ private void generateOutputsStructureDefinition(FetchedFile f, FetchedResource r fragment("StructureDefinition-"+prefixForContainer+sd.getId()+"-experimental-warning"+langSfx, sdr.experimentalWarning(), f.getOutputNames(), r, vars, null, start, "experimental-warning", "StructureDefinition"); } + if (igpkp.wantGen(r, "eview")) { + long start = System.currentTimeMillis(); + fragment("StructureDefinition-"+prefixForContainer+sd.getId()+"-eview"+langSfx, sdr.eview(igpkp.getDefinitionsName(r), otherFilesRun, tabbedSnapshots, StructureDefinitionRendererMode.SUMMARY, false), f.getOutputNames(), r, vars, null, start, "eview", "StructureDefinition"); + fragment("StructureDefinition-"+prefixForContainer+sd.getId()+"-eview-all"+langSfx, sdr.eview(igpkp.getDefinitionsName(r), otherFilesRun, tabbedSnapshots, StructureDefinitionRendererMode.SUMMARY, true), f.getOutputNames(), r, vars, null, start, "eview", "StructureDefinition"); + } + if (igpkp.wantGen(r, "adl") && sd.hasUserData(UserDataNames.archetypeSource)) { + long start = System.currentTimeMillis(); + fragment("StructureDefinition-"+prefixForContainer+sd.getId()+"-adl"+langSfx, sdr.adl(), f.getOutputNames(), r, vars, null, start, "adl", "StructureDefinition"); + } if (igpkp.wantGen(r, "diff")) { long start = System.currentTimeMillis(); fragment("StructureDefinition-"+prefixForContainer+sd.getId()+"-diff"+langSfx, sdr.diff(igpkp.getDefinitionsName(r), otherFilesRun, tabbedSnapshots, StructureDefinitionRendererMode.SUMMARY, false), f.getOutputNames(), r, vars, null, start, "diff", "StructureDefinition"); @@ -13905,8 +13998,12 @@ public static String buildReport(String ig, String source, String log, String qa public static void main(String[] args) throws Exception { int exitCode = 0; + // Prevents SLF4J(I) from printing unnecessary info to the console. + System.setProperty("slf4j.internal.verbosity", "WARN"); + org.hl7.fhir.utilities.FileFormat.checkCharsetAndWarnIfNotUTF8(System.out); - NpmPackage.setLoadCustomResources(true); + + NpmPackage.setLoadCustomResources(true); if (CliParams.hasNamedParam(args, FHIR_SETTINGS_PARAM)) { FhirSettings.setExplicitFilePath(CliParams.getNamedParam(args, FHIR_SETTINGS_PARAM)); } diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/R4ToR4BAnalyser.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/R4ToR4BAnalyser.java index 565567f32..73a7eaac4 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/R4ToR4BAnalyser.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/R4ToR4BAnalyser.java @@ -467,8 +467,6 @@ private void processFileSame(NPMPackageGenerator gen, String folder, String file // System.out.println("** Exclude "+res.fhirType()+"/"+res.getId()+" from same version"); } } - } else if (filename.equals("ig-r4.json") || filename.equals("ig-r4.jsonX")) { - gen.addFile(folder, filename, updateIGR4(content, ver, pver)); } else if (filename.equals("spec.internals")) { gen.addFile(folder, filename, updateSpecInternals(content, ver, pver)); } else { diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/SimpleFetcher.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/SimpleFetcher.java index c9353a9fb..3f2802c16 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/SimpleFetcher.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/SimpleFetcher.java @@ -32,12 +32,14 @@ import java.util.List; import org.hl7.fhir.exceptions.FHIRException; +import org.hl7.fhir.igtools.openehr.ArchetypeImporter; import org.hl7.fhir.r5.context.ILoggingService; import org.hl7.fhir.r5.context.ILoggingService.LogCategory; import org.hl7.fhir.r5.context.IWorkerContext; import org.hl7.fhir.r5.elementmodel.FmlParser; import org.hl7.fhir.r5.elementmodel.ValidatedFragment; import org.hl7.fhir.r5.formats.FormatUtilities; +import org.hl7.fhir.r5.model.Bundle; import org.hl7.fhir.r5.model.CanonicalType; import org.hl7.fhir.r5.model.DataType; import org.hl7.fhir.r5.model.Reference; @@ -59,6 +61,7 @@ public int compare(FetchedFile o1, FetchedFile o2) { private static final String[] EXTENSIONS = new String[] {".xml", ".json", ".map", ".fml", ".phinvads"}; private IGKnowledgeProvider pkp; + private IWorkerContext context; private List resourceDirs; private ILoggingService log; private String rootDir; @@ -81,6 +84,14 @@ public void setPkp(IGKnowledgeProvider pkp) { this.pkp = pkp; } + public IWorkerContext getContext() { + return context; + } + + public void setContext(IWorkerContext context) { + this.context = context; + } + public String getRootDir() { return rootDir; } @@ -124,7 +135,7 @@ public FetchedFile fetch(String path) throws Exception { return ff; } - private boolean isIgnoredFile(String name) { + public static boolean isIgnoredFile(String name) { return name.startsWith(".") || Utilities.existsInList(Utilities.getFileExtension(name), "ini"); } @@ -351,7 +362,7 @@ public List scan(String sourceDir, IWorkerContext context, boolean } } } - if (!ok && !Utilities.existsInList(ext, "xml", "ttl", "html", "txt", "fml")) { + if (!ok && !Utilities.existsInList(ext, "xml", "ttl", "html", "txt", "fml", "adl")) { try { List el = new org.hl7.fhir.r5.elementmodel.JsonParser(context).parse(new FileInputStream(fn)); if (el.size() == 1) { @@ -370,7 +381,7 @@ public List scan(String sourceDir, IWorkerContext context, boolean } } } - if (!ok && !Utilities.existsInList(ext, "json", "xml", "html", "txt", "fml")) { + if (!ok && !Utilities.existsInList(ext, "json", "xml", "html", "txt", "fml", "adl")) { try { org.hl7.fhir.r5.elementmodel.Element e = new org.hl7.fhir.r5.elementmodel.TurtleParser(context).parseSingle(new FileInputStream(fn), null); addFile(res, f, e, "application/fhir+turtle"); @@ -388,7 +399,7 @@ public List scan(String sourceDir, IWorkerContext context, boolean } } } - if (!ok && !Utilities.existsInList(ext, "json", "xml", "html", "txt")) { + if (!ok && !Utilities.existsInList(ext, "json", "xml", "html", "txt", "adl")) { try { if (fp==null) { fp = new FmlParser(context); @@ -397,6 +408,23 @@ public List scan(String sourceDir, IWorkerContext context, boolean addFile(res, f, e, "fml"); count++; ok = true; + } catch (Exception e) { + if (!f.getName().startsWith("Binary-")) { // we don't notify here because Binary is special. + if (report) { + log.logMessage("ADL Error loading "+f+": "+e.getMessage()); + if (debug) { + e.printStackTrace(); + } + } + } + } + } + if (!ok && !Utilities.existsInList(ext, "json", "xml", "html", "txt", "ttl")) { + try { + new ArchetypeImporter(context, pkp.getCanonical()).checkArchetype(new FileInputStream(f), f.getName()); + addFile(res, f, null, "adl"); + count++; + ok = true; } catch (Exception e) { if (!f.getName().startsWith("Binary-")) { // we don't notify here because Binary is special. if (report) { @@ -421,14 +449,14 @@ public List scan(String sourceDir, IWorkerContext context, boolean private List fixedFileTypes() { return Utilities.strings( // known file types we have parsers for - "json", "ttl", "html", "txt", "fml", + "json", "ttl", "html", "txt", "fml", "adl", // known files types to not even try parsing "jpg", "png", "gif", "mp3", "mp4", "pfd", "doc", "docx", "ppt", "pptx", "svg"); } private void addFile(List res, File f, org.hl7.fhir.r5.elementmodel.Element e, String cnt) throws IOException { - if (!e.fhirType().equals("ImplementationGuide") && !(f.getName().startsWith("Binary") && !"Binary".equals(e.fhirType()))) { + if (( e == null || !e.fhirType().equals("ImplementationGuide")) && !(f.getName().startsWith("Binary") && !"Binary".equals(e.fhirType()))) { addFile(res, f, cnt); } } diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/loaders/PublisherLoader.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/loaders/PublisherLoader.java index 568102aa7..9e9e3e633 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/loaders/PublisherLoader.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/publisher/loaders/PublisherLoader.java @@ -16,7 +16,9 @@ import org.hl7.fhir.igtools.publisher.SpecMapManager.SpecialPackageType; import org.hl7.fhir.r5.context.IContextResourceLoader; import org.hl7.fhir.r5.model.CanonicalResource; +import org.hl7.fhir.r5.model.DomainResource; import org.hl7.fhir.r5.model.Resource; +import org.hl7.fhir.r5.utils.ToolingExtensions; import org.hl7.fhir.r5.utils.UserDataNames; import org.hl7.fhir.utilities.Utilities; import org.hl7.fhir.utilities.VersionUtilities; @@ -106,6 +108,12 @@ private String getIgPath(Resource r) { } else { path = pathToSpec+"/"+ igpkp.doReplacements(p, r, null, null); } + if (r instanceof DomainResource) { + DomainResource dr = (DomainResource) r; + if (dr.hasExtension(ToolingExtensions.EXT_WEB_SOURCE)) { + path = ToolingExtensions.readStringExtension(dr, ToolingExtensions.EXT_WEB_SOURCE); + } + } r.setWebPath(path); if (path.contains("vsac")) { r.setUserData(UserDataNames.render_external_link, "https://vsac.nlm.nih.gov"); diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/StructureDefinitionRenderer.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/StructureDefinitionRenderer.java index a3b2813ea..6b2bc332d 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/StructureDefinitionRenderer.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/renderers/StructureDefinitionRenderer.java @@ -490,6 +490,10 @@ public String diff(String defnFile, Set outputTracker, boolean toTabs, S } } + public String eview(String defnFile, Set outputTracker, boolean toTabs, StructureDefinitionRendererMode mode, boolean all) throws IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException { + return new XhtmlComposer(XhtmlComposer.HTML).compose(sdr.buildElementTable(new RenderingStatus(), defnFile, sd, destDir, false, sd.getId(), false, corePath, "", sd.getKind() == StructureDefinitionKind.LOGICAL, false, outputTracker, false, gen.withUniqueLocalPrefix(all ? "da" : "d"), toTabs ? ANCHOR_PREFIX_DIFF : ANCHOR_PREFIX_SNAP, resE)); + } + public String snapshot(String defnFile, Set outputTracker, boolean toTabs, StructureDefinitionRendererMode mode, boolean all) throws IOException, FHIRException, org.hl7.fhir.exceptions.FHIRException { if (sd.getSnapshot().getElement().isEmpty()) return ""; @@ -886,7 +890,7 @@ public void txItem(Map txmap, StringBuilder b, String strengthInh = true; } - if (brd.vsn.equals("?ext")) { + if ("?ext".equals(brd.vsn)) { if (tx.getValueSet() != null) System.out.println("Value set '"+tx.getValueSet()+"' at " + url + "#" + path + " not found"); else if (!tx.hasDescription()) @@ -2563,4 +2567,8 @@ public String experimentalWarning() { return ""; } } + + public String adl() { + return "
"+Utilities.escapeXml(sd.getUserString(UserDataNames.archetypeSource))+"
"; + } } diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/Template.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/Template.java index 9bd9d19ac..9bbdc1c85 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/Template.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/Template.java @@ -87,6 +87,7 @@ public class Template { private String templateReason; private Set summaryRows = new HashSet<>(); private Set templateParams = new HashSet<>(); + private boolean wantLog; /** unpack the template into /template * @@ -96,11 +97,12 @@ public class Template { * * @throws IOException - only if the path is incorrect or the disk runs out of space */ - public Template(String rootDir, boolean canExecute, String templateThatCantExecute, String templateReason) throws IOException { + public Template(String rootDir, boolean canExecute, String templateThatCantExecute, String templateReason, boolean wantLog) throws IOException { root = rootDir; this.canExecute = canExecute; this.templateThatCantExecute = templateThatCantExecute; this.templateReason = templateReason; + this.wantLog = wantLog; templateDir = Utilities.path(rootDir, "template"); @@ -126,7 +128,11 @@ public Template(String rootDir, boolean canExecute, String templateThatCantExecu DefaultLogger consoleLogger = new DefaultLogger(); consoleLogger.setErrorPrintStream(System.err); consoleLogger.setOutputPrintStream(System.out); - consoleLogger.setMessageOutputLevel(Project.MSG_INFO); + if (wantLog) { + consoleLogger.setMessageOutputLevel(Project.MSG_INFO); + } else { + consoleLogger.setMessageOutputLevel(Project.MSG_ERR); + } antProject.addBuildListener(consoleLogger); antProject.setBasedir(root); antProject.setProperty("ig.root", root); diff --git a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/TemplateManager.java b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/TemplateManager.java index 6c4dac1b0..c32c970dd 100644 --- a/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/TemplateManager.java +++ b/org.hl7.fhir.publisher.core/src/main/java/org/hl7/fhir/igtools/templates/TemplateManager.java @@ -62,7 +62,7 @@ public TemplateManager(FilesystemPackageCacheManager pcm, ILoggingService logger this.logger = logger; } - public Template loadTemplate(String template, String rootFolder, String packageId, boolean autoMode) throws FHIRException, IOException { + public Template loadTemplate(String template, String rootFolder, String packageId, boolean autoMode, boolean wantLog) throws FHIRException, IOException { this.autoMode = autoMode; String templateDir = Utilities.path(rootFolder, "template"); boolean inPlace = template.equals("#template"); @@ -84,7 +84,7 @@ public Template loadTemplate(String template, String rootFolder, String packageI if (!canExecute) { logger.logMessage("IG template '"+templateThatCantExecute+"' is not trusted. No scripts will be executed"); } - return new Template(rootFolder, canExecute, templateThatCantExecute, templateReason); + return new Template(rootFolder, canExecute, templateThatCantExecute, templateReason, wantLog); } private void installTemplate(String template, String rootFolder, String templateDir, List scriptIds, ArrayList loadedIds, int level) throws FHIRException, IOException { diff --git a/org.hl7.fhir.publisher.core/src/main/resources/log4j2.component.properties b/org.hl7.fhir.publisher.core/src/main/resources/log4j2.component.properties new file mode 100644 index 000000000..51eb50a73 --- /dev/null +++ b/org.hl7.fhir.publisher.core/src/main/resources/log4j2.component.properties @@ -0,0 +1,4 @@ +# This forces log4j2 to use the included simple logger. One of our dependencies is using log4j2 for logging +# (no idea which one) instead of slf4j, and complains when log4j2 is unavailable. This prevents that complaint from +# appearing. +log4j2.loggerContextFactory = org.apache.logging.log4j.simple.SimpleLoggerContextFactory \ No newline at end of file diff --git a/org.hl7.fhir.publisher.core/src/main/resources/logback.xml b/org.hl7.fhir.publisher.core/src/main/resources/logback.xml index 1c8de9da3..0c5bc3c28 100644 --- a/org.hl7.fhir.publisher.core/src/main/resources/logback.xml +++ b/org.hl7.fhir.publisher.core/src/main/resources/logback.xml @@ -1,5 +1,5 @@ - - + + INFO @@ -13,4 +13,6 @@ + + \ No newline at end of file diff --git a/plot-ig-builder-auto.png b/plot-ig-builder-auto.png index 7ddfa26c7..e667733d6 100644 Binary files a/plot-ig-builder-auto.png and b/plot-ig-builder-auto.png differ diff --git a/pom.xml b/pom.xml index 5593ba3f3..f5f98e6a9 100644 --- a/pom.xml +++ b/pom.xml @@ -4,11 +4,11 @@ org.hl7.fhir.publisher org.hl7.fhir.publisher - 1.8.5-SNAPSHOT + 1.8.7-SNAPSHOT pom - 6.5.3-SNAPSHOT + 6.5.5-SNAPSHOT 3.0.0-M5 5.2.1 1.18.32 @@ -20,7 +20,7 @@ 11 11 11 - 1.2.13 + 1.5.15 HL7 FHIR IG Publisher @@ -304,6 +304,14 @@ plantuml-mit 1.2023.9
+ + + + com.nedap.healthcare.archie + archie-all + 3.12.0 + +
diff --git a/test-statistics.csv b/test-statistics.csv index 79ff1ab45..adf629d11 100644 --- a/test-statistics.csv +++ b/test-statistics.csv @@ -39,3 +39,4 @@ Version,example.fhir.uv.myig,fhir.base.template.ig,hl7.base.template.ig,hl7.cda. 1.8.0,96450,909,101,125337,924598,2516,1835676,1008860,664863,25501,122847,787419,744787,63131,263028 1.8.2,83030,892,73,127855,374880,1529,1130025,972980,715751,22748,106312,616601,568020,60323,285311 1.8.3,83295,1126,135,124745,391499,880,1416797,817577,703037,23874,113096,622828,557070,66592,269669 +1.8.5,106121,866,71,124640,480557,865,2043810,1073528,714008,27440,118104,805263,729205,68389,316861 diff --git a/test-statistics.json b/test-statistics.json index 1b92e829b..0eaa88dbd 100644 --- a/test-statistics.json +++ b/test-statistics.json @@ -4317,5 +4317,114 @@ "time" : 703037, "memory" : 6111631280 } + }, + "1.8.5" : { + "sync-date" : "2024-06-30", + "date" : "2025-01-04", + "hl7.fhir.template.ig" : { + "errors" : 0, + "warnings" : 0, + "hints" : 0, + "time" : 865, + "memory" : 18377136 + }, + "hl7.cda.uv.core" : { + "errors" : 51, + "warnings" : 59, + "hints" : 0, + "time" : 124640, + "memory" : 3087830168 + }, + "hl7.fhir.us.ecr" : { + "errors" : 4866, + "warnings" : 373, + "hints" : 88, + "time" : 1073528, + "memory" : 7628618984 + }, + "hl7.fhir.uv.ipa" : { + "errors" : 60, + "warnings" : 9, + "hints" : 39, + "time" : 118104, + "memory" : 2807872760 + }, + "hl7.fhir.uv.ips" : { + "errors" : 104, + "warnings" : 21, + "hints" : 37, + "time" : 805263, + "memory" : 3561104128 + }, + "ihe.mhd.fhir" : { + "errors" : 117, + "warnings" : 3, + "hints" : 28, + "time" : 316861, + "memory" : 2466331872 + }, + "hl7.fhir.uv.sdc" : { + "errors" : 383, + "warnings" : 85, + "hints" : 413, + "time" : 729205, + "memory" : 1094943560 + }, + "hl7.fhir.uv.tools" : { + "errors" : 147, + "warnings" : 2, + "hints" : 0, + "time" : 68389, + "memory" : 3207499504 + }, + "hl7.base.template.ig" : { + "errors" : 0, + "warnings" : 0, + "hints" : 0, + "time" : 71, + "memory" : 4196328 + }, + "hl7.fhir.uv.howto" : { + "errors" : 2, + "warnings" : 2, + "hints" : 1, + "time" : 27440, + "memory" : 1979783448 + }, + "fhir.base.template.ig" : { + "errors" : 0, + "warnings" : 0, + "hints" : 0, + "time" : 866, + "memory" : 152491160 + }, + "hl7.fhir.au.base" : { + "errors" : 322, + "warnings" : 141, + "hints" : 77, + "time" : 480557, + "memory" : 4188995120 + }, + "example.fhir.uv.myig" : { + "errors" : 29, + "warnings" : 19, + "hints" : 7, + "time" : 106121, + "memory" : 2227752256 + }, + "hl7.fhir.us.core" : { + "errors" : 259, + "warnings" : 33, + "hints" : 58, + "time" : 2043810, + "memory" : 7365404176 + }, + "hl7.fhir.uv.extensions" : { + "errors" : 2801, + "warnings" : 706, + "hints" : 1612, + "time" : 714008, + "memory" : 5493375872 + } } }