Add R4B code + update generator for R4B generation

This commit is contained in:
Grahame Grieve 2022-01-01 16:33:10 +11:00
parent 0c108545b4
commit c627ec43d9
560 changed files with 778587 additions and 174 deletions

View File

@ -59,6 +59,11 @@
<artifactId>org.hl7.fhir.r4</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.r4b</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.r5</artifactId>

View File

@ -1,4 +1,4 @@
package org.hl7.fhir.r5.model;
package org.hl7.fhir.{{jid}}.model;
// generated

View File

@ -1,4 +1,4 @@
package org.hl7.fhir.r5.formats;
package org.hl7.fhir.{{jid}}.formats;
// generated
@ -6,7 +6,7 @@ package org.hl7.fhir.r5.formats;
{{startMark}}
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.{{jid}}.model.*;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.exceptions.FHIRFormatError;

View File

@ -1,4 +1,4 @@
package org.hl7.fhir.r5.formats;
package org.hl7.fhir.{{jid}}.formats;
// generated
@ -7,8 +7,8 @@ package org.hl7.fhir.r5.formats;
{{startMark}}
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.StringType;
import org.hl7.fhir.{{jid}}.model.*;
import org.hl7.fhir.{{jid}}.model.StringType;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.exceptions.FHIRException;

View File

@ -1,4 +1,4 @@
package org.hl7.fhir.r5.model;
package org.hl7.fhir.{{jid}}.model;
{{license}}

View File

@ -1,4 +1,4 @@
package org.hl7.fhir.r5.model;
package org.hl7.fhir.{{jid}}.model;
{{license}}

View File

@ -1,4 +1,4 @@
package org.hl7.fhir.r5.formats;
package org.hl7.fhir.{{jid}}.formats;
// generated
@ -6,8 +6,8 @@ package org.hl7.fhir.r5.formats;
{{startMark}}
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.Enumerations.FHIRVersion;
import org.hl7.fhir.{{jid}}.model.*;
import org.hl7.fhir.{{jid}}.model.Enumerations.FHIRVersion;
import org.xmlpull.v1.*;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.exceptions.FHIRFormatError;

View File

@ -30,15 +30,15 @@ Resource = org.hl7.fhir.instance.model.api.IAnyResource
Period = ca.uhn.fhir.model.api.TemporalPrecisionEnum
Extension = org.hl7.fhir.instance.model.api.IBaseExtension, org.hl7.fhir.instance.model.api.IBaseDatatype, org.hl7.fhir.instance.model.api.IBaseHasExtensions
HumanName = ca.uhn.fhir.util.DatatypeUtil, org.hl7.fhir.instance.model.api.IPrimitiveType
StructureMap = org.hl7.fhir.r5.utils.structuremap.StructureMapUtilities
StructureMap = org.hl7.fhir.{{jid}}.utils.structuremap.StructureMapUtilities
Narrative = org.hl7.fhir.instance.model.api.INarrative
Coding = org.hl7.fhir.instance.model.api.IBaseCoding
OperationOutcome = org.hl7.fhir.instance.model.api.IBaseOperationOutcome
CapabilityStatement = org.hl7.fhir.instance.model.api.IBaseConformance
DataType = org.hl7.fhir.instance.model.api.IBaseDatatype, ca.uhn.fhir.model.api.IElement
ElementDefinition = org.hl7.fhir.instance.model.api.ICompositeType, org.hl7.fhir.r5.model.Enumerations.BindingStrength, org.hl7.fhir.r5.model.Enumerations.BindingStrengthEnumFactory, org.hl7.fhir.r5.utils.ToolingExtensions, org.hl7.fhir.instance.model.api.IBaseDatatypeElement, org.hl7.fhir.utilities.CommaSeparatedStringBuilder
ElementDefinition = org.hl7.fhir.instance.model.api.ICompositeType, org.hl7.fhir.{{jid}}.model.Enumerations.BindingStrength, org.hl7.fhir.{{jid}}.model.Enumerations.BindingStrengthEnumFactory, org.hl7.fhir.{{jid}}.utils.ToolingExtensions, org.hl7.fhir.instance.model.api.IBaseDatatypeElement, org.hl7.fhir.utilities.CommaSeparatedStringBuilder
Binary = org.hl7.fhir.instance.model.api.IBaseBinary
ContactDetail = org.hl7.fhir.r5.model.ContactPoint.ContactPointSystem
ContactDetail = org.hl7.fhir.{{jid}}.model.ContactPoint.ContactPointSystem
[shared]
http://hl7.org/fhir/ValueSet/concept-map-relationship = true

View File

@ -10,12 +10,12 @@ import java.util.List;
import javax.xml.parsers.ParserConfigurationException;
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.DomainResource;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.test.utils.TestingUtilities;
import org.hl7.fhir.{{jid}}.formats.IParser.OutputStyle;
import org.hl7.fhir.{{jid}}.formats.JsonParser;
import org.hl7.fhir.{{jid}}.formats.XmlParser;
import org.hl7.fhir.{{jid}}.model.DomainResource;
import org.hl7.fhir.{{jid}}.model.Resource;
import org.hl7.fhir.{{jid}}.test.utils.TestingUtilities;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xml.XMLUtil;

View File

@ -19,35 +19,46 @@ import org.hl7.fhir.r5.model.Enumerations.ResourceTypeEnum;
import org.hl7.fhir.r5.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
public class Analyser {
private Definitions definitions;
private Configuration config;
private String version;
public Analyser(Definitions definitions, Configuration config) {
public Analyser(Definitions definitions, Configuration config, String version) {
this.definitions = definitions;
this.config = config;
this.version = version;
}
public Analysis analyse(StructureDefinition sd) throws Exception {
Analysis res = new Analysis(definitions, sd);
res.setAncestor(definitions.getStructures().get(sd.getBaseDefinition()));
if (VersionUtilities.isR4BVer(version)) {
res.setAncestor(definitions.getStructures().get(getR4bAncestor(sd)));
} else {
res.setAncestor(definitions.getStructures().get(sd.getBaseDefinition()));
}
res.setAbstract(sd.getAbstract());
res.setInterface(sd.hasExtension("http://hl7.org/fhir/StructureDefinition/structuredefinition-interface"));
res.setClassName(sd.getName().equals("List") ? "ListResource" : sd.getName());
TypeInfo type = new TypeInfo();
type.setName(res.getClassName());
type.setAncestorName(res.getAncestor().getName());
if (res.getAncestor() != null) {
type.setAncestorName(res.getAncestor().getName());
}
res.getTypes().put(type.getName(), type);
res.setRootType(type);
sd.setUserData("java.type.info", type);
type.setDefn(sd.getSnapshot().getElementFirstRep());
type.setChildren(filterChildren(new ProfileUtilities(null, null, null).getChildList(sd, type.getDefn())));
type.setInheritedChildren(getAbstractChildren(res.getAncestor()));
if (res.getAncestor() != null) {
type.setInheritedChildren(getAbstractChildren(res.getAncestor()));
}
for (ElementDefinition e : type.getChildren()) {
scanNestedTypes(res, type, type.getName(), e);
@ -72,6 +83,16 @@ public class Analyser {
return res;
}
private String getR4bAncestor(StructureDefinition sd) {
switch (sd.getKind()) {
case COMPLEXTYPE: return "http://hl7.org/fhir/StructureDefinition/DataType";
case LOGICAL: return "http://hl7.org/fhir/StructureDefinition/Element";
case PRIMITIVETYPE: return "http://hl7.org/fhir/StructureDefinition/PrimitiveType";
case RESOURCE: return sd.getBaseDefinition();
default: return null;
}
}
protected List<ElementDefinition> filterChildren(List<ElementDefinition> childList) {
List<ElementDefinition> res = new ArrayList<>();
res.addAll(childList);
@ -110,6 +131,9 @@ public class Analyser {
analysis.getEnums().put(tn, ei);
ei.setValueSet(vs);
}
if (tn.equals("SubscriptionStatus")) { // work around cause there's a Resource with the same name
tn = "org.hl7.fhir.r4b.model.Enumerations."+tn;
}
e.setUserData("java.type", "Enumeration<"+tn+">");
e.setUserData("java.enum", ei);
}

View File

@ -54,13 +54,15 @@ public class JavaBaseGenerator extends OutputStreamWriter {
protected Configuration config;
protected String version;
protected Date genDate;
protected String jid;
public JavaBaseGenerator(OutputStream arg0, Definitions definitions, Configuration config, String version, Date genDate) throws UnsupportedEncodingException {
public JavaBaseGenerator(OutputStream arg0, Definitions definitions, Configuration config, String version, Date genDate, String jid) throws UnsupportedEncodingException {
super(arg0, "UTF-8");
this.definitions = definitions;
this.config = config;
this.version = version;
this.genDate = genDate;
this.jid = jid;
}
public void startMark(String version, Date genDate) throws IOException {

View File

@ -13,8 +13,8 @@ import org.hl7.fhir.utilities.VersionUtilities;
public class JavaConstantsGenerator extends JavaBaseGenerator {
public JavaConstantsGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate);
public JavaConstantsGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version, String jid) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate, jid);
}
public void generate() throws Exception {
@ -29,6 +29,7 @@ public class JavaConstantsGenerator extends JavaBaseGenerator {
}
String template = config.getAdornments().get("Constants");
template = template.replace("{{jid}}", jid);
template = template.replace("{{license}}", config.getLicense());
template = template.replace("{{startMark}}", startVMarkValue());

View File

@ -57,12 +57,12 @@ changes for James
*/
public class JavaEnumerationsGenerator extends JavaBaseGenerator {
public JavaEnumerationsGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate);
public JavaEnumerationsGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version, String jid) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate, jid);
}
public void generate() throws Exception {
write("package org.hl7.fhir.r5.model;\r\n");
write("package org.hl7.fhir."+jid+".model;\r\n");
startMark(version, genDate);
write("\r\n");
write("import org.hl7.fhir.instance.model.api.*;\r\n");
@ -153,7 +153,7 @@ public class JavaEnumerationsGenerator extends JavaBaseGenerator {
for (ValueSetExpansionContainsComponent c : vs.getExpansion().getContains()) {
String cc = Utilities.camelCase(c.getCode());
cc = makeConst(cc);
write(" case "+cc+": return \""+c.getCode()+"\";\r\n");
write(" case "+cc+": return \""+c.getCode()+"\";\r\n");
}
write(" case NULL: return null;\r\n");
write(" default: return \"?\";\r\n");

View File

@ -50,13 +50,14 @@ import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
public class JavaFactoryGenerator extends JavaBaseGenerator {
public JavaFactoryGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate);
public JavaFactoryGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version, String jid) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate, jid);
}
public void generate() throws Exception {
String template = config.getAdornments().get("ResourceFactory");
template = template.replace("{{license}}", config.getLicense());
template = template.replace("{{jid}}", jid);
template = template.replace("{{license}}", config.getLicense());
template = template.replace("{{startMark}}", startVMarkValue());
template = template.replace("{{resource-factory}}", genResourceFactory());
template = template.replace("{{type-factory}}", genTypeFactory());

View File

@ -67,8 +67,8 @@ public class JavaParserJsonGenerator extends JavaBaseGenerator {
private StringBuilder cregtp = new StringBuilder();
private StringBuilder cregti = new StringBuilder();
public JavaParserJsonGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate);
public JavaParserJsonGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version, String jid) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate, jid);
}
public void seeClass(Analysis analysis) throws Exception {
@ -93,6 +93,7 @@ public class JavaParserJsonGenerator extends JavaBaseGenerator {
public void generate() throws Exception {
String template = config.getAdornments().get("JsonParser");
template = template.replace("{{jid}}", jid);
template = template.replace("{{license}}", config.getLicense());
template = template.replace("{{startMark}}", startVMarkValue());
@ -168,7 +169,9 @@ public class JavaParserJsonGenerator extends JavaBaseGenerator {
parser.append(" protected void parse"+upFirst(tn).replace(".", "")+"Properties(JsonObject json, "+tn+" res) throws IOException, FHIRFormatError {\r\n");
parser.append(" parse"+analysis.getAncestor().getName()+"Properties(json, res);\r\n");
if (!"Element".equals(tn) && analysis.getAncestor() != null) {
parser.append(" parse"+analysis.getAncestor().getName()+"Properties(json, res);\r\n");
}
if (!analysis.isInterface()) {
for (ElementDefinition e : analysis.getRootType().getChildren()) {
genElementParser(analysis, analysis.getRootType(), e, bUseOwner, null);
@ -312,7 +315,9 @@ public class JavaParserJsonGenerator extends JavaBaseGenerator {
String tn = analysis.getRootType().getName();
composer.append(" protected void compose"+tn+"Properties("+tn+" element) throws IOException {\r\n");
composer.append(" compose"+analysis.getAncestor().getName()+"Properties(element);\r\n");
if (!"Element".equals(tn) && analysis.getAncestor() != null) {
composer.append(" compose"+analysis.getAncestor().getName()+"Properties(element);\r\n");
}
if (!analysis.isInterface()) {
for (ElementDefinition e : analysis.getRootType().getChildren()) {
genElementComposer(analysis, analysis.getRootType(), e, null);

View File

@ -51,8 +51,8 @@ public class JavaParserRdfGenerator extends JavaBaseGenerator {
private StringBuilder reg = new StringBuilder();
private StringBuilder regt = new StringBuilder();
public JavaParserRdfGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate);
public JavaParserRdfGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version, String jid) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate, jid);
}
public void seeClass(Analysis analysis) throws Exception {
@ -70,6 +70,7 @@ public class JavaParserRdfGenerator extends JavaBaseGenerator {
public void generate() throws Exception {
String template = config.getAdornments().get("RdfParser");
template = template.replace("{{jid}}", jid);
template = template.replace("{{license}}", config.getLicense());
template = template.replace("{{startMark}}", startVMarkValue());

View File

@ -62,8 +62,8 @@ public class JavaParserXmlGenerator extends JavaBaseGenerator {
private StringBuilder cRN = new StringBuilder();
private StringBuilder cType = new StringBuilder();
public JavaParserXmlGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate);
public JavaParserXmlGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version, String jid) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate, jid);
}
public void seeClass(Analysis analysis) throws Exception {
@ -88,6 +88,7 @@ public class JavaParserXmlGenerator extends JavaBaseGenerator {
public void generate() throws Exception {
String template = config.getAdornments().get("XmlParser");
template = template.replace("{{jid}}", jid);
template = template.replace("{{license}}", config.getLicense());
template = template.replace("{{startMark}}", startVMarkValue());
@ -128,7 +129,7 @@ public class JavaParserXmlGenerator extends JavaBaseGenerator {
if (!analysis.isAbstract() || ti != analysis.getRootType()) {
parser.append(" protected "+stn+" parse"+pfx+tn+"(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError {\r\n");
parser.append(" "+stn+" res = new "+stn+"();\r\n");
parser.append(" "+stn+" res = new "+stn+"();\r\n");
if (ti == analysis.getRootType() && analysis.getStructure().getKind() == StructureDefinitionKind.RESOURCE) {
parser.append(" parseResourceAttributes(xpp, res);\r\n");
} else {
@ -187,7 +188,11 @@ public class JavaParserXmlGenerator extends JavaBaseGenerator {
parser.append(" } else ");
else
parser.append(" ");
parser.append("if (!parse"+ti.getAncestorName()+"Content(eventType, xpp, res)){ \r\n");
if (ti.getAncestorName() != null) {
parser.append("if (!parse"+ti.getAncestorName()+"Content(eventType, xpp, res)){ \r\n");
} else {
parser.append(" { \r\n");
}
parser.append(" return false;\r\n");
parser.append(" }\r\n");
parser.append(" return true;\r\n");
@ -296,7 +301,9 @@ public class JavaParserXmlGenerator extends JavaBaseGenerator {
composer.append(" }\r\n\r\n");
composer.append(" protected void compose"+pfx+tn+"Elements("+stn+" element) throws IOException {\r\n");
composer.append(" compose"+ti.getAncestorName()+"Elements(element);\r\n");
if (!"Element".equals(tn) && analysis.getAncestor() != null) {
composer.append(" compose"+ti.getAncestorName()+"Elements(element);\r\n");
}
for (ElementDefinition ed : ti.getChildren()) {
if (!ed.hasRepresentation(PropertyRepresentation.XMLATTR)) {

View File

@ -83,8 +83,8 @@ public class JavaResourceGenerator extends JavaBaseGenerator {
private long hashSum;
public JavaResourceGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate);
public JavaResourceGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version, String jid) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate, jid);
}
// public void generate(ElementDefinition root, String name, JavaGenClass clss, ProfiledType cd, Date genDate, String version, boolean isAbstract, Map<String, SearchParameterDefn> nameToSearchParamDef, ElementDefinition template) throws Exception {
@ -94,7 +94,7 @@ public class JavaResourceGenerator extends JavaBaseGenerator {
} else {
clss = JavaGenClass.Type;
}
write("package org.hl7.fhir.r5.model;\r\n");
write("package org.hl7.fhir."+jid+".model;\r\n");
startMark(version, genDate);
boolean hl = true; // hasList(root);
@ -121,7 +121,7 @@ public class JavaResourceGenerator extends JavaBaseGenerator {
if (hs)
write("import org.hl7.fhir.utilities.Utilities;\r\n");
if (he)
write("import org.hl7.fhir.r5.model.Enumerations.*;\r\n");
write("import org.hl7.fhir."+jid+".model.Enumerations.*;\r\n");
}
if (hn) {
if (clss == JavaGenClass.Resource) {
@ -150,14 +150,15 @@ public class JavaResourceGenerator extends JavaBaseGenerator {
write("\r\n");
if (config.getIni().hasProperty("imports", analysis.getName())) {
for (String imp : config.getIni().getStringProperty("imports", analysis.getName()).split("\\,")) {
write("import "+imp+";\r\n");
write("import "+imp.replace("{{jid}}", jid)+";\r\n");
}
}
jdoc("", replaceTitle(analysis.getName(), analysis.getStructure().getDescription()));
TypeInfo ti = analysis.getRootType();
boolean hasChildren = ti.getChildren().size() > 0;
String hierarchy = "extends "+analysis.getAncestor().getName();
String hierarchy = analysis.getAncestor() != null ? "extends "+analysis.getAncestor().getName() : "";
if (clss == JavaGenClass.Resource) {
if (!analysis.isAbstract()) {
write("@ResourceDef(name=\""+upFirst(analysis.getName()).replace("ListResource", "List")+"\", profile=\"http://hl7.org/fhir/StructureDefinition/"+upFirst(analysis.getName())+"\")\r\n");
@ -167,7 +168,7 @@ public class JavaResourceGenerator extends JavaBaseGenerator {
hierarchy = hierarchy + " implements ICompositeType";
}
if (config.getIni().hasProperty("hierarchy", analysis.getName())) {
if (config.getIni().hasProperty("hierarchy", analysis.getName()) && analysis.getAncestor() != null) {
hierarchy = config.getIni().getStringProperty("hierarchy", analysis.getName()).replace("{{super}}", analysis.getAncestor().getName());
}
@ -211,7 +212,7 @@ public class JavaResourceGenerator extends JavaBaseGenerator {
generateAccessors(analysis, ti, e, " ", matchingInheritedElement(ti.getInheritedChildren(), e));
}
}
if (!analysis.isInterface()) {
if (!analysis.isInterface() && ti.getInheritedChildren() != null) {
for (ElementDefinition e : filterInherited(ti.getInheritedChildren(), ti.getChildren())) {
generateUnimplementedAccessors(analysis, ti, e, " ");
}
@ -249,7 +250,7 @@ public class JavaResourceGenerator extends JavaBaseGenerator {
write(" return ResourceType."+analysis.getName()+";\r\n");
write(" }\r\n");
write("\r\n");
} else if (analysis.isAbstract() && Utilities.noString(analysis.getAncestor().getName())) {
} else if (analysis.isAbstract() && analysis.getAncestor() != null && Utilities.noString(analysis.getAncestor().getName())) {
write("\r\n");
write(" @Override\r\n");
write(" public String getIdBase() {\r\n");
@ -261,7 +262,7 @@ public class JavaResourceGenerator extends JavaBaseGenerator {
write(" setId(value);\r\n");
write(" }\r\n");
write(" public abstract ResourceType getResourceType();\r\n");
} else if (analysis.isAbstract() && Utilities.noString(analysis.getAncestor().getName())) {
} else if (analysis.isAbstract() && analysis.getAncestor() != null && Utilities.noString(analysis.getAncestor().getName())) {
write(" @Override\r\n");
write(" public String getIdBase() {\r\n");
write(" return getId();\r\n");
@ -1180,6 +1181,7 @@ private void generatePropertyMaker(Analysis analysis, TypeInfo ti, String indent
cc = makeConst(cc);
write(" case "+cc+": return \""+c.getCode()+"\";\r\n");
}
write(" case NULL: return null;\r\n");
write(" default: return \"?\";\r\n");
write(" }\r\n");
write(" }\r\n");
@ -1191,6 +1193,7 @@ private void generatePropertyMaker(Analysis analysis, TypeInfo ti, String indent
cc = makeConst(cc);
write(" case "+cc+": return \""+c.getSystem()+"\";\r\n");
}
write(" case NULL: return null;\r\n");
write(" default: return \"?\";\r\n");
write(" }\r\n");
write(" }\r\n");
@ -1203,6 +1206,7 @@ private void generatePropertyMaker(Analysis analysis, TypeInfo ti, String indent
String definition = definitions.getCodeDefinition(c.getSystem(), c.getCode());
write(" case "+cc+": return \""+Utilities.escapeJava(definition)+"\";\r\n");
}
write(" case NULL: return null;\r\n");
write(" default: return \"?\";\r\n");
write(" }\r\n");
write(" }\r\n");
@ -1214,6 +1218,7 @@ private void generatePropertyMaker(Analysis analysis, TypeInfo ti, String indent
cc = makeConst(cc);
write(" case "+cc+": return \""+Utilities.escapeJava(Utilities.noString(c.getDisplay()) ? c.getCode() : c.getDisplay())+"\";\r\n");
}
write(" case NULL: return null;\r\n");
write(" default: return \"?\";\r\n");
write(" }\r\n");
write(" }\r\n");
@ -1267,7 +1272,7 @@ private void generatePropertyMaker(Analysis analysis, TypeInfo ti, String indent
write(" }\r\n");
write(" }\r\n");
write("\r\n");
// enumInfo.put("org.hl7.fhir.r5.model."+name+"."+tns, url+"|"+el.toString());
// enumInfo.put("org.hl7.fhir."+jid+".model."+name+"."+tns, url+"|"+el.toString());
}
private void generateType(Analysis analysis, TypeInfo ti) throws Exception {
@ -1672,6 +1677,8 @@ private void generatePropertyMaker(Analysis analysis, TypeInfo ti, String indent
}
private void generateAccessors(Analysis analysis, TypeInfo ti, ElementDefinition e, String indent, ElementDefinition inh) throws Exception {
String tn = e.getUserString("java.type");
StructureDefinition sd = e.hasType() ? definitions.getStructures().get(pfxType(e.getTypeFirstRep().getCode())) : null;
boolean abstractTarget = (sd != null) && sd.getAbstract() && !sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/Element")&& !sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/BackboneElement");
String className = ti.getName();
if (Utilities.noString(tn)) {
@ -1804,27 +1811,30 @@ private void generatePropertyMaker(Analysis analysis, TypeInfo ti, String indent
write("\r\n");
} else {
if (!definitions.hasResource(tn)) {
/*
* addXXX() for repeatable composite
*/
write(indent+"public "+tn+" add"+getTitle(getElementName(e.getName(), false))+"() { //3\r\n");
if (!e.unbounded()) {
write(indent+" if (this."+getElementName(e.getName(), true)+" == null) {\r\n");
write(indent+" this."+getElementName(e.getName(), true)+" = new "+tn+"();\r\n");
write(indent+" } else {\r\n");
write(indent+" throw new Error(\"Cannot have more than one "+e.getPath()+"\");\r\n");
write(indent+" }\r\n");
write(indent+" return this."+getElementName(e.getName(), true)+";\r\n");
if (abstractTarget) {
System.out.println(e.getPath()+" is abstract");
} else {
write(indent+" "+tn+" t = new "+tn+"();\r\n");
write(indent+" if (this."+getElementName(e.getName(), true)+" == null)\r\n");
write(indent+" this."+getElementName(e.getName(), true)+" = new ArrayList<"+tn+">();\r\n");
write(indent+" this."+getElementName(e.getName(), true)+".add(t);\r\n");
write(indent+" return t;\r\n");
/*
* addXXX() for repeatable composite
*/
write(indent+"public "+tn+" add"+getTitle(getElementName(e.getName(), false))+"() { //3\r\n");
if (!e.unbounded()) {
write(indent+" if (this."+getElementName(e.getName(), true)+" == null) {\r\n");
write(indent+" this."+getElementName(e.getName(), true)+" = new "+tn+"();\r\n");
write(indent+" } else {\r\n");
write(indent+" throw new Error(\"Cannot have more than one "+e.getPath()+"\");\r\n");
write(indent+" }\r\n");
write(indent+" return this."+getElementName(e.getName(), true)+";\r\n");
} else {
write(indent+" "+tn+" t = new "+tn+"();\r\n");
write(indent+" if (this."+getElementName(e.getName(), true)+" == null)\r\n");
write(indent+" this."+getElementName(e.getName(), true)+" = new ArrayList<"+tn+">();\r\n");
write(indent+" this."+getElementName(e.getName(), true)+".add(t);\r\n");
write(indent+" return t;\r\n");
}
write(indent+"}\r\n");
write("\r\n");
}
write(indent+"}\r\n");
write("\r\n");
/*
* addXXX(foo) for repeatable composite
*/
@ -2029,6 +2039,10 @@ private void generatePropertyMaker(Analysis analysis, TypeInfo ti, String indent
}
private String pfxType(String code) {
return "http://hl7.org/fhir/StructureDefinition/"+code;
}
private void generateAbstractAccessors(Analysis analysis, TypeInfo ti, ElementDefinition e, String indent) throws Exception {
String tn = e.getUserString("java.type");

View File

@ -50,12 +50,13 @@ import org.hl7.fhir.r5.model.StructureDefinition.TypeDerivationRule;
public class JavaTypeGenerator extends JavaBaseGenerator {
public JavaTypeGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate);
public JavaTypeGenerator(OutputStream out, Definitions definitions, Configuration configuration, Date genDate, String version, String jid) throws UnsupportedEncodingException {
super(out, definitions, configuration, version, genDate, jid);
}
public void generate() throws Exception {
String template = config.getAdornments().get("ResourceType");
template = template.replace("{{jid}}", jid);
template = template.replace("{{license}}", config.getLicense());
template = template.replace("{{startMark}}", startVMarkValue());
template = template.replace("{{types-enum}}", genEnums());

View File

@ -45,9 +45,9 @@ import org.hl7.fhir.utilities.Utilities;
public class JavaConverterGenerator extends JavaBaseGenerator {
public JavaConverterGenerator(OutputStream arg0, Definitions definitions, Configuration config, String version, Date genDate)
public JavaConverterGenerator(OutputStream arg0, Definitions definitions, Configuration config, String version, Date genDate, String jid)
throws UnsupportedEncodingException {
super(arg0, definitions, config, version, genDate);
super(arg0, definitions, config, version, genDate, jid);
// TODO Auto-generated constructor stub
}
// public enum JavaGenClass { Structure, Type, Resource, AbstractResource, BackboneElement, Constraint }

View File

@ -58,9 +58,9 @@ changes for James
*/
public class JavaPatternImplGenerator extends JavaBaseGenerator {
public JavaPatternImplGenerator(OutputStream arg0, Definitions definitions, Configuration config, String version, Date genDate)
public JavaPatternImplGenerator(OutputStream arg0, Definitions definitions, Configuration config, String version, Date genDate, String jid)
throws UnsupportedEncodingException {
super(arg0, definitions, config, version, genDate);
super(arg0, definitions, config, version, genDate, jid);
// TODO Auto-generated constructor stub
}
//

View File

@ -59,9 +59,9 @@ changes for James
*/
public class JavaPatternIntfGenerator extends JavaBaseGenerator {
public JavaPatternIntfGenerator(OutputStream arg0, Definitions definitions, Configuration config, String version, Date genDate)
public JavaPatternIntfGenerator(OutputStream arg0, Definitions definitions, Configuration config, String version, Date genDate, String jid)
throws UnsupportedEncodingException {
super(arg0, definitions, config, version, genDate);
super(arg0, definitions, config, version, genDate, jid);
// TODO Auto-generated constructor stub
}
//

View File

@ -6,6 +6,7 @@ import org.hl7.fhir.r5.model.CodeSystem;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.CompartmentDefinition;
import org.hl7.fhir.r5.model.ConceptMap;
import org.hl7.fhir.r5.model.ElementDefinition;
import org.hl7.fhir.r5.model.OperationDefinition;
import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.model.StructureDefinition;
@ -75,6 +76,27 @@ public class Definitions {
StructureDefinition sd = structures.get("http://hl7.org/fhir/StructureDefinition/"+tn);
return sd;
}
public void fix() {
StructureDefinition aa = structures.get("http://hl7.org/fhir/StructureDefinition/ArtifactAssessment");
if (aa != null) {
for (ElementDefinition ed : aa.getSnapshot().getElement()) {
fixAATypes(ed);
}
for (ElementDefinition ed : aa.getDifferential().getElement()) {
fixAATypes(ed);
}
}
}
private void fixAATypes(ElementDefinition ed) {
if (ed.getPath().equals("ArtifactAssessment.approvalDate")) {
ed.getTypeFirstRep().setCode("date");
}
if (ed.getPath().equals("ArtifactAssessment.lastReviewDate")) {
ed.getTypeFirstRep().setCode("date");
}
}
}

View File

@ -19,7 +19,7 @@ import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.npm.ToolsVersion;
public class DefinitionsLoader {
public class DefinitionsLoaderR5 {
public static Definitions load(NpmPackage npm) throws IOException {
Definitions res = new Definitions();
@ -48,29 +48,29 @@ public class DefinitionsLoader {
for (String t : npm.listResources("CompartmentDefinition")) {
res.getCompartments().see((CompartmentDefinition) load(npm, t), null);
}
Bundle bnd = (Bundle) load(npm, "Bundle-searchParams.json");
if (bnd != null) {
for (BundleEntryComponent be : bnd.getEntry()) {
Resource r = be.getResource();
if (r instanceof CodeSystem) {
res.getCodeSystems().see((CodeSystem) r, null);
} else if (r instanceof ValueSet) {
res.getValuesets().see((ValueSet) r, null);
} else if (r instanceof ConceptMap) {
res.getConceptMaps().see((ConceptMap) r, null);
} else if (r instanceof CapabilityStatement) {
res.getStatements().see((CapabilityStatement) r, null);
} else if (r instanceof StructureDefinition) {
res.getStructures().see((StructureDefinition) r, null);
} else if (r instanceof OperationDefinition) {
res.getOperations().see((OperationDefinition) r, null);
} else if (r instanceof SearchParameter) {
res.getSearchParams().see((SearchParameter) r, null);
} else if (r instanceof CompartmentDefinition) {
res.getCompartments().see((CompartmentDefinition) r, null);
}
}
}
// Bundle bnd = (Bundle) load(npm, "Bundle-searchParams.json");
// if (bnd != null) {
// for (BundleEntryComponent be : bnd.getEntry()) {
// Resource r = be.getResource();
// if (r instanceof CodeSystem) {
// res.getCodeSystems().see((CodeSystem) r, null);
// } else if (r instanceof ValueSet) {
// res.getValuesets().see((ValueSet) r, null);
// } else if (r instanceof ConceptMap) {
// res.getConceptMaps().see((ConceptMap) r, null);
// } else if (r instanceof CapabilityStatement) {
// res.getStatements().see((CapabilityStatement) r, null);
// } else if (r instanceof StructureDefinition) {
// res.getStructures().see((StructureDefinition) r, null);
// } else if (r instanceof OperationDefinition) {
// res.getOperations().see((OperationDefinition) r, null);
// } else if (r instanceof SearchParameter) {
// res.getSearchParams().see((SearchParameter) r, null);
// } else if (r instanceof CompartmentDefinition) {
// res.getCompartments().see((CompartmentDefinition) r, null);
// }
// }
// }
return res;
}

View File

@ -3,6 +3,7 @@ package org.hl7.fhir.core.generator.engine;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
@ -30,6 +31,7 @@ import org.hl7.fhir.r5.model.SearchParameter;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.npm.ToolsVersion;
@ -61,110 +63,73 @@ public class JavaCoreGenerator {
String ap = Utilities.path(src);
System.out.println("Load Configuration from "+ap);
Configuration config = new Configuration(ap);
String pid = "r5";
String jid = "r5";
String pid = VersionUtilities.isR4BVer(version) ? "r4b" : "r5";
String jid = VersionUtilities.isR4BVer(version) ? "r4b" : "r5";
FilesystemPackageCacheManager pcm = new FilesystemPackageCacheManager(true, ToolsVersion.TOOLS_VERSION);
System.out.println("Cache: "+pcm.getFolder());
System.out.println("Load hl7.fhir."+pid+".core");
NpmPackage npm = pcm.loadPackage("hl7.fhir."+pid+".core", version);
Definitions master = DefinitionsLoader.load(npm);
Definitions master = VersionUtilities.isR4BVer(version) ? DefinitionsLoaderR4B.load(npm) : DefinitionsLoaderR5.load(npm);
master.fix();
markValueSets(master, config);
System.out.println("Load hl7.fhir."+pid+".expansions");
Definitions expansions = DefinitionsLoader.load(pcm.loadPackage("hl7.fhir."+pid+".expansions", version));
Definitions expansions = DefinitionsLoaderR5.load(pcm.loadPackage("hl7.fhir."+pid+".expansions", version));
System.out.println("Process Expansions");
updateExpansions(master, expansions);
System.out.println("Generate Model");
System.out.println(" .. Constants");
JavaConstantsGenerator cgen = new JavaConstantsGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "model", "Constants.java")), master, config, date, npm.version());
JavaConstantsGenerator cgen = new JavaConstantsGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", jid, "model", "Constants.java")), master, config, date, npm.version(), jid);
cgen.generate();
cgen.close();
System.out.println(" .. Enumerations");
JavaEnumerationsGenerator egen = new JavaEnumerationsGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "model", "Enumerations.java")), master, config, date, npm.version());
JavaEnumerationsGenerator egen = new JavaEnumerationsGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", jid, "model", "Enumerations.java")), master, config, date, npm.version(), jid);
egen.generate();
egen.close();
JavaFactoryGenerator fgen = new JavaFactoryGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "model", "ResourceFactory.java")), master, config, date, npm.version());
JavaTypeGenerator tgen = new JavaTypeGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "model", "ResourceType.java")), master, config, date, npm.version());
JavaParserJsonGenerator jgen = new JavaParserJsonGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "formats", "JsonParser.java")), master, config, date, npm.version());
JavaParserXmlGenerator xgen = new JavaParserXmlGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "formats", "XmlParser.java")), master, config, date, npm.version());
JavaParserRdfGenerator rgen = new JavaParserRdfGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "formats", "RdfParser.java")), master, config, date, npm.version());
JavaFactoryGenerator fgen = new JavaFactoryGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", jid, "model", "ResourceFactory.java")), master, config, date, npm.version(), jid);
JavaTypeGenerator tgen = new JavaTypeGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", jid, "model", "ResourceType.java")), master, config, date, npm.version(), jid);
JavaParserJsonGenerator jgen = new JavaParserJsonGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", jid, "formats", "JsonParser.java")), master, config, date, npm.version(), jid);
JavaParserXmlGenerator xgen = new JavaParserXmlGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", jid, "formats", "XmlParser.java")), master, config, date, npm.version(), jid);
JavaParserRdfGenerator rgen = new JavaParserRdfGenerator(new FileOutputStream(Utilities.path(dest, "src", "org", "hl7", "fhir", jid, "formats", "RdfParser.java")), master, config, date, npm.version(), jid);
if (VersionUtilities.isR4BVer(version)) {
StructureDefinition sd = master.getStructures().get("http://hl7.org/fhir/StructureDefinition/Element");
genClass(version, dest, date, config, jid, npm, master, jgen, xgen, rgen, sd);
}
for (StructureDefinition sd : master.getStructures().getList()) {
if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) {
if (!Utilities.existsInList(sd.getName(), "Base", "PrimitiveType") && !sd.getName().contains(".") && sd.getAbstract()) {
String name = javaName(sd.getName());
System.out.println(" .. "+name);
Analyser jca = new Analyser(master, config);
Analysis analysis = jca.analyse(sd);
String fn = Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "model", name+".java");
JavaResourceGenerator gen = new JavaResourceGenerator(new FileOutputStream(fn), master, config, date, npm.version());
gen.generate(analysis);
gen.close();
jgen.seeClass(analysis);
xgen.seeClass(analysis);
rgen.seeClass(analysis);
genClass(version, dest, date, config, jid, npm, master, jgen, xgen, rgen, sd);
}
}
}
for (StructureDefinition sd : master.getStructures().getList()) {
if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.COMPLEXTYPE) {
if (!Utilities.existsInList(sd.getName(), "Base", "PrimitiveType") && !sd.getName().contains(".") && !sd.getAbstract()) {
String name = javaName(sd.getName());
System.out.println(" .. "+name);
Analyser jca = new Analyser(master, config);
Analysis analysis = jca.analyse(sd);
String fn = Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "model", name+".java");
JavaResourceGenerator gen = new JavaResourceGenerator(new FileOutputStream(fn), master, config, date, npm.version());
gen.generate(analysis);
gen.close();
jgen.seeClass(analysis);
xgen.seeClass(analysis);
rgen.seeClass(analysis);
genClass(version, dest, date, config, jid, npm, master, jgen, xgen, rgen, sd);
}
}
}
if (VersionUtilities.isR4BVer(version)) {
StructureDefinition sd = master.getStructures().get("http://hl7.org/fhir/StructureDefinition/Resource");
genClass(version, dest, date, config, jid, npm, master, jgen, xgen, rgen, sd);
}
for (StructureDefinition sd : master.getStructures().getList()) {
if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.RESOURCE) {
if (!Utilities.existsInList(sd.getName(), "Base", "PrimitiveType") && !sd.getName().contains(".") && sd.getAbstract()) {
String name = javaName(sd.getName());
System.out.println(" .. "+name);
Analyser jca = new Analyser(master, config);
Analysis analysis = jca.analyse(sd);
String fn = Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "model", name+".java");
JavaResourceGenerator gen = new JavaResourceGenerator(new FileOutputStream(fn), master, config, date, npm.version());
gen.generate(analysis);
gen.close();
jgen.seeClass(analysis);
xgen.seeClass(analysis);
rgen.seeClass(analysis);
genClass(version, dest, date, config, jid, npm, master, jgen, xgen, rgen, sd);
}
}
}
for (StructureDefinition sd : master.getStructures().getList()) {
if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && sd.getKind() == StructureDefinitionKind.RESOURCE) {
if (!Utilities.existsInList(sd.getName(), "Base", "PrimitiveType") && !sd.getName().contains(".") && !sd.getAbstract()) {
String name = javaName(sd.getName());
System.out.println(" .. "+name);
Analyser jca = new Analyser(master, config);
Analysis analysis = jca.analyse(sd);
String fn = Utilities.path(dest, "src", "org", "hl7", "fhir", "r5", "model", name+".java");
JavaResourceGenerator gen = new JavaResourceGenerator(new FileOutputStream(fn), master, config, date, npm.version());
gen.generate(analysis);
gen.close();
jgen.seeClass(analysis);
xgen.seeClass(analysis);
rgen.seeClass(analysis);
genClass(version, dest, date, config, jid, npm, master, jgen, xgen, rgen, sd);
}
}
}
@ -187,6 +152,24 @@ public class JavaCoreGenerator {
}
public void genClass(String version, String dest, Date date, Configuration config, String jid, NpmPackage npm, Definitions master,
JavaParserJsonGenerator jgen, JavaParserXmlGenerator xgen, JavaParserRdfGenerator rgen, StructureDefinition sd)
throws Exception, IOException, UnsupportedEncodingException, FileNotFoundException {
String name = javaName(sd.getName());
System.out.println(" .. "+name);
Analyser jca = new Analyser(master, config, version);
Analysis analysis = jca.analyse(sd);
String fn = Utilities.path(dest, "src", "org", "hl7", "fhir", jid, "model", name+".java");
JavaResourceGenerator gen = new JavaResourceGenerator(new FileOutputStream(fn), master, config, date, npm.version(), jid);
gen.generate(analysis);
gen.close();
jgen.seeClass(analysis);
xgen.seeClass(analysis);
rgen.seeClass(analysis);
}
@SuppressWarnings("unchecked")
private void markValueSets(Definitions defns, Configuration config) {
for (StructureDefinition sd : defns.getStructures().getList()) {

View File

@ -10832,7 +10832,9 @@ public class JsonParser extends JsonParserBase {
if (json.has("include")) {
JsonArray array = json.getAsJsonArray("include");
for (int i = 0; i < array.size(); i++) {
res.getInclude().add(parseValueSetConceptSetComponent(array.get(i).getAsJsonObject(), owner));
if (!array.get(i).isJsonNull()) {
res.getInclude().add(parseValueSetConceptSetComponent(array.get(i).getAsJsonObject(), owner));
}
}
};
if (json.has("exclude")) {

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
<attribute name="test" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="output" path="target/classes"/>
</classpath>

36
org.hl7.fhir.r4b/.project Normal file
View File

@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>org.hl7.fhir.r4b</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.jetbrains.kotlin.ui.kotlinBuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.jetbrains.kotlin.core.kotlinNature</nature>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
<linkedResources>
<link>
<name>kotlin_bin</name>
<type>2</type>
<locationURI>org.jetbrains.kotlin.core.filesystem:/org.hl7.fhir.r4b/kotlin_bin</locationURI>
</link>
</linkedResources>
</projectDescription>

188
org.hl7.fhir.r4b/pom.xml Normal file
View File

@ -0,0 +1,188 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>5.6.21-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>org.hl7.fhir.r4b</artifactId>
<packaging>bundle</packaging>
<dependencies>
<!-- HAPI Dependencies -->
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.utilities</artifactId>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-base</artifactId>
<exclusions>
<exclusion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-utilities</artifactId>
</exclusion>
</exclusions>
</dependency>
<!-- UCUM -->
<dependency>
<groupId>org.fhir</groupId>
<artifactId>ucum</artifactId>
<version>1.0.3</version>
<optional>true</optional>
</dependency>
<!-- XML Parsers -->
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>xpp3</groupId>
<artifactId>xpp3_xpath</artifactId>
<optional>true</optional>
</dependency>
<!-- JSON Parsers -->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<optional>true</optional>
</dependency>
<!-- Apache POI -->
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi</artifactId>
<version>4.0.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml</artifactId>
<version>4.0.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>poi-ooxml-schemas</artifactId>
<version>4.0.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.apache.poi</groupId>
<artifactId>ooxml-schemas</artifactId>
<version>1.4</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.antlr</groupId>
<artifactId>ST4</artifactId>
<version>4.1</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<optional>true</optional>
</dependency>
<!-- HTTP Client -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>4.9.0</version>
<optional>true</optional>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>logging-interceptor</artifactId>
<version>4.9.0</version>
<optional>true</optional>
</dependency>
<!-- Test dependencies -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>net.sf.saxon</groupId>
<artifactId>Saxon-HE</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hl7.fhir.testcases</groupId>
<artifactId>fhir-test-cases</artifactId>
<version>${validator_test_case_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>mockwebserver</artifactId>
<version>4.9.0</version>
<optional>true</optional>
<scope>test</scope>
</dependency>
<!-- JUnit Jupiter -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-api</artifactId>
<version>${junit_jupiter_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter-params</artifactId>
<version>${junit_jupiter_version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.atlassian.commonmark</groupId>
<artifactId>commonmark-ext-gfm-tables</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.basepom.maven</groupId>
<artifactId>duplicate-finder-maven-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
<excludes>
<exclude>**/*.out</exclude>
</excludes>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,318 @@
package org.hl7.fhir.r4b.comparison;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r4b.model.CanonicalResource;
import org.hl7.fhir.r4b.model.CanonicalType;
import org.hl7.fhir.r4b.model.CapabilityStatement;
import org.hl7.fhir.r4b.model.CodeType;
import org.hl7.fhir.r4b.model.CodeableConcept;
import org.hl7.fhir.r4b.model.Coding;
import org.hl7.fhir.r4b.model.PrimitiveType;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
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.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public abstract class CanonicalResourceComparer extends ResourceComparer {
public abstract class CanonicalResourceComparison<T extends CanonicalResource> extends ResourceComparison {
protected T left;
protected T right;
protected T union;
protected T intersection;
protected Map<String, StructuralMatch<String>> metadata = new HashMap<>();
public CanonicalResourceComparison(T left, T right) {
super(left.getId(), right.getId());
this.left = left;
this.right = right;
}
public T getLeft() {
return left;
}
public T getRight() {
return right;
}
public T getUnion() {
return union;
}
public T getIntersection() {
return intersection;
}
public Map<String, StructuralMatch<String>> getMetadata() {
return metadata;
}
public void setLeft(T left) {
this.left = left;
}
public void setRight(T right) {
this.right = right;
}
public void setUnion(T union) {
this.union = union;
}
public void setIntersection(T intersection) {
this.intersection = intersection;
}
@Override
protected String toTable() {
String s = "";
s = s + refCell(left);
s = s + refCell(right);
s = s + "<td><a href=\""+getId()+".html\">Comparison</a></td>";
s = s + "<td>"+outcomeSummary()+"</td>";
return "<tr style=\"background-color: "+color()+"\">"+s+"</tr>\r\n";
}
@Override
protected void countMessages(MessageCounts cnts) {
for (StructuralMatch<String> sm : metadata.values()) {
sm.countMessages(cnts);
}
}
}
public CanonicalResourceComparer(ComparisonSession session) {
super(session);
}
protected void compareMetadata(CanonicalResource left, CanonicalResource right, Map<String, StructuralMatch<String>> comp, CanonicalResourceComparison<? extends CanonicalResource> res) {
comparePrimitives("url", left.getUrlElement(), right.getUrlElement(), comp, IssueSeverity.ERROR, res);
comparePrimitives("version", left.getVersionElement(), right.getVersionElement(), comp, IssueSeverity.ERROR, res);
comparePrimitives("name", left.getNameElement(), right.getNameElement(), comp, IssueSeverity.INFORMATION, res);
comparePrimitives("title", left.getTitleElement(), right.getTitleElement(), comp, IssueSeverity.INFORMATION, res);
comparePrimitives("status", left.getStatusElement(), right.getStatusElement(), comp, IssueSeverity.INFORMATION, res);
comparePrimitives("experimental", left.getExperimentalElement(), right.getExperimentalElement(), comp, IssueSeverity.WARNING, res);
comparePrimitives("date", left.getDateElement(), right.getDateElement(), comp, IssueSeverity.INFORMATION, res);
comparePrimitives("publisher", left.getPublisherElement(), right.getPublisherElement(), comp, IssueSeverity.INFORMATION, res);
comparePrimitives("description", left.getDescriptionElement(), right.getDescriptionElement(), comp, IssueSeverity.NULL, res);
comparePrimitives("purpose", left.getPurposeElement(), right.getPurposeElement(), comp, IssueSeverity.NULL, res);
comparePrimitives("copyright", left.getCopyrightElement(), right.getCopyrightElement(), comp, IssueSeverity.INFORMATION, res);
compareCodeableConceptList("jurisdiction", left.getJurisdiction(), right.getJurisdiction(), comp, IssueSeverity.INFORMATION, res, res.getUnion().getJurisdiction(), res.getIntersection().getJurisdiction());
}
protected void compareCodeableConceptList(String name, List<CodeableConcept> left, List<CodeableConcept> right, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, List<CodeableConcept> union, List<CodeableConcept> intersection ) {
List<CodeableConcept> matchR = new ArrayList<>();
StructuralMatch<String> combined = new StructuralMatch<String>();
for (CodeableConcept l : left) {
CodeableConcept r = findCodeableConceptInList(right, l);
if (r == null) {
union.add(l);
combined.getChildren().add(new StructuralMatch<String>(gen(l), vm(IssueSeverity.INFORMATION, "Removed the item '"+gen(l)+"'", fhirType()+"."+name, res.getMessages())));
} else {
matchR.add(r);
union.add(r);
intersection.add(r);
StructuralMatch<String> sm = new StructuralMatch<String>(gen(l), gen(r));
combined.getChildren().add(sm);
}
}
for (CodeableConcept r : right) {
if (!matchR.contains(r)) {
union.add(r);
combined.getChildren().add(new StructuralMatch<String>(vm(IssueSeverity.INFORMATION, "Added the item '"+gen(r)+"'", fhirType()+"."+name, res.getMessages()), gen(r)));
}
}
comp.put(name, combined);
}
private CodeableConcept findCodeableConceptInList(List<CodeableConcept> list, CodeableConcept item) {
for (CodeableConcept t : list) {
if (t.matches(item)) {
return t;
}
}
return null;
}
protected String gen(CodeableConcept cc) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (Coding c : cc.getCoding()) {
b.append(gen(c));
}
return b.toString();
}
protected String gen(Coding c) {
return c.getSystem()+(c.hasVersion() ? "|"+c.getVersion() : "")+"#"+c.getCode();
}
protected void compareCanonicalList(String name, List<CanonicalType> left, List<CanonicalType> right, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, List<CanonicalType> union, List<CanonicalType> intersection ) {
List<CanonicalType> matchR = new ArrayList<>();
StructuralMatch<String> combined = new StructuralMatch<String>();
for (CanonicalType l : left) {
CanonicalType r = findCanonicalInList(right, l);
if (r == null) {
union.add(l);
combined.getChildren().add(new StructuralMatch<String>(l.getValue(), vm(IssueSeverity.INFORMATION, "Removed the item '"+l.getValue()+"'", fhirType()+"."+name, res.getMessages())));
} else {
matchR.add(r);
union.add(r);
intersection.add(r);
StructuralMatch<String> sm = new StructuralMatch<String>(l.getValue(), r.getValue());
combined.getChildren().add(sm);
}
}
for (CanonicalType r : right) {
if (!matchR.contains(r)) {
union.add(r);
combined.getChildren().add(new StructuralMatch<String>(vm(IssueSeverity.INFORMATION, "Added the item '"+r.getValue()+"'", fhirType()+"."+name, res.getMessages()), r.getValue()));
}
}
comp.put(name, combined);
}
private CanonicalType findCanonicalInList(List<CanonicalType> list, CanonicalType item) {
for (CanonicalType t : list) {
if (t.getValue().equals(item.getValue())) {
return t;
}
}
return null;
}
protected void compareCodeList(String name, List<CodeType> left, List<CodeType> right, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res, List<CodeType> union, List<CodeType> intersection ) {
List<CodeType> matchR = new ArrayList<>();
StructuralMatch<String> combined = new StructuralMatch<String>();
for (CodeType l : left) {
CodeType r = findCodeInList(right, l);
if (r == null) {
union.add(l);
combined.getChildren().add(new StructuralMatch<String>(l.getValue(), vm(IssueSeverity.INFORMATION, "Removed the item '"+l.getValue()+"'", fhirType()+"."+name, res.getMessages())));
} else {
matchR.add(r);
union.add(r);
intersection.add(r);
StructuralMatch<String> sm = new StructuralMatch<String>(l.getValue(), r.getValue());
combined.getChildren().add(sm);
}
}
for (CodeType r : right) {
if (!matchR.contains(r)) {
union.add(r);
combined.getChildren().add(new StructuralMatch<String>(vm(IssueSeverity.INFORMATION, "Added the item '"+r.getValue()+"'", fhirType()+"."+name, res.getMessages()), r.getValue()));
}
}
comp.put(name, combined);
}
private CodeType findCodeInList(List<CodeType> list, CodeType item) {
for (CodeType t : list) {
if (t.getValue().equals(item.getValue())) {
return t;
}
}
return null;
}
@SuppressWarnings("rawtypes")
protected void comparePrimitives(String name, PrimitiveType l, PrimitiveType r, Map<String, StructuralMatch<String>> comp, IssueSeverity level, CanonicalResourceComparison<? extends CanonicalResource> res) {
StructuralMatch<String> match = null;
if (l.isEmpty() && r.isEmpty()) {
match = new StructuralMatch<>(null, null, null);
} else if (l.isEmpty()) {
match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "Added the item '"+r.primitiveValue()+"'", fhirType()+"."+name));
} else if (r.isEmpty()) {
match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "Removed the item '"+l.primitiveValue()+"'", fhirType()+"."+name));
} else if (!l.hasValue() && !r.hasValue()) {
match = new StructuralMatch<>(null, null, vmI(IssueSeverity.INFORMATION, "No Value", fhirType()+"."+name));
} else if (!l.hasValue()) {
match = new StructuralMatch<>(null, r.primitiveValue(), vmI(IssueSeverity.INFORMATION, "No Value on Left", fhirType()+"."+name));
} else if (!r.hasValue()) {
match = new StructuralMatch<>(l.primitiveValue(), null, vmI(IssueSeverity.INFORMATION, "No Value on Right", fhirType()+"."+name));
} else if (l.getValue().equals(r.getValue())) {
match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), null);
} else {
match = new StructuralMatch<>(l.primitiveValue(), r.primitiveValue(), vmI(level, "Values Differ", fhirType()+"."+name));
if (level != IssueSeverity.NULL) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, fhirType()+"."+name, "Values for "+name+" differ: '"+l.primitiveValue()+"' vs '"+r.primitiveValue()+"'", level));
}
}
comp.put(name, match);
}
protected abstract String fhirType();
public XhtmlNode renderMetadata(CanonicalResourceComparison<? extends CanonicalResource> comparison, String id, String prefix) throws FHIRException, IOException {
// columns: code, display (left|right), properties (left|right)
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false);
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
model.getTitles().add(gen.new Title(null, null, "Name", "Property Name", null, 100));
model.getTitles().add(gen.new Title(null, null, "Value", "The value of the property", null, 200, 2));
model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
for (String n : sorted(comparison.getMetadata().keySet())) {
StructuralMatch<String> t = comparison.getMetadata().get(n);
addRow(gen, model.getRows(), n, t);
}
return gen.generate(model, prefix, 0, null);
}
private void addRow(HierarchicalTableGenerator gen, List<Row> rows, String name, StructuralMatch<String> t) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, name, null, null));
if (t.hasLeft() && t.hasRight()) {
if (t.getLeft().equals(t.getRight())) {
r.getCells().add(gen.new Cell(null, null, t.getLeft(), null, null).span(2));
} else {
r.getCells().add(gen.new Cell(null, null, t.getLeft(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
r.getCells().add(gen.new Cell(null, null, t.getRight(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
}
} else if (t.hasLeft()) {
r.setColor(COLOR_NO_ROW_RIGHT);
r.getCells().add(gen.new Cell(null, null, t.getLeft(), null, null));
r.getCells().add(missingCell(gen));
} else if (t.hasRight()) {
r.setColor(COLOR_NO_ROW_LEFT);
r.getCells().add(missingCell(gen));
r.getCells().add(gen.new Cell(null, null, t.getRight(), null, null));
} else {
r.getCells().add(missingCell(gen).span(2));
}
r.getCells().add(cellForMessages(gen, t.getMessages()));
int i = 0;
for (StructuralMatch<String> c : t.getChildren()) {
addRow(gen, r.getSubRows(), name+"["+i+"]", c);
i++;
}
}
private List<String> sorted(Set<String> keys) {
List<String> res = new ArrayList<>();
res.addAll(keys);
Collections.sort(res);
return res;
}
}

View File

@ -0,0 +1,941 @@
package org.hl7.fhir.r4b.comparison;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.model.BackboneElement;
import org.hl7.fhir.r4b.model.BooleanType;
import org.hl7.fhir.r4b.model.CanonicalResource;
import org.hl7.fhir.r4b.model.CanonicalType;
import org.hl7.fhir.r4b.model.CapabilityStatement;
import org.hl7.fhir.r4b.model.CapabilityStatement.CapabilityStatementRestComponent;
import org.hl7.fhir.r4b.model.CapabilityStatement.CapabilityStatementRestResourceComponent;
import org.hl7.fhir.r4b.model.CapabilityStatement.CapabilityStatementRestResourceOperationComponent;
import org.hl7.fhir.r4b.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent;
import org.hl7.fhir.r4b.model.CapabilityStatement.CapabilityStatementRestSecurityComponent;
import org.hl7.fhir.r4b.model.CapabilityStatement.ResourceInteractionComponent;
import org.hl7.fhir.r4b.model.CapabilityStatement.ResourceVersionPolicy;
import org.hl7.fhir.r4b.model.CodeType;
import org.hl7.fhir.r4b.model.CodeableConcept;
import org.hl7.fhir.r4b.model.Coding;
import org.hl7.fhir.r4b.model.DataType;
import org.hl7.fhir.r4b.model.Element;
import org.hl7.fhir.r4b.model.Enumeration;
import org.hl7.fhir.r4b.model.Extension;
import org.hl7.fhir.r4b.model.PrimitiveType;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.StructureDefinition;
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.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class CapabilityStatementComparer extends CanonicalResourceComparer {
public class CapabilityStatementComparison extends CanonicalResourceComparison<CapabilityStatement> {
private StructuralMatch<Element> combined;
public CapabilityStatementComparison(CapabilityStatement left, CapabilityStatement right) {
super(left, right);
combined = new StructuralMatch<Element>(); // base
}
public StructuralMatch<Element> getCombined() {
return combined;
}
@Override
protected String abbreviation() {
return "cps";
}
@Override
protected String summary() {
return "CapabilityStatement: "+left.present()+" vs "+right.present();
}
@Override
protected String fhirType() {
return "CapabilityStatement";
}
@Override
protected void countMessages(MessageCounts cnts) {
super.countMessages(cnts);
combined.countMessages(cnts);
}
}
public CapabilityStatementComparer(ComparisonSession session) {
super(session);
}
public CapabilityStatementComparison compare(CapabilityStatement left, CapabilityStatement right) {
if (left == null)
throw new DefinitionException("No CapabilityStatement provided (left)");
if (right == null)
throw new DefinitionException("No CapabilityStatement provided (right)");
CapabilityStatementComparison res = new CapabilityStatementComparison(left, right);
session.identify(res);
CapabilityStatement cs = new CapabilityStatement();
res.setUnion(cs);
session.identify(cs);
cs.setName("Union"+left.getName()+"And"+right.getName());
cs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
cs.setStatus(left.getStatus());
cs.setDate(new Date());
CapabilityStatement cs1 = new CapabilityStatement();
res.setIntersection(cs1);
session.identify(cs1);
cs1.setName("Intersection"+left.getName()+"And"+right.getName());
cs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
cs1.setStatus(left.getStatus());
cs1.setDate(new Date());
compareMetadata(left, right, res.getMetadata(), res);
comparePrimitives("kind", left.getKindElement(), right.getKindElement(), res.getMetadata(), IssueSeverity.ERROR, res);
compareCanonicalList("instantiates", left.getInstantiates(), right.getInstantiates(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getInstantiates(), cs1.getInstantiates());
compareCanonicalList("imports", left.getImports(), right.getImports(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getImports(), cs1.getImports());
comparePrimitives("software.name", left.getSoftware().getNameElement(), right.getSoftware().getNameElement(), res.getMetadata(), IssueSeverity.ERROR, res);
comparePrimitives("software.version", left.getSoftware().getVersionElement(), right.getSoftware().getVersionElement(), res.getMetadata(), IssueSeverity.ERROR, res);
comparePrimitives("software.releaseDate", left.getSoftware().getReleaseDateElement(), right.getSoftware().getReleaseDateElement(), res.getMetadata(), IssueSeverity.ERROR, res);
comparePrimitives("implementation.description", left.getImplementation().getDescriptionElement(), right.getImplementation().getDescriptionElement(), res.getMetadata(), IssueSeverity.ERROR, res);
comparePrimitives("implementation.url", left.getImplementation().getUrlElement(), right.getImplementation().getUrlElement(), res.getMetadata(), IssueSeverity.ERROR, res);
comparePrimitives("fhirVersion", left.getFhirVersionElement(), right.getFhirVersionElement(), res.getMetadata(), IssueSeverity.ERROR, res);
compareCodeList("format", left.getFormat(), right.getFormat(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getFormat(), cs1.getFormat());
compareCodeList("patchFormat", left.getPatchFormat(), right.getPatchFormat(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getPatchFormat(), cs1.getPatchFormat());
compareCanonicalList("implementationGuide", left.getImplementationGuide(), right.getImplementationGuide(), res.getMetadata(), IssueSeverity.ERROR, res, cs.getImplementationGuide(), cs1.getImplementationGuide());
compareRests(left.getRest(), right.getRest(), res.getCombined(), res.getUnion().getRest(), res.getIntersection().getRest(), res.getUnion(), res.getIntersection(), res, "CapabilityStatement.rest");
return res;
}
private void compareRests(List<CapabilityStatementRestComponent> left, List<CapabilityStatementRestComponent> right, StructuralMatch<Element> combined, List<CapabilityStatementRestComponent> union, List<CapabilityStatementRestComponent> intersection, CapabilityStatement csU, CapabilityStatement csI, CapabilityStatementComparison res, String path) {
List<CapabilityStatementRestComponent> matchR = new ArrayList<>();
for (CapabilityStatementRestComponent l : left) {
CapabilityStatementRestComponent r = findInList(right, l);
if (r == null) {
union.add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", path)));
} else {
matchR.add(r);
CapabilityStatementRestComponent cdM = merge(l, r, res);
CapabilityStatementRestComponent cdI = intersect(l, r, res);
union.add(cdM);
intersection.add(cdI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
compare(sm, l, r, path+".where(mode='"+l.getMode()+"')", res);
combined.getChildren().add(sm);
compareRestSecurity(l, r, sm, cdM.getSecurity(), cdI.getSecurity(), csU, csI, res, path+".security");
compareRestResources(l, r, sm, cdM, cdI, csU, csI, res, path+".resource");
compareSearchParams(combined, l.getSearchParam(), r.getSearchParam(), path, res, cdM.getSearchParam(), cdI.getSearchParam());
compareOperations(combined, l.getOperation(), r.getOperation(), path, res, cdM.getOperation(), cdI.getOperation());
compareItemPropertyList(sm, "compartment", l.getCompartment(), r.getCompartment(), path, res, cdM.getCompartment(), cdI.getCompartment(), IssueSeverity.ERROR);
}
}
for (CapabilityStatementRestComponent r : right) {
if (!matchR.contains(r)) {
union.add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));
}
}
}
private CapabilityStatementRestComponent findInList(List<CapabilityStatementRestComponent> list, CapabilityStatementRestComponent item) {
for (CapabilityStatementRestComponent t : list) {
if (t.getMode().equals(item.getMode())) {
return t;
}
}
return null;
}
private void compare(StructuralMatch<Element> sm, CapabilityStatementRestComponent l, CapabilityStatementRestComponent r, String path, CapabilityStatementComparison res) {
compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.WARNING, res);
}
private void compareRestSecurity(CapabilityStatementRestComponent l, CapabilityStatementRestComponent r, StructuralMatch<Element> smp, CapabilityStatementRestSecurityComponent merge, CapabilityStatementRestSecurityComponent intersect, CapabilityStatement csU, CapabilityStatement csI, CapabilityStatementComparison res, String path) {
CapabilityStatementRestSecurityComponent ls = l.hasSecurity() ? l.getSecurity() : null;
CapabilityStatementRestSecurityComponent rs = r.hasSecurity() ? r.getSecurity() : null;
StructuralMatch<Element> sm = new StructuralMatch<Element>(ls, rs);
smp.getChildren().add(sm);
compareBooleans(path, sm.getMessages(), l.getSecurity().getCorsElement(), r.getSecurity().getCorsElement(), "security.cors", IssueSeverity.WARNING, res);
compareStrings(path, sm.getMessages(), l.getSecurity().getDescription(), r.getSecurity().getDescription(), "security.description", IssueSeverity.INFORMATION, res);
compareRestSecurityService(ls, rs, sm, merge, intersect, csU, csI, res, path+".security");
}
private void compareRestSecurityService(CapabilityStatementRestSecurityComponent left, CapabilityStatementRestSecurityComponent right, StructuralMatch<Element> combined, CapabilityStatementRestSecurityComponent union, CapabilityStatementRestSecurityComponent intersection, CapabilityStatement csU, CapabilityStatement csI, CapabilityStatementComparison res, String path) {
List<CodeableConcept> matchR = new ArrayList<>();
for (CodeableConcept l : left.getService()) {
CodeableConcept r = findInList(right.getService(), l);
if (r == null) {
union.getService().add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", path)));
} else {
matchR.add(r);
CodeableConcept cdM = CodeableConcept.merge(l, r);
CodeableConcept cdI = CodeableConcept.intersect(l, r);
union.getService().add(cdM);
intersection.getService().add(cdI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
compare(sm, l, r, path, res);
combined.getChildren().add(sm);
}
}
for (CodeableConcept r : right.getService()) {
if (!matchR.contains(r)) {
union.getService().add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));
}
}
}
private void compare(StructuralMatch<Element> sm, CodeableConcept l, CodeableConcept r, String path, CapabilityStatementComparison res) {
compareStrings(path, sm.getMessages(), l.getText(), r.getText(), "text", IssueSeverity.INFORMATION, res);
List<Coding> matches = new ArrayList<>();
for (Coding lc : l.getCoding()) {
boolean m = false;
for (Coding rc : r.getCoding()) {
if (lc.matches(rc)) {
matches.add(rc);
m = true;
}
}
if (!m) {
sm.getMessages().add(vmI(IssueSeverity.INFORMATION, "Value for "+gen(lc)+" removed", path));
}
}
for (Coding rc : r.getCoding()) {
if (!matches.contains(rc)) {
sm.getMessages().add(vmI(IssueSeverity.INFORMATION, "Value for "+gen(rc)+" added", path));
}
}
}
private CodeableConcept findInList(List<CodeableConcept> list, CodeableConcept item) {
for (CodeableConcept t : list) {
if (t.matches(item)) {
return t;
}
}
return null;
}
private void compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CapabilityStatementComparison res) {
if (!Utilities.noString(right)) {
if (Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" added", path));
} else if (!left.equals(right)) {
if (level != IssueSeverity.NULL) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
}
msgs.add(vmI(level, name+" changed from left to right", path));
}
} else if (!Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" removed", path));
}
}
private void compareExpectations(StructuralMatch<Element> combined, Element left, Element right, String path, CapabilityStatementComparison res, Element union, Element intersection) {
Extension l = left.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation");
Extension r = right.getExtensionByUrl("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation");
if (l != null || r != null) {
if (l == null) {
union.addExtension(r.copy());
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this expectation", path), r));
} else if (r == null) {
union.addExtension(l.copy());
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this expectation", path)));
} else {
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
combined.getChildren().add(sm);
String ls = l.getValue().primitiveValue();
String rs = r.getValue().primitiveValue();
if (ls.equals(rs)) {
union.addExtension(l.copy());
intersection.addExtension(l.copy());
} else {
sm.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+".extension('http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation')", "Changed value for expectation: '"+ls+"' vs '"+rs+"'", IssueSeverity.WARNING));
String lowest = lower(ls, rs) ? ls : rs;
String highest = lower(ls, rs) ? rs : ls;
union.addExtension("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation", new CodeType(lowest));
intersection.addExtension("http://hl7.org/fhir/StructureDefinition/capabilitystatement-expectation", new CodeType(highest));
}
}
}
}
private boolean lower(String ls, String rs) {
if (ls.equals("MAY")) {
return true;
}
if (ls.equals("SHALL")) {
return false;
}
if (rs.equals("MAY")) {
return false;
}
if (rs.equals("SHALL")) {
return true;
}
return false;
}
private void compareBooleans(String path, List<ValidationMessage> msgs, BooleanType left, BooleanType right, String name, IssueSeverity level, CapabilityStatementComparison res) {
if (!right.isEmpty()) {
if (left.isEmpty()) {
msgs.add(vmI(level, "Value for "+name+" added", path));
} else if (left.getValue() != right.getValue()) {
if (level != IssueSeverity.NULL) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
}
msgs.add(vmI(level, name+" changed from left to right", path));
}
} else if (!left.isEmpty()) {
msgs.add(vmI(level, "Value for "+name+" removed", path));
}
}
private CapabilityStatementRestComponent merge(CapabilityStatementRestComponent l, CapabilityStatementRestComponent r, CapabilityStatementComparison res) {
CapabilityStatementRestComponent cd = l.copy();
if (!l.hasDocumentation() && r.hasDocumentation()) {
cd.setDocumentation(r.getDocumentation());
}
if (r.hasSecurity()) {
if (!l.getSecurity().hasCors() && r.getSecurity().hasCors()) {
cd.getSecurity().setCors(r.getSecurity().getCors());
}
mergeCodeableConcepts(cd.getSecurity().getService(), r.getSecurity().getService());
if (!l.getSecurity().hasDescription() && r.getSecurity().hasDescription()) {
cd.getSecurity().setDescription(r.getSecurity().getDescription());
}
}
return cd;
}
private void mergeCodeableConcepts(List<CodeableConcept> tgt, List<CodeableConcept> src) {
for (CodeableConcept cd : src) {
boolean add = true;
for (CodeableConcept t : tgt) {
if (t.matches(cd)) {
add = false;
}
}
if (add) {
tgt.add(cd.copy());
}
}
}
private CapabilityStatementRestComponent intersect(CapabilityStatementRestComponent l, CapabilityStatementRestComponent r, CapabilityStatementComparison res) {
CapabilityStatementRestComponent cd = l.copy();
if (l.hasDocumentation() && !r.hasDocumentation()) {
cd.setDocumentation(null);
}
if (!r.hasSecurity()) {
cd.setSecurity(null);
} else {
if (!r.getSecurity().hasCors()) {
cd.getSecurity().setCorsElement(null);
}
intersectCodeableConcepts(cd.getSecurity().getService(), r.getSecurity().getService());
if (!r.getSecurity().hasDescription()) {
cd.getSecurity().setDescription(null);
}
}
return cd;
}
private void intersectCodeableConcepts(List<CodeableConcept> tgt, List<CodeableConcept> src) {
List<CodeableConcept> toRemove = new ArrayList<CodeableConcept>();
for (CodeableConcept cd : src) {
boolean remove = false;
for (CodeableConcept t : tgt) {
if (t.matches(cd)) {
remove = true;
}
}
if (remove) {
toRemove.add(cd);
}
}
tgt.removeAll(toRemove);
}
private void compareRestResources(CapabilityStatementRestComponent left, CapabilityStatementRestComponent right, StructuralMatch<Element> combined, CapabilityStatementRestComponent union, CapabilityStatementRestComponent intersection, CapabilityStatement csU, CapabilityStatement csI, CapabilityStatementComparison res, String path) {
List<CapabilityStatementRestResourceComponent> matchR = new ArrayList<>();
for (CapabilityStatementRestResourceComponent l : left.getResource()) {
CapabilityStatementRestResourceComponent r = findInList(right.getResource(), l);
if (r == null) {
union.getResource().add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", path)));
} else {
matchR.add(r);
CapabilityStatementRestResourceComponent cdM = mergeRestResource(l, r);
CapabilityStatementRestResourceComponent cdI = intersectRestResource(l, r);
union.getResource().add(cdM);
intersection.getResource().add(cdI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
compareRestResource(sm, l, r, path, res, cdM, cdI);
combined.getChildren().add(sm);
}
}
for (CapabilityStatementRestResourceComponent r : right.getResource()) {
if (!matchR.contains(r)) {
union.getResource().add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));
}
}
}
private void compareRestResource(StructuralMatch<Element> sm, CapabilityStatementRestResourceComponent l, CapabilityStatementRestResourceComponent r, String path, CapabilityStatementComparison res, CapabilityStatementRestResourceComponent union, CapabilityStatementRestResourceComponent intersection) {
compareProfiles(path, sm, l.getProfileElement(), r.getProfileElement(), res, union, intersection);
// todo: supported profiles
compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.INFORMATION, res);
compareExpectations(sm, l, r, path, res, union, intersection);
compareRestResourceInteractions(sm, l, r, path, res, union, intersection);
compareItemProperty(sm, "versioning", l.getVersioningElement(), r.getVersioningElement(), path, res, union.getVersioningElement(), intersection.getVersioningElement(), IssueSeverity.WARNING);
compareItemProperty(sm, "readHistory", l.getReadHistoryElement(), r.getReadHistoryElement(), path, res, union.getReadHistoryElement(), intersection.getReadHistoryElement(), IssueSeverity.INFORMATION);
compareItemProperty(sm, "updateCreate", l.getUpdateCreateElement(), r.getUpdateCreateElement(), path, res, union.getUpdateCreateElement(), intersection.getUpdateCreateElement(), IssueSeverity.WARNING);
compareItemProperty(sm, "conditionalCreate", l.getConditionalCreateElement(), r.getConditionalCreateElement(), path, res, union.getConditionalCreateElement(), intersection.getConditionalCreateElement(), IssueSeverity.WARNING);
compareItemProperty(sm, "conditionalRead", l.getConditionalReadElement(), r.getConditionalReadElement(), path, res, union.getConditionalReadElement(), intersection.getConditionalReadElement(), IssueSeverity.WARNING);
compareItemProperty(sm, "conditionalUpdate", l.getConditionalUpdateElement(), r.getConditionalUpdateElement(), path, res, union.getConditionalUpdateElement(), intersection.getConditionalUpdateElement(), IssueSeverity.WARNING);
compareItemProperty(sm, "conditionalDelete", l.getConditionalDeleteElement(), r.getConditionalDeleteElement(), path, res, union.getConditionalDeleteElement(), intersection.getConditionalDeleteElement(), IssueSeverity.WARNING);
compareItemPropertyList(sm, "referencePolicy", l.getReferencePolicy(), r.getReferencePolicy(), path, res, union.getReferencePolicy(), intersection.getReferencePolicy(), IssueSeverity.WARNING);
compareItemPropertyList(sm, "searchInclude", l.getSearchInclude(), r.getSearchInclude(), path, res, union.getSearchInclude(), intersection.getSearchInclude(), IssueSeverity.WARNING);
compareItemPropertyList(sm, "searchRevInclude", l.getSearchRevInclude(), r.getSearchRevInclude(), path, res, union.getSearchRevInclude(), intersection.getSearchRevInclude(), IssueSeverity.WARNING);
compareSearchParams(sm, l.getSearchParam(), r.getSearchParam(), path, res, union.getSearchParam(), intersection.getSearchParam());
compareOperations(sm, l.getOperation(), r.getOperation(), path, res, union.getOperation(), intersection.getOperation());
}
private void compareProfiles(String path, StructuralMatch<Element> combined, CanonicalType left, CanonicalType right, CapabilityStatementComparison res, CapabilityStatementRestResourceComponent union, CapabilityStatementRestResourceComponent intersection) {
if (!left.hasValue() && !right.hasValue()) {
// nothing in this case
} else if (!left.hasValue()) {
// the intersection is anything in right. The union is everything (or nothing, in this case)
intersection.setProfileElement(right.copy());
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.WARNING, "Added this profile", path), right).setName("profile"));
} else if (!right.hasValue()) {
// the intersection is anything in right. The union is everything (or nothing, in this case)
intersection.setProfileElement(left.copy());
combined.getChildren().add(new StructuralMatch<Element>(left, vmI(IssueSeverity.WARNING, "Removed this profile", path)).setName("profile"));
} else {
// profiles on both sides...
StructureDefinition sdLeft = session.getContextLeft().fetchResource(StructureDefinition.class, left.getValue());
StructureDefinition sdRight = session.getContextRight().fetchResource(StructureDefinition.class, right.getValue());
if (sdLeft == null && sdRight == null) {
combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.ERROR, "Cannot compare profiles because neither is known", path)).setName("profile"));
} else if (sdLeft == null) {
combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.ERROR, "Cannot compare profiles because '"+left.getValue()+"' is not known", path)).setName("profile"));
} else if (sdRight == null) {
combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.ERROR, "Cannot compare profiles because '"+right.getValue()+"' is not known", path)).setName("profile"));
} else if (sdLeft.getUrl().equals(sdRight.getUrl())) {
intersection.setProfileElement(left.copy());
union.setProfileElement(left.copy());
combined.getChildren().add(new StructuralMatch<Element>(left, right).setName("profile"));
} else if (profileInherits(sdLeft, sdRight, session.getContextLeft())) {
// if left inherits from right:
intersection.setProfileElement(left.copy());
union.setProfileElement(right.copy());
combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.WARNING, "Changed this profile to a broader profile", path)).setName("profile"));
} else if (profileInherits(sdRight, sdLeft, session.getContextRight())) {
intersection.setProfileElement(right.copy());
union.setProfileElement(left.copy());
combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.WARNING, "Changed this profile to a narrower one", path)).setName("profile"));
} else {
combined.getChildren().add(new StructuralMatch<Element>(left, right, vmI(IssueSeverity.WARNING, "Different", path)).setName("profile"));
throw new Error("Not done yet");
}
}
}
private boolean profileInherits(StructureDefinition sdFocus, StructureDefinition sdOther, IWorkerContext ctxt) {
while (sdFocus != null) {
if (sdFocus.getUrl().equals(sdOther.getUrl()) && sdFocus.getVersion().equals(sdOther.getVersion())) {
return true;
}
sdFocus = ctxt.fetchResource(StructureDefinition.class, sdFocus.getBaseDefinition());
}
return false;
}
private <T> void compareItemProperty(StructuralMatch<Element> combined, String name, PrimitiveType<T> left, PrimitiveType<T> right, String path, CapabilityStatementComparison res, PrimitiveType<T> union, PrimitiveType<T> intersection, IssueSeverity issueSeverity) {
if (!left.isEmpty() || !right.isEmpty()) {
if (left.isEmpty()) {
union.copyValues(right);
combined.getChildren().add(new StructuralMatch<Element>(vmI(issueSeverity, "Added this "+name, path), right).setName(name));
} else if (right.isEmpty()) {
union.copyValues(left);
combined.getChildren().add(new StructuralMatch<Element>(left, vmI(issueSeverity, "Removed this expectation", path)).setName(name));
} else {
StructuralMatch<Element> sm = new StructuralMatch<Element>(left, right).setName(name);
combined.getChildren().add(sm);
String ls = left.primitiveValue();
String rs = right.primitiveValue();
if (ls.equals(rs)) {
union.copyValues(left);
intersection.copyValues(left);
} else {
sm.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+ls+"' vs '"+rs+"'", issueSeverity));
union.copyValues(left);
intersection.copyValues(left);
}
compareExpectations(sm, left, right, path, res, union, intersection);
}
}
}
private <T extends Element> void compareItemPropertyList(StructuralMatch<Element> combined, String name, List<T> left, List<T> right, String path, CapabilityStatementComparison res, List<T> union, List<T> intersection, IssueSeverity issueSeverity) {
List<T> matchR = new ArrayList<>();
for (T l : left) {
T r = findInListT(right, l);
if (r == null) {
union.add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(issueSeverity, "Removed this "+name, path)).setName(name));
} else {
matchR.add(r);
union.add(l);
intersection.add(l);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r).setName(name);
combined.getChildren().add(sm);
}
}
for (T r : right) {
if (!matchR.contains(r)) {
union.add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(issueSeverity, "Added this "+name, path), r).setName(name));
}
}
}
private <T extends Element> T findInListT(List<T> list, T item) {
for (T t : list) {
if (t.equalsDeep(item)) {
return t;
}
}
return null;
}
private CapabilityStatementRestResourceComponent mergeRestResource(CapabilityStatementRestResourceComponent l, CapabilityStatementRestResourceComponent r) {
CapabilityStatementRestResourceComponent res = l.copy();
// todo: compare profiles, not just copy
if (!l.hasProfile() && r.hasProfile()) {
res.setProfile(r.getProfile());
}
if (!l.hasDocumentation() && r.hasDocumentation()) {
res.setDocumentation(r.getDocumentation());
}
return res;
}
private CapabilityStatementRestResourceComponent intersectRestResource(CapabilityStatementRestResourceComponent l, CapabilityStatementRestResourceComponent r) {
CapabilityStatementRestResourceComponent res = new CapabilityStatementRestResourceComponent();
res.setType(l.getType());
// todo: compare profiles, not just copy
if (l.hasProfile() && l.getProfile().equals(r.getProfile())) {
res.setProfile(l.getProfile());
}
if (l.hasDocumentation() && l.getDocumentation().equals(r.getDocumentation())) {
res.setDocumentation(l.getDocumentation());
}
return res;
}
private CapabilityStatementRestResourceComponent findInList(List<CapabilityStatementRestResourceComponent> list, CapabilityStatementRestResourceComponent item) {
for (CapabilityStatementRestResourceComponent t : list) {
if (t.hasType() && t.getType().equals(item.getType())) {
return t;
}
}
return null;
}
private void compareRestResourceInteractions(StructuralMatch<Element> combined, CapabilityStatementRestResourceComponent left, CapabilityStatementRestResourceComponent right, String path, CapabilityStatementComparison res, CapabilityStatementRestResourceComponent union, CapabilityStatementRestResourceComponent intersection) {
List<ResourceInteractionComponent> matchR = new ArrayList<>();
for (ResourceInteractionComponent l : left.getInteraction()) {
ResourceInteractionComponent r = findInList(right.getInteraction(), l);
if (r == null) {
union.getInteraction().add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", path)));
} else {
matchR.add(r);
ResourceInteractionComponent cdM = mergeRestResourceInteractions(l, r);
ResourceInteractionComponent cdI = intersectRestResourceInteractions(l, r);
union.getInteraction().add(cdM);
intersection.getInteraction().add(cdI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.INFORMATION, res);
compareExpectations(sm, l, r, path, res, union, intersection);
combined.getChildren().add(sm);
}
}
for (ResourceInteractionComponent r : right.getInteraction()) {
if (!matchR.contains(r)) {
union.getInteraction().add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));
}
}
}
private ResourceInteractionComponent mergeRestResourceInteractions(ResourceInteractionComponent l, ResourceInteractionComponent r) {
ResourceInteractionComponent res = l.copy();
if (!res.hasDocumentation() && r.hasDocumentation()) {
res.setDocumentation(r.getDocumentation());
}
return res;
}
private ResourceInteractionComponent intersectRestResourceInteractions(ResourceInteractionComponent l, ResourceInteractionComponent r) {
ResourceInteractionComponent res = l.copy();
if (res.hasDocumentation() && !r.hasDocumentation()) {
res.setDocumentation(null);
}
return res;
}
private ResourceInteractionComponent findInList(List<ResourceInteractionComponent> list, ResourceInteractionComponent item) {
for (ResourceInteractionComponent t : list) {
if (t.hasCode() && t.getCode().equals(item.getCode())) {
return t;
}
}
return null;
}
private void compareSearchParams(StructuralMatch<Element> combined, List<CapabilityStatementRestResourceSearchParamComponent> left, List<CapabilityStatementRestResourceSearchParamComponent> right, String path, CapabilityStatementComparison res, List<CapabilityStatementRestResourceSearchParamComponent> union, List<CapabilityStatementRestResourceSearchParamComponent> intersection) {
List<CapabilityStatementRestResourceSearchParamComponent> matchR = new ArrayList<>();
for (CapabilityStatementRestResourceSearchParamComponent l : left) {
CapabilityStatementRestResourceSearchParamComponent r = findInList(right, l);
if (r == null) {
union.add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this Search Parameter", path)));
} else {
matchR.add(r);
CapabilityStatementRestResourceSearchParamComponent cdM = mergeSearchParams(l, r);
CapabilityStatementRestResourceSearchParamComponent cdI = intersectSearchParams(l, r);
union.add(cdM);
intersection.add(cdI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.INFORMATION, res);
compareItemProperty(sm, "type", l.getTypeElement(), r.getTypeElement(), path, res, cdM.getTypeElement(), cdI.getTypeElement(), IssueSeverity.ERROR);
compareItemProperty(sm, "definition", l.getDefinitionElement(), r.getDefinitionElement(), path, res, cdM.getDefinitionElement(), cdI.getDefinitionElement(), IssueSeverity.ERROR);
compareExpectations(sm, l, r, path, res, cdM, cdI);
combined.getChildren().add(sm);
}
}
for (CapabilityStatementRestResourceSearchParamComponent r : right) {
if (!matchR.contains(r)) {
union.add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this Search Parameter", path), r));
}
}
}
private CapabilityStatementRestResourceSearchParamComponent mergeSearchParams(CapabilityStatementRestResourceSearchParamComponent l, CapabilityStatementRestResourceSearchParamComponent r) {
CapabilityStatementRestResourceSearchParamComponent res = l.copy();
if (!res.hasDocumentation() && r.hasDocumentation()) {
res.setDocumentation(r.getDocumentation());
}
return res;
}
private CapabilityStatementRestResourceSearchParamComponent intersectSearchParams(CapabilityStatementRestResourceSearchParamComponent l, CapabilityStatementRestResourceSearchParamComponent r) {
CapabilityStatementRestResourceSearchParamComponent res = new CapabilityStatementRestResourceSearchParamComponent();
res.setName(l.getName());
if (l.hasDocumentation() && r.hasDocumentation()) {
res.setDocumentation(l.getDocumentation());
}
return res;
}
private CapabilityStatementRestResourceSearchParamComponent findInList(List<CapabilityStatementRestResourceSearchParamComponent> list, CapabilityStatementRestResourceSearchParamComponent item) {
for (CapabilityStatementRestResourceSearchParamComponent t : list) {
if (t.hasName() && t.getName().equals(item.getName())) {
return t;
}
}
return null;
}
private void compareOperations(StructuralMatch<Element> combined, List<CapabilityStatementRestResourceOperationComponent> left, List<CapabilityStatementRestResourceOperationComponent> right, String path, CapabilityStatementComparison res, List<CapabilityStatementRestResourceOperationComponent> union, List<CapabilityStatementRestResourceOperationComponent> intersection) {
List<CapabilityStatementRestResourceOperationComponent> matchR = new ArrayList<>();
for (CapabilityStatementRestResourceOperationComponent l : left) {
CapabilityStatementRestResourceOperationComponent r = findInList(right, l);
if (r == null) {
union.add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this Search Parameter", path)));
} else {
matchR.add(r);
CapabilityStatementRestResourceOperationComponent cdM = mergeOperations(l, r);
CapabilityStatementRestResourceOperationComponent cdI = intersectOperations(l, r);
union.add(cdM);
intersection.add(cdI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
compareStrings(path, sm.getMessages(), l.getDocumentation(), r.getDocumentation(), "documentation", IssueSeverity.INFORMATION, res);
compareItemProperty(sm, "definition", l.getDefinitionElement(), r.getDefinitionElement(), path, res, cdM.getDefinitionElement(), cdI.getDefinitionElement(), IssueSeverity.ERROR);
compareExpectations(sm, l, r, path, res, cdM, cdI);
combined.getChildren().add(sm);
}
}
for (CapabilityStatementRestResourceOperationComponent r : right) {
if (!matchR.contains(r)) {
union.add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this Search Parameter", path), r));
}
}
}
private CapabilityStatementRestResourceOperationComponent mergeOperations(CapabilityStatementRestResourceOperationComponent l, CapabilityStatementRestResourceOperationComponent r) {
CapabilityStatementRestResourceOperationComponent res = l.copy();
if (!res.hasDocumentation() && r.hasDocumentation()) {
res.setDocumentation(r.getDocumentation());
}
return res;
}
private CapabilityStatementRestResourceOperationComponent intersectOperations(CapabilityStatementRestResourceOperationComponent l, CapabilityStatementRestResourceOperationComponent r) {
CapabilityStatementRestResourceOperationComponent res = new CapabilityStatementRestResourceOperationComponent();
res.setName(l.getName());
if (l.hasDocumentation() && r.hasDocumentation()) {
res.setDocumentation(l.getDocumentation());
}
return res;
}
private CapabilityStatementRestResourceOperationComponent findInList(List<CapabilityStatementRestResourceOperationComponent> list, CapabilityStatementRestResourceOperationComponent item) {
for (CapabilityStatementRestResourceOperationComponent t : list) {
if (t.hasName() && t.getName().equals(item.getName())) {
return t;
}
}
return null;
}
// 6 columns: path | left value | left doco | right value | right doco | comments
public XhtmlNode renderStatements(CapabilityStatementComparison comparison, String id, String prefix) throws FHIRException, IOException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false);
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
model.getTitles().add(gen.new Title(null, null, "Type", "The type of item", null, 100));
model.getTitles().add(gen.new Title(null, null, "Left Value", "The left value for the item", null, 200, 1));
model.getTitles().add(gen.new Title(null, null, "Left Doco", "The left documentation for the item", null, 200, 1));
model.getTitles().add(gen.new Title(null, null, "Right Value", "The right value for the item", null, 200, 1));
model.getTitles().add(gen.new Title(null, null, "Right Doco", "The right documentation for the item", null, 200, 1));
model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
for (StructuralMatch<Element> t : comparison.getCombined().getChildren()) {
addRow(gen, model.getRows(), t, comparison);
}
return gen.generate(model, prefix, 0, null);
}
private void addRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = null;
if (t.either() instanceof CapabilityStatementRestComponent) {
r = addRestRow(gen, rows, t, comparison);
} else if (t.either() instanceof CapabilityStatementRestSecurityComponent) {
r = addRestSecurityRow(gen, rows, t, comparison);
} else if (t.either() instanceof CapabilityStatementRestResourceComponent) {
r = addRestResourceRow(gen, rows, t, comparison);
} else if (t.either() instanceof ResourceInteractionComponent) {
r = addRestResourceInteractionRow(gen, rows, t, comparison);
} else if (t.either() instanceof CapabilityStatementRestResourceSearchParamComponent) {
r = addRestSearchParamRow(gen, rows, t, comparison);
} else if (t.either() instanceof CapabilityStatementRestResourceOperationComponent) {
r = addRestOperationRow(gen, rows, t, comparison);
} else if (t.either() instanceof CodeableConcept) {
r = addRestSecurityServiceRow(gen, rows, t, comparison);
} else if (t.either() instanceof Extension) {
r = addExtensionRow(gen, rows, t, comparison);
} else if (t.either() instanceof PrimitiveType) {
r = addPrimitiveTypeRow(gen, rows, t, comparison);
} else {
throw new Error("Not Done Yet: "+t.either().getClass().getName());
}
for (StructuralMatch<Element> c : t.getChildren()) {
addRow(gen, r.getSubRows(), c, comparison);
}
}
private Row addRestRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, "mode", null, null));
CapabilityStatementRestComponent left = t.hasLeft() ? (CapabilityStatementRestComponent) t.getLeft() : null;
CapabilityStatementRestComponent right = t.hasRight() ? (CapabilityStatementRestComponent) t.getRight() : null;
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getMode().toCode() : "", null, null), left != null ? left.getMode().toCode() : null, right != null ? right.getMode().toCode() : null, true));
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getMode().toCode() : "", null, null), left != null ? left.getMode().toCode() : null, right != null ? right.getMode().toCode() : null, false));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(cellForMessages(gen, t.getMessages()));
return r;
}
private Row addRestSecurityRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, "security", null, null));
CapabilityStatementRestSecurityComponent left = t.hasLeft() ? (CapabilityStatementRestSecurityComponent) t.getLeft() : null;
CapabilityStatementRestSecurityComponent right = t.hasRight() ? (CapabilityStatementRestSecurityComponent) t.getRight() : null;
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getCorsElement().primitiveValue() : "", null, null), left != null ? left.getCorsElement().primitiveValue() : null, right != null ? right.getCorsElement().primitiveValue() : null, true));
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDescription() : "", null, null), left != null ? left.getDescription() : null, right != null ? right.getDescription() : null, true));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getCorsElement().primitiveValue() : "", null, null), left != null ? left.getCorsElement().primitiveValue() : null, right != null ? right.getCorsElement().primitiveValue() : null, false));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDescription() : "", null, null), left != null ? left.getDescription() : null, right != null ? right.getDescription() : null, true));
r.getCells().add(cellForMessages(gen, t.getMessages()));
return r;
}
private Row addRestResourceRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, "resource", null, null));
CapabilityStatementRestResourceComponent left = t.hasLeft() ? (CapabilityStatementRestResourceComponent) t.getLeft() : null;
CapabilityStatementRestResourceComponent right = t.hasRight() ? (CapabilityStatementRestResourceComponent) t.getRight() : null;
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getType() : "", null, null), left != null ? left.getType() : null, right != null ? right.getType() : null, true));
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getType() : "", null, null), left != null ? left.getType() : null, right != null ? right.getType() : null, false));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(cellForMessages(gen, t.getMessages()));
return r;
}
private Row addRestSearchParamRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, "searchParam", null, null));
CapabilityStatementRestResourceSearchParamComponent left = t.hasLeft() ? (CapabilityStatementRestResourceSearchParamComponent) t.getLeft() : null;
CapabilityStatementRestResourceSearchParamComponent right = t.hasRight() ? (CapabilityStatementRestResourceSearchParamComponent) t.getRight() : null;
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getName() : "", null, null), left != null ? left.getName() : null, right != null ? right.getName() : null, true));
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getName() : "", null, null), left != null ? left.getName() : null, right != null ? right.getName() : null, false));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(cellForMessages(gen, t.getMessages()));
return r;
}
private Row addRestOperationRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, "operation", null, null));
CapabilityStatementRestResourceOperationComponent left = t.hasLeft() ? (CapabilityStatementRestResourceOperationComponent) t.getLeft() : null;
CapabilityStatementRestResourceOperationComponent right = t.hasRight() ? (CapabilityStatementRestResourceOperationComponent) t.getRight() : null;
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getName() : "", null, null), left != null ? left.getName() : null, right != null ? right.getName() : null, true));
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getName() : "", null, null), left != null ? left.getName() : null, right != null ? right.getName() : null, false));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(cellForMessages(gen, t.getMessages()));
return r;
}
private Row addRestSecurityServiceRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, "service", null, null));
CodeableConcept left = t.hasLeft() ? (CodeableConcept) t.getLeft() : null;
CodeableConcept right = t.hasRight() ? (CodeableConcept) t.getRight() : null;
r.getCells().add(style(gen.new Cell(null, null, left != null ? gen(left) : "", null, null), left != null ? gen(left) : null, right != null ? gen(right) : null, true));
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getText() : "", null, null), left != null ? left.getText() : null, right != null ? right.getText() : null, true));
r.getCells().add(style(gen.new Cell(null, null, right != null ? gen(right) : "", null, null), left != null ? gen(left) : null, right != null ? gen(right) : null, false));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getText() : "", null, null), left != null ? left.getText() : null, right != null ? right.getText() : null, true));
r.getCells().add(cellForMessages(gen, t.getMessages()));
return r;
}
private Row addRestResourceInteractionRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, "interaction", null, null));
ResourceInteractionComponent left = t.hasLeft() ? (ResourceInteractionComponent) t.getLeft() : null;
ResourceInteractionComponent right = t.hasRight() ? (ResourceInteractionComponent) t.getRight() : null;
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getCode().getDisplay() : "", null, null), left != null ? left.getCode().getDisplay() : null, right != null ? right.getCode().getDisplay() : null, true));
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getCode().getDisplay() : "", null, null), left != null ? left.getCode().getDisplay() : null, right != null ? right.getCode().getDisplay() : null, false));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getDocumentation() : "", null, null), left != null ? left.getDocumentation() : null, right != null ? right.getDocumentation() : null, true));
r.getCells().add(cellForMessages(gen, t.getMessages()));
return r;
}
private Row addExtensionRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, "expectation", null, null));
Extension left = t.hasLeft() ? (Extension) t.getLeft() : null;
Extension right = t.hasRight() ? (Extension) t.getRight() : null;
r.getCells().add(style(gen.new Cell(null, null, left != null ? left.getValue().primitiveValue() : "", null, null), left != null ? left.getValue().primitiveValue() : null, right != null ? right.getValue().primitiveValue() : null, true));
r.getCells().add(gen.new Cell(null, null, "", null, null));
r.getCells().add(style(gen.new Cell(null, null, right != null ? right.getValue().primitiveValue() : "", null, null), left != null ? left.getValue().primitiveValue() : null, right != null ? right.getValue().primitiveValue() : null, false));
r.getCells().add(gen.new Cell(null, null, "", null, null));
r.getCells().add(cellForMessages(gen, t.getMessages()));
return r;
}
@SuppressWarnings("rawtypes")
private Row addPrimitiveTypeRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, CapabilityStatementComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, t.getName(), null, null));
PrimitiveType left = t.hasLeft() ? (PrimitiveType) t.getLeft() : null;
PrimitiveType right = t.hasRight() ? (PrimitiveType) t.getRight() : null;
CanonicalResource crL = left == null ? null : (CanonicalResource) session.getContextLeft().fetchResource(Resource.class, left.primitiveValue());
CanonicalResource crR = right == null ? null : (CanonicalResource) session.getContextRight().fetchResource(Resource.class, right.primitiveValue());
String refL = crL != null && crL.hasUserData("path") ? crL.getUserString("path") : null;
String dispL = crL != null && refL != null ? crL.present() : left == null ? "" : left.primitiveValue();
String refR = crR != null && crR.hasUserData("path") ? crR.getUserString("path") : null;
String dispR = crR != null && refR != null ? crR.present() : right == null ? "" : right.primitiveValue();
r.getCells().add(style(gen.new Cell(null, refL, dispL, null, null), left != null ? left.primitiveValue() : null, right != null ? right.primitiveValue() : null, true));
r.getCells().add(gen.new Cell(null, null, "", null, null));
r.getCells().add(style(gen.new Cell(null, refR, dispR, null, null), left != null ? left.primitiveValue() : null, right != null ? right.primitiveValue() : null, false));
r.getCells().add(gen.new Cell(null, null, "", null, null));
r.getCells().add(cellForMessages(gen, t.getMessages()));
return r;
}
private Cell style(Cell cell, String left, String right, boolean isLeft) {
if (left != null && right != null) {
if (!left.equals(right)) {
cell.setStyle("background-color: "+COLOR_DIFFERENT);
}
} else if (left != null) {
if (!isLeft) {
cell.setStyle("background-color: "+COLOR_NO_CELL_RIGHT);
}
} else if (right != null) {
if (isLeft) {
cell.setStyle("background-color: "+COLOR_NO_CELL_LEFT);
}
}
return cell;
}
@Override
protected String fhirType() {
return "CapabilityStatement";
}
}

View File

@ -0,0 +1,438 @@
package org.hl7.fhir.r4b.comparison;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r4b.model.CodeSystem;
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionDesignationComponent;
import org.hl7.fhir.r4b.model.CodeSystem.ConceptPropertyComponent;
import org.hl7.fhir.r4b.model.CodeSystem.PropertyComponent;
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.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class CodeSystemComparer extends CanonicalResourceComparer {
public class CodeSystemComparison extends CanonicalResourceComparison<CodeSystem> {
private StructuralMatch<ConceptDefinitionComponent> combined;
private Map<String, String> propMap = new HashMap<>(); // right to left; left retains it's name
public CodeSystemComparison(CodeSystem left, CodeSystem right) {
super(left, right);
combined = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(); // base
}
public Map<String, String> getPropMap() {
return propMap;
}
public StructuralMatch<ConceptDefinitionComponent> getCombined() {
return combined;
}
@Override
protected String abbreviation() {
return "cs";
}
@Override
protected String summary() {
return "CodeSystem: "+left.present()+" vs "+right.present();
}
@Override
protected String fhirType() {
return "CodeSystem";
}
@Override
protected void countMessages(MessageCounts cnts) {
super.countMessages(cnts);
combined.countMessages(cnts);
}
}
private CodeSystem right;
public CodeSystemComparer(ComparisonSession session) {
super(session);
}
public CodeSystemComparison compare(CodeSystem left, CodeSystem right) {
if (left == null)
throw new DefinitionException("No CodeSystem provided (left)");
if (right == null)
throw new DefinitionException("No CodeSystem provided (right)");
CodeSystemComparison res = new CodeSystemComparison(left, right);
session.identify(res);
CodeSystem cs = new CodeSystem();
res.setUnion(cs);
session.identify(cs);
cs.setName("Union"+left.getName()+"And"+right.getName());
cs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
cs.setStatus(left.getStatus());
cs.setDate(new Date());
for (PropertyComponent pL : left.getProperty()) {
cs.addProperty(pL.copy());
}
for (PropertyComponent pR : left.getProperty()) {
PropertyComponent pL = findProperty(left, pR);
if (pL == null) {
String code = getUniqued(pR.getCode(), cs.getProperty());
cs.addProperty(pR.copy().setCode(code));
} else {
res.getPropMap().put(pR.getCode(), pL.getCode());
}
}
CodeSystem cs1 = new CodeSystem();
res.setIntersection(cs1);
session.identify(cs1);
cs1.setName("Intersection"+left.getName()+"And"+right.getName());
cs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
cs1.setStatus(left.getStatus());
cs1.setDate(new Date());
cs1.getProperty().addAll(cs.getProperty());
compareMetadata(left, right, res.getMetadata(), res);
comparePrimitives("caseSensitive", left.getCaseSensitiveElement(), right.getCaseSensitiveElement(), res.getMetadata(), IssueSeverity.ERROR, res);
comparePrimitives("hierarchyMeaning", left.getHierarchyMeaningElement(), right.getHierarchyMeaningElement(), res.getMetadata(), IssueSeverity.ERROR, res);
comparePrimitives("compositional", left.getCompositionalElement(), right.getCompositionalElement(), res.getMetadata(), IssueSeverity.WARNING, res);
comparePrimitives("versionNeeded", left.getVersionNeededElement(), right.getVersionNeededElement(), res.getMetadata(), IssueSeverity.INFORMATION, res);
comparePrimitives("content", left.getContentElement(), right.getContentElement(), res.getMetadata(), IssueSeverity.WARNING, res);
compareConcepts(left.getConcept(), right.getConcept(), res.getCombined(), res.getUnion().getConcept(), res.getIntersection().getConcept(), res.getUnion(), res.getIntersection(), res, "CodeSystem.concept");
return res;
}
private String getUniqued(String code, List<PropertyComponent> list) {
int i = 0;
while (true) {
boolean ok = true;
String res = code+(i == 0 ? "" : i);
for (PropertyComponent t : list) {
if (res.equals(t.getCode())) {
ok = false;
}
}
if (ok) {
return res;
}
}
}
private PropertyComponent findProperty(CodeSystem left, PropertyComponent p) {
for (PropertyComponent t : left.getProperty()) {
if (p.hasUri() && t.hasUri() && p.getUri().equals(t.getUri())) {
return t;
} else if (!p.hasUri() && !t.hasUri() && p.getCode().equals(t.getCode())) {
return t;
}
}
return null;
}
private void compareConcepts(List<ConceptDefinitionComponent> left, List<ConceptDefinitionComponent> right, StructuralMatch<ConceptDefinitionComponent> combined,
List<ConceptDefinitionComponent> union, List<ConceptDefinitionComponent> intersection, CodeSystem csU, CodeSystem csI, CodeSystemComparison res, String path) {
List<ConceptDefinitionComponent> matchR = new ArrayList<>();
for (ConceptDefinitionComponent l : left) {
ConceptDefinitionComponent r = findInList(right, l);
if (r == null) {
union.add(l);
combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed this concept", path)));
} else {
matchR.add(r);
ConceptDefinitionComponent cdM = merge(l, r, csU.getProperty(), res);
ConceptDefinitionComponent cdI = intersect(l, r, res);
union.add(cdM);
intersection.add(cdI);
StructuralMatch<ConceptDefinitionComponent> sm = new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(l, r);
compare(sm.getMessages(), l, r, path+".where(code='"+l.getCode()+"')", res);
combined.getChildren().add(sm);
compareConcepts(l.getConcept(), r.getConcept(), sm, cdM.getConcept(), cdI.getConcept(), csU, csI, res, path+".where(code='"+l.getCode()+"').concept");
}
}
for (ConceptDefinitionComponent r : right) {
if (!matchR.contains(r)) {
union.add(r);
combined.getChildren().add(new StructuralMatch<CodeSystem.ConceptDefinitionComponent>(vmI(IssueSeverity.INFORMATION, "Added this concept", path), r));
}
}
}
private ConceptDefinitionComponent findInList(List<ConceptDefinitionComponent> list, ConceptDefinitionComponent item) {
for (ConceptDefinitionComponent t : list) {
if (t.getCode().equals(item.getCode())) {
return t;
}
}
return null;
}
private void compare(List<ValidationMessage> msgs, ConceptDefinitionComponent l, ConceptDefinitionComponent r, String path, CodeSystemComparison res) {
compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res);
compareStrings(path, msgs, l.getDefinition(), r.getDefinition(), "definition", IssueSeverity.INFORMATION, res);
}
private void compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, CodeSystemComparison res) {
if (!Utilities.noString(right)) {
if (Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" added", path));
} else if (!left.equals(right)) {
if (level != IssueSeverity.NULL) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+"."+name, "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
}
msgs.add(vmI(level, name+" changed from left to right", path));
}
} else if (!Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" removed", path));
}
}
private ConceptDefinitionComponent merge(ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
ConceptDefinitionComponent cd = l.copy();
if (!l.hasDisplay() && r.hasDisplay()) {
cd.setDisplay(r.getDisplay());
}
if (!l.hasDefinition() && r.hasDefinition()) {
cd.setDefinition(r.getDefinition());
}
mergeProps(cd, l, r, destProps, res);
mergeDesignations(cd, l, r);
return cd;
}
private ConceptDefinitionComponent intersect(ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
ConceptDefinitionComponent cd = l.copy();
if (l.hasDisplay() && !r.hasDisplay()) {
cd.setDisplay(null);
}
if (l.hasDefinition() && !r.hasDefinition()) {
cd.setDefinition(null);
}
intersectProps(cd, l, r, res);
// mergeDesignations(cd, l, r);
return cd;
}
private void mergeDesignations(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r) {
for (ConceptDefinitionDesignationComponent td : l.getDesignation()) {
if (hasDesignation(td, r.getDesignation())) {
cd.getDesignation().add(td);
}
}
for (ConceptDefinitionDesignationComponent td : r.getDesignation()) {
if (hasDesignation(td, l.getDesignation())) {
cd.getDesignation().add(td);
}
}
}
private boolean hasDesignation(ConceptDefinitionDesignationComponent td, List<ConceptDefinitionDesignationComponent> designation) {
for (ConceptDefinitionDesignationComponent t : designation) {
if (designationsMatch(td, t)) {
return true;
}
}
return false;
}
private boolean designationsMatch(ConceptDefinitionDesignationComponent l, ConceptDefinitionDesignationComponent r) {
if (l.hasUse() != r.hasUse()) {
return false;
}
if (l.hasLanguage() != r.hasLanguage()) {
return false;
}
if (l.hasValue() != r.hasValue()) {
return false;
}
if (l.hasUse()) {
if (l.getUse().equalsDeep(r.getUse())) {
return false;
}
}
if (l.hasLanguage()) {
if (l.getLanguageElement().equalsDeep(r.getLanguageElement())) {
return false;
}
}
if (l.hasValue()) {
if (l.getValueElement().equalsDeep(r.getValueElement())) {
return false;
}
}
return true;
}
private void mergeProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, List<PropertyComponent> destProps, CodeSystemComparison res) {
List<ConceptPropertyComponent> matchR = new ArrayList<>();
for (ConceptPropertyComponent lp : l.getProperty()) {
ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
if (rp == null) {
cd.getProperty().add(lp);
} else {
matchR.add(rp);
cd.getProperty().add(lp);
if (lp.getValue().equalsDeep(rp.getValue())) {
cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));
}
}
}
for (ConceptPropertyComponent rp : r.getProperty()) {
if (!matchR.contains(rp)) {
cd.getProperty().add(rp.setCode(res.getPropMap().get(rp.getCode())));
}
}
}
private void intersectProps(ConceptDefinitionComponent cd, ConceptDefinitionComponent l, ConceptDefinitionComponent r, CodeSystemComparison res) {
for (ConceptPropertyComponent lp : l.getProperty()) {
ConceptPropertyComponent rp = findRightProp(r.getProperty(), lp, res);
if (rp != null) {
cd.getProperty().add(lp);
}
}
}
private ConceptPropertyComponent findRightProp(List<ConceptPropertyComponent> rightProperties, ConceptPropertyComponent lp, CodeSystemComparison res) {
for (ConceptPropertyComponent p : rightProperties) {
if (res.getPropMap().get(p.getCode()).equals(lp.getCode())) {
return p;
}
}
return null;
}
public XhtmlNode renderConcepts(CodeSystemComparison comparison, String id, String prefix) throws FHIRException, IOException {
// columns: code, display (left|right), properties (left|right)
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "compare"), false);
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
model.getTitles().add(gen.new Title(null, null, "Code", "The code for the concept", null, 100));
model.getTitles().add(gen.new Title(null, null, "Display", "The display for the concept", null, 200, 2));
for (PropertyComponent p : comparison.getUnion().getProperty()) {
model.getTitles().add(gen.new Title(null, null, p.getCode(), p.getDescription(), null, 100, 2));
}
model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
for (StructuralMatch<ConceptDefinitionComponent> t : comparison.getCombined().getChildren()) {
addRow(gen, model.getRows(), t, comparison);
}
return gen.generate(model, prefix, 0, null);
}
private void addRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ConceptDefinitionComponent> t, CodeSystemComparison comparison) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, t.either().getCode(), null, null));
if (t.hasLeft() && t.hasRight()) {
if (t.getLeft().hasDisplay() && t.getRight().hasDisplay()) {
if (t.getLeft().getDisplay().equals(t.getRight().getDisplay())) {
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));
} else {
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
}
} else if (t.getLeft().hasDisplay()) {
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null));
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
} else if (t.getRight().hasDisplay()) {
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null));
} else {
r.getCells().add(missingCell(gen).span(2));
}
for (PropertyComponent p : comparison.getUnion().getProperty()) {
ConceptPropertyComponent lp = getProp(t.getLeft(), p, false, comparison);
ConceptPropertyComponent rp = getProp(t.getRight(), p, true, comparison);
if (lp != null && rp != null) {
if (lp.getValue().equals(rp.getValue())) {
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2));
} else {
r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));
r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));
}
} else if (lp != null) {
r.getCells().add(gen.new Cell(null, null, lp.getValue().toString(), null, null));
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
} else if (rp != null) {
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
r.getCells().add(gen.new Cell(null, null, rp.getValue().toString(), null, null));
} else {
r.getCells().add(missingCell(gen).span(2));
}
}
} else if (t.hasLeft()) {
r.setColor(COLOR_NO_ROW_RIGHT);
r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
r.getCells().add(missingCell(gen));
for (PropertyComponent p : comparison.getUnion().getProperty()) {
r.getCells().add(propertyCell(gen, t.getLeft(), p, false, comparison));
r.getCells().add(missingCell(gen));
}
} else {
r.setColor(COLOR_NO_ROW_LEFT);
r.getCells().add(missingCell(gen));
r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
for (PropertyComponent p : comparison.getUnion().getProperty()) {
r.getCells().add(missingCell(gen));
r.getCells().add(propertyCell(gen, t.getLeft(), p, true, comparison));
}
}
r.getCells().add(cellForMessages(gen, t.getMessages()));
}
private Cell propertyCell(HierarchicalTableGenerator gen, ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
ConceptPropertyComponent cp = getProp(cd, p, right, comp);
if (cp == null) {
return missingCell(gen, right ? COLOR_NO_CELL_RIGHT : COLOR_NO_CELL_LEFT);
} else {
return gen.new Cell(null, null, cp.getValue().toString(), null, null);
}
}
public ConceptPropertyComponent getProp(ConceptDefinitionComponent cd, PropertyComponent p, boolean right, CodeSystemComparison comp) {
String c = p.getCode();
if (right) {
c = comp.getPropMap().get(c);
}
ConceptPropertyComponent cp = null;
if (cd != null) {
for (ConceptPropertyComponent t : cd.getProperty()) {
if (t.getCode().equals(c)) {
cp = t;
}
}
}
return cp;
}
@Override
protected String fhirType() {
return "CodeSystem";
}
}

View File

@ -0,0 +1,275 @@
package org.hl7.fhir.r4b.comparison;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.r4b.comparison.CodeSystemComparer.CodeSystemComparison;
import org.hl7.fhir.r4b.comparison.ProfileComparer.ProfileComparison;
import org.hl7.fhir.r4b.comparison.ResourceComparer.PlaceHolderComparison;
import org.hl7.fhir.r4b.comparison.ResourceComparer.ResourceComparison;
import org.hl7.fhir.r4b.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.model.Base;
import org.hl7.fhir.r4b.model.ExpressionNode.CollectionStatus;
import org.hl7.fhir.r4b.model.StringType;
import org.hl7.fhir.r4b.model.Tuple;
import org.hl7.fhir.r4b.model.TypeDetails;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.utils.FHIRPathEngine.IEvaluationContext;
import org.hl7.fhir.r4b.utils.LiquidEngine;
import org.hl7.fhir.r4b.utils.LiquidEngine.LiquidDocument;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
public class ComparisonRenderer implements IEvaluationContext {
private IWorkerContext contextLeft;
private IWorkerContext contextRight;
private ComparisonSession session;
private Map<String, String> templates = new HashMap<>();
private String folder;
public ComparisonRenderer(IWorkerContext contextLeft, IWorkerContext contextRight, String folder, ComparisonSession session) {
super();
this.contextLeft = contextLeft;
this.contextRight = contextRight;
this.folder = folder;
this.session = session;
}
public Map<String, String> getTemplates() {
return templates;
}
public File render(String leftName, String rightName) throws IOException {
dumpBinaries();
StringBuilder b = new StringBuilder();
b.append("<table class=\"grid\">\r\n");
b.append(" <tr>\r\n");
b.append(" <td width=\"260\"><b>"+Utilities.escapeXml(leftName)+"</b></td>\r\n");
b.append(" <td width=\"260\"><b>"+Utilities.escapeXml(rightName)+"</b></td>\r\n");
b.append(" <td width=\"100\"><b>Difference</b></td>\r\n");
b.append(" <td width=\"260\"><b>Notes</b></td>\r\n");
b.append(" </tr>\r\n");
List<String> list = sorted(session.getCompares().keySet());
processList(list, b, "CodeSystem");
processList(list, b, "ValueSet");
processList(list, b, "StructureDefinition");
processList(list, b, "CapabilityStatement");
b.append("</table>\r\n");
Map<String, Base> vars = new HashMap<>();
vars.put("title", new StringType(session.getTitle()));
vars.put("list", new StringType(b.toString()));
String template = templates.get("Index");
String cnt = processTemplate(template, "CodeSystem", vars);
TextFile.stringToFile(cnt, file("index.html"));
return new File(file("index.html"));
}
private void processList(List<String> list, StringBuilder b, String name) throws IOException {
// TODO Auto-generated method stub
boolean first = true;
for (String id : list) {
ResourceComparison comp = session.getCompares().get(id);
if (comp.fhirType().equals(name)) {
if (first) {
first = false;
b.append("<tr><td colspan=\"4\"><b>"+Utilities.pluralize(name, 2)+"</b></td></tr>\r\n");
}
try {
renderComparison(id, comp);
} catch (Exception e) {
System.out.println("Exception rendering "+id+": "+e.getMessage());
e.printStackTrace();
}
b.append(comp.toTable());
//"<li><a href=\""+comp.getId()+".html\">"+Utilities.escapeXml(comp.summary())+"</a></li>\r\n"
}
}
}
private List<String> sorted(Set<String> keySet) {
List<String> list = new ArrayList<>();
list.addAll(keySet);
Collections.sort(list);
return list;
}
private void dumpBinaries() throws IOException {
if (contextLeft != null && contextLeft.getBinaries() != null) {
for (String k : contextLeft.getBinaries().keySet()) {
TextFile.bytesToFile(contextLeft.getBinaries().get(k), Utilities.path(folder, k));
}
}
if (contextRight != null && contextRight.getBinaries() != null) {
for (String k : contextRight.getBinaries().keySet()) {
TextFile.bytesToFile(contextRight.getBinaries().get(k), Utilities.path(folder, k));
}
}
}
private void renderComparison(String id, ResourceComparison comp) throws IOException {
if (comp instanceof ProfileComparison) {
renderProfile(id, (ProfileComparison) comp);
} else if (comp instanceof ValueSetComparison) {
renderValueSet(id, (ValueSetComparison) comp);
} else if (comp instanceof CodeSystemComparison) {
renderCodeSystem(id, (CodeSystemComparison) comp);
} else if (comp instanceof PlaceHolderComparison) {
renderPlaceHolder(id, (PlaceHolderComparison) comp);
}
}
private void renderPlaceHolder(String id, PlaceHolderComparison comp) throws IOException {
String cnt = "";
if (comp.getE() != null) {
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
comp.getE().printStackTrace(pw);
cnt = sw.toString();
}
cnt = "<html><body><pre>"+cnt+"</pre></body></html>\r\n";
TextFile.stringToFile(cnt, file(comp.getId()+".html"));
}
private void renderCodeSystem(String id, CodeSystemComparison comp) throws IOException {
String template = templates.get("CodeSystem");
Map<String, Base> vars = new HashMap<>();
CodeSystemComparer cs = new CodeSystemComparer(session);
vars.put("left", new StringType(comp.getLeft().present()));
vars.put("right", new StringType(comp.getRight().present()));
vars.put("leftId", new StringType(comp.getLeft().getId()));
vars.put("rightId", new StringType(comp.getRight().getId()));
vars.put("leftUrl", new StringType(comp.getLeft().getUrl()));
vars.put("rightUrl", new StringType(comp.getRight().getUrl()));
vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp))));
vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", ""))));
vars.put("concepts", new StringType(new XhtmlComposer(true).compose(cs.renderConcepts(comp, "", ""))));
String cnt = processTemplate(template, "CodeSystem", vars);
TextFile.stringToFile(cnt, file(comp.getId()+".html"));
new org.hl7.fhir.r4b.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-union.json")), comp.getUnion());
new org.hl7.fhir.r4b.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-intersection.json")), comp.getIntersection());
}
private String file(String name) throws IOException {
return Utilities.path(folder, name);
}
private void renderValueSet(String id, ValueSetComparison comp) throws FHIRException, IOException {
String template = templates.get("ValueSet");
Map<String, Base> vars = new HashMap<>();
ValueSetComparer cs = new ValueSetComparer(session);
vars.put("left", new StringType(comp.getLeft().present()));
vars.put("right", new StringType(comp.getRight().present()));
vars.put("leftId", new StringType(comp.getLeft().getId()));
vars.put("rightId", new StringType(comp.getRight().getId()));
vars.put("leftUrl", new StringType(comp.getLeft().getUrl()));
vars.put("rightUrl", new StringType(comp.getRight().getUrl()));
vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp))));
vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", ""))));
vars.put("compose", new StringType(new XhtmlComposer(true).compose(cs.renderCompose(comp, "", ""))));
vars.put("expansion", new StringType(new XhtmlComposer(true).compose(cs.renderExpansion(comp, "", ""))));
String cnt = processTemplate(template, "ValueSet", vars);
TextFile.stringToFile(cnt, file(comp.getId()+".html"));
new org.hl7.fhir.r4b.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-union.json")), comp.getUnion());
new org.hl7.fhir.r4b.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-intersection.json")), comp.getIntersection());
}
private void renderProfile(String id, ProfileComparison comp) throws IOException {
String template = templates.get("Profile");
Map<String, Base> vars = new HashMap<>();
ProfileComparer cs = new ProfileComparer(session, new ProfileUtilities(session.getContextLeft(), null, session.getPkp()), new ProfileUtilities(session.getContextRight(), null, session.getPkp()));
vars.put("left", new StringType(comp.getLeft().present()));
vars.put("right", new StringType(comp.getRight().present()));
vars.put("leftId", new StringType(comp.getLeft().getId()));
vars.put("rightId", new StringType(comp.getRight().getId()));
vars.put("leftUrl", new StringType(comp.getLeft().getUrl()));
vars.put("rightUrl", new StringType(comp.getRight().getUrl()));
vars.put("errors", new StringType(new XhtmlComposer(true).compose(cs.renderErrors(comp))));
vars.put("metadata", new StringType(new XhtmlComposer(true).compose(cs.renderMetadata(comp, "", ""))));
vars.put("structure", new StringType(new XhtmlComposer(true).compose(cs.renderStructure(comp, "", "", "http://hl7.org/fhir"))));
String cnt = processTemplate(template, "CodeSystem", vars);
TextFile.stringToFile(cnt, file(comp.getId()+".html"));
new org.hl7.fhir.r4b.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-union.json")), comp.getUnion());
new org.hl7.fhir.r4b.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path(folder, comp.getId() + "-intersection.json")), comp.getIntersection());
}
private String processTemplate(String template, String name, Map<String, Base> vars) {
LiquidEngine engine = new LiquidEngine(contextRight, this);
LiquidDocument doc = engine.parse(template, name+".template");
return engine.evaluate(doc, Tuple.fromMap(vars), vars);
}
@Override
public List<Base> resolveConstant(Object appContext, String name, boolean beforeContext) throws PathEngineException {
@SuppressWarnings("unchecked")
Map<String, Base> vars = (Map<String, Base>) appContext;
List<Base> res = new ArrayList<>();
if (vars.containsKey(name)) {
res.add(vars.get(name));
}
return res;
}
@Override
public TypeDetails resolveConstantType(Object appContext, String name) throws PathEngineException {
@SuppressWarnings("unchecked")
Map<String, Base> vars = (Map<String, Base>) appContext;
Base b = vars.get(name);
return new TypeDetails(CollectionStatus.SINGLETON, b == null ? "Base" : b.fhirType());
}
@Override
public boolean log(String argument, List<Base> focus) {
return false;
}
@Override
public FunctionDetails resolveFunction(String functionName) {
return null;
}
@Override
public TypeDetails checkFunction(Object appContext, String functionName, List<TypeDetails> parameters) throws PathEngineException {
return null;
}
@Override
public List<Base> executeFunction(Object appContext, List<Base> focus, String functionName, List<List<Base>> parameters) {
return null;
}
@Override
public Base resolveReference(Object appContext, String url, Base refContext) throws FHIRException {
return null;
}
@Override
public boolean conformsToProfile(Object appContext, Base item, String url) throws FHIRException {
return false;
}
@Override
public ValueSet resolveValueSet(Object appContext, String url) {
return null;
}
}

View File

@ -0,0 +1,151 @@
package org.hl7.fhir.r4b.comparison;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.comparison.CodeSystemComparer.CodeSystemComparison;
import org.hl7.fhir.r4b.comparison.ProfileComparer.ProfileComparison;
import org.hl7.fhir.r4b.comparison.ResourceComparer.ResourceComparison;
import org.hl7.fhir.r4b.comparison.ValueSetComparer.ValueSetComparison;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.conformance.ProfileUtilities.ProfileKnowledgeProvider;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.model.CanonicalResource;
import org.hl7.fhir.r4b.model.CodeSystem;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.utilities.Utilities;
public class ComparisonSession {
private Map<String, ResourceComparison> compares = new HashMap<>();
private IWorkerContext contextLeft;
private IWorkerContext contextRight;
private String sessiondId;
private int count;
private boolean debug;
private String title;
private ProfileKnowledgeProvider pkp;
public ComparisonSession(IWorkerContext contextLeft, IWorkerContext contextRight, String title, ProfileKnowledgeProvider pkp) {
super();
this.contextLeft = contextLeft;
this.contextRight = contextRight;
this.sessiondId = UUID.randomUUID().toString().toLowerCase();
this.title = title;
this.pkp = pkp;
// debug = true;
}
public IWorkerContext getContextLeft() {
return contextLeft;
}
public IWorkerContext getContextRight() {
return contextRight;
}
public String getTitle() {
return title;
}
public ResourceComparison compare(String left, String right) throws DefinitionException, FHIRFormatError, IOException {
CanonicalResource l = (CanonicalResource) contextLeft.fetchResource(Resource.class, left);
if (l == null) {
throw new DefinitionException("Unable to resolve "+left);
}
CanonicalResource r = (CanonicalResource) contextRight.fetchResource(Resource.class, right);
if (r == null) {
throw new DefinitionException("Unable to resolve "+right);
}
return compare(l, r);
}
public ResourceComparison compare(CanonicalResource left, CanonicalResource right) throws DefinitionException, FHIRFormatError, IOException {
if (left != null && right != null) {
String key = key(left.getUrl(), left.getVersion(), right.getUrl(), right.getVersion());
if (compares.containsKey(key)) {
// if null then the comparison is in progress.
// this can happen when profiles refer to each other
return compares.get(key);
}
compares.put(key, null);
try {
if (left instanceof CodeSystem && right instanceof CodeSystem) {
CodeSystemComparer cs = new CodeSystemComparer(this);
CodeSystemComparison csc = cs.compare((CodeSystem) left, (CodeSystem) right);
compares.put(key, csc);
return csc;
} else if (left instanceof ValueSet && right instanceof ValueSet) {
ValueSetComparer cs = new ValueSetComparer(this);
ValueSetComparison csc = cs.compare((ValueSet) left, (ValueSet) right);
compares.put(key, csc);
return csc;
} else if (left instanceof StructureDefinition && right instanceof StructureDefinition) {
ProfileComparer cs = new ProfileComparer(this, new ProfileUtilities(contextLeft, null, pkp), new ProfileUtilities(contextRight, null, pkp));
ProfileComparison csc = cs.compare((StructureDefinition) left, (StructureDefinition) right);
compares.put(key, csc);
return csc;
} else {
throw new FHIRException("Unable to compare resources of type "+left.fhirType()+" and "+right.fhirType());
}
} catch (Throwable e) {
ResourceComparer.PlaceHolderComparison csc = new ResourceComparer.PlaceHolderComparison(left, right, e);
compares.put(key, csc);
return csc;
}
} else if (left != null) {
String key = key(left.getUrl(), left.getVersion(), left.getUrl(), left.getVersion());
if (compares.containsKey(key)) {
return compares.get(key);
}
ResourceComparer.PlaceHolderComparison csc = new ResourceComparer.PlaceHolderComparison(left, right);
compares.put(key, csc);
return csc;
} else {
String key = key(right.getUrl(), right.getVersion(), right.getUrl(), right.getVersion());
if (compares.containsKey(key)) {
return compares.get(key);
}
ResourceComparer.PlaceHolderComparison csc = new ResourceComparer.PlaceHolderComparison(left, right);
compares.put(key, csc);
return csc;
}
}
private String key(String urlL, String verL, String urlR, String verR) {
return urlL+"|"+verL+"||"+urlR+"|"+verR;
}
public void identify(CanonicalResource res) {
count++;
res.setId(sessiondId+"-"+count);
res.setUrl("http://hl7.org/fhir/comparison/"+res.fhirType()+"/"+res.getId());
}
public void identify(ResourceComparison res) {
count++;
}
public boolean isDebug() {
return debug;
}
public void setDebug(boolean debug) {
this.debug = debug;
}
public Map<String, ResourceComparison> getCompares() {
return compares;
}
public ProfileKnowledgeProvider getPkp() {
return pkp;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,327 @@
package org.hl7.fhir.r4b.comparison;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.r4b.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r4b.comparison.ResourceComparer.ResourceComparison;
import org.hl7.fhir.r4b.model.CanonicalResource;
import org.hl7.fhir.r4b.model.CodeSystem;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.ValueSet;
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.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Cell;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Piece;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class ResourceComparer {
public static class MessageCounts {
private int errors;
private int warnings;
private int hints;
public int getErrors() {
return errors;
}
public int getWarnings() {
return warnings;
}
public int getHints() {
return hints;
}
public void error() {
errors++;
}
public void warning() {
warnings++;
}
public void hint() {
hints++;
}
}
public static abstract class ResourceComparison {
private String id;
private String leftId;
private String rightId;
private MessageCounts cnts;
public ResourceComparison(String leftId, String rightId) {
super();
this.leftId = leftId;
this.rightId = rightId;
id = abbreviation()+"-"+leftId + "-" + rightId;
}
protected String refCell(CanonicalResource cr) {
if (cr == null) {
return "<td></td>";
}
String t = cr.present();
if (Utilities.noString(t)) {
t = cr.getId();
}
if (cr.hasUserData("path")) {
String p = cr.getUserString("path");
return "<td><a href=\""+(Utilities.isAbsoluteUrl(p) ? "" : "../")+p+"\">"+Utilities.escapeXml(t)+"</td>";
} else
return "<td>"+Utilities.escapeXml(t)+"</td>";
}
protected abstract String abbreviation();
public String getLeftId() {
return leftId;
}
public String getRightId() {
return rightId;
}
protected List<ValidationMessage> messages = new ArrayList<>();
public List<ValidationMessage> getMessages() {
return messages;
}
public String getId() {
return id;
}
protected abstract String summary();
protected abstract String fhirType();
protected abstract String toTable();
protected String color() {
if (hasErrors()) {
return COLOR_DIFFERENT;
} else if (noChange()) {
return COLOR_NO_CHANGE;
} else {
return COLOR_DIFFERENT_LESS;
}
}
protected boolean hasErrors() {
MessageCounts cnts = getCounts();
return cnts.getErrors() > 0;
}
protected boolean noChange() {
MessageCounts cnts = getCounts();
return cnts.getErrors() + cnts.getWarnings() + cnts.getHints() == 0;
}
protected String outcomeSummary() {
MessageCounts cnts = getCounts();
return
Integer.toString(cnts.getErrors())+" "+Utilities.pluralize("Breaking Change", cnts.getErrors())+", "+
Integer.toString(cnts.getWarnings())+" "+Utilities.pluralize("Change", cnts.getWarnings())+", "+
Integer.toString(cnts.getHints())+" "+Utilities.pluralize("Note", cnts.getHints());
}
public MessageCounts getCounts() {
if (cnts == null) {
cnts = new MessageCounts();
countMessages(cnts);
}
return cnts;
}
protected abstract void countMessages(MessageCounts cnts);
}
public static class PlaceHolderComparison extends ResourceComparison {
private CanonicalResource left;
private CanonicalResource right;
private Throwable e;
public PlaceHolderComparison(CanonicalResource left, CanonicalResource right) {
super(left == null ? right.getId() : left.getId(), right == null ? left.getId() : right.getId());
this.left = left;
this.right = right;
}
public PlaceHolderComparison(CanonicalResource left, CanonicalResource right, Throwable e) {
super(left == null ? right.getId() : left.getId(), right == null ? left.getId() : right.getId());
this.e = e;
this.left = left;
this.right = right;
}
@Override
protected String abbreviation() {
CanonicalResource cr = left == null ? right : left;
if (cr instanceof CodeSystem) {
return "cs";
} else if (cr instanceof ValueSet) {
return "vs";
} else if (cr instanceof StructureDefinition) {
return "sd";
} else {
return "xx";
}
}
@Override
protected String summary() {
if (e != null) {
return e.getMessage();
}
CanonicalResource cr = left == null ? right : left;
return cr.fhirType()+(left == null ? " Added" : " Removed");
}
@Override
protected String fhirType() {
CanonicalResource cr = left == null ? right : left;
return cr.fhirType();
}
@Override
protected String toTable() {
String s = "";
String color = null;
s = s + refCell(left);
s = s + refCell(right);
if (left == null) {
s = s + "<td>Added</td>";
color = COLOR_NO_ROW_LEFT;
} else if (right == null) {
s = s + "<td>Removed</td>";
color = COLOR_NO_ROW_RIGHT;
} else {
s = s + "<td><a href=\""+getId()+".html\">Failed<a></td>";
color = COLOR_ISSUE;
}
s = s + "<td>"+(e != null ? Utilities.escapeXml(e.getMessage()) : "")+"</td>";
return "<tr style=\"background-color: "+color+"\">"+s+"</tr>\r\n";
}
public Throwable getE() {
return e;
}
@Override
protected void countMessages(MessageCounts cnts) {
if (e != null) {
cnts.error();
}
}
}
public final static String COLOR_NO_ROW_LEFT = "#ffffb3";
public final static String COLOR_NO_CELL_LEFT = "#ffff4d";
public final static String COLOR_NO_ROW_RIGHT = "#ffecb3";
public final static String COLOR_NO_CELL_RIGHT = "#ffcc33";
public final static String COLOR_DIFFERENT = "#f0b3ff";
public final static String COLOR_DIFFERENT_LESS = "#f8e6ff";
public final static String COLOR_ISSUE = "#ffad99";
public final static String COLOR_NO_CHANGE = "#ffffff";
protected ComparisonSession session;
public ResourceComparer(ComparisonSession session) {
super();
this.session = session;
}
public Cell missingCell(HierarchicalTableGenerator gen) {
Cell c = gen.new Cell(null, null, "", null, null);
return c;
}
public Cell missingCell(HierarchicalTableGenerator gen, String color) {
Cell c = gen.new Cell(null, null, "", null, null);
if (color != null) {
c.setStyle("background-color: "+color);
}
return c;
}
public XhtmlNode renderErrors(ResourceComparison csc) {
XhtmlNode div = new XhtmlNode(NodeType.Element, "div");
XhtmlNode tbl = div.table("grid");
for (ValidationMessage vm : csc.messages) {
XhtmlNode tr = tbl.tr();
tr.style("background-color: "+colorForLevel(vm.getLevel()));
tr.td().tx(vm.getLevel().getDisplay());
tr.td().tx(vm.getLocation());
tr.td().tx(vm.getMessage().replace("\"", "'"));
}
return div;
}
protected ValidationMessage vmI(IssueSeverity level, String message, String path) {
ValidationMessage vm = new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, message, level == IssueSeverity.NULL ? IssueSeverity.INFORMATION : level);
return vm;
}
protected void vm(IssueSeverity level, String message, String path, List<ValidationMessage> genMessages, List<ValidationMessage> specMessages) {
ValidationMessage vm = new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, message, level == IssueSeverity.NULL ? IssueSeverity.INFORMATION : level);
genMessages.add(vm);
if (specMessages != null) {
specMessages.add(vm);
}
}
protected ValidationMessage vm(IssueSeverity level, String message, String path, List<ValidationMessage> genMessages) {
ValidationMessage vm = new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path, message, level == IssueSeverity.NULL ? IssueSeverity.INFORMATION : level);
genMessages.add(vm);
return vm;
}
private String colorForLevel(IssueSeverity level) {
switch (level) {
case ERROR:
return "#ffcccc";
case FATAL:
return "#ff9999";
case WARNING:
return "#ffebcc";
default: // INFORMATION:
return "#ffffe6";
}
}
private String halfColorForLevel(IssueSeverity level) {
switch (level) {
case ERROR:
return "#ffeeee";
case FATAL:
return "#ffcccc";
case WARNING:
return "#fff4ee";
default: // INFORMATION:
return "#fffff2";
}
}
protected Cell cellForMessages(HierarchicalTableGenerator gen, List<ValidationMessage> messages) {
Cell cell = gen.new Cell();
Piece piece = gen.new Piece("ul");
cell.addPiece(piece);
for (ValidationMessage msg : messages) {
XhtmlNode li = new XhtmlNode(NodeType.Element, "li");
piece.getChildren().add(li);
li.style("background-color: "+halfColorForLevel(msg.getLevel()));
li.tx(msg.getMessage());
}
return cell;
}
}

View File

@ -0,0 +1,117 @@
package org.hl7.fhir.r4b.comparison;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.r4b.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
public class StructuralMatch<T> {
private String name; // this is used in some contexts to carry name when T is a pretty abstract type
private T left;
private T right;
private List<ValidationMessage> messages = new ArrayList<>();
private List<StructuralMatch<T>> children = new ArrayList<>();
public StructuralMatch() {
// root, just a place holder...
}
public StructuralMatch(T left, T right) {
super();
this.left = left;
this.right = right;
}
public StructuralMatch(T left, T right, ValidationMessage msg) {
super();
this.left = left;
this.right = right;
if (msg != null) {
this.messages.add(msg);
}
}
public StructuralMatch(ValidationMessage msg, T right) {
super();
this.messages.add(msg);
this.right = right;
}
public StructuralMatch(T left, ValidationMessage msg) {
super();
this.left = left;
this.messages.add(msg);
}
public T getLeft() {
return left;
}
public T getRight() {
return right;
}
public List<StructuralMatch<T>> getChildren() {
return children;
}
/**
* return left if it exists, or return right (which might be null)
*
* This is used when accessing whatever makes the items common
*
* @return
*/
public T either() {
return left != null ? left : right;
}
public boolean hasLeft() {
return left != null;
}
public boolean hasRight() {
return right != null;
}
public List<ValidationMessage> getMessages() {
return messages;
}
public boolean hasErrors() {
for (ValidationMessage vm : messages) {
if (vm.getLevel() == IssueSeverity.ERROR) {
return true;
}
}
return false;
}
public void countMessages(MessageCounts cnts) {
for (ValidationMessage vm : getMessages()) {
if (vm.getLevel() == IssueSeverity.ERROR) {
cnts.error();
} else if (vm.getLevel() == IssueSeverity.WARNING) {
cnts.warning();
} else if (vm.getLevel() == IssueSeverity.INFORMATION) {
cnts.hint();
}
}
for (StructuralMatch<T> c : children) {
c.countMessages(cnts);
}
}
public String getName() {
return name;
}
public StructuralMatch<T> setName(String name) {
this.name = name;
return this;
}
}

View File

@ -0,0 +1,840 @@
package org.hl7.fhir.r4b.comparison;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.comparison.ResourceComparer.MessageCounts;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.model.CanonicalType;
import org.hl7.fhir.r4b.model.Element;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.model.ValueSet.ConceptReferenceComponent;
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r4b.model.ValueSet.ValueSetComposeComponent;
import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
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.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.Row;
import org.hl7.fhir.utilities.xhtml.HierarchicalTableGenerator.TableModel;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class ValueSetComparer extends CanonicalResourceComparer {
public class ValueSetComparison extends CanonicalResourceComparison<ValueSet> {
public ValueSetComparison(ValueSet left, ValueSet right) {
super(left, right);
}
private StructuralMatch<Element> includes = new StructuralMatch<>();
private StructuralMatch<Element> excludes = new StructuralMatch<>();
private StructuralMatch<ValueSetExpansionContainsComponent> expansion;
public StructuralMatch<Element> getIncludes() {
return includes;
}
public StructuralMatch<Element> getExcludes() {
return excludes;
}
public StructuralMatch<ValueSetExpansionContainsComponent> getExpansion() {
return expansion;
}
public StructuralMatch<ValueSetExpansionContainsComponent> forceExpansion() {
if (expansion == null) {
expansion = new StructuralMatch<>();
}
return expansion;
}
@Override
protected String abbreviation() {
return "vs";
}
@Override
protected String summary() {
return "ValueSet: "+left.present()+" vs "+right.present();
}
@Override
protected String fhirType() {
return "ValueSet";
}
@Override
protected void countMessages(MessageCounts cnts) {
super.countMessages(cnts);
if (includes != null) {
includes.countMessages(cnts);
}
if (excludes != null) {
excludes.countMessages(cnts);
}
if (expansion != null) {
expansion.countMessages(cnts);
}
}
}
public ValueSetComparer(ComparisonSession session) {
super(session);
}
public ValueSetComparison compare(ValueSet left, ValueSet right) {
if (left == null)
throw new DefinitionException("No ValueSet provided (left)");
if (right == null)
throw new DefinitionException("No ValueSet provided (right)");
ValueSetComparison res = new ValueSetComparison(left, right);
session.identify(res);
ValueSet vs = new ValueSet();
res.setUnion(vs);
session.identify(vs);
vs.setName("Union"+left.getName()+"And"+right.getName());
vs.setTitle("Union of "+left.getTitle()+" And "+right.getTitle());
vs.setStatus(left.getStatus());
vs.setDate(new Date());
ValueSet vs1 = new ValueSet();
res.setIntersection(vs1);
session.identify(vs1);
vs1.setName("Intersection"+left.getName()+"And"+right.getName());
vs1.setTitle("Intersection of "+left.getTitle()+" And "+right.getTitle());
vs1.setStatus(left.getStatus());
vs1.setDate(new Date());
compareMetadata(left, right, res.getMetadata(), res);
comparePrimitives("immutable", left.getImmutableElement(), right.getImmutableElement(), res.getMetadata(), IssueSeverity.WARNING, res);
if (left.hasCompose() || right.hasCompose()) {
comparePrimitives("compose.lockedDate", left.getCompose().getLockedDateElement(), right.getCompose().getLockedDateElement(), res.getMetadata(), IssueSeverity.WARNING, res);
comparePrimitives("compose.inactive", left.getCompose().getInactiveElement(), right.getCompose().getInactiveElement(), res.getMetadata(), IssueSeverity.WARNING, res);
}
compareCompose(left.getCompose(), right.getCompose(), res, res.getUnion().getCompose(), res.getIntersection().getCompose());
compareExpansions(left, right, res);
return res;
}
private void compareCompose(ValueSetComposeComponent left, ValueSetComposeComponent right, ValueSetComparison res, ValueSetComposeComponent union, ValueSetComposeComponent intersection) {
// first, the includes
List<ConceptSetComponent> matchR = new ArrayList<>();
for (ConceptSetComponent l : left.getInclude()) {
ConceptSetComponent r = findInList(right.getInclude(), l, left.getInclude());
if (r == null) {
union.getInclude().add(l);
res.getIncludes().getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed Include", "ValueSet.compose.include")));
} else {
matchR.add(r);
ConceptSetComponent csM = new ConceptSetComponent();
ConceptSetComponent csI = new ConceptSetComponent();
union.getInclude().add(csM);
intersection.getInclude().add(csI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
res.getIncludes().getChildren().add(sm);
compareDefinitions(l, r, sm, csM, csI);
}
}
for (ConceptSetComponent r : right.getInclude()) {
if (!matchR.contains(r)) {
union.getInclude().add(r);
res.getIncludes().getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added Include", "ValueSet.compose.include"), r));
}
}
// now. the excludes
matchR.clear();
for (ConceptSetComponent l : left.getExclude()) {
ConceptSetComponent r = findInList(right.getExclude(), l, left.getExclude());
if (r == null) {
union.getExclude().add(l);
res.getExcludes().getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed Exclude", "ValueSet.compose.exclude")));
} else {
matchR.add(r);
ConceptSetComponent csM = new ConceptSetComponent();
ConceptSetComponent csI = new ConceptSetComponent();
union.getExclude().add(csM);
intersection.getExclude().add(csI);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
res.getExcludes().getChildren().add(sm);
compareDefinitions(l, r, sm, csM, csI);
}
}
for (ConceptSetComponent r : right.getExclude()) {
if (!matchR.contains(r)) {
union.getExclude().add(r);
res.getExcludes().getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added Exclude", "ValueSet.compose.exclude"), r));
}
}
}
private ConceptSetComponent findInList(List<ConceptSetComponent> matches, ConceptSetComponent item, List<ConceptSetComponent> source) {
if (matches.size() == 1 && source.size() == 1) {
return matches.get(0);
}
int matchCount = countMatchesBySystem(matches, item);
int sourceCount = countMatchesBySystem(source, item);
if (matchCount == 1 && sourceCount == 1) {
for (ConceptSetComponent t : matches) {
if (t.getSystem().equals(item.getSystem())) {
return t;
}
}
}
// if there's more than one candidate match by system, then we look for a full match
for (ConceptSetComponent t : matches) {
if (t.equalsDeep(item)) {
return t;
}
}
return null;
}
private int countMatchesBySystem(List<ConceptSetComponent> list, ConceptSetComponent item) {
int c = 0;
for (ConceptSetComponent t : list) {
if (t.hasSystem() && t.getSystem().equals(item.getSystem())) {
c++;
}
}
return c;
}
private void compareDefinitions(ConceptSetComponent left, ConceptSetComponent right, StructuralMatch<Element> combined, ConceptSetComponent union, ConceptSetComponent intersection) {
// system must match, but the rest might not. we're going to do the full comparison whatever, so the outcome looks consistent to the user
List<CanonicalType> matchVSR = new ArrayList<>();
for (CanonicalType l : left.getValueSet()) {
CanonicalType r = findInList(right.getValueSet(), l, left.getValueSet());
if (r == null) {
union.getValueSet().add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed ValueSet", "ValueSet.compose.include.valueSet")));
} else {
matchVSR.add(r);
if (l.getValue().equals(r.getValue())) {
union.getValueSet().add(l);
intersection.getValueSet().add(l);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, null);
combined.getChildren().add(sm);
} else {
union.getValueSet().add(l);
union.getValueSet().add(r);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Values are different", "ValueSet.compose.include.valueSet"));
combined.getChildren().add(sm);
}
}
}
for (CanonicalType r : right.getValueSet()) {
if (!matchVSR.contains(r)) {
union.getValueSet().add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Add ValueSet", "ValueSet.compose.include.valueSet"), r));
}
}
List<ConceptReferenceComponent> matchCR = new ArrayList<>();
for (ConceptReferenceComponent l : left.getConcept()) {
ConceptReferenceComponent r = findInList(right.getConcept(), l, left.getConcept());
if (r == null) {
union.getConcept().add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this Concept", "ValueSet.compose.include.concept")));
} else {
matchCR.add(r);
if (l.getCode().equals(r.getCode())) {
ConceptReferenceComponent cu = new ConceptReferenceComponent();
ConceptReferenceComponent ci = new ConceptReferenceComponent();
union.getConcept().add(cu);
intersection.getConcept().add(ci);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
combined.getChildren().add(sm);
compareConcepts(l, r, sm, cu, ci);
} else {
union.getConcept().add(l);
union.getConcept().add(r);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Concepts are different", "ValueSet.compose.include.concept"));
combined.getChildren().add(sm);
compareConcepts(l, r, sm, null, null);
}
}
}
for (ConceptReferenceComponent r : right.getConcept()) {
if (!matchCR.contains(r)) {
union.getConcept().add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this Concept", "ValueSet.compose.include.concept"), r));
}
}
List<ConceptSetFilterComponent> matchFR = new ArrayList<>();
for (ConceptSetFilterComponent l : left.getFilter()) {
ConceptSetFilterComponent r = findInList(right.getFilter(), l, left.getFilter());
if (r == null) {
union.getFilter().add(l);
combined.getChildren().add(new StructuralMatch<Element>(l, vmI(IssueSeverity.INFORMATION, "Removed this item", "ValueSet.compose.include.filter")));
} else {
matchFR.add(r);
if (l.getProperty().equals(r.getProperty()) && l.getOp().equals(r.getOp())) {
ConceptSetFilterComponent cu = new ConceptSetFilterComponent();
ConceptSetFilterComponent ci = new ConceptSetFilterComponent();
union.getFilter().add(cu);
intersection.getFilter().add(ci);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r);
combined.getChildren().add(sm);
compareFilters(l, r, sm, cu, ci);
} else {
union.getFilter().add(l);
union.getFilter().add(r);
StructuralMatch<Element> sm = new StructuralMatch<Element>(l, r, vmI(IssueSeverity.INFORMATION, "Codes are different", "ValueSet.compose.include.filter"));
combined.getChildren().add(sm);
compareFilters(l, r, sm, null, null);
}
}
}
for (ConceptSetFilterComponent r : right.getFilter()) {
if (!matchFR.contains(r)) {
union.getFilter().add(r);
combined.getChildren().add(new StructuralMatch<Element>(vmI(IssueSeverity.INFORMATION, "Added this item", "ValueSet.compose.include.filter"), r));
}
}
}
private void compareConcepts(ConceptReferenceComponent l, ConceptReferenceComponent r, StructuralMatch<Element> sm, ConceptReferenceComponent cu, ConceptReferenceComponent ci) {
sm.getChildren().add(new StructuralMatch<Element>(l.getCodeElement(), r.getCodeElement(), l.getCode().equals(r.getCode()) ? null : vmI(IssueSeverity.INFORMATION, "Codes do not match", "ValueSet.compose.include.concept")));
if (ci != null) {
ci.setCode(l.getCode());
cu.setCode(l.getCode());
}
if (l.hasDisplay() && r.hasDisplay()) {
sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), r.getDisplayElement(), l.getDisplay().equals(r.getDisplay()) ? null : vmI(IssueSeverity.INFORMATION, "Displays do not match", "ValueSet.compose.include.concept")));
if (ci != null) {
ci.setDisplay(r.getDisplay());
cu.setDisplay(r.getDisplay());
}
} else if (l.hasDisplay()) {
sm.getChildren().add(new StructuralMatch<Element>(l.getDisplayElement(), null, vmI(IssueSeverity.INFORMATION, "Display Removed", "ValueSet.compose.include.concept")));
if (ci != null) {
ci.setDisplay(l.getDisplay());
cu.setDisplay(l.getDisplay());
}
} else if (r.hasDisplay()) {
sm.getChildren().add(new StructuralMatch<Element>(null, r.getDisplayElement(), vmI(IssueSeverity.INFORMATION, "Display added", "ValueSet.compose.include.concept")));
if (ci != null) {
ci.setDisplay(r.getDisplay());
cu.setDisplay(r.getDisplay());
}
} else {
sm.getChildren().add(new StructuralMatch<Element>(null, null, vmI(IssueSeverity.INFORMATION, "No Display", "ValueSet.compose.include.concept")));
}
}
private void compareFilters(ConceptSetFilterComponent l, ConceptSetFilterComponent r, StructuralMatch<Element> sm, ConceptSetFilterComponent cu, ConceptSetFilterComponent ci) {
sm.getChildren().add(new StructuralMatch<Element>(l.getPropertyElement(), r.getPropertyElement(), l.getProperty().equals(r.getProperty()) ? null : vmI(IssueSeverity.INFORMATION, "Properties do not match", "ValueSet.compose.include.concept")));
sm.getChildren().add(new StructuralMatch<Element>(l.getOpElement(), r.getOpElement(), l.getOp().equals(r.getOp()) ? null : vmI(IssueSeverity.INFORMATION, "Filter Operations do not match", "ValueSet.compose.include.concept")));
sm.getChildren().add(new StructuralMatch<Element>(l.getValueElement(), r.getValueElement(), l.getValue().equals(r.getValue()) ? null : vmI(IssueSeverity.INFORMATION, "Values do not match", "ValueSet.compose.include.concept")));
if (ci != null) {
ci.setProperty(l.getProperty());
ci.setOp(l.getOp());
ci.setValue(l.getValue());
cu.setProperty(l.getProperty());
cu.setOp(l.getOp());
cu.setValue(l.getValue());
}
}
private CanonicalType findInList(List<CanonicalType> matches, CanonicalType item, List<CanonicalType> source) {
if (matches.size() == 1 && source.size() == 1) {
return matches.get(0);
}
for (CanonicalType t : matches) {
if (t.getValue().equals(item.getValue())) {
return t;
}
}
return null;
}
private ConceptReferenceComponent findInList(List<ConceptReferenceComponent> matches, ConceptReferenceComponent item, List<ConceptReferenceComponent> source) {
if (matches.size() == 1 && source.size() == 1) {
return matches.get(0);
}
for (ConceptReferenceComponent t : matches) {
if (t.getCode().equals(item.getCode())) {
return t;
}
}
return null;
}
private ConceptSetFilterComponent findInList(List<ConceptSetFilterComponent> matches, ConceptSetFilterComponent item, List<ConceptSetFilterComponent> source) {
if (matches.size() == 1 && source.size() == 1) {
return matches.get(0);
}
for (ConceptSetFilterComponent t : matches) {
if (t.getProperty().equals(item.getProperty()) && t.getOp().equals(item.getOp()) ) {
return t;
}
}
return null;
}
private void compareExpansions(ValueSet left, ValueSet right, ValueSetComparison res) {
ValueSet expL = left.hasExpansion() ? left : expand(left, res, "left", session.getContextLeft());
ValueSet expR = right.hasExpansion() ? right : expand(right, res, "right", session.getContextRight());
if (expL != null && expR != null) {
// ignore the parameters for now
compareConcepts(expL.getExpansion().getContains(), expR.getExpansion().getContains(), res.forceExpansion(), res.getUnion().getExpansion().getContains(), res.getIntersection().getExpansion().getContains(), "ValueSet.expansion.contains", res);
}
}
private ValueSet expand(ValueSet vs, ValueSetComparison res, String name, IWorkerContext ctxt) {
ValueSetExpansionOutcome vse = ctxt.expandVS(vs, true, false);
if (vse.getValueset() != null) {
return vse.getValueset();
} else {
res.getMessages().add(new ValidationMessage(Source.TerminologyEngine, IssueType.EXCEPTION, "ValueSet", "Error Expanding "+name+":"+vse.getError(), IssueSeverity.ERROR));
return null;
}
}
private void compareConcepts(List<ValueSetExpansionContainsComponent> left, List<ValueSetExpansionContainsComponent> right, StructuralMatch<ValueSetExpansionContainsComponent> combined, List<ValueSetExpansionContainsComponent> union, List<ValueSetExpansionContainsComponent> intersection, String path, ValueSetComparison res) {
List<ValueSetExpansionContainsComponent> matchR = new ArrayList<>();
for (ValueSetExpansionContainsComponent l : left) {
ValueSetExpansionContainsComponent r = findInList(right, l);
if (r == null) {
union.add(l);
combined.getChildren().add(new StructuralMatch<ValueSetExpansionContainsComponent>(l, vmI(IssueSeverity.INFORMATION, "Removed from expansion", path)));
} else {
matchR.add(r);
ValueSetExpansionContainsComponent ccU = merge(l, r);
ValueSetExpansionContainsComponent ccI = intersect(l, r);
union.add(ccU);
intersection.add(ccI);
StructuralMatch<ValueSetExpansionContainsComponent> sm = new StructuralMatch<ValueSetExpansionContainsComponent>(l, r);
compareItem(sm.getMessages(), path, l, r, res);
combined.getChildren().add(sm);
compareConcepts(l.getContains(), r.getContains(), sm, ccU.getContains(), ccI.getContains(), path+".where(code = '"+l.getCode()+"').contains", res);
}
}
for (ValueSetExpansionContainsComponent r : right) {
if (!matchR.contains(r)) {
union.add(r);
combined.getChildren().add(new StructuralMatch<ValueSetExpansionContainsComponent>(vmI(IssueSeverity.INFORMATION, "Added to expansion", path), r));
}
}
}
private void compareItem(List<ValidationMessage> msgs, String path, ValueSetExpansionContainsComponent l, ValueSetExpansionContainsComponent r, ValueSetComparison res) {
compareStrings(path, msgs, l.getDisplay(), r.getDisplay(), "display", IssueSeverity.WARNING, res);
}
private void compareStrings(String path, List<ValidationMessage> msgs, String left, String right, String name, IssueSeverity level, ValueSetComparison res) {
if (!Utilities.noString(right)) {
if (Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" added", path));
} else if (!left.equals(right)) {
if (level != IssueSeverity.NULL) {
res.getMessages().add(new ValidationMessage(Source.ProfileComparer, IssueType.INFORMATIONAL, path+".name", "Changed value for "+name+": '"+left+"' vs '"+right+"'", level));
}
msgs.add(vmI(level, name+" changed from left to right", path));
}
} else if (!Utilities.noString(left)) {
msgs.add(vmI(level, "Value for "+name+" removed", path));
}
}
private ValueSetExpansionContainsComponent findInList(List<ValueSetExpansionContainsComponent> list, ValueSetExpansionContainsComponent item) {
for (ValueSetExpansionContainsComponent t : list) {
if (t.getSystem().equals(item.getSystem()) && t.getCode().equals(item.getCode())) {
return t;
}
}
return null;
}
private ValueSetExpansionContainsComponent intersect(ValueSetExpansionContainsComponent l, ValueSetExpansionContainsComponent r) {
ValueSetExpansionContainsComponent res = new ValueSetExpansionContainsComponent();
if (l.hasAbstract() && r.hasAbstract()) {
res.setAbstract(l.getAbstract());
}
if (l.hasCode() && r.hasCode()) {
res.setCode(l.getCode());
}
if (l.hasSystem() && r.hasSystem()) {
res.setSystem(l.getSystem());
}
if (l.hasVersion() && r.hasVersion()) {
res.setVersion(l.getVersion());
}
if (l.hasDisplay() && r.hasDisplay()) {
res.setDisplay(l.getDisplay());
}
return res;
}
private ValueSetExpansionContainsComponent merge(ValueSetExpansionContainsComponent l, ValueSetExpansionContainsComponent r) {
ValueSetExpansionContainsComponent res = new ValueSetExpansionContainsComponent();
if (l.hasAbstract()) {
res.setAbstract(l.getAbstract());
} else if (r.hasAbstract()) {
res.setAbstract(r.getAbstract());
}
if (l.hasCode()) {
res.setCode(l.getCode());
} else if (r.hasCode()) {
res.setCode(r.getCode());
}
if (l.hasSystem()) {
res.setSystem(l.getSystem());
} else if (r.hasSystem()) {
res.setSystem(r.getSystem());
}
if (l.hasVersion()) {
res.setVersion(l.getVersion());
} else if (r.hasVersion()) {
res.setVersion(r.getVersion());
}
if (l.hasDisplay()) {
res.setDisplay(l.getDisplay());
} else if (r.hasDisplay()) {
res.setDisplay(r.getDisplay());
}
return res;
}
@Override
protected String fhirType() {
return "ValueSet";
}
public XhtmlNode renderCompose(ValueSetComparison csc, String id, String prefix) throws FHIRException, IOException {
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "comparison"), false);
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
model.getTitles().add(gen.new Title(null, null, "Item", "The type of item being compared", null, 100));
model.getTitles().add(gen.new Title(null, null, "Property", "The system for the concept", null, 100, 2));
model.getTitles().add(gen.new Title(null, null, "Value", "The display for the concept", null, 200, 2));
model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
for (StructuralMatch<Element> t : csc.getIncludes().getChildren()) {
addComposeRow(gen, model.getRows(), t, "include");
}
for (StructuralMatch<Element> t : csc.getExcludes().getChildren()) {
addComposeRow(gen, model.getRows(), t, "exclude");
}
return gen.generate(model, prefix, 0, null);
}
private void addComposeRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t, String name) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, name, null, null));
if (t.hasLeft() && t.hasRight()) {
ConceptSetComponent csL = (ConceptSetComponent) t.getLeft();
ConceptSetComponent csR = (ConceptSetComponent) t.getRight();
if (csL.hasSystem() && csL.getSystem().equals(csR.getSystem())) {
r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).span(2).center());
} else {
r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
r.getCells().add(gen.new Cell(null, null, csR.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
}
if (csL.hasVersion() && csR.hasVersion()) {
if (csL.getVersion().equals(csR.getVersion())) {
r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).span(2).center());
} else {
r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
}
} else if (csL.hasVersion()) {
r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null));
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
} else if (csR.hasVersion()) {
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null));
} else {
r.getCells().add(missingCell(gen).span(2).center());
}
} else if (t.hasLeft()) {
r.setColor(COLOR_NO_ROW_RIGHT);
ConceptSetComponent cs = (ConceptSetComponent) t.getLeft();
r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null));
r.getCells().add(missingCell(gen));
r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null));
r.getCells().add(missingCell(gen));
} else {
r.setColor(COLOR_NO_ROW_LEFT);
ConceptSetComponent cs = (ConceptSetComponent) t.getRight();
r.getCells().add(missingCell(gen));
r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null));
r.getCells().add(missingCell(gen));
r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null));
}
r.getCells().add(cellForMessages(gen, t.getMessages()));
for (StructuralMatch<Element> c : t.getChildren()) {
if (c.either() instanceof ConceptReferenceComponent) {
addSetConceptRow(gen, r.getSubRows(), c);
} else {
addSetFilterRow(gen, r.getSubRows(), c);
}
}
}
private void addSetConceptRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t) {
Row r = gen.new Row();
rows.add(r);
r.getCells().add(gen.new Cell(null, null, "Concept", null, null));
if (t.hasLeft() && t.hasRight()) {
ConceptReferenceComponent csL = (ConceptReferenceComponent) t.getLeft();
ConceptReferenceComponent csR = (ConceptReferenceComponent) t.getRight();
// we assume both have codes
if (csL.getCode().equals(csR.getCode())) {
r.getCells().add(gen.new Cell(null, null, csL.getCode(), null, null).span(2).center());
} else {
r.getCells().add(gen.new Cell(null, null, csL.getCode(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
r.getCells().add(gen.new Cell(null, null, csR.getCode(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
}
if (csL.hasDisplay() && csR.hasDisplay()) {
if (csL.getDisplay().equals(csR.getDisplay())) {
r.getCells().add(gen.new Cell(null, null, csL.getDisplay(), null, null).span(2).center());
} else {
r.getCells().add(gen.new Cell(null, null, csL.getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
r.getCells().add(gen.new Cell(null, null, csR.getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
}
} else if (csL.hasDisplay()) {
r.getCells().add(gen.new Cell(null, null, csL.getDisplay(), null, null));
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
} else if (csR.hasDisplay()) {
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
r.getCells().add(gen.new Cell(null, null, csR.getDisplay(), null, null));
} else {
r.getCells().add(missingCell(gen).span(2).center());
}
} else if (t.hasLeft()) {
r.setColor(COLOR_NO_ROW_RIGHT);
ConceptReferenceComponent cs = (ConceptReferenceComponent) t.getLeft();
r.getCells().add(gen.new Cell(null, null, cs.getCode(), null, null));
r.getCells().add(missingCell(gen));
r.getCells().add(gen.new Cell(null, null, cs.hasDisplay() ? "Version: "+cs.getDisplay() : "", null, null));
r.getCells().add(missingCell(gen));
} else {
r.setColor(COLOR_NO_ROW_LEFT);
ConceptReferenceComponent cs = (ConceptReferenceComponent) t.getRight();
r.getCells().add(missingCell(gen));
r.getCells().add(gen.new Cell(null, null, cs.getCode(), null, null));
r.getCells().add(missingCell(gen));
r.getCells().add(gen.new Cell(null, null, cs.hasDisplay() ? "Version: "+cs.getDisplay() : "", null, null));
}
r.getCells().add(cellForMessages(gen, t.getMessages()));
}
private void addSetFilterRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<Element> t) {
// Row r = gen.new Row();
// rows.add(r);
// r.getCells().add(gen.new Cell(null, null, "Filter", null, null));
// if (t.hasLeft() && t.hasRight()) {
// ConceptSetComponent csL = (ConceptSetComponent) t.getLeft();
// ConceptSetComponent csR = (ConceptSetComponent) t.getRight();
// // we assume both have systems
// if (csL.getSystem().equals(csR.getSystem())) {
// r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).span(2).center());
// } else {
// r.getCells().add(gen.new Cell(null, null, csL.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
// r.getCells().add(gen.new Cell(null, null, csR.getSystem(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
// }
//
// if (csL.hasVersion() && csR.hasVersion()) {
// if (csL.getVersion().equals(csR.getVersion())) {
// r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).span(2).center());
// } else {
// r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
// r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
// }
// } else if (csL.hasVersion()) {
// r.getCells().add(gen.new Cell(null, null, csL.getVersion(), null, null));
// r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
// } else if (csR.hasVersion()) {
// r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
// r.getCells().add(gen.new Cell(null, null, csR.getVersion(), null, null));
// } else {
// r.getCells().add(missingCell(gen).span(2).center());
// }
//
// } else if (t.hasLeft()) {
// r.setColor(COLOR_NO_ROW_RIGHT);
// ConceptSetComponent cs = (ConceptSetComponent) t.getLeft();
// r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null));
// r.getCells().add(missingCell(gen));
// r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null));
// r.getCells().add(missingCell(gen));
// } else {
// r.setColor(COLOR_NO_ROW_LEFT);
// ConceptSetComponent cs = (ConceptSetComponent) t.getRight();
// r.getCells().add(missingCell(gen));
// r.getCells().add(gen.new Cell(null, null, cs.getSystem(), null, null));
// r.getCells().add(missingCell(gen));
// r.getCells().add(gen.new Cell(null, null, cs.hasVersion() ? "Version: "+cs.getVersion() : "", null, null));
// }
// r.getCells().add(gen.new Cell(null, null, t.getError(), null, null));
}
public XhtmlNode renderExpansion(ValueSetComparison csc, String id, String prefix) throws IOException {
if (csc.getExpansion() == null) {
XhtmlNode p = new XhtmlNode(NodeType.Element, "p");
p.tx("Unable to generate expansion - see errors");
return p;
}
if (csc.getExpansion().getChildren().isEmpty()) {
XhtmlNode p = new XhtmlNode(NodeType.Element, "p");
p.tx("Expansion is empty");
return p;
}
// columns: code(+system), version, display , abstract, inactive,
boolean hasSystem = csc.getExpansion().getChildren().isEmpty() ? false : getSystemVaries(csc.getExpansion(), csc.getExpansion().getChildren().get(0).either().getSystem());
boolean hasVersion = findVersion(csc.getExpansion());
boolean hasAbstract = findAbstract(csc.getExpansion());
boolean hasInactive = findInactive(csc.getExpansion());
HierarchicalTableGenerator gen = new HierarchicalTableGenerator(Utilities.path("[tmp]", "comparison"), false);
TableModel model = gen.new TableModel(id, true);
model.setAlternating(true);
if (hasSystem) {
model.getTitles().add(gen.new Title(null, null, "System", "The code for the concept", null, 100));
}
model.getTitles().add(gen.new Title(null, null, "Code", "The system for the concept", null, 100));
model.getTitles().add(gen.new Title(null, null, "Display", "The display for the concept", null, 200, 2));
// if (hasVersion) {
// model.getTitles().add(gen.new Title(null, null, "Version", "The version for the concept", null, 200, 2));
// }
// if (hasAbstract) {
// model.getTitles().add(gen.new Title(null, null, "Abstract", "The abstract flag for the concept", null, 200, 2));
// }
// if (hasInactive) {
// model.getTitles().add(gen.new Title(null, null, "Inactive", "The inactive flag for the concept", null, 200, 2));
// }
model.getTitles().add(gen.new Title(null, null, "Comments", "Additional information about the comparison", null, 200));
for (StructuralMatch<ValueSetExpansionContainsComponent> t : csc.getExpansion().getChildren()) {
addExpansionRow(gen, model.getRows(), t, hasSystem, hasVersion, hasAbstract, hasInactive);
}
return gen.generate(model, prefix, 0, null);
}
private void addExpansionRow(HierarchicalTableGenerator gen, List<Row> rows, StructuralMatch<ValueSetExpansionContainsComponent> t, boolean hasSystem, boolean hasVersion, boolean hasAbstract, boolean hasInactive) {
Row r = gen.new Row();
rows.add(r);
if (hasSystem) {
r.getCells().add(gen.new Cell(null, null, t.either().getSystem(), null, null));
}
r.getCells().add(gen.new Cell(null, null, t.either().getCode(), null, null));
if (t.hasLeft() && t.hasRight()) {
if (t.getLeft().hasDisplay() && t.getRight().hasDisplay()) {
if (t.getLeft().getDisplay().equals(t.getRight().getDisplay())) {
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).span(2).center());
} else {
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null).setStyle("background-color: "+COLOR_DIFFERENT));
}
} else if (t.getLeft().hasDisplay()) {
r.getCells().add(gen.new Cell(null, null, t.getLeft().getDisplay(), null, null));
r.getCells().add(missingCell(gen, COLOR_NO_CELL_RIGHT));
} else if (t.getRight().hasDisplay()) {
r.getCells().add(missingCell(gen, COLOR_NO_CELL_LEFT));
r.getCells().add(gen.new Cell(null, null, t.getRight().getDisplay(), null, null));
} else {
r.getCells().add(missingCell(gen).span(2).center());
}
} else if (t.hasLeft()) {
r.setColor(COLOR_NO_ROW_RIGHT);
r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
r.getCells().add(missingCell(gen));
} else {
r.setColor(COLOR_NO_ROW_LEFT);
r.getCells().add(missingCell(gen));
r.getCells().add(gen.new Cell(null, null, t.either().getDisplay(), null, null));
}
r.getCells().add(cellForMessages(gen, t.getMessages()));
for (StructuralMatch<ValueSetExpansionContainsComponent> c : t.getChildren()) {
addExpansionRow(gen, r.getSubRows(), c, hasSystem, hasVersion, hasAbstract, hasInactive);
}
}
private boolean getSystemVaries(StructuralMatch<ValueSetExpansionContainsComponent> list, String system) {
for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) {
if (t.hasLeft() && !system.equals(t.getLeft().getSystem())) {
return true;
}
if (t.hasRight() && !system.equals(t.getRight().getSystem())) {
return true;
}
if (getSystemVaries(t, system)) {
return true;
}
}
return false;
}
private boolean findInactive(StructuralMatch<ValueSetExpansionContainsComponent> list) {
for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) {
if (t.hasLeft() && t.getLeft().getInactive()) {
return true;
}
if (t.hasRight() && t.getRight().getInactive()) {
return true;
}
if (findInactive(t)) {
return true;
}
}
return false;
}
private boolean findAbstract(StructuralMatch<ValueSetExpansionContainsComponent> list) {
for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) {
if (t.hasLeft() && t.getLeft().getAbstract()) {
return true;
}
if (t.hasRight() && t.getRight().getAbstract()) {
return true;
}
if (findAbstract(t)) {
return true;
}
}
return false;
}
private boolean findVersion(StructuralMatch<ValueSetExpansionContainsComponent> list) {
for (StructuralMatch<ValueSetExpansionContainsComponent> t : list.getChildren()) {
if (t.hasLeft() && t.getLeft().hasVersion()) {
return true;
}
if (t.hasRight() && t.getRight().hasVersion()) {
return true;
}
if (findVersion(t)) {
return true;
}
}
return false;
}
}

View File

@ -0,0 +1,6 @@
package org.hl7.fhir.r4b.conformance;
public class CapabilityStatementUtilities {
}

View File

@ -0,0 +1,81 @@
package org.hl7.fhir.r4b.conformance;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.utilities.Utilities;
public class ConstraintJavaGenerator {
private IWorkerContext context; // for doing expansions
private String version; // for getting includes correct
private String folder; //dest dir where the profile will be generated into
private String packageName;
public ConstraintJavaGenerator(IWorkerContext context, String version, String folder, String packageName) {
super();
this.context = context;
this.version = version;
this.folder = folder;
this.packageName = packageName;
}
public String generate(StructureDefinition sd) throws FHIRException, IOException {
String name = sd.hasName() ? Utilities.titleize(sd.getName().replace(".", "").replace("-", "").replace("\"", "")).replace(" ", "") : "";
if (!Utilities.nmtokenize(name).equals(name)) {
System.out.println("Cannot generate Java code for profile "+sd.getUrl()+" because the name \""+name+"\" is not a valid Java class name");
return null;
}
File destFile = new File(Utilities.path(folder, name+".java"));
OutputStreamWriter dest = new OutputStreamWriter(new FileOutputStream(destFile), "UTF-8");
dest.write("package "+packageName+";\r\n");
dest.write("\r\n");
dest.write("import org.hl7.fhir.r4b.model.ProfilingWrapper;\r\n");
dest.write("\r\n");
dest.write("public class "+name+" {\r\n");
dest.write("\r\n");
dest.write("}\r\n");
dest.flush();
dest.close();
return destFile.getAbsolutePath();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,781 @@
package org.hl7.fhir.r4b.conformance;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.model.Constants;
import org.hl7.fhir.r4b.model.DataType;
import org.hl7.fhir.r4b.model.DomainResource;
import org.hl7.fhir.r4b.model.ElementDefinition;
import org.hl7.fhir.r4b.model.Enumerations;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander;
import org.stringtemplate.v4.ST;
public class ShExGenerator {
public enum HTMLLinkPolicy {
NONE, EXTERNAL, INTERNAL
}
public boolean doDatatypes = true; // add data types
public boolean withComments = true; // include comments
public boolean completeModel = false; // doing complete build (fhir.shex)
private static String SHEX_TEMPLATE = "$header$\n\n" +
"$shapeDefinitions$";
// A header is a list of prefixes, a base declaration and a start node
private static String FHIR = "http://hl7.org/fhir/";
private static String FHIR_VS = FHIR + "ValueSet/";
private static String HEADER_TEMPLATE =
"PREFIX fhir: <$fhir$> \n" +
"PREFIX fhirvs: <$fhirvs$>\n" +
"PREFIX xsd: <http://www.w3.org/2001/XMLSchema#> \n" +
"BASE <http://hl7.org/fhir/shape/>\n$start$";
// Start template for single (open) entry
private static String START_TEMPLATE = "\n\nstart=@<$id$> AND {fhir:nodeRole [fhir:treeRoot]}\n";
// Start template for complete (closed) model
private static String ALL_START_TEMPLATE = "\n\nstart=@<All>\n";
private static String ALL_TEMPLATE = "\n<All> $all_entries$\n";
private static String ALL_ENTRY_TEMPLATE = "(NOT { fhir:nodeRole [fhir:treeRoot] ; a [fhir:$id$] } OR @<$id$>)";
// Shape Definition
// the shape name
// an optional resource declaration (type + treeRoot)
// the list of element declarations
// an optional index element (for appearances inside ordered lists)
private static String SHAPE_DEFINITION_TEMPLATE =
"$comment$\n<$id$> CLOSED {\n $resourceDecl$" +
"\n $elements$" +
"\n fhir:index xsd:integer? # Relative position in a list\n}\n";
// Resource Definition
// an open shape of type Resource. Used when completeModel = false.
private static String RESOURCE_SHAPE_TEMPLATE =
"$comment$\n<Resource> {a .+;" +
"\n $elements$" +
"\n fhir:index xsd:integer?" +
"\n}\n";
// If we have knowledge of all of the possible resources available to us (completeModel = true), we can build
// a model of all possible resources.
private static String COMPLETE_RESOURCE_TEMPLATE =
"<Resource> @<$resources$>" +
"\n\n";
// Resource Declaration
// a type node
// an optional treeRoot declaration (identifies the entry point)
private static String RESOURCE_DECL_TEMPLATE = "\na [fhir:$id$];$root$";
// Root Declaration.
private static String ROOT_TEMPLATE = "\nfhir:nodeRole [fhir:treeRoot]?;";
// Element
// a predicate, type and cardinality triple
private static String ELEMENT_TEMPLATE = "$id$$defn$$card$$comment$";
private static int COMMENT_COL = 40;
private static int MAX_CHARS = 35;
private static int MIN_COMMENT_SEP = 2;
// Inner Shape Definition
private static String INNER_SHAPE_TEMPLATE = "($comment$\n $defn$\n)$card$";
// Simple Element
// a shape reference
private static String SIMPLE_ELEMENT_DEFN_TEMPLATE = "@<$typ$>$vsdef$";
// Value Set Element
private static String VALUESET_DEFN_TEMPLATE = " AND\n\t{fhir:value @$vsn$}";
// Fixed Value Template
private static String FIXED_VALUE_TEMPLATE = " AND\n\t{fhir:value [\"$val$\"]}";
// A primitive element definition
// the actual type reference
private static String PRIMITIVE_ELEMENT_DEFN_TEMPLATE = "$typ$$facets$";
// Facets
private static String MINVALUE_TEMPLATE = " MININCLUSIVE $val$";
private static String MAXVALUE_TEMPLATE = " MAXINCLUSIVE $val$";
private static String MAXLENGTH_TEMPLATE = " MAXLENGTH $val$";
private static String PATTERN_TEMPLATE = " PATTERN \"$val$\"";
// A choice of alternative shape definitions
// rendered as an inner anonymous shape
private static String ALTERNATIVE_SHAPES_TEMPLATE = "fhir:$id$$comment$\n( $altEntries$\n)$card$";
// A typed reference definition
private static String REFERENCE_DEFN_TEMPLATE = "@<$ref$Reference>";
// What we emit for an xhtml
private static String XHTML_TYPE_TEMPLATE = "xsd:string";
// Additional type for Coding
private static String CONCEPT_REFERENCE_TEMPLATE = "a NONLITERAL?;";
// Additional type for CodedConcept
private static String CONCEPT_REFERENCES_TEMPLATE = "a NONLITERAL*;";
// Untyped resource has the extra link entry
private static String RESOURCE_LINK_TEMPLATE = "fhir:link IRI?;";
// Extension template
// No longer used -- we emit the actual definition
// private static String EXTENSION_TEMPLATE = "<Extension> {fhir:extension @<Extension>*;" +
// "\n fhir:index xsd:integer?" +
// "\n}\n";
// A typed reference -- a fhir:uri with an optional type and the possibility of a resolvable shape
private static String TYPED_REFERENCE_TEMPLATE = "\n<$refType$Reference> CLOSED {" +
"\n fhir:Element.id @<id>?;" +
"\n fhir:Element.extension @<Extension>*;" +
"\n fhir:link @<$refType$> OR CLOSED {a [fhir:$refType$]}?;" +
"\n fhir:Reference.reference @<string>?;" +
"\n fhir:Reference.display @<string>?;" +
"\n fhir:index xsd:integer?" +
"\n}";
private static String TARGET_REFERENCE_TEMPLATE = "\n<$refType$> {" +
"\n a [fhir:$refType$];" +
"\n fhir:nodeRole [fhir:treeRoot]?" +
"\n}";
// A value set definition
private static String VALUE_SET_DEFINITION = "# $comment$\n$vsuri$$val_list$\n";
/**
* this makes internal metadata services available to the generator - retrieving structure definitions, and value set expansion etc
*/
private IWorkerContext context;
private ProfileUtilities profileUtilities;
/**
* innerTypes -- inner complex types. Currently flattened in ShEx (doesn't have to be, btw)
* emittedInnerTypes -- set of inner types that have been generated
* datatypes, emittedDatatypes -- types used in the definition, types that have been generated
* references -- Reference types (Patient, Specimen, etc)
* uniq_structures -- set of structures on the to be generated list...
* doDataTypes -- whether or not to emit the data types.
*/
private HashSet<Pair<StructureDefinition, ElementDefinition>> innerTypes, emittedInnerTypes;
private HashSet<String> datatypes, emittedDatatypes;
private HashSet<String> references;
private LinkedList<StructureDefinition> uniq_structures;
private HashSet<String> uniq_structure_urls;
private HashSet<ValueSet> required_value_sets;
private HashSet<String> known_resources; // Used when generating a full definition
public ShExGenerator(IWorkerContext context) {
super();
this.context = context;
profileUtilities = new ProfileUtilities(context, null, null);
innerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>();
emittedInnerTypes = new HashSet<Pair<StructureDefinition, ElementDefinition>>();
datatypes = new HashSet<String>();
emittedDatatypes = new HashSet<String>();
references = new HashSet<String>();
required_value_sets = new HashSet<ValueSet>();
known_resources = new HashSet<String>();
}
public String generate(HTMLLinkPolicy links, StructureDefinition structure) {
List<StructureDefinition> list = new ArrayList<StructureDefinition>();
list.add(structure);
innerTypes.clear();
emittedInnerTypes.clear();
datatypes.clear();
emittedDatatypes.clear();
references.clear();
required_value_sets.clear();
known_resources.clear();
return generate(links, list);
}
public class SortById implements Comparator<StructureDefinition> {
@Override
public int compare(StructureDefinition arg0, StructureDefinition arg1) {
return arg0.getId().compareTo(arg1.getId());
}
}
private ST tmplt(String template) {
return new ST(template, '$', '$');
}
/**
* this is called externally to generate a set of structures to a single ShEx file
* generally, it will be called with a single structure, or a long list of structures (all of them)
*
* @param links HTML link rendering policy
* @param structures list of structure definitions to render
* @return ShEx definition of structures
*/
public String generate(HTMLLinkPolicy links, List<StructureDefinition> structures) {
ST shex_def = tmplt(SHEX_TEMPLATE);
String start_cmd;
if(completeModel || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE))
// || structures.get(0).getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE))
start_cmd = completeModel? tmplt(ALL_START_TEMPLATE).render() :
tmplt(START_TEMPLATE).add("id", structures.get(0).getId()).render();
else
start_cmd = "";
shex_def.add("header", tmplt(HEADER_TEMPLATE).
add("start", start_cmd).
add("fhir", FHIR).
add("fhirvs", FHIR_VS).render());
Collections.sort(structures, new SortById());
StringBuilder shapeDefinitions = new StringBuilder();
// For unknown reasons, the list of structures carries duplicates. We remove them
// Also, it is possible for the same sd to have multiple hashes...
uniq_structures = new LinkedList<StructureDefinition>();
uniq_structure_urls = new HashSet<String>();
for (StructureDefinition sd : structures) {
if (!uniq_structure_urls.contains(sd.getUrl())) {
uniq_structures.add(sd);
uniq_structure_urls.add(sd.getUrl());
}
}
for (StructureDefinition sd : uniq_structures) {
shapeDefinitions.append(genShapeDefinition(sd, true));
}
shapeDefinitions.append(emitInnerTypes());
if(doDatatypes) {
shapeDefinitions.append("\n#---------------------- Data Types -------------------\n");
while (emittedDatatypes.size() < datatypes.size() ||
emittedInnerTypes.size() < innerTypes.size()) {
shapeDefinitions.append(emitDataTypes());
shapeDefinitions.append(emitInnerTypes());
}
}
shapeDefinitions.append("\n#---------------------- Reference Types -------------------\n");
for(String r: references) {
shapeDefinitions.append("\n").append(tmplt(TYPED_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
if (!"Resource".equals(r) && !known_resources.contains(r))
shapeDefinitions.append("\n").append(tmplt(TARGET_REFERENCE_TEMPLATE).add("refType", r).render()).append("\n");
}
shex_def.add("shapeDefinitions", shapeDefinitions);
if(completeModel && known_resources.size() > 0) {
shapeDefinitions.append("\n").append(tmplt(COMPLETE_RESOURCE_TEMPLATE)
.add("resources", StringUtils.join(known_resources, "> OR\n\t@<")).render());
List<String> all_entries = new ArrayList<String>();
for(String kr: known_resources)
all_entries.add(tmplt(ALL_ENTRY_TEMPLATE).add("id", kr).render());
shapeDefinitions.append("\n").append(tmplt(ALL_TEMPLATE)
.add("all_entries", StringUtils.join(all_entries, " OR\n\t")).render());
}
shapeDefinitions.append("\n#---------------------- Value Sets ------------------------\n");
for(ValueSet vs: required_value_sets)
shapeDefinitions.append("\n").append(genValueSet(vs));
return shex_def.render();
}
/**
* Emit a ShEx definition for the supplied StructureDefinition
* @param sd Structure definition to emit
* @param top_level True means outermost type, False means recursively called
* @return ShEx definition
*/
private String genShapeDefinition(StructureDefinition sd, boolean top_level) {
// xhtml is treated as an atom
if("xhtml".equals(sd.getName()) || (completeModel && "Resource".equals(sd.getName())))
return "";
ST shape_defn;
// Resources are either incomplete items or consist of everything that is defined as a resource (completeModel)
if("Resource".equals(sd.getName())) {
shape_defn = tmplt(RESOURCE_SHAPE_TEMPLATE);
known_resources.add(sd.getName());
} else {
shape_defn = tmplt(SHAPE_DEFINITION_TEMPLATE).add("id", sd.getId());
if (sd.getKind().equals(StructureDefinition.StructureDefinitionKind.RESOURCE)) {
// || sd.getKind().equals(StructureDefinition.StructureDefinitionKind.COMPLEXTYPE)) {
known_resources.add(sd.getName());
ST resource_decl = tmplt(RESOURCE_DECL_TEMPLATE).
add("id", sd.getId()).
add("root", tmplt(ROOT_TEMPLATE));
// add("root", top_level ? tmplt(ROOT_TEMPLATE) : "");
shape_defn.add("resourceDecl", resource_decl.render());
} else {
shape_defn.add("resourceDecl", "");
}
}
// Generate the defining elements
List<String> elements = new ArrayList<String>();
// Add the additional entries for special types
String sdn = sd.getName();
if (sdn.equals("Coding"))
elements.add(tmplt(CONCEPT_REFERENCE_TEMPLATE).render());
else if (sdn.equals("CodeableConcept"))
elements.add(tmplt(CONCEPT_REFERENCES_TEMPLATE).render());
else if (sdn.equals("Reference"))
elements.add(tmplt(RESOURCE_LINK_TEMPLATE).render());
// else if (sdn.equals("Extension"))
// return tmplt(EXTENSION_TEMPLATE).render();
String root_comment = null;
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if(!ed.getPath().contains("."))
root_comment = ed.getShort();
else if (StringUtils.countMatches(ed.getPath(), ".") == 1 && !"0".equals(ed.getMax())) {
elements.add(genElementDefinition(sd, ed));
}
}
shape_defn.add("elements", StringUtils.join(elements, "\n"));
shape_defn.add("comment", root_comment == null? " " : "# " + root_comment);
return shape_defn.render();
}
/**
* Generate a flattened definition for the inner types
* @return stringified inner type definitions
*/
private String emitInnerTypes() {
StringBuilder itDefs = new StringBuilder();
while(emittedInnerTypes.size() < innerTypes.size()) {
for (Pair<StructureDefinition, ElementDefinition> it : new HashSet<Pair<StructureDefinition, ElementDefinition>>(innerTypes)) {
if (!emittedInnerTypes.contains(it)) {
itDefs.append("\n").append(genInnerTypeDef(it.getLeft(), it.getRight()));
emittedInnerTypes.add(it);
}
}
}
return itDefs.toString();
}
/**
* Generate a shape definition for the current set of datatypes
* @return stringified data type definitions
*/
private String emitDataTypes() {
StringBuilder dtDefs = new StringBuilder();
while (emittedDatatypes.size() < datatypes.size()) {
for (String dt : new HashSet<String>(datatypes)) {
if (!emittedDatatypes.contains(dt)) {
StructureDefinition sd = context.fetchResource(StructureDefinition.class,
ProfileUtilities.sdNs(dt, null));
// TODO: Figure out why the line below doesn't work
// if (sd != null && !uniq_structures.contains(sd))
if(sd != null && !uniq_structure_urls.contains(sd.getUrl()))
dtDefs.append("\n").append(genShapeDefinition(sd, false));
emittedDatatypes.add(dt);
}
}
}
return dtDefs.toString();
}
private ArrayList<String> split_text(String text, int max_col) {
int pos = 0;
ArrayList<String> rval = new ArrayList<String>();
if (text.length() <= max_col) {
rval.add(text);
} else {
String[] words = text.split(" ");
int word_idx = 0;
while(word_idx < words.length) {
StringBuilder accum = new StringBuilder();
while (word_idx < words.length && accum.length() + words[word_idx].length() < max_col)
accum.append(words[word_idx++] + " ");
if (accum.length() == 0) {
accum.append(words[word_idx].substring(0, max_col - 3) + "-");
words[word_idx] = words[word_idx].substring(max_col - 3);
}
rval.add(accum.toString());
accum = new StringBuilder();
}
}
return rval;
}
private void addComment(ST tmplt, ElementDefinition ed) {
if(withComments && ed.hasShort() && !ed.getId().startsWith("Extension.")) {
int nspaces;
char[] sep;
nspaces = Integer.max(COMMENT_COL - tmplt.add("comment", "#").render().indexOf('#'), MIN_COMMENT_SEP);
tmplt.remove("comment");
sep = new char[nspaces];
Arrays.fill(sep, ' ');
ArrayList<String> comment_lines = split_text(ed.getShort().replace("\n", " "), MAX_CHARS);
StringBuilder comment = new StringBuilder("# ");
char[] indent = new char[COMMENT_COL];
Arrays.fill(indent, ' ');
for(int i = 0; i < comment_lines.size();) {
comment.append(comment_lines.get(i++));
if(i < comment_lines.size())
comment.append("\n" + new String(indent) + "# ");
}
tmplt.add("comment", new String(sep) + comment.toString());
} else {
tmplt.add("comment", " ");
}
}
/**
* Generate a ShEx element definition
* @param sd Containing structure definition
* @param ed Containing element definition
* @return ShEx definition
*/
private String genElementDefinition(StructureDefinition sd, ElementDefinition ed) {
String id = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();
String shortId = id.substring(id.lastIndexOf(".") + 1);
String defn;
ST element_def;
String card = ("*".equals(ed.getMax()) ? (ed.getMin() == 0 ? "*" : "+") : (ed.getMin() == 0 ? "?" : "")) + ";";
if(id.endsWith("[x]")) {
element_def = ed.getType().size() > 1? tmplt(INNER_SHAPE_TEMPLATE) : tmplt(ELEMENT_TEMPLATE);
element_def.add("id", "");
} else {
element_def = tmplt(ELEMENT_TEMPLATE);
element_def.add("id", "fhir:" + (id.charAt(0) == id.toLowerCase().charAt(0)? shortId : id) + " ");
}
List<ElementDefinition> children = profileUtilities.getChildList(sd, ed);
if (children.size() > 0) {
innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed));
defn = simpleElement(sd, ed, id);
} else if(id.endsWith("[x]")) {
defn = genChoiceTypes(sd, ed, id);
}
else if (ed.getType().size() == 1) {
// Single entry
defn = genTypeRef(sd, ed, id, ed.getType().get(0));
} else if (ed.getContentReference() != null) {
// Reference to another element
String ref = ed.getContentReference();
if(!ref.startsWith("#"))
throw new AssertionError("Not equipped to deal with absolute path references: " + ref);
String refPath = null;
for(ElementDefinition ed1: sd.getSnapshot().getElement()) {
if(ed1.getId() != null && ed1.getId().equals(ref.substring(1))) {
refPath = ed1.getPath();
break;
}
}
if(refPath == null)
throw new AssertionError("Reference path not found: " + ref);
// String typ = id.substring(0, id.indexOf(".") + 1) + ed.getContentReference().substring(1);
defn = simpleElement(sd, ed, refPath);
} else if(id.endsWith("[x]")) {
defn = genChoiceTypes(sd, ed, id);
} else {
// TODO: Refactoring required here
element_def = genAlternativeTypes(ed, id, shortId);
element_def.add("id", id.charAt(0) == id.toLowerCase().charAt(0)? shortId : id);
element_def.add("card", card);
addComment(element_def, ed);
return element_def.render();
}
element_def.add("defn", defn);
element_def.add("card", card);
addComment(element_def, ed);
return element_def.render();
}
/**
* Generate a type reference and optional value set definition
* @param sd Containing StructureDefinition
* @param ed Element being defined
* @param typ Element type
* @return Type definition
*/
private String simpleElement(StructureDefinition sd, ElementDefinition ed, String typ) {
String addldef = "";
ElementDefinition.ElementDefinitionBindingComponent binding = ed.getBinding();
if(binding.hasStrength() && binding.getStrength() == Enumerations.BindingStrength.REQUIRED && "code".equals(typ)) {
ValueSet vs = resolveBindingReference(sd, binding.getValueSet());
if (vs != null) {
addldef = tmplt(VALUESET_DEFN_TEMPLATE).add("vsn", vsprefix(vs.getUrl())).render();
required_value_sets.add(vs);
}
}
// TODO: check whether value sets and fixed are mutually exclusive
if(ed.hasFixed()) {
addldef = tmplt(FIXED_VALUE_TEMPLATE).add("val", ed.getFixed().primitiveValue()).render();
}
return tmplt(SIMPLE_ELEMENT_DEFN_TEMPLATE).add("typ", typ).add("vsdef", addldef).render();
}
private String vsprefix(String uri) {
if(uri.startsWith(FHIR_VS))
return "fhirvs:" + uri.replace(FHIR_VS, "");
return "<" + uri + ">";
}
/**
* Generate a type reference
* @param sd Containing structure definition
* @param ed Containing element definition
* @param id Element id
* @param typ Element type
* @return Type reference string
*/
private String genTypeRef(StructureDefinition sd, ElementDefinition ed, String id, ElementDefinition.TypeRefComponent typ) {
if(typ.hasProfile()) {
if(typ.getWorkingCode().equals("Reference"))
return genReference("", typ);
else if(profileUtilities.getChildList(sd, ed).size() > 0) {
// inline anonymous type - give it a name and factor it out
innerTypes.add(new ImmutablePair<StructureDefinition, ElementDefinition>(sd, ed));
return simpleElement(sd, ed, id);
}
else {
String ref = getTypeName(typ);
datatypes.add(ref);
return simpleElement(sd, ed, ref);
}
} else if (typ.getWorkingCode().startsWith(Constants.NS_SYSTEM_TYPE)) {
String xt = typ.getWorkingCode();
// TODO: Remove the next line when the type of token gets switched to string
// TODO: Add a rdf-type entry for valueInteger to xsd:integer (instead of int)
ST td_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE).add("typ",
xt.replace("xsd:token", "xsd:string").replace("xsd:int", "xsd:integer"));
StringBuilder facets = new StringBuilder();
if(ed.hasMinValue()) {
DataType mv = ed.getMinValue();
facets.append(tmplt(MINVALUE_TEMPLATE).add("val", mv.primitiveValue()).render());
}
if(ed.hasMaxValue()) {
DataType mv = ed.getMaxValue();
facets.append(tmplt(MAXVALUE_TEMPLATE).add("val", mv.primitiveValue()).render());
}
if(ed.hasMaxLength()) {
int ml = ed.getMaxLength();
facets.append(tmplt(MAXLENGTH_TEMPLATE).add("val", ml).render());
}
if(ed.hasPattern()) {
DataType pat = ed.getPattern();
facets.append(tmplt(PATTERN_TEMPLATE).add("val",pat.primitiveValue()).render());
}
td_entry.add("facets", facets.toString());
return td_entry.render();
} else if (typ.getWorkingCode() == null) {
ST primitive_entry = tmplt(PRIMITIVE_ELEMENT_DEFN_TEMPLATE);
primitive_entry.add("typ", "xsd:string");
return primitive_entry.render();
} else if(typ.getWorkingCode().equals("xhtml")) {
return tmplt(XHTML_TYPE_TEMPLATE).render();
} else {
datatypes.add(typ.getWorkingCode());
return simpleElement(sd, ed, typ.getWorkingCode());
}
}
/**
* Generate a set of alternative shapes
* @param ed Containing element definition
* @param id Element definition identifier
* @param shortId id to use in the actual definition
* @return ShEx list of alternative anonymous shapes separated by "OR"
*/
private ST genAlternativeTypes(ElementDefinition ed, String id, String shortId) {
ST shex_alt = tmplt(ALTERNATIVE_SHAPES_TEMPLATE);
List<String> altEntries = new ArrayList<String>();
for(ElementDefinition.TypeRefComponent typ : ed.getType()) {
altEntries.add(genAltEntry(id, typ));
}
shex_alt.add("altEntries", StringUtils.join(altEntries, " OR\n "));
return shex_alt;
}
/**
* Generate an alternative shape for a reference
* @param id reference name
* @param typ shape type
* @return ShEx equivalent
*/
private String genAltEntry(String id, ElementDefinition.TypeRefComponent typ) {
if(!typ.getWorkingCode().equals("Reference"))
throw new AssertionError("We do not handle " + typ.getWorkingCode() + " alternatives");
return genReference(id, typ);
}
/**
* Generate a list of type choices for a "name[x]" style id
* @param sd Structure containing ed
* @param ed element definition
* @param id choice identifier
* @return ShEx fragment for the set of choices
*/
private String genChoiceTypes(StructureDefinition sd, ElementDefinition ed, String id) {
List<String> choiceEntries = new ArrayList<String>();
String base = id.replace("[x]", "");
for(ElementDefinition.TypeRefComponent typ : ed.getType())
choiceEntries.add(genChoiceEntry(sd, ed, id, base, typ));
return StringUtils.join(choiceEntries, " |\n");
}
/**
* Generate an entry in a choice list
* @param base base identifier
* @param typ type/discriminant
* @return ShEx fragment for choice entry
*/
private String genChoiceEntry(StructureDefinition sd, ElementDefinition ed, String id, String base, ElementDefinition.TypeRefComponent typ) {
ST shex_choice_entry = tmplt(ELEMENT_TEMPLATE);
String ext = typ.getWorkingCode();
shex_choice_entry.add("id", "fhir:" + base+Character.toUpperCase(ext.charAt(0)) + ext.substring(1) + " ");
shex_choice_entry.add("card", "");
shex_choice_entry.add("defn", genTypeRef(sd, ed, id, typ));
shex_choice_entry.add("comment", " ");
return shex_choice_entry.render();
}
/**
* Generate a definition for a referenced element
* @param sd Containing structure definition
* @param ed Inner element
* @return ShEx representation of element reference
*/
private String genInnerTypeDef(StructureDefinition sd, ElementDefinition ed) {
String path = ed.hasBase() ? ed.getBase().getPath() : ed.getPath();;
ST element_reference = tmplt(SHAPE_DEFINITION_TEMPLATE);
element_reference.add("resourceDecl", ""); // Not a resource
element_reference.add("id", path);
String comment = ed.getShort();
element_reference.add("comment", comment == null? " " : "# " + comment);
List<String> elements = new ArrayList<String>();
for (ElementDefinition child: profileUtilities.getChildList(sd, path, null))
elements.add(genElementDefinition(sd, child));
element_reference.add("elements", StringUtils.join(elements, "\n"));
return element_reference.render();
}
/**
* Generate a reference to a resource
* @param id attribute identifier
* @param typ possible reference types
* @return string that represents the result
*/
private String genReference(String id, ElementDefinition.TypeRefComponent typ) {
ST shex_ref = tmplt(REFERENCE_DEFN_TEMPLATE);
String ref = getTypeName(typ);
shex_ref.add("id", id);
shex_ref.add("ref", ref);
references.add(ref);
return shex_ref.render();
}
/**
* Return the type name for typ
* @param typ type to get name for
* @return name
*/
private String getTypeName(ElementDefinition.TypeRefComponent typ) {
// TODO: This is brittle. There has to be a utility to do this...
if (typ.hasTargetProfile()) {
String[] els = typ.getTargetProfile().get(0).getValue().split("/");
return els[els.length - 1];
} else if (typ.hasProfile()) {
String[] els = typ.getProfile().get(0).getValue().split("/");
return els[els.length - 1];
} else {
return typ.getWorkingCode();
}
}
private String genValueSet(ValueSet vs) {
ST vsd = tmplt(VALUE_SET_DEFINITION).add("vsuri", vsprefix(vs.getUrl())).add("comment", vs.getDescription());
ValueSetExpander.ValueSetExpansionOutcome vse = context.expandVS(vs, true, false);
List<String> valid_codes = new ArrayList<String>();
if(vse != null &&
vse.getValueset() != null &&
vse.getValueset().hasExpansion() &&
vse.getValueset().getExpansion().hasContains()) {
for(ValueSet.ValueSetExpansionContainsComponent vsec : vse.getValueset().getExpansion().getContains())
valid_codes.add("\"" + vsec.getCode() + "\"");
}
return vsd.add("val_list", valid_codes.size() > 0? " [" + StringUtils.join(valid_codes, " ") + ']' : " EXTERNAL").render();
}
// TODO: find a utility that implements this
private ValueSet resolveBindingReference(DomainResource ctxt, String reference) {
try {
return context.fetchResource(ValueSet.class, reference);
} catch (Throwable e) {
return null;
}
}
}

View File

@ -0,0 +1,29 @@
package org.hl7.fhir.r4b.conformance;
import org.hl7.fhir.r4b.model.ElementDefinition;
import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.utilities.VersionUtilities;
/**
* This doesn't do anythign at this time
*
* @author graha
*
*/
public class StructureDefinitionHacker {
private String version;
public StructureDefinitionHacker(String version) {
super();
this.version = version;
}
public Resource fixSD(StructureDefinition sd) {
return sd;
}
}

View File

@ -0,0 +1,554 @@
package org.hl7.fhir.r4b.conformance;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.FileOutputStream;
/*
Copyright (c) 2011+, HL7, Inc
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.model.ElementDefinition;
import org.hl7.fhir.r4b.model.ElementDefinition.PropertyRepresentation;
import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.utils.ToolingExtensions;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.Utilities;
public class XmlSchemaGenerator {
public class QName {
public String type;
public String typeNs;
@Override
public String toString() {
return typeNs+":"+type;
}
}
public class ElementToGenerate {
private String tname;
private StructureDefinition sd;
private ElementDefinition ed;
public ElementToGenerate(String tname, StructureDefinition sd, ElementDefinition edc) {
this.tname = tname;
this.sd = sd;
this.ed = edc;
}
}
private String folder;
private IWorkerContext context;
private boolean single;
private String version;
private String genDate;
private String license;
private boolean annotations;
private ProfileUtilities profileUtilities;
public XmlSchemaGenerator(String folder, IWorkerContext context) {
this.folder = folder;
this.context = context;
this.profileUtilities = new ProfileUtilities(context, null, null);
}
public boolean isSingle() {
return single;
}
public void setSingle(boolean single) {
this.single = single;
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getGenDate() {
return genDate;
}
public void setGenDate(String genDate) {
this.genDate = genDate;
}
public String getLicense() {
return license;
}
public void setLicense(String license) {
this.license = license;
}
public boolean isAnnotations() {
return annotations;
}
public void setAnnotations(boolean annotations) {
this.annotations = annotations;
}
private Set<ElementDefinition> processed = new HashSet<ElementDefinition>();
private Set<StructureDefinition> processedLibs = new HashSet<StructureDefinition>();
private Set<String> typeNames = new HashSet<String>();
private OutputStreamWriter writer;
private Map<String, String> namespaces = new HashMap<String, String>();
private Queue<ElementToGenerate> queue = new LinkedList<ElementToGenerate>();
private Queue<StructureDefinition> queueLib = new LinkedList<StructureDefinition>();
private Map<String, StructureDefinition> library;
private boolean useNarrative;
private void w(String s) throws IOException {
writer.write(s);
}
private void ln(String s) throws IOException {
writer.write(s);
writer.write("\r\n");
}
private void close() throws IOException {
if (writer != null) {
ln("</xs:schema>");
writer.flush();
writer.close();
writer = null;
}
}
private String start(StructureDefinition sd, String ns) throws IOException, FHIRException {
String lang = "en";
if (sd.hasLanguage())
lang = sd.getLanguage();
if (single && writer != null) {
if (!ns.equals(getNs(sd)))
throw new FHIRException("namespace inconsistency: "+ns+" vs "+getNs(sd));
return lang;
}
close();
writer = new OutputStreamWriter(new FileOutputStream(Utilities.path(folder, tail(sd.getType()+".xsd"))), "UTF-8");
ln("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
ln("<!-- ");
ln(license);
ln("");
ln(" Generated on "+genDate+" for FHIR v"+version+" ");
ln("");
ln(" Note: this schema does not contain all the knowledge represented in the underlying content model");
ln("");
ln("-->");
ln("<xs:schema xmlns:xs=\"http://www.w3.org/2001/XMLSchema\" xmlns:fhir=\"http://hl7.org/fhir\" xmlns:xhtml=\"http://www.w3.org/1999/xhtml\" "+
"xmlns:lm=\""+ns+"\" targetNamespace=\""+ns+"\" elementFormDefault=\"qualified\" version=\"1.0\">");
ln(" <xs:import schemaLocation=\"fhir-common.xsd\" namespace=\"http://hl7.org/fhir\"/>");
if (useNarrative) {
if (ns.equals("urn:hl7-org:v3"))
ln(" <xs:include schemaLocation=\"cda-narrative.xsd\"/>");
else
ln(" <xs:import schemaLocation=\"cda-narrative.xsd\" namespace=\"urn:hl7-org:v3\"/>");
}
namespaces.clear();
namespaces.put(ns, "lm");
namespaces.put("http://hl7.org/fhir", "fhir");
typeNames.clear();
return lang;
}
private String getNs(StructureDefinition sd) {
String ns = "http://hl7.org/fhir";
if (sd.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
ns = ToolingExtensions.readStringExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
return ns;
}
public void generate(StructureDefinition entry, Map<String, StructureDefinition> library) throws Exception {
processedLibs.clear();
this.library = library;
checkLib(entry);
String ns = getNs(entry);
String lang = start(entry, ns);
w(" <xs:element name=\""+tail(entry.getType())+"\" type=\"lm:"+tail(entry.getType())+"\"");
if (annotations) {
ln(">");
ln(" <xs:annotation>");
ln(" <xs:documentation xml:lang=\""+lang+"\">"+Utilities.escapeXml(entry.getDescription())+"</xs:documentation>");
ln(" </xs:annotation>");
ln(" </xs:element>");
} else
ln("/>");
produceType(entry, entry.getSnapshot().getElement().get(0), tail(entry.getType()), getQN(entry, entry.getBaseDefinition()), lang);
while (!queue.isEmpty()) {
ElementToGenerate q = queue.poll();
produceType(q.sd, q.ed, q.tname, getQN(q.sd, q.ed, "http://hl7.org/fhir/StructureDefinition/Element", false), lang);
}
while (!queueLib.isEmpty()) {
generateInner(queueLib.poll());
}
close();
}
private void checkLib(StructureDefinition entry) {
for (ElementDefinition ed : entry.getSnapshot().getElement()) {
if (ed.hasRepresentation(PropertyRepresentation.CDATEXT)) {
useNarrative = true;
}
}
for (StructureDefinition sd : library.values()) {
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.hasRepresentation(PropertyRepresentation.CDATEXT)) {
useNarrative = true;
}
}
}
}
private void generateInner(StructureDefinition sd) throws IOException, FHIRException {
if (processedLibs.contains(sd))
return;
processedLibs.add(sd);
String ns = getNs(sd);
String lang = start(sd, ns);
if (sd.getSnapshot().getElement().isEmpty())
throw new FHIRException("no snap shot on "+sd.getUrl());
produceType(sd, sd.getSnapshot().getElement().get(0), tail(sd.getType()), getQN(sd, sd.getBaseDefinition()), lang);
while (!queue.isEmpty()) {
ElementToGenerate q = queue.poll();
produceType(q.sd, q.ed, q.tname, getQN(q.sd, q.ed, "http://hl7.org/fhir/StructureDefinition/Element", false), lang);
}
}
private String tail(String url) {
return url.contains("/") ? url.substring(url.lastIndexOf("/")+1) : url;
}
private String root(String url) {
return url.contains("/") ? url.substring(0, url.lastIndexOf("/")) : "";
}
private String tailDot(String url) {
return url.contains(".") ? url.substring(url.lastIndexOf(".")+1) : url;
}
private void produceType(StructureDefinition sd, ElementDefinition ed, String typeName, QName typeParent, String lang) throws IOException, FHIRException {
if (processed.contains(ed))
return;
processed.add(ed);
// ok
ln(" <xs:complexType name=\""+typeName+"\">");
if (annotations) {
ln(" <xs:annotation>");
ln(" <xs:documentation xml:lang=\""+lang+"\">"+Utilities.escapeXml(ed.getDefinition())+"</xs:documentation>");
ln(" </xs:annotation>");
}
ln(" <xs:complexContent>");
ln(" <xs:extension base=\""+typeParent.toString()+"\">");
ln(" <xs:sequence>");
// hack....
for (ElementDefinition edc : profileUtilities.getChildList(sd, ed)) {
if (!(edc.hasRepresentation(PropertyRepresentation.XMLATTR) || edc.hasRepresentation(PropertyRepresentation.XMLTEXT)) && !inheritedElement(edc))
produceElement(sd, ed, edc, lang);
}
ln(" </xs:sequence>");
for (ElementDefinition edc : profileUtilities.getChildList(sd, ed)) {
if ((edc.hasRepresentation(PropertyRepresentation.XMLATTR) || edc.hasRepresentation(PropertyRepresentation.XMLTEXT)) && !inheritedElement(edc))
produceAttribute(sd, ed, edc, lang);
}
ln(" </xs:extension>");
ln(" </xs:complexContent>");
ln(" </xs:complexType>");
}
private boolean inheritedElement(ElementDefinition edc) {
return !edc.getPath().equals(edc.getBase().getPath());
}
private void produceElement(StructureDefinition sd, ElementDefinition ed, ElementDefinition edc, String lang) throws IOException, FHIRException {
if (edc.getType().size() == 0)
throw new Error("No type at "+edc.getPath());
if (edc.getType().size() > 1 && edc.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
// first, find the common base type
StructureDefinition lib = getCommonAncestor(edc.getType());
if (lib == null)
throw new Error("Common ancester not found at "+edc.getPath());
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (TypeRefComponent t : edc.getType()) {
b.append(getQN(sd, edc, t.getWorkingCode(), true).toString());
}
String name = tailDot(edc.getPath());
String min = String.valueOf(edc.getMin());
String max = edc.getMax();
if ("*".equals(max))
max = "unbounded";
QName qn = getQN(sd, edc, lib.getUrl(), true);
ln(" <xs:element name=\""+name+"\" minOccurs=\""+min+"\" maxOccurs=\""+max+"\" type=\""+qn.typeNs+":"+qn.type+"\">");
ln(" <xs:annotation>");
ln(" <xs:appinfo xml:lang=\"en\">Possible types: "+b.toString()+"</xs:appinfo>");
if (annotations && edc.hasDefinition())
ln(" <xs:documentation xml:lang=\""+lang+"\">"+Utilities.escapeXml(edc.getDefinition())+"</xs:documentation>");
ln(" </xs:annotation>");
ln(" </xs:element>");
} else for (TypeRefComponent t : edc.getType()) {
String name = tailDot(edc.getPath());
if (edc.getType().size() > 1)
name = name + Utilities.capitalize(t.getWorkingCode());
QName qn = getQN(sd, edc, t.getWorkingCode(), true);
String min = String.valueOf(edc.getMin());
String max = edc.getMax();
if ("*".equals(max))
max = "unbounded";
w(" <xs:element name=\""+name+"\" minOccurs=\""+min+"\" maxOccurs=\""+max+"\" type=\""+qn.typeNs+":"+qn.type+"\"");
if (annotations && edc.hasDefinition()) {
ln(">");
ln(" <xs:annotation>");
ln(" <xs:documentation xml:lang=\""+lang+"\">"+Utilities.escapeXml(edc.getDefinition())+"</xs:documentation>");
ln(" </xs:annotation>");
ln(" </xs:element>");
} else
ln("/>");
}
}
public QName getQN(StructureDefinition sd, String type) throws FHIRException {
return getQN(sd, sd.getSnapshot().getElementFirstRep(), type, false);
}
public QName getQN(StructureDefinition sd, ElementDefinition edc, String t, boolean chase) throws FHIRException {
QName qn = new QName();
qn.type = Utilities.isAbsoluteUrl(t) ? tail(t) : t;
if (Utilities.isAbsoluteUrl(t)) {
String ns = root(t);
if (ns.equals(root(sd.getUrl())))
ns = getNs(sd);
if (ns.equals("http://hl7.org/fhir/StructureDefinition"))
ns = "http://hl7.org/fhir";
if (!namespaces.containsKey(ns))
throw new FHIRException("Unknown type namespace "+ns+" for "+edc.getPath());
qn.typeNs = namespaces.get(ns);
StructureDefinition lib = library.get(t);
if (lib == null && !Utilities.existsInList(t, "http://hl7.org/fhir/cda/StructureDefinition/StrucDoc.Text", "http://hl7.org/fhir/StructureDefinition/Element"))
throw new FHIRException("Unable to resolve "+t+" for "+edc.getPath());
if (lib != null)
queueLib.add(lib);
} else
qn.typeNs = namespaces.get("http://hl7.org/fhir");
if (chase && qn.type.equals("Element")) {
String tname = typeNameFromPath(edc);
if (typeNames.contains(tname)) {
int i = 1;
while (typeNames.contains(tname+i))
i++;
tname = tname+i;
}
queue.add(new ElementToGenerate(tname, sd, edc));
qn.typeNs = "lm";
qn.type = tname;
}
return qn;
}
private StructureDefinition getCommonAncestor(List<TypeRefComponent> type) throws FHIRException {
StructureDefinition sd = library.get(type.get(0).getWorkingCode());
if (sd == null)
throw new FHIRException("Unable to find definition for "+type.get(0).getWorkingCode());
for (int i = 1; i < type.size(); i++) {
StructureDefinition t = library.get(type.get(i).getWorkingCode());
if (t == null)
throw new FHIRException("Unable to find definition for "+type.get(i).getWorkingCode());
sd = getCommonAncestor(sd, t);
}
return sd;
}
private StructureDefinition getCommonAncestor(StructureDefinition sd1, StructureDefinition sd2) throws FHIRException {
// this will always return something because everything comes from Element
List<StructureDefinition> chain1 = new ArrayList<>();
List<StructureDefinition> chain2 = new ArrayList<>();
chain1.add(sd1);
chain2.add(sd2);
StructureDefinition root = library.get("Element");
StructureDefinition common = findIntersection(chain1, chain2);
boolean chain1Done = false;
boolean chain2Done = false;
while (common == null) {
chain1Done = checkChain(chain1, root, chain1Done);
chain2Done = checkChain(chain2, root, chain2Done);
if (chain1Done && chain2Done)
return null;
common = findIntersection(chain1, chain2);
}
return common;
}
private StructureDefinition findIntersection(List<StructureDefinition> chain1, List<StructureDefinition> chain2) {
for (StructureDefinition sd1 : chain1)
for (StructureDefinition sd2 : chain2)
if (sd1 == sd2)
return sd1;
return null;
}
public boolean checkChain(List<StructureDefinition> chain1, StructureDefinition root, boolean chain1Done) throws FHIRException {
if (!chain1Done) {
StructureDefinition sd = chain1.get(chain1.size()-1);
String bu = sd.getBaseDefinition();
if (bu == null)
throw new FHIRException("No base definition for "+sd.getUrl());
StructureDefinition t = library.get(bu);
if (t == null)
chain1Done = true;
else
chain1.add(t);
}
return chain1Done;
}
private StructureDefinition getBase(StructureDefinition structureDefinition) {
return null;
}
private String typeNameFromPath(ElementDefinition edc) {
StringBuilder b = new StringBuilder();
boolean up = true;
for (char ch : edc.getPath().toCharArray()) {
if (ch == '.')
up = true;
else if (up) {
b.append(Character.toUpperCase(ch));
up = false;
} else
b.append(ch);
}
return b.toString();
}
private void produceAttribute(StructureDefinition sd, ElementDefinition ed, ElementDefinition edc, String lang) throws IOException, FHIRException {
TypeRefComponent t = edc.getTypeFirstRep();
String name = tailDot(edc.getPath());
String min = String.valueOf(edc.getMin());
String max = edc.getMax();
// todo: check it's a code...
// if (!max.equals("1"))
// throw new FHIRException("Illegal cardinality \""+max+"\" for attribute "+edc.getPath());
String tc = t.getWorkingCode();
if (Utilities.isAbsoluteUrl(tc))
throw new FHIRException("Only FHIR primitive types are supported for attributes ("+tc+")");
String typeNs = namespaces.get("http://hl7.org/fhir");
String type = tc;
w(" <xs:attribute name=\""+name+"\" use=\""+(min.equals("0") || edc.hasFixed() || edc.hasDefaultValue() ? "optional" : "required")+"\" type=\""+typeNs+":"+type+(typeNs.equals("fhir") ? "-primitive" : "")+"\""+
(edc.hasFixed() ? " fixed=\""+edc.getFixed().primitiveValue()+"\"" : "")+(edc.hasDefaultValue() && !edc.hasFixed() ? " default=\""+edc.getDefaultValue().primitiveValue()+"\"" : "")+"");
if (annotations && edc.hasDefinition()) {
ln(">");
ln(" <xs:annotation>");
ln(" <xs:documentation xml:lang=\""+lang+"\">"+Utilities.escapeXml(edc.getDefinition())+"</xs:documentation>");
ln(" </xs:annotation>");
ln(" </xs:attribute>");
} else
ln("/>");
}
}

View File

@ -0,0 +1,23 @@
package org.hl7.fhir.r4b.context;
public class BaseLogger {
private int id = 0;
private String lastId;
public String getLastId() {
return lastId;
}
protected String nextId() {
id++;
lastId = Integer.toString(id);
return lastId;
}
public void clearLastId() {
lastId = null;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,413 @@
package org.hl7.fhir.r4b.context;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.context.CanonicalResourceManager.CanonicalResourceProxy;
import org.hl7.fhir.r4b.context.IWorkerContext.PackageVersion;
import org.hl7.fhir.r4b.model.CanonicalResource;
import org.hl7.fhir.r4b.model.CodeSystem;
import org.hl7.fhir.r4b.model.DomainResource;
import org.hl7.fhir.r4b.terminologies.CodeSystemUtilities;
import org.hl7.fhir.utilities.VersionUtilities;
/**
* This manages a cached list of resources, and provides high speed access by URL / URL+version, and assumes that patch version doesn't matter for access
* note, though, that not all resources have semver versions
*
* @author graha
*
*/
public class CanonicalResourceManager<T extends CanonicalResource> {
public static abstract class CanonicalResourceProxy {
private String type;
private String id;
private String url;
private String version;
private CanonicalResource resource;
public CanonicalResourceProxy(String type, String id, String url, String version) {
super();
this.type = type;
this.id = id;
this.url = url;
this.version = version;
}
public String getType() {
return type;
}
public String getId() {
return id;
}
public String getUrl() {
return url;
}
public String getVersion() {
return version;
}
public boolean hasId() {
return id != null;
}
public boolean hasUrl() {
return url != null;
}
public boolean hasVersion() {
return version != null;
}
public CanonicalResource getResource() throws FHIRException {
if (resource == null) {
resource = loadResource();
if (resource instanceof CodeSystem) {
CodeSystemUtilities.crossLinkCodeSystem((CodeSystem) resource);
}
}
return resource;
}
public void setResource(CanonicalResource resource) {
this.resource = resource;
}
public abstract CanonicalResource loadResource() throws FHIRException;
@Override
public String toString() {
return type+"/"+id+": "+url+"|"+version;
}
}
public class CanonicalListSorter implements Comparator<CanonicalResource> {
@Override
public int compare(CanonicalResource arg0, CanonicalResource arg1) {
String u0 = arg0.getUrl();
String u1 = arg1.getUrl();
return u0.compareTo(u1);
}
}
private class CachedCanonicalResource<T1 extends CanonicalResource> {
private T1 resource;
private CanonicalResourceProxy proxy;
private PackageVersion packageInfo;
public CachedCanonicalResource(T1 resource, PackageVersion packageInfo) {
super();
this.resource = resource;
this.packageInfo = packageInfo;
}
public CachedCanonicalResource(CanonicalResourceProxy proxy, PackageVersion packageInfo) {
super();
this.proxy = proxy;
this.packageInfo = packageInfo;
}
public T1 getResource() {
if (resource == null) {
@SuppressWarnings("unchecked")
T1 res = (T1) proxy.getResource();
synchronized (this) {
resource = res;
}
proxy = null;
}
return resource;
}
public PackageVersion getPackageInfo() {
return packageInfo;
}
public String getUrl() {
return resource != null ? resource.getUrl() : proxy.getUrl();
}
public String getId() {
return resource != null ? resource.getId() : proxy.getId();
}
public String getVersion() {
return resource != null ? resource.getVersion() : proxy.getVersion();
}
public boolean hasVersion() {
return resource != null ? resource.hasVersion() : proxy.getVersion() != null;
}
@Override
public String toString() {
return resource != null ? resource.fhirType()+"/"+resource.getId()+": "+resource.getUrl()+"|"+resource.getVersion() : proxy.toString();
}
}
public class MetadataResourceVersionComparator<T1 extends CachedCanonicalResource<T>> implements Comparator<T1> {
@Override
public int compare(T1 arg1, T1 arg2) {
String v1 = arg1.getVersion();
String v2 = arg2.getVersion();
if (v1 == null && v2 == null) {
return Integer.compare(list.indexOf(arg1), list.indexOf(arg2)); // retain original order
} else if (v1 == null) {
return -1;
} else if (v2 == null) {
return 1;
} else {
String mm1 = VersionUtilities.getMajMin(v1);
String mm2 = VersionUtilities.getMajMin(v2);
if (mm1 == null || mm2 == null) {
return v1.compareTo(v2);
} else {
return mm1.compareTo(mm2);
}
}
}
}
private boolean enforceUniqueId;
private List<CachedCanonicalResource<T>> list = new ArrayList<>();
private Map<String, CachedCanonicalResource<T>> map = new HashMap<>();
public CanonicalResourceManager(boolean enforceUniqueId) {
super();
this.enforceUniqueId = enforceUniqueId;
}
public void copy(CanonicalResourceManager<T> source) {
list.clear();
map.clear();
list.addAll(source.list);
map.putAll(source.map);
}
public void register(CanonicalResourceProxy r, PackageVersion packgeInfo) {
if (!r.hasId()) {
throw new FHIRException("An id is required for a deferred load resource");
}
CanonicalResourceManager<T>.CachedCanonicalResource<T> cr = new CachedCanonicalResource<T>(r, packgeInfo);
see(cr);
}
public void see(T r, PackageVersion packgeInfo) {
if (r != null) {
if (!r.hasId()) {
r.setId(UUID.randomUUID().toString());
}
CanonicalResourceManager<T>.CachedCanonicalResource<T> cr = new CachedCanonicalResource<T>(r, packgeInfo);
see(cr);
}
}
public void see(CachedCanonicalResource<T> cr) {
// ignore UTG NUCC erroneous code system
if (cr.getPackageInfo() != null && cr.getPackageInfo().getId() != null && cr.getPackageInfo().getId().startsWith("hl7.terminology") && "http://nucc.org/provider-taxonomy".equals(cr.getUrl())) {
return;
}
if (enforceUniqueId && map.containsKey(cr.getId())) {
drop(cr.getId());
}
// special case logic for UTG support prior to version 5
if (cr.getPackageInfo() != null && cr.getPackageInfo().getId().startsWith("hl7.terminology")) {
List<CachedCanonicalResource<T>> toDrop = new ArrayList<>();
for (CachedCanonicalResource<T> n : list) {
if (n.getUrl() != null && n.getUrl().equals(cr.getUrl()) && isBasePackage(n.getPackageInfo())) {
toDrop.add(n);
}
}
for (CachedCanonicalResource<T> n : toDrop) {
drop(n.getId());
}
}
CachedCanonicalResource<T> existing = cr.hasVersion() ? map.get(cr.getUrl()+"|"+cr.getVersion()) : map.get(cr.getUrl()+"|#0");
if (map.get(cr.getUrl()) != null && (cr.getPackageInfo() != null && cr.getPackageInfo().isExamplesPackage())) {
return;
}
if (existing != null) {
list.remove(existing);
}
list.add(cr);
map.put(cr.getId(), cr); // we do this so we can drop by id
map.put(cr.getUrl(), cr);
if (cr.getUrl() != null) {
// first, this is the correct reosurce for this version (if it has a version)
if (cr.hasVersion()) {
map.put(cr.getUrl()+"|"+cr.getVersion(), cr);
} else {
map.put(cr.getUrl()+"|#0", cr);
}
updateList(cr.getUrl(), cr.getVersion());
}
}
private boolean isBasePackage(PackageVersion packageInfo) {
return packageInfo == null ? false : VersionUtilities.isCorePackage(packageInfo.getId());
}
private void updateList(String url, String version) {
List<CachedCanonicalResource<T>> rl = new ArrayList<>();
for (CachedCanonicalResource<T> t : list) {
if (url.equals(t.getUrl()) && !rl.contains(t)) {
rl.add(t);
}
}
if (rl.size() > 0) {
// sort by version as much as we are able
Collections.sort(rl, new MetadataResourceVersionComparator<CachedCanonicalResource<T>>());
// the current is the latest
map.put(url, rl.get(rl.size()-1));
// now, also, the latest for major/minor
if (version != null) {
CachedCanonicalResource<T> latest = null;
for (CachedCanonicalResource<T> t : rl) {
if (VersionUtilities.versionsCompatible(t.getVersion(), version)) {
latest = t;
}
}
if (latest != null) { // might be null if it's not using semver
String lv = VersionUtilities.getMajMin(latest.getVersion());
if (lv != null && !lv.equals(version))
map.put(url+"|"+lv, rl.get(rl.size()-1));
}
}
}
}
public T get(String url) {
return map.containsKey(url) ? map.get(url).getResource() : null;
}
public PackageVersion getPackageInfo(String system, String version) {
if (version == null) {
return map.containsKey(system) ? map.get(system).getPackageInfo() : null;
} else {
if (map.containsKey(system+"|"+version))
return map.get(system+"|"+version).getPackageInfo();
String mm = VersionUtilities.getMajMin(version);
if (mm != null && map.containsKey(system+"|"+mm))
return map.get(system+"|"+mm).getPackageInfo();
else
return null;
}
}
public boolean has(String url) {
return map.containsKey(url);
}
public T get(String system, String version) {
if (version == null) {
return get(system);
} else {
if (map.containsKey(system+"|"+version))
return map.get(system+"|"+version).getResource();
String mm = VersionUtilities.getMajMin(version);
if (mm != null && map.containsKey(system+"|"+mm))
return map.get(system+"|"+mm).getResource();
else
return null;
}
}
public boolean has(String system, String version) {
if (map.containsKey(system+"|"+version))
return true;
String mm = VersionUtilities.getMajMin(version);
if (mm != null)
return map.containsKey(system+"|"+mm);
else
return false;
}
public int size() {
return list.size();
}
public void drop(String id) {
CachedCanonicalResource<T> res = null;
do {
res = null;
for (CachedCanonicalResource<T> t : list) {
if (t.getId().equals(id)) {
res = t;
}
}
if (res != null) {
list.remove(res);
map.remove(id);
map.remove(res.getUrl());
if (res.hasVersion()) {
map.remove(res.getUrl()+"|"+res.getVersion());
String mm = VersionUtilities.getMajMin(res.getVersion());
if (mm != null) {
map.remove(res.getUrl()+"|"+mm);
}
}
updateList(res.getUrl(), res.getVersion());
}
} while (res != null);
}
public void listAll(List<T> result) {
for (CachedCanonicalResource<T> t : list) {
result.add(t.getResource());
}
}
public void listAllM(List<CanonicalResource> result) {
for (CachedCanonicalResource<T> t : list) {
result.add(t.getResource());
}
}
public void clear() {
list.clear();
map.clear();
}
public List<T> getList() {
List<T> res = new ArrayList<>();
for (CachedCanonicalResource<T> t : list) {
if (!res.contains(t.getResource())) {
res.add(t.getResource());
}
}
return res;
}
public List<T> getSortedList() {
List<T> res = getList();
Collections.sort(res, new CanonicalListSorter());
return res;
}
public Set<String> keys() {
return map.keySet();
}
public boolean isEnforceUniqueId() {
return enforceUniqueId;
}
}

View File

@ -0,0 +1,127 @@
package org.hl7.fhir.r4b.context;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.List;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.Utilities;
public class HTMLClientLogger extends BaseLogger implements ToolingClientLogger {
private static final boolean DEBUG = false;
private boolean req = false;
private PrintStream file;
public HTMLClientLogger(String log) {
if (log != null) {
try {
file = new PrintStream(new FileOutputStream(log));
} catch (FileNotFoundException e) {
}
}
}
@Override
public void logRequest(String method, String url, List<String> headers, byte[] body) {
if (DEBUG) {
System.out.println(" txlog req: " +method+" "+url+" "+present(body));
}
if (file == null)
return;
String id = nextId();
file.println("<hr/><a name=\"l"+id+"\"> </a>");
file.println("<p>#"+id+"</p>");
file.println("<pre>");
file.println(method+" "+url+" HTTP/1.0");
if (headers != null) {
for (String s : headers) {
file.println(Utilities.escapeXml(s));
}
}
if (body != null) {
file.println("");
try {
file.println(Utilities.escapeXml(new String(body, "UTF-8")));
} catch (UnsupportedEncodingException e) {
}
}
file.println("</pre>");
req = true;
}
@Override
public void logResponse(String outcome, List<String> headers, byte[] body) {
if (DEBUG) {
System.out.println(" txlog resp: " +outcome+" "+present(body));
}
if (file == null)
return;
if (!req) {
System.out.println("Record Response without request");
}
req = false;
file.println("<pre>");
file.println(outcome);
for (String s : headers)
file.println(Utilities.escapeXml(s));
if (body != null) {
file.println("");
try {
file.println(Utilities.escapeXml(new String(body, "UTF-8")));
} catch (UnsupportedEncodingException e) {
}
}
file.println("</pre>");
}
private String present(byte[] body) {
if (body == null) {
return "";
}
String cnt = new String(body);
cnt = cnt.replace("\n", " ").replace("\r", "");
if (cnt.length() > 800) {
return cnt.substring(0, 798)+"...";
} else {
return cnt;
}
}
}

View File

@ -0,0 +1,866 @@
package org.hl7.fhir.r4b.context;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.fhir.ucum.UcumService;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r4b.context.TerminologyCache.CacheToken;
import org.hl7.fhir.r4b.elementmodel.Element;
import org.hl7.fhir.r4b.formats.IParser;
import org.hl7.fhir.r4b.formats.ParserType;
import org.hl7.fhir.r4b.model.Bundle;
import org.hl7.fhir.r4b.model.CanonicalResource;
import org.hl7.fhir.r4b.model.CodeSystem;
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4b.model.CodeableConcept;
import org.hl7.fhir.r4b.model.Coding;
import org.hl7.fhir.r4b.model.ConceptMap;
import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r4b.model.Parameters;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.StructureMap;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r4b.utils.validation.IResourceValidator;
import org.hl7.fhir.r4b.utils.validation.ValidationContextCarrier;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import com.google.gson.JsonSyntaxException;
/**
* This is the standard interface used for access to underlying FHIR
* services through the tools and utilities provided by the reference
* implementation.
*
* The functionality it provides is
* - get access to parsers, validators, narrative builders etc
* (you can't create these directly because they need access
* to the right context for their information)
*
* - find resources that the tools need to carry out their tasks
*
* - provide access to terminology services they need.
* (typically, these terminology service requests are just
* passed through to the local implementation's terminology
* service)
*
* @author Grahame
*/
public interface IWorkerContext {
public class CodingValidationRequest {
private Coding coding;
private ValidationResult result;
private CacheToken cacheToken;
public CodingValidationRequest(Coding coding) {
super();
this.coding = coding;
}
public ValidationResult getResult() {
return result;
}
public void setResult(ValidationResult result) {
this.result = result;
}
public Coding getCoding() {
return coding;
}
public boolean hasResult() {
return result != null;
}
/**
* internal logic; external users of batch validation should ignore this property
*
* @return
*/
public CacheToken getCacheToken() {
return cacheToken;
}
/**
* internal logic; external users of batch validation should ignore this property
*
* @param cacheToken
*/
public void setCacheToken(CacheToken cacheToken) {
this.cacheToken = cacheToken;
}
}
public class PackageVersion {
private String id;
private String version;
public PackageVersion(String source) {
if (source == null) {
throw new Error("Source cannot be null");
}
if (!source.contains("#")) {
throw new FHIRException("Source ");
}
id = source.substring(0, source.indexOf("#"));
version = source.substring(source.indexOf("#")+1);
}
public PackageVersion(String id, String version) {
super();
this.id = id;
this.version = version;
}
public String getId() {
return id;
}
public String getVersion() {
return version;
}
public boolean isExamplesPackage() {
boolean b = id.startsWith("hl7.fhir.") && id.endsWith(".examples");
return b;
}
@Override
public String toString() {
return id+"#"+version;
}
}
public class PackageDetails extends PackageVersion {
private String name;
private String canonical;
private String web;
public PackageDetails(String id, String version, String name, String canonical, String web) {
super(id, version);
this.name = name;
this.canonical = canonical;
this.web = web;
}
public String getName() {
return name;
}
public String getCanonical() {
return canonical;
}
public String getWeb() {
return web;
}
}
public interface ICanonicalResourceLocator {
void findResource(Object caller, String url); // if it can be found, put it in the context
}
public interface IContextResourceLoader {
/**
* @return List of the resource types that should be loaded
*/
String[] getTypes();
/**
* Request to actually load the resources and do whatever is required
*
* @param stream
* @param isJson
* @return A bundle because some single resources become multiple resources after loading
* @throws FHIRException
* @throws IOException
*/
Bundle loadBundle(InputStream stream, boolean isJson) throws FHIRException, IOException;
/**
* Load a single resources (lazy load)
*
* @param stream
* @param isJson
* @return
* @throws FHIRException - throw this if you a single resource can't be returned - can't lazy load in this circumstance
* @throws IOException
*/
Resource loadResource(InputStream stream, boolean isJson) throws FHIRException, IOException;
/**
* get the path for references to this resource.
* @param resource
* @return null if not tracking paths
*/
String getResourcePath(Resource resource);
/**
* called when a mew package is being loaded
*
* this is called by loadPacakgeAndDependencies when a new package is loaded
* @param npm
* @return
* @throws IOException
* @throws JsonSyntaxException
*/
IContextResourceLoader getNewLoader(NpmPackage npm) throws JsonSyntaxException, IOException;
}
/**
* Get the versions of the definitions loaded in context
* @return
*/
public String getVersion();
/**
* return the link to the base of the specification for the loaded version e.g. http://hl7.org/fhir/R4
*/
public String getSpecUrl();
// get the UCUM service (might not be available)
public UcumService getUcumService();
// -- Parsers (read and write instances) ----------------------------------------
/**
* Get a parser to read/write instances. Use the defined type (will be extended
* as further types are added, though the only currently anticipate type is RDF)
*
* XML/JSON - the standard renderers
* XHTML - render the narrative only (generate it if necessary)
*
* @param type
* @return
*/
public IParser getParser(ParserType type);
/**
* Get a parser to read/write instances. Determine the type
* from the stated type. Supported value for type:
* - the recommended MIME types
* - variants of application/xml and application/json
* - _format values xml, json
*
* @param type
* @return
*/
public IParser getParser(String type);
/**
* Get a JSON parser
*
* @return
*/
public IParser newJsonParser();
/**
* Get an XML parser
*
* @return
*/
public IParser newXmlParser();
/**
* Get a validator that can check whether a resource is valid
*
* @return a prepared generator
* @throws FHIRException
* @
*/
public IResourceValidator newValidator() throws FHIRException;
// -- resource fetchers ---------------------------------------------------
/**
* Find an identified resource. The most common use of this is to access the the
* standard conformance resources that are part of the standard - structure
* definitions, value sets, concept maps, etc.
*
* Also, the narrative generator uses this, and may access any kind of resource
*
* The URI is called speculatively for things that might exist, so not finding
* a matching resouce, return null, not an error
*
* The URI can have one of 3 formats:
* - a full URL e.g. http://acme.org/fhir/ValueSet/[id]
* - a relative URL e.g. ValueSet/[id]
* - a logical id e.g. [id]
*
* It's an error if the second form doesn't agree with class_. It's an
* error if class_ is null for the last form
*
* @param resource
* @param Reference
* @return
* @throws FHIRException
* @throws Exception
*/
public <T extends Resource> T fetchResource(Class<T> class_, String uri);
public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException;
public <T extends Resource> T fetchResource(Class<T> class_, String uri, String version);
/** has the same functionality as fetchResource, but passes in information about the source of the
* reference (this may affect resolution of version)
*
* @param <T>
* @param class_
* @param uri
* @param canonicalForSource
* @return
*/
public <T extends Resource> T fetchResource(Class<T> class_, String uri, CanonicalResource canonicalForSource);
/**
* Variation of fetchResource when you have a string type, and don't need the right class
*
* The URI can have one of 3 formats:
* - a full URL e.g. http://acme.org/fhir/ValueSet/[id]
* - a relative URL e.g. ValueSet/[id]
* - a logical id e.g. [id]
*
* if type == null, the URI can't be a simple logical id
*
* @param type
* @param uri
* @return
*/
public Resource fetchResourceById(String type, String uri);
/**
* find whether a resource is available.
*
* Implementations of the interface can assume that if hasResource ruturns
* true, the resource will usually be fetched subsequently
*
* @param class_
* @param uri
* @return
*/
public <T extends Resource> boolean hasResource(Class<T> class_, String uri);
/**
* cache a resource for later retrieval using fetchResource.
*
* Note that various context implementations will have their own ways of loading
* rseources, and not all need implement cacheResource.
*
* If the resource is loaded out of a package, call cacheResourceFromPackage instead
* @param res
* @throws FHIRException
*/
public void cacheResource(Resource res) throws FHIRException;
/**
* cache a resource for later retrieval using fetchResource.
*
* The package information is used to help manage the cache internally, and to
* help with reference resolution. Packages should be define using cachePackage (but don't have to be)
*
* Note that various context implementations will have their own ways of loading
* rseources, and not all need implement cacheResource
*
* @param res
* @throws FHIRException
*/
public void cacheResourceFromPackage(Resource res, PackageVersion packageDetails) throws FHIRException;
/**
* Inform the cache about package dependencies. This can be used to help resolve references
*
* Note that the cache doesn't load dependencies
*
* @param packageInfo
*/
public void cachePackage(PackageDetails packageDetails, List<PackageVersion> dependencies);
// -- profile services ---------------------------------------------------------
/**
* @return a list of the resource names defined for this version
*/
public List<String> getResourceNames();
/**
* @return a set of the resource names defined for this version
*/
public Set<String> getResourceNamesAsSet();
/**
* @return a list of the resource and type names defined for this version
*/
public List<String> getTypeNames();
/**
* @return a list of all structure definitions, with snapshots generated (if possible)
*/
public List<StructureDefinition> allStructures();
/**
* @return a list of all structure definitions, without trying to generate snapshots
*/
public List<StructureDefinition> getStructures();
/**
* @return a list of all conformance resources
*/
public List<CanonicalResource> allConformanceResources();
/**
* Given a structure definition, generate a snapshot (or regenerate it)
* @param p
* @throws DefinitionException
* @throws FHIRException
*/
public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException;
public void generateSnapshot(StructureDefinition mr, boolean ifLogical);
// -- Terminology services ------------------------------------------------------
/**
* Set the expansion parameters passed through the terminology server when txServer calls are made
*
* Note that the Validation Options override these when they are specified on validateCode
*/
public Parameters getExpansionParameters();
/**
* Get the expansion parameters passed through the terminology server when txServer calls are made
*
* Note that the Validation Options override these when they are specified on validateCode
*/
public void setExpansionProfile(Parameters expParameters);
// these are the terminology services used internally by the tools
/**
* Find the code system definition for the nominated system uri.
* return null if there isn't one (then the tool might try
* supportsSystem)
*
* @param system
* @return
*/
public CodeSystem fetchCodeSystem(String system);
public CodeSystem fetchCodeSystem(String system, String version);
/**
* True if the underlying terminology service provider will do
* expansion and code validation for the terminology. Corresponds
* to the extension
*
* http://hl7.org/fhir/StructureDefinition/capabilitystatement-supported-system
*
* in the Conformance resource
*
* @param system
* @return
* @throws Exception
*/
public boolean supportsSystem(String system) throws TerminologyServiceException;
/**
* find concept maps for a source
* @param url
* @return
* @throws FHIRException
*/
public List<ConceptMap> findMapsForSource(String url) throws FHIRException;
/**
* ValueSet Expansion - see $expand
*
* @param source
* @return
*/
public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical);
/**
* ValueSet Expansion - see $expand
*
* @param source
* @return
*/
public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical, boolean incompleteOk);
/**
* ValueSet Expansion - see $expand, but resolves the binding first
*
* @param source
* @return
* @throws FHIRException
*/
public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException;
/**
* Value set expanion inside the internal expansion engine - used
* for references to supported system (see "supportsSystem") for
* which there is no value set.
*
* @param inc
* @return
* @throws FHIRException
*/
ValueSetExpansionOutcome expandVS(ConceptSetComponent inc, boolean hierarchical) throws TerminologyServiceException;
Locale getLocale();
void setLocale(Locale locale);
String formatMessage(String theMessage, Object... theMessageArguments);
void setValidationMessageLanguage(Locale locale);
class ValidationResult {
private ConceptDefinitionComponent definition;
private String system;
private IssueSeverity severity;
private String message;
private TerminologyServiceErrorClass errorClass;
private String txLink;
@Override
public String toString() {
return "ValidationResult [definition=" + definition + ", system=" + system + ", severity=" + severity + ", message=" + message + ", errorClass="
+ errorClass + ", txLink=" + txLink + "]";
}
public ValidationResult(IssueSeverity severity, String message) {
this.severity = severity;
this.message = message;
}
public ValidationResult(String system, ConceptDefinitionComponent definition) {
this.system = system;
this.definition = definition;
}
public ValidationResult(IssueSeverity severity, String message, String system, ConceptDefinitionComponent definition) {
this.severity = severity;
this.message = message;
this.system = system;
this.definition = definition;
}
public ValidationResult(IssueSeverity severity, String message, TerminologyServiceErrorClass errorClass) {
this.severity = severity;
this.message = message;
this.errorClass = errorClass;
}
public boolean isOk() {
return severity == null || severity == IssueSeverity.INFORMATION || severity == IssueSeverity.WARNING;
}
public String getSystem() {
return system;
}
public String getDisplay() {
return definition == null ? null : definition.getDisplay();
}
public String getCode() {
return definition == null ? null : definition.getCode();
}
public String getDefinition() {
return definition == null ? null : definition.getDefinition();
}
public ConceptDefinitionComponent asConceptDefinition() {
return definition;
}
public IssueSeverity getSeverity() {
return severity;
}
public String getMessage() {
return message;
}
public boolean IsNoService() {
return errorClass == TerminologyServiceErrorClass.NOSERVICE;
}
public TerminologyServiceErrorClass getErrorClass() {
return errorClass;
}
public ValidationResult setSeverity(IssueSeverity severity) {
this.severity = severity;
return this;
}
public ValidationResult setMessage(String message) {
this.message = message;
return this;
}
public ValidationResult setErrorClass(TerminologyServiceErrorClass errorClass) {
this.errorClass = errorClass;
return this;
}
public String getTxLink() {
return txLink;
}
public ValidationResult setTxLink(String txLink) {
this.txLink = txLink;
return this;
}
public boolean hasMessage() {
return message != null;
}
public Coding asCoding() {
if (isOk() && definition != null && definition.getCode() != null) {
return new Coding(system, definition.getCode(), definition.getDisplay());
} else {
return null;
}
}
}
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* in this case, the system will be inferred from the value set. It's an error to call this one without the value set
*
* @param options - validation options (required)
* @param code he code to validate (required)
* @param vs the applicable valueset (required)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs);
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param options - validation options (required)
* @param system - equals Coding.system (required)
* @param code - equals Coding.code (required)
* @param display - equals Coding.display (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display);
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param options - validation options (required)
* @param system - equals Coding.system (required)
* @param code - equals Coding.code (required)
* @param display - equals Coding.display (optional)
* @param vs the applicable valueset (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, String system, String version, String code, String display, ValueSet vs);
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* Note that this doesn't validate binding strength (e.g. is just text allowed?)
*
* @param options - validation options (required)
* @param code - CodeableConcept to validate
* @param vs the applicable valueset (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs);
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* in this case, the system will be inferred from the value set. It's an error to call this one without the value set
*
* @param options - validation options (required)
* @param code - Coding to validate
* @param vs the applicable valueset (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs);
public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs, ValidationContextCarrier ctxt);
public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs);
/**
* returns the recommended tla for the type (from the structure definitions)
*
* @param name
* @return
*/
public String getAbbreviation(String name);
/**
* translate an OID to a URI (look through known NamingSystems)
* @param code
* @return
*/
public String oid2Uri(String code);
/**
* @return true if the contxt has a terminology caching service internally
*/
public boolean hasCache();
public interface ILoggingService {
public enum LogCategory {
INIT,
PROGRESS,
TX,
CONTEXT,
GENERATE,
HTML
}
public void logMessage(String message); // status messages, always display
public void logDebugMessage(LogCategory category, String message); // verbose; only when debugging
}
public void setLogger(ILoggingService logger);
public ILoggingService getLogger();
public boolean isNoTerminologyServer();
public Set<String> getCodeSystemsUsed();
public TranslationServices translator();
public List<StructureMap> listTransforms();
public StructureMap getTransform(String url);
public String getOverrideVersionNs();
public void setOverrideVersionNs(String value);
public StructureDefinition fetchTypeDefinition(String typeName);
public StructureDefinition fetchRawProfile(String url);
public void setUcumService(UcumService ucumService);
public String getLinkForUrl(String corePath, String s);
public Map<String, byte[]> getBinaries();
/**
* Load relevant resources of the appropriate types (as specified by the loader) from the nominated package
*
* note that the package system uses lazy loading; the loader will be called later when the classes that use the context need the relevant resource
*
* @param pi - the package to load
* @param loader - an implemenation of IContextResourceLoader that knows how to read the resources in the package (e.g. for the appropriate version).
* @return the number of resources loaded
*/
int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException;
/**
* Load relevant resources of the appropriate types (as specified by the loader) from the nominated package
*
* note that the package system uses lazy loading; the loader will be called later when the classes that use the context need the relevant resource
*
* Deprecated - use the simpler method where the types come from the loader.
*
* @param pi - the package to load
* @param loader - an implemenation of IContextResourceLoader that knows how to read the resources in the package (e.g. for the appropriate version).
* @param types - which types of resources to load
* @return the number of resources loaded
*/
@Deprecated
int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FileNotFoundException, IOException, FHIRException;
/**
* Load relevant resources of the appropriate types (as specified by the loader) from the nominated package
*
* note that the package system uses lazy loading; the loader will be called later when the classes that use the context need the relevant resource
*
* This method also loads all the packages that the package depends on (recursively)
*
* @param pi - the package to load
* @param loader - an implemenation of IContextResourceLoader that knows how to read the resources in the package (e.g. for the appropriate version).
* @param pcm - used to find and load additional dependencies
* @return the number of resources loaded
*/
int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FileNotFoundException, IOException, FHIRException;
public boolean hasPackage(String id, String ver);
public boolean hasPackage(PackageVersion pack);
public PackageDetails getPackage(PackageVersion pack);
public int getClientRetryCount();
public IWorkerContext setClientRetryCount(int value);
public TimeTracker clock();
public PackageVersion getPackageForUrl(String url);
}

View File

@ -0,0 +1,776 @@
package org.hl7.fhir.r4b.context;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import org.fhir.ucum.UcumService;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r4b.context.IWorkerContext.CodingValidationRequest;
import org.hl7.fhir.r4b.context.TerminologyCache.CacheToken;
import org.hl7.fhir.r4b.formats.IParser;
import org.hl7.fhir.r4b.formats.ParserType;
import org.hl7.fhir.r4b.model.Bundle;
import org.hl7.fhir.r4b.model.CanonicalResource;
import org.hl7.fhir.r4b.model.CodeSystem;
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4b.model.CodeableConcept;
import org.hl7.fhir.r4b.model.Coding;
import org.hl7.fhir.r4b.model.ConceptMap;
import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r4b.model.Parameters;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.StructureMap;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r4b.utils.IResourceValidator;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import com.google.gson.JsonSyntaxException;
/**
* This is the standard interface used for access to underlying FHIR
* services through the tools and utilities provided by the reference
* implementation.
*
* The functionality it provides is
* - get access to parsers, validators, narrative builders etc
* (you can't create these directly because they need access
* to the right context for their information)
*
* - find resources that the tools need to carry out their tasks
*
* - provide access to terminology services they need.
* (typically, these terminology service requests are just
* passed through to the local implementation's terminology
* service)
*
* @author Grahame
*/
public interface IWorkerContext {
public class CodingValidationRequest {
private Coding coding;
private ValidationResult result;
private CacheToken cacheToken;
public CodingValidationRequest(Coding coding) {
super();
this.coding = coding;
}
public ValidationResult getResult() {
return result;
}
public void setResult(ValidationResult result) {
this.result = result;
}
public Coding getCoding() {
return coding;
}
public boolean hasResult() {
return result != null;
}
/**
* internal logic; external users of batch validation should ignore this property
*
* @return
*/
public CacheToken getCacheToken() {
return cacheToken;
}
/**
* internal logic; external users of batch validation should ignore this property
*
* @param cacheToken
*/
public void setCacheToken(CacheToken cacheToken) {
this.cacheToken = cacheToken;
}
}
public class PackageVersion {
private String id;
private String version;
public PackageVersion(String source) {
if (source == null) {
throw new Error("Source cannot be null");
}
if (!source.contains("#")) {
throw new FHIRException("Source ");
}
id = source.substring(0, source.indexOf("#"));
version = source.substring(source.indexOf("#")+1);
}
public PackageVersion(String id, String version) {
super();
this.id = id;
this.version = version;
}
public String getId() {
return id;
}
public String getVersion() {
return version;
}
}
public interface IContextResourceLoader {
/**
* @return List of the resource types that shoud be loaded
*/
String[] getTypes();
/**
* Request to actually load the resources and do whatever is required
*
* @param stream
* @param isJson
* @return A bundle because some single resources become multiple resources after loading
* @throws FHIRException
* @throws IOException
*/
Bundle loadBundle(InputStream stream, boolean isJson) throws FHIRException, IOException;
/**
* Load a single resources (lazy load)
*
* @param stream
* @param isJson
* @return
* @throws FHIRException - throw this if you a single resource can't be returned - can't lazy load in this circumstance
* @throws IOException
*/
Resource loadResource(InputStream stream, boolean isJson) throws FHIRException, IOException;
/**
* get the path for references to this resource.
* @param resource
* @return null if not tracking paths
*/
String getResourcePath(Resource resource);
/**
* called when a mew package is being loaded
*
* this is called by loadPacakgeAndDependencies when a new package is loaded
* @param npm
* @return
* @throws IOException
* @throws JsonSyntaxException
*/
IContextResourceLoader getNewLoader(NpmPackage npm) throws JsonSyntaxException, IOException;
}
/**
* Get the versions of the definitions loaded in context
* @return
*/
public String getVersion();
public String getSpecUrl();
// get the UCUM service (might not be available)
public UcumService getUcumService();
// -- Parsers (read and write instances) ----------------------------------------
/**
* Get a parser to read/write instances. Use the defined type (will be extended
* as further types are added, though the only currently anticipate type is RDF)
*
* XML/JSON - the standard renderers
* XHTML - render the narrative only (generate it if necessary)
*
* @param type
* @return
*/
public IParser getParser(ParserType type);
/**
* Get a parser to read/write instances. Determine the type
* from the stated type. Supported value for type:
* - the recommended MIME types
* - variants of application/xml and application/json
* - _format values xml, json
*
* @param type
* @return
*/
public IParser getParser(String type);
/**
* Get a JSON parser
*
* @return
*/
public IParser newJsonParser();
/**
* Get an XML parser
*
* @return
*/
public IParser newXmlParser();
/**
* Get a validator that can check whether a resource is valid
*
* @return a prepared generator
* @throws FHIRException
* @
*/
public IResourceValidator newValidator() throws FHIRException;
// -- resource fetchers ---------------------------------------------------
/**
* Find an identified resource. The most common use of this is to access the the
* standard conformance resources that are part of the standard - structure
* definitions, value sets, concept maps, etc.
*
* Also, the narrative generator uses this, and may access any kind of resource
*
* The URI is called speculatively for things that might exist, so not finding
* a matching resouce, return null, not an error
*
* The URI can have one of 3 formats:
* - a full URL e.g. http://acme.org/fhir/ValueSet/[id]
* - a relative URL e.g. ValueSet/[id]
* - a logical id e.g. [id]
*
* It's an error if the second form doesn't agree with class_. It's an
* error if class_ is null for the last form
*
* @param resource
* @param Reference
* @return
* @throws FHIRException
* @throws Exception
*/
public <T extends Resource> T fetchResource(Class<T> class_, String uri);
public <T extends Resource> T fetchResourceWithException(Class<T> class_, String uri) throws FHIRException;
/** has the same functionality as fetchResource, but passes in information about the source of the
* reference (this may affect resolution of version)
*
* @param <T>
* @param class_
* @param uri
* @param canonicalForSource
* @return
*/
public <T extends Resource> T fetchResource(Class<T> class_, String uri, CanonicalResource canonicalForSource);
/**
* Variation of fetchResource when you have a string type, and don't need the right class
*
* The URI can have one of 3 formats:
* - a full URL e.g. http://acme.org/fhir/ValueSet/[id]
* - a relative URL e.g. ValueSet/[id]
* - a logical id e.g. [id]
*
* if type == null, the URI can't be a simple logical id
*
* @param type
* @param uri
* @return
*/
public Resource fetchResourceById(String type, String uri);
/**
* find whether a resource is available.
*
* Implementations of the interface can assume that if hasResource ruturns
* true, the resource will usually be fetched subsequently
*
* @param class_
* @param uri
* @return
*/
public <T extends Resource> boolean hasResource(Class<T> class_, String uri);
/**
* cache a resource for later retrieval using fetchResource.
*
* Note that various context implementations will have their own ways of loading
* rseources, and not all need implement cacheResource.
*
* If the resource is loaded out of a package, call cacheResourceFromPackage instead
* @param res
* @throws FHIRException
*/
public void cacheResource(Resource res) throws FHIRException;
/**
* cache a resource for later retrieval using fetchResource.
*
* The package information is used to help manage the cache internally, and to
* help with reference resolution. Packages should be define using cachePackage (but don't have to be)
*
* Note that various context implementations will have their own ways of loading
* rseources, and not all need implement cacheResource
*
* @param res
* @throws FHIRException
*/
public void cacheResourceFromPackage(Resource res, PackageVersion packageDetails) throws FHIRException;
/**
* Inform the cache about package dependencies. This can be used to help resolve references
*
* Note that the cache doesn't load dependencies
*
* @param packageInfo
*/
public void cachePackage(PackageVersion packageDetails, List<PackageVersion> dependencies);
// -- profile services ---------------------------------------------------------
/**
* @return a list of the resource names defined for this version
*/
public List<String> getResourceNames();
/**
* @return a set of the resource names defined for this version
*/
public Set<String> getResourceNamesAsSet();
/**
* @return a list of the resource and type names defined for this version
*/
public List<String> getTypeNames();
/**
* @return a list of all structure definitions, with snapshots generated (if possible)
*/
public List<StructureDefinition> allStructures();
/**
* @return a list of all structure definitions, without trying to generate snapshots
*/
public List<StructureDefinition> getStructures();
/**
* @return a list of all conformance resources
*/
public List<CanonicalResource> allConformanceResources();
/**
* Given a structure definition, generate a snapshot (or regenerate it)
* @param p
* @throws DefinitionException
* @throws FHIRException
*/
public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException;
public void generateSnapshot(StructureDefinition mr, boolean ifLogical);
// -- Terminology services ------------------------------------------------------
/**
* Set the expansion parameters passed through the terminology server when txServer calls are made
*
* Note that the Validation Options override these when they are specified on validateCode
*/
public Parameters getExpansionParameters();
/**
* Get the expansion parameters passed through the terminology server when txServer calls are made
*
* Note that the Validation Options override these when they are specified on validateCode
*/
public void setExpansionProfile(Parameters expParameters);
// these are the terminology services used internally by the tools
/**
* Find the code system definition for the nominated system uri.
* return null if there isn't one (then the tool might try
* supportsSystem)
*
* @param system
* @return
*/
public CodeSystem fetchCodeSystem(String system);
/**
* True if the underlying terminology service provider will do
* expansion and code validation for the terminology. Corresponds
* to the extension
*
* http://hl7.org/fhir/StructureDefinition/capabilitystatement-supported-system
*
* in the Conformance resource
*
* @param system
* @return
* @throws Exception
*/
public boolean supportsSystem(String system) throws TerminologyServiceException;
/**
* find concept maps for a source
* @param url
* @return
* @throws FHIRException
*/
public List<ConceptMap> findMapsForSource(String url) throws FHIRException;
/**
* ValueSet Expansion - see $expand
*
* @param source
* @return
*/
public ValueSetExpansionOutcome expandVS(ValueSet source, boolean cacheOk, boolean heiarchical);
/**
* ValueSet Expansion - see $expand, but resolves the binding first
*
* @param source
* @return
* @throws FHIRException
*/
public ValueSetExpansionOutcome expandVS(ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) throws FHIRException;
/**
* Value set expanion inside the internal expansion engine - used
* for references to supported system (see "supportsSystem") for
* which there is no value set.
*
* @param inc
* @return
* @throws FHIRException
*/
ValueSetExpansionOutcome expandVS(ConceptSetComponent inc, boolean hierarchical) throws TerminologyServiceException;
Locale getLocale();
void setLocale(Locale locale);
String formatMessage(String theMessage, Object... theMessageArguments);
void setValidationMessageLanguage(Locale locale);
class ValidationResult {
private ConceptDefinitionComponent definition;
private IssueSeverity severity;
private String message;
private TerminologyServiceErrorClass errorClass;
private String txLink;
public ValidationResult(IssueSeverity severity, String message) {
this.severity = severity;
this.message = message;
}
public ValidationResult(ConceptDefinitionComponent definition) {
this.definition = definition;
}
public ValidationResult(IssueSeverity severity, String message, ConceptDefinitionComponent definition) {
this.severity = severity;
this.message = message;
this.definition = definition;
}
public ValidationResult(IssueSeverity severity, String message, TerminologyServiceErrorClass errorClass) {
this.severity = severity;
this.message = message;
this.errorClass = errorClass;
}
public boolean isOk() {
return severity == null || severity == IssueSeverity.INFORMATION || severity == IssueSeverity.WARNING;
}
public String getDisplay() {
// We don't want to return question-marks because that prevents something more useful from being displayed (e.g. the code) if there's no display value
// return definition == null ? "??" : definition.getDisplay();
return definition == null ? null : definition.getDisplay();
}
public ConceptDefinitionComponent asConceptDefinition() {
return definition;
}
public IssueSeverity getSeverity() {
return severity;
}
public String getMessage() {
return message;
}
public boolean IsNoService() {
return errorClass == TerminologyServiceErrorClass.NOSERVICE;
}
public TerminologyServiceErrorClass getErrorClass() {
return errorClass;
}
public ValidationResult setSeverity(IssueSeverity severity) {
this.severity = severity;
return this;
}
public ValidationResult setMessage(String message) {
this.message = message;
return this;
}
public String getTxLink() {
return txLink;
}
public ValidationResult setTxLink(String txLink) {
this.txLink = txLink;
return this;
}
}
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* in this case, the system will be inferred from the value set. It's an error to call this one without the value set
*
* @param options - validation options (required)
* @param code he code to validate (required)
* @param vs the applicable valueset (required)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, String code, ValueSet vs);
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param options - validation options (required)
* @param system - equals Coding.system (required)
* @param code - equals Coding.code (required)
* @param display - equals Coding.display (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, String system, String code, String display);
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* @param options - validation options (required)
* @param system - equals Coding.system (required)
* @param code - equals Coding.code (required)
* @param display - equals Coding.display (optional)
* @param vs the applicable valueset (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, String system, String code, String display, ValueSet vs);
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* Note that this doesn't validate binding strength (e.g. is just text allowed?)
*
* @param options - validation options (required)
* @param code - CodeableConcept to validate
* @param vs the applicable valueset (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, CodeableConcept code, ValueSet vs);
/**
* Validation of a code - consult the terminology infrstructure and/or service
* to see whether it is known. If known, return a description of it
*
* note: always return a result, with either an error or a code description
*
* corresponds to 2 terminology service calls: $validate-code and $lookup
*
* in this case, the system will be inferred from the value set. It's an error to call this one without the value set
*
* @param options - validation options (required)
* @param code - Coding to validate
* @param vs the applicable valueset (optional)
* @return
*/
public ValidationResult validateCode(ValidationOptions options, Coding code, ValueSet vs);
public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs);
/**
* returns the recommended tla for the type (from the structure definitions)
*
* @param name
* @return
*/
public String getAbbreviation(String name);
/**
* translate an OID to a URI (look through known NamingSystems)
* @param code
* @return
*/
public String oid2Uri(String code);
/**
* @return true if the contxt has a terminology caching service internally
*/
public boolean hasCache();
public interface ILoggingService {
public enum LogCategory {
INIT,
PROGRESS,
TX,
CONTEXT,
GENERATE,
HTML
}
public void logMessage(String message); // status messages, always display
public void logDebugMessage(LogCategory category, String message); // verbose; only when debugging
}
public void setLogger(ILoggingService logger);
public ILoggingService getLogger();
public boolean isNoTerminologyServer();
public Set<String> getCodeSystemsUsed();
public TranslationServices translator();
public List<StructureMap> listTransforms();
public StructureMap getTransform(String url);
public String getOverrideVersionNs();
public void setOverrideVersionNs(String value);
public StructureDefinition fetchTypeDefinition(String typeName);
public StructureDefinition fetchRawProfile(String url);
public void setUcumService(UcumService ucumService);
public String getLinkForUrl(String corePath, String s);
public Map<String, byte[]> getBinaries();
/**
* Load relevant resources of the appropriate types (as specified by the loader) from the nominated package
*
* note that the package system uses lazy loading; the loader will be called later when the classes that use the context need the relevant resource
*
* @param pi - the package to load
* @param loader - an implemenation of IContextResourceLoader that knows how to read the resources in the package (e.g. for the appropriate version).
* @return the number of resources loaded
*/
int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException;
/**
* Load relevant resources of the appropriate types (as specified by the loader) from the nominated package
*
* note that the package system uses lazy loading; the loader will be called later when the classes that use the context need the relevant resource
*
* Deprecated - use the simpler method where the types come from the loader.
*
* @param pi - the package to load
* @param loader - an implemenation of IContextResourceLoader that knows how to read the resources in the package (e.g. for the appropriate version).
* @param types - which types of resources to load
* @return the number of resources loaded
*/
@Deprecated
int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FileNotFoundException, IOException, FHIRException;
/**
* Load relevant resources of the appropriate types (as specified by the loader) from the nominated package
*
* note that the package system uses lazy loading; the loader will be called later when the classes that use the context need the relevant resource
*
* This method also loads all the packages that the package depends on (recursively)
*
* @param pi - the package to load
* @param loader - an implemenation of IContextResourceLoader that knows how to read the resources in the package (e.g. for the appropriate version).
* @param pcm - used to find and load additional dependencies
* @return the number of resources loaded
*/
int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FileNotFoundException, IOException, FHIRException;
public boolean hasPackage(String id, String ver);
public int getClientRetryCount();
public IWorkerContext setClientRetryCount(int value);
public TimeTracker clock();
}

View File

@ -0,0 +1,867 @@
package org.hl7.fhir.r4b.context;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.ByteArrayInputStream;
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.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.conformance.ProfileUtilities.ProfileKnowledgeProvider;
import org.hl7.fhir.r4b.context.CanonicalResourceManager.CanonicalResourceProxy;
import org.hl7.fhir.r4b.context.IWorkerContext.ILoggingService.LogCategory;
import org.hl7.fhir.r4b.formats.IParser;
import org.hl7.fhir.r4b.formats.JsonParser;
import org.hl7.fhir.r4b.formats.ParserType;
import org.hl7.fhir.r4b.formats.XmlParser;
import org.hl7.fhir.r4b.model.Bundle;
import org.hl7.fhir.r4b.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4b.model.CanonicalResource;
import org.hl7.fhir.r4b.model.CapabilityStatement;
import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r4b.model.Questionnaire;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.ResourceType;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r4b.model.StructureMap;
import org.hl7.fhir.r4b.model.StructureMap.StructureMapModelMode;
import org.hl7.fhir.r4b.model.StructureMap.StructureMapStructureComponent;
import org.hl7.fhir.r4b.terminologies.TerminologyClient;
import org.hl7.fhir.r4b.utils.validation.IResourceValidator;
import org.hl7.fhir.r4b.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.CSFileInputStream;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import ca.uhn.fhir.parser.DataFormatException;
/*
* This is a stand alone implementation of worker context for use inside a tool.
* It loads from the validation package (validation-min.xml.zip), and has a
* very light client to connect to an open unauthenticated terminology service
*/
public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider {
public static class PackageResourceLoader extends CanonicalResourceProxy {
private String filename;
private IContextResourceLoader loader;
public PackageResourceLoader(PackageResourceInformation pri, IContextResourceLoader loader) {
super(pri.getType(), pri.getId(), pri.getUrl(),pri.getVersion());
this.filename = pri.getFilename();
this.loader = loader;
}
@Override
public CanonicalResource loadResource() {
try {
FileInputStream f = new FileInputStream(filename);
try {
if (loader != null) {
return (CanonicalResource) loader.loadResource(f, true);
} else {
return (CanonicalResource) new JsonParser().parse(f);
}
} finally {
f.close();
}
} catch (Exception e) {
throw new FHIRException("Error loading "+filename+": "+e.getMessage(), e);
}
}
}
public interface ILoadFilter {
boolean isOkToLoad(Resource resource);
boolean isOkToLoad(String resourceType);
}
public interface IValidatorFactory {
IResourceValidator makeValidator(IWorkerContext ctxt) throws FHIRException;
IResourceValidator makeValidator(IWorkerContext ctxts, XVerExtensionManager xverManager) throws FHIRException;
}
private Questionnaire questionnaire;
private String revision;
private String date;
private IValidatorFactory validatorFactory;
private boolean ignoreProfileErrors;
private boolean progress;
private List<String> loadedPackages = new ArrayList<String>();
private boolean canNoTS;
private XVerExtensionManager xverManager;
public SimpleWorkerContext() throws FileNotFoundException, IOException, FHIRException {
super();
}
public SimpleWorkerContext(Locale locale) throws FileNotFoundException, IOException, FHIRException {
super(locale);
}
public SimpleWorkerContext(SimpleWorkerContext other) throws FileNotFoundException, IOException, FHIRException {
super();
copy(other);
}
public SimpleWorkerContext(SimpleWorkerContext other, Locale locale) throws FileNotFoundException, IOException, FHIRException {
super(locale);
copy(other);
}
protected void copy(SimpleWorkerContext other) {
super.copy(other);
questionnaire = other.questionnaire;
binaries.putAll(other.binaries);
version = other.version;
revision = other.revision;
date = other.date;
validatorFactory = other.validatorFactory;
}
public List<String> getLoadedPackages() {
return loadedPackages;
}
// -- Initializations
/**
* Load the working context from the validation pack
*
* @param path
* filename of the validation pack
* @return
* @throws IOException
* @throws FileNotFoundException
* @throws FHIRException
* @throws Exception
*/
public static SimpleWorkerContext fromPack(String path) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.loadFromPack(path, null);
return res;
}
public static SimpleWorkerContext fromPackage(NpmPackage pi, boolean allowDuplicates) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.setAllowLoadingDuplicates(allowDuplicates);
res.loadFromPackage(pi, null);
return res;
}
public static SimpleWorkerContext fromPackage(NpmPackage pi) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.loadFromPackage(pi, null);
return res;
}
public static SimpleWorkerContext fromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.setAllowLoadingDuplicates(true);
res.version = pi.getNpm().get("version").getAsString();
res.loadFromPackage(pi, loader);
res.finishLoading();
return res;
}
public static SimpleWorkerContext fromPack(String path, boolean allowDuplicates) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.setAllowLoadingDuplicates(allowDuplicates);
res.loadFromPack(path, null);
return res;
}
public static SimpleWorkerContext fromPack(String path, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.loadFromPack(path, loader);
return res;
}
public static SimpleWorkerContext fromClassPath() throws IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.json.zip"), null);
return res;
}
public static SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException {
return fromClassPath(name, false);
}
public static SimpleWorkerContext fromClassPath(String name, boolean allowDuplicates) throws IOException, FHIRException {
InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name);
SimpleWorkerContext res = new SimpleWorkerContext();
res.setAllowLoadingDuplicates(allowDuplicates);
res.loadFromStream(s, null);
return res;
}
public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source, IContextResourceLoader loader, PackageVersion pi) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
for (String name : source.keySet()) {
try {
res.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name)), loader, null, pi);
} catch (Exception e) {
System.out.println("Error loading "+name+": "+e.getMessage());
throw new FHIRException("Error loading "+name+": "+e.getMessage(), e);
}
}
return res;
}
public static SimpleWorkerContext fromNothing() throws FileNotFoundException, FHIRException, IOException {
SimpleWorkerContext res = new SimpleWorkerContext();
return res;
}
private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException {
if (name.endsWith(".xml"))
loadFromFile(stream, name, loader, filter);
else if (name.endsWith(".json"))
loadFromFileJson(stream, name, loader, filter, pi);
else if (name.equals("version.info"))
readVersionInfo(stream);
else
loadBytes(name, stream);
}
public String connectToTSServer(TerminologyClient client, String log) {
try {
tlog("Connect to "+client.getAddress());
txClient = client;
if (log != null && log.endsWith(".txt")) {
txLog = new TextClientLogger(log);
} else {
txLog = new HTMLClientLogger(log);
}
txClient.setLogger(txLog);
txClient.setUserAgent(userAgent);
CapabilityStatement cps = txClient.getCapabilitiesStatementQuick();
setTxCaps(txClient.getTerminologyCapabilities());
return cps.getSoftware().getVersion();
} catch (Exception e) {
throw new FHIRException(formatMessage(canNoTS ? I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER_USE_PARAMETER_TX_NA_TUN_RUN_WITHOUT_USING_TERMINOLOGY_SERVICES_TO_VALIDATE_LOINC_SNOMED_ICDX_ETC_ERROR__ : I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER, e.getMessage()), e);
}
}
public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader) throws IOException, FHIRException {
loadFromFile(stream, name, loader, null);
}
public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter) throws IOException, FHIRException {
Resource f;
try {
if (loader != null)
f = loader.loadBundle(stream, false);
else {
XmlParser xml = new XmlParser();
f = xml.parse(stream);
}
} catch (DataFormatException e1) {
throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
} catch (Exception e1) {
throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
}
if (f instanceof Bundle) {
Bundle bnd = (Bundle) f;
for (BundleEntryComponent e : bnd.getEntry()) {
if (e.getFullUrl() == null) {
logger.logDebugMessage(LogCategory.CONTEXT, "unidentified resource in " + name+" (no fullUrl)");
}
if (filter == null || filter.isOkToLoad(e.getResource())) {
String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
if (path != null) {
e.getResource().setUserData("path", path);
}
cacheResource(e.getResource());
}
}
} else if (f instanceof CanonicalResource) {
if (filter == null || filter.isOkToLoad(f)) {
String path = loader != null ? loader.getResourcePath(f) : null;
if (path != null) {
f.setUserData("path", path);
}
cacheResource(f);
}
}
}
private void loadFromFileJson(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException {
Bundle f = null;
try {
if (loader != null)
f = loader.loadBundle(stream, true);
else {
JsonParser json = new JsonParser();
Resource r = json.parse(stream);
if (r instanceof Bundle)
f = (Bundle) r;
else if (filter == null || filter.isOkToLoad(f)) {
cacheResourceFromPackage(r, pi);
}
}
} catch (FHIRFormatError e1) {
throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1);
}
if (f != null)
for (BundleEntryComponent e : f.getEntry()) {
if (filter == null || filter.isOkToLoad(e.getResource())) {
String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
if (path != null) {
e.getResource().setUserData("path", path);
}
cacheResourceFromPackage(e.getResource(), pi);
}
}
}
private void loadFromPack(String path, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
loadFromStream(new CSFileInputStream(path), loader);
}
@Override
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
return loadFromPackageInt(pi, loader, loader == null ? defaultTypesToLoad() : loader.getTypes());
}
public static String[] defaultTypesToLoad() {
// there's no penalty for listing resources that don't exist, so we just all the relevant possibilities for all versions
return new String[] {"CodeSystem", "ValueSet", "ConceptMap", "NamingSystem",
"StructureDefinition", "StructureMap",
"SearchParameter", "OperationDefinition", "CapabilityStatement", "Conformance",
"Questionnaire", "ImplementationGuide", "Measure" };
}
@Override
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FileNotFoundException, IOException, FHIRException {
return loadFromPackageInt(pi, loader, types);
}
@Override
public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FileNotFoundException, IOException, FHIRException {
return loadFromPackageAndDependenciesInt(pi, loader, pcm, pi.name()+"#"+pi.version());
}
public int loadFromPackageAndDependenciesInt(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm, String path) throws FileNotFoundException, IOException, FHIRException {
int t = 0;
for (String e : pi.dependencies()) {
if (!loadedPackages.contains(e) && !VersionUtilities.isCorePackage(e)) {
NpmPackage npm = pcm.loadPackage(e);
if (!VersionUtilities.versionsMatch(version, npm.fhirVersion())) {
System.out.println(formatMessage(I18nConstants.PACKAGE_VERSION_MISMATCH, e, version, npm.fhirVersion(), path));
}
t = t + loadFromPackageAndDependenciesInt(npm, loader.getNewLoader(npm), pcm, path+" -> "+npm.name()+"#"+npm.version());
}
}
t = t + loadFromPackageInt(pi, loader, loader.getTypes());
return t;
}
public int loadFromPackageInt(NpmPackage pi, IContextResourceLoader loader, String... types) throws FileNotFoundException, IOException, FHIRException {
int t = 0;
if (progress) {
System.out.println("Load Package "+pi.name()+"#"+pi.version());
}
if (loadedPackages.contains(pi.id()+"#"+pi.version())) {
return 0;
}
loadedPackages.add(pi.id()+"#"+pi.version());
if ((types == null || types.length == 0) && loader != null) {
types = loader.getTypes();
}
if (VersionUtilities.isR2Ver(pi.fhirVersion()) || !pi.canLazyLoad()) {
// can't lazy load R2 because of valueset/codesystem implementation
if (types.length == 0) {
types = new String[] { "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" };
}
for (String s : pi.listResources(types)) {
try {
loadDefinitionItem(s, pi.load("package", s), loader, null, new PackageVersion(pi.id(), pi.version()));
t++;
} catch (Exception e) {
throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e);
}
}
} else {
if (types.length == 0) {
types = new String[] { "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem", "Measures" };
}
for (PackageResourceInformation pri : pi.listIndexedResources(types)) {
try {
registerResourceFromPackage(new PackageResourceLoader(pri, loader), new PackageVersion(pi.id(), pi.version()));
t++;
} catch (FHIRException e) {
throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e);
}
}
}
for (String s : pi.list("other")) {
binaries.put(s, TextFile.streamToBytes(pi.load("other", s)));
}
if (version == null) {
version = pi.version();
}
return t;
}
public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException {
loadDefinitionItem(file, new CSFileInputStream(file), loader, null, null);
}
private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException {
ZipInputStream zip = new ZipInputStream(stream);
ZipEntry ze;
while ((ze = zip.getNextEntry()) != null) {
loadDefinitionItem(ze.getName(), zip, loader, null, null);
zip.closeEntry();
}
zip.close();
}
private void readVersionInfo(InputStream stream) throws IOException, DefinitionException {
byte[] bytes = IOUtils.toByteArray(stream);
binaries.put("version.info", bytes);
String[] vi = new String(bytes).split("\\r?\\n");
for (String s : vi) {
if (s.startsWith("version=")) {
if (version == null)
version = s.substring(8);
else if (!version.equals(s.substring(8)))
throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8)));
}
if (s.startsWith("revision="))
revision = s.substring(9);
if (s.startsWith("date="))
date = s.substring(5);
}
}
private void loadBytes(String name, InputStream stream) throws IOException {
byte[] bytes = IOUtils.toByteArray(stream);
binaries.put(name, bytes);
}
@Override
public IParser getParser(ParserType type) {
switch (type) {
case JSON: return newJsonParser();
case XML: return newXmlParser();
default:
throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString()));
}
}
@Override
public IParser getParser(String type) {
if (type.equalsIgnoreCase("JSON"))
return new JsonParser();
if (type.equalsIgnoreCase("XML"))
return new XmlParser();
throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString()));
}
@Override
public IParser newJsonParser() {
return new JsonParser();
}
@Override
public IParser newXmlParser() {
return new XmlParser();
}
@Override
public IResourceValidator newValidator() throws FHIRException {
if (validatorFactory == null)
throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED));
return validatorFactory.makeValidator(this, xverManager);
}
@Override
public List<String> getResourceNames() {
List<String> result = new ArrayList<String>();
for (StructureDefinition sd : listStructures()) {
if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
result.add(sd.getName());
}
Collections.sort(result);
return result;
}
@Override
public List<String> getTypeNames() {
List<String> result = new ArrayList<String>();
for (StructureDefinition sd : listStructures()) {
if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
result.add(sd.getName());
}
Collections.sort(result);
return result;
}
@Override
public String getAbbreviation(String name) {
return "xxx";
}
@Override
public boolean isDatatype(String typeSimple) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isResource(String t) {
StructureDefinition sd;
try {
sd = fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t);
} catch (Exception e) {
return false;
}
if (sd == null)
return false;
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT)
return false;
return sd.getKind() == StructureDefinitionKind.RESOURCE;
}
@Override
public boolean hasLinkFor(String typeSimple) {
return false;
}
@Override
public String getLinkFor(String corePath, String typeSimple) {
return null;
}
@Override
public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) {
return null;
}
@Override
public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) {
return null;
}
@Override
public String getLinkForProfile(StructureDefinition profile, String url) {
return null;
}
public Questionnaire getQuestionnaire() {
return questionnaire;
}
public void setQuestionnaire(Questionnaire questionnaire) {
this.questionnaire = questionnaire;
}
@Override
public List<StructureDefinition> allStructures() {
List<StructureDefinition> result = new ArrayList<StructureDefinition>();
Set<StructureDefinition> set = new HashSet<StructureDefinition>();
for (StructureDefinition sd : listStructures()) {
if (!set.contains(sd)) {
try {
generateSnapshot(sd);
// new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
} catch (Exception e) {
System.out.println("Unable to generate snapshot for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
if (true) {
e.printStackTrace();
}
}
result.add(sd);
set.add(sd);
}
}
return result;
}
public void loadBinariesFromFolder(String folder) throws FileNotFoundException, Exception {
for (String n : new File(folder).list()) {
loadBytes(n, new FileInputStream(Utilities.path(folder, n)));
}
}
public void loadBinariesFromFolder(NpmPackage pi) throws FileNotFoundException, Exception {
for (String n : pi.list("other")) {
loadBytes(n, pi.load("other", n));
}
}
public void loadFromFolder(String folder) throws FileNotFoundException, Exception {
for (String n : new File(folder).list()) {
if (n.endsWith(".json"))
loadFromFile(Utilities.path(folder, n), new JsonParser());
else if (n.endsWith(".xml"))
loadFromFile(Utilities.path(folder, n), new XmlParser());
}
}
private void loadFromFile(String filename, IParser p) throws FileNotFoundException, Exception {
Resource r;
try {
r = p.parse(new FileInputStream(filename));
if (r.getResourceType() == ResourceType.Bundle) {
for (BundleEntryComponent e : ((Bundle) r).getEntry()) {
cacheResource(e.getResource());
}
} else {
cacheResource(r);
}
} catch (Exception e) {
return;
}
}
@Override
public boolean prependLinks() {
return false;
}
@Override
public boolean hasCache() {
return true;
}
@Override
public String getVersion() {
return version;
}
public List<StructureMap> findTransformsforSource(String url) {
List<StructureMap> res = new ArrayList<StructureMap>();
for (StructureMap map : listTransforms()) {
boolean match = false;
boolean ok = true;
for (StructureMapStructureComponent t : map.getStructure()) {
if (t.getMode() == StructureMapModelMode.SOURCE) {
match = match || t.getUrl().equals(url);
ok = ok && t.getUrl().equals(url);
}
}
if (match && ok)
res.add(map);
}
return res;
}
public IValidatorFactory getValidatorFactory() {
return validatorFactory;
}
public void setValidatorFactory(IValidatorFactory validatorFactory) {
this.validatorFactory = validatorFactory;
}
@Override
public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
T r = super.fetchResource(class_, uri);
if (r instanceof StructureDefinition) {
StructureDefinition p = (StructureDefinition)r;
try {
generateSnapshot(p);
} catch (Exception e) {
// not sure what to do in this case?
System.out.println("Unable to generate snapshot for "+uri+": "+e.getMessage());
}
}
return r;
}
@Override
public StructureDefinition fetchRawProfile(String uri) {
StructureDefinition r = super.fetchResource(StructureDefinition.class, uri);
return r;
}
@Override
public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException {
generateSnapshot(p, false);
}
@Override
public void generateSnapshot(StructureDefinition p, boolean logical) throws DefinitionException, FHIRException {
if ((!p.hasSnapshot() || isProfileNeedsRegenerate(p) ) && (logical || p.getKind() != StructureDefinitionKind.LOGICAL)) {
if (!p.hasBaseDefinition())
throw new DefinitionException(formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl()));
StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBaseDefinition());
if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) {
sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion());
}
if (sd == null) {
throw new DefinitionException(formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition()));
}
List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
List<String> errors = new ArrayList<String>();
ProfileUtilities pu = new ProfileUtilities(this, msgs, this);
pu.setAutoFixSliceNames(true);
pu.setThrowException(false);
if (xverManager == null) {
xverManager = new XVerExtensionManager(this);
}
pu.setXver(xverManager);
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
pu.sortDifferential(sd, p, p.getUrl(), errors, true);
}
pu.setDebug(false);
for (String err : errors)
msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR));
pu.generateSnapshot(sd, p, p.getUrl(), Utilities.extractBaseUrl(sd.getUserString("path")), p.getName());
for (ValidationMessage msg : msgs) {
if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL)
throw new DefinitionException(formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage()));
}
if (!p.hasSnapshot())
throw new FHIRException(formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl()));
pu = null;
}
}
// work around the fact that some Implementation guides were published with old snapshot generators that left invalid snapshots behind.
private boolean isProfileNeedsRegenerate(StructureDefinition p) {
boolean needs = !p.hasUserData("hack.regnerated") && Utilities.existsInList(p.getUrl(), "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaireresponse");
if (needs) {
p.setUserData("hack.regnerated", "yes");
}
return needs;
}
public boolean isIgnoreProfileErrors() {
return ignoreProfileErrors;
}
public void setIgnoreProfileErrors(boolean ignoreProfileErrors) {
this.ignoreProfileErrors = ignoreProfileErrors;
}
public String listMapUrls() {
return Utilities.listCanonicalUrls(transforms.keys());
}
public boolean isProgress() {
return progress;
}
public void setProgress(boolean progress) {
this.progress = progress;
}
public void setClock(TimeTracker tt) {
clock = tt;
}
public boolean isCanNoTS() {
return canNoTS;
}
public void setCanNoTS(boolean canNoTS) {
this.canNoTS = canNoTS;
}
public XVerExtensionManager getXVer() {
if (xverManager == null) {
xverManager = new XVerExtensionManager(this);
}
return xverManager;
}
public void cachePackage(PackageVersion packageDetails, List<PackageVersion> dependencies) {
// nothing yet
}
@Override
public boolean hasPackage(String id, String ver) {
return loadedPackages.contains(id+"#"+ver);
}
public boolean hasPackage(String idAndver) {
return loadedPackages.contains(idAndver);
}
@Override
public void cachePackage(PackageDetails packageDetails, List<PackageVersion> dependencies) {
// TODO Auto-generated method stub
}
@Override
public boolean hasPackage(PackageVersion pack) {
return false;
}
@Override
public PackageDetails getPackage(PackageVersion pack) {
return null;
}
}

View File

@ -0,0 +1,847 @@
package org.hl7.fhir.r4b.context;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.ByteArrayInputStream;
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.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.conformance.ProfileUtilities.ProfileKnowledgeProvider;
import org.hl7.fhir.r4b.context.CanonicalResourceManager.CanonicalResourceProxy;
import org.hl7.fhir.r4b.context.IWorkerContext.ILoggingService.LogCategory;
import org.hl7.fhir.r4b.context.SimpleWorkerContext.PackageResourceLoader;
import org.hl7.fhir.r4b.formats.IParser;
import org.hl7.fhir.r4b.formats.JsonParser;
import org.hl7.fhir.r4b.formats.ParserType;
import org.hl7.fhir.r4b.formats.XmlParser;
import org.hl7.fhir.r4b.model.Bundle;
import org.hl7.fhir.r4b.model.Bundle.BundleEntryComponent;
import org.hl7.fhir.r4b.model.CanonicalResource;
import org.hl7.fhir.r4b.model.CapabilityStatement;
import org.hl7.fhir.r4b.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r4b.model.Questionnaire;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.ResourceType;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r4b.model.StructureMap;
import org.hl7.fhir.r4b.model.StructureMap.StructureMapModelMode;
import org.hl7.fhir.r4b.model.StructureMap.StructureMapStructureComponent;
import org.hl7.fhir.r4b.terminologies.TerminologyClient;
import org.hl7.fhir.r4b.utils.IResourceValidator;
import org.hl7.fhir.r4b.utils.XVerExtensionManager;
import org.hl7.fhir.utilities.CSFileInputStream;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.npm.BasePackageCacheManager;
import org.hl7.fhir.utilities.npm.FilesystemPackageCacheManager;
import org.hl7.fhir.utilities.npm.NpmPackage;
import org.hl7.fhir.utilities.npm.NpmPackage.PackageResourceInformation;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
import com.google.gson.JsonObject;
import ca.uhn.fhir.parser.DataFormatException;
/*
* This is a stand alone implementation of worker context for use inside a tool.
* It loads from the validation package (validation-min.xml.zip), and has a
* very light client to connect to an open unauthenticated terminology service
*/
public class SimpleWorkerContext extends BaseWorkerContext implements IWorkerContext, ProfileKnowledgeProvider {
public static class PackageResourceLoader extends CanonicalResourceProxy {
private String filename;
private IContextResourceLoader loader;
public PackageResourceLoader(PackageResourceInformation pri, IContextResourceLoader loader) {
super(pri.getType(), pri.getId(), pri.getUrl(),pri.getVersion());
this.filename = pri.getFilename();
this.loader = loader;
}
@Override
public CanonicalResource loadResource() {
try {
FileInputStream f = new FileInputStream(filename);
try {
if (loader != null) {
return (CanonicalResource) loader.loadResource(f, true);
} else {
return (CanonicalResource) new JsonParser().parse(f);
}
} finally {
f.close();
}
} catch (Exception e) {
throw new FHIRException("Error loading "+filename+": "+e.getMessage(), e);
}
}
}
public interface ILoadFilter {
boolean isOkToLoad(Resource resource);
boolean isOkToLoad(String resourceType);
}
public interface IValidatorFactory {
IResourceValidator makeValidator(IWorkerContext ctxt) throws FHIRException;
IResourceValidator makeValidator(IWorkerContext ctxts, XVerExtensionManager xverManager) throws FHIRException;
}
private Questionnaire questionnaire;
private String revision;
private String date;
private IValidatorFactory validatorFactory;
private boolean ignoreProfileErrors;
private boolean progress;
private List<String> loadedPackages = new ArrayList<String>();
private boolean canNoTS;
private XVerExtensionManager xverManager;
public SimpleWorkerContext() throws FileNotFoundException, IOException, FHIRException {
super();
}
public SimpleWorkerContext(Locale locale) throws FileNotFoundException, IOException, FHIRException {
super(locale);
}
public SimpleWorkerContext(SimpleWorkerContext other) throws FileNotFoundException, IOException, FHIRException {
super();
copy(other);
}
public SimpleWorkerContext(SimpleWorkerContext other, Locale locale) throws FileNotFoundException, IOException, FHIRException {
super(locale);
copy(other);
}
protected void copy(SimpleWorkerContext other) {
super.copy(other);
questionnaire = other.questionnaire;
binaries.putAll(other.binaries);
version = other.version;
revision = other.revision;
date = other.date;
validatorFactory = other.validatorFactory;
}
public List<String> getLoadedPackages() {
return loadedPackages;
}
// -- Initializations
/**
* Load the working context from the validation pack
*
* @param path
* filename of the validation pack
* @return
* @throws IOException
* @throws FileNotFoundException
* @throws FHIRException
* @throws Exception
*/
public static SimpleWorkerContext fromPack(String path) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.loadFromPack(path, null);
return res;
}
public static SimpleWorkerContext fromPackage(NpmPackage pi, boolean allowDuplicates) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.setAllowLoadingDuplicates(allowDuplicates);
res.loadFromPackage(pi, null);
return res;
}
public static SimpleWorkerContext fromPackage(NpmPackage pi) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.loadFromPackage(pi, null);
return res;
}
public static SimpleWorkerContext fromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.setAllowLoadingDuplicates(true);
res.version = pi.getNpm().get("version").getAsString();
res.loadFromPackage(pi, loader);
res.finishLoading();
return res;
}
public static SimpleWorkerContext fromPack(String path, boolean allowDuplicates) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.setAllowLoadingDuplicates(allowDuplicates);
res.loadFromPack(path, null);
return res;
}
public static SimpleWorkerContext fromPack(String path, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.loadFromPack(path, loader);
return res;
}
public static SimpleWorkerContext fromClassPath() throws IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
res.loadFromStream(SimpleWorkerContext.class.getResourceAsStream("validation.json.zip"), null);
return res;
}
public static SimpleWorkerContext fromClassPath(String name) throws IOException, FHIRException {
return fromClassPath(name, false);
}
public static SimpleWorkerContext fromClassPath(String name, boolean allowDuplicates) throws IOException, FHIRException {
InputStream s = SimpleWorkerContext.class.getResourceAsStream("/" + name);
SimpleWorkerContext res = new SimpleWorkerContext();
res.setAllowLoadingDuplicates(allowDuplicates);
res.loadFromStream(s, null);
return res;
}
public static SimpleWorkerContext fromDefinitions(Map<String, byte[]> source, IContextResourceLoader loader, PackageVersion pi) throws FileNotFoundException, IOException, FHIRException {
SimpleWorkerContext res = new SimpleWorkerContext();
for (String name : source.keySet()) {
try {
res.loadDefinitionItem(name, new ByteArrayInputStream(source.get(name)), loader, null, pi);
} catch (Exception e) {
System.out.println("Error loading "+name+": "+e.getMessage());
throw new FHIRException("Error loading "+name+": "+e.getMessage(), e);
}
}
return res;
}
public static SimpleWorkerContext fromNothing() throws FileNotFoundException, FHIRException, IOException {
SimpleWorkerContext res = new SimpleWorkerContext();
return res;
}
private void loadDefinitionItem(String name, InputStream stream, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException {
if (name.endsWith(".xml"))
loadFromFile(stream, name, loader, filter);
else if (name.endsWith(".json"))
loadFromFileJson(stream, name, loader, filter, pi);
else if (name.equals("version.info"))
readVersionInfo(stream);
else
loadBytes(name, stream);
}
public String connectToTSServer(TerminologyClient client, String log) {
try {
tlog("Connect to "+client.getAddress());
txClient = client;
if (log != null && log.endsWith(".txt")) {
txLog = new TextClientLogger(log);
} else {
txLog = new HTMLClientLogger(log);
}
txClient.setLogger(txLog);
CapabilityStatement cps = txClient.getCapabilitiesStatementQuick();
setTxCaps(txClient.getTerminologyCapabilities());
return cps.getSoftware().getVersion();
} catch (Exception e) {
throw new FHIRException(formatMessage(canNoTS ? I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER_USE_PARAMETER_TX_NA_TUN_RUN_WITHOUT_USING_TERMINOLOGY_SERVICES_TO_VALIDATE_LOINC_SNOMED_ICDX_ETC_ERROR__ : I18nConstants.UNABLE_TO_CONNECT_TO_TERMINOLOGY_SERVER, e.getMessage()), e);
}
}
public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader) throws IOException, FHIRException {
loadFromFile(stream, name, loader, null);
}
public void loadFromFile(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter) throws IOException, FHIRException {
Resource f;
try {
if (loader != null)
f = loader.loadBundle(stream, false);
else {
XmlParser xml = new XmlParser();
f = xml.parse(stream);
}
} catch (DataFormatException e1) {
throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
} catch (Exception e1) {
throw new org.hl7.fhir.exceptions.FHIRFormatError(formatMessage(I18nConstants.ERROR_PARSING_, name, e1.getMessage()), e1);
}
if (f instanceof Bundle) {
Bundle bnd = (Bundle) f;
for (BundleEntryComponent e : bnd.getEntry()) {
if (e.getFullUrl() == null) {
logger.logDebugMessage(LogCategory.CONTEXT, "unidentified resource in " + name+" (no fullUrl)");
}
if (filter == null || filter.isOkToLoad(e.getResource())) {
String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
if (path != null) {
e.getResource().setUserData("path", path);
}
cacheResource(e.getResource());
}
}
} else if (f instanceof CanonicalResource) {
if (filter == null || filter.isOkToLoad(f)) {
String path = loader != null ? loader.getResourcePath(f) : null;
if (path != null) {
f.setUserData("path", path);
}
cacheResource(f);
}
}
}
private void loadFromFileJson(InputStream stream, String name, IContextResourceLoader loader, ILoadFilter filter, PackageVersion pi) throws IOException, FHIRException {
Bundle f = null;
try {
if (loader != null)
f = loader.loadBundle(stream, true);
else {
JsonParser json = new JsonParser();
Resource r = json.parse(stream);
if (r instanceof Bundle)
f = (Bundle) r;
else if (filter == null || filter.isOkToLoad(f)) {
cacheResourceFromPackage(r, pi);
}
}
} catch (FHIRFormatError e1) {
throw new org.hl7.fhir.exceptions.FHIRFormatError(e1.getMessage(), e1);
}
if (f != null)
for (BundleEntryComponent e : f.getEntry()) {
if (filter == null || filter.isOkToLoad(e.getResource())) {
String path = loader != null ? loader.getResourcePath(e.getResource()) : null;
if (path != null) {
e.getResource().setUserData("path", path);
}
cacheResourceFromPackage(e.getResource(), pi);
}
}
}
private void loadFromPack(String path, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
loadFromStream(new CSFileInputStream(path), loader);
}
@Override
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FileNotFoundException, IOException, FHIRException {
return loadFromPackageInt(pi, loader, loader == null ? defaultTypesToLoad() : loader.getTypes());
}
public static String[] defaultTypesToLoad() {
// there's no penalty for listing resources that don't exist, so we just all the relevant possibilities for all versions
return new String[] {"CodeSystem", "ValueSet", "ConceptMap", "NamingSystem",
"StructureDefinition", "StructureMap",
"SearchParameter", "OperationDefinition", "CapabilityStatement", "Conformance",
"Questionnaire", "ImplementationGuide", "Measure" };
}
@Override
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FileNotFoundException, IOException, FHIRException {
return loadFromPackageInt(pi, loader, types);
}
@Override
public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FileNotFoundException, IOException, FHIRException {
return loadFromPackageAndDependenciesInt(pi, loader, pcm, pi.name()+"#"+pi.version());
}
public int loadFromPackageAndDependenciesInt(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm, String path) throws FileNotFoundException, IOException, FHIRException {
int t = 0;
for (String e : pi.dependencies()) {
if (!loadedPackages.contains(e) && !VersionUtilities.isCorePackage(e)) {
NpmPackage npm = pcm.loadPackage(e);
if (!version.equals(npm.fhirVersion())) {
System.out.println(formatMessage(I18nConstants.PACKAGE_VERSION_MISMATCH, e, version, npm.fhirVersion(), path));
}
t = t + loadFromPackageAndDependenciesInt(npm, loader.getNewLoader(npm), pcm, path+" -> "+npm.name()+"#"+npm.version());
}
}
t = t + loadFromPackageInt(pi, loader, loader.getTypes());
return t;
}
public int loadFromPackageInt(NpmPackage pi, IContextResourceLoader loader, String... types) throws FileNotFoundException, IOException, FHIRException {
int t = 0;
if (progress) {
System.out.println("Load Package "+pi.name()+"#"+pi.version());
}
if (loadedPackages.contains(pi.id()+"#"+pi.version())) {
return 0;
}
loadedPackages.add(pi.id()+"#"+pi.version());
if ((types == null || types.length == 0) && loader != null) {
types = loader.getTypes();
}
if (VersionUtilities.isR2Ver(pi.fhirVersion()) || !pi.canLazyLoad()) {
// can't lazy load R2 because of valueset/codesystem implementation
if (types.length == 0) {
types = new String[] { "StructureDefinition", "ValueSet", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem" };
}
for (String s : pi.listResources(types)) {
try {
loadDefinitionItem(s, pi.load("package", s), loader, null, new PackageVersion(pi.id(), pi.version()));
t++;
} catch (FHIRException e) {
throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, s, pi.name(), pi.version(), e.getMessage()), e);
}
}
} else {
if (types.length == 0) {
types = new String[] { "StructureDefinition", "ValueSet", "CodeSystem", "SearchParameter", "OperationDefinition", "Questionnaire", "ConceptMap", "StructureMap", "NamingSystem", "Measures" };
}
for (PackageResourceInformation pri : pi.listIndexedResources(types)) {
try {
registerResourceFromPackage(new PackageResourceLoader(pri, loader), new PackageVersion(pi.id(), pi.version()));
t++;
} catch (FHIRException e) {
throw new FHIRException(formatMessage(I18nConstants.ERROR_READING__FROM_PACKAGE__, pri.getFilename(), pi.name(), pi.version(), e.getMessage()), e);
}
}
}
for (String s : pi.list("other")) {
binaries.put(s, TextFile.streamToBytes(pi.load("other", s)));
}
if (version == null) {
version = pi.version();
}
return t;
}
public void loadFromFile(String file, IContextResourceLoader loader) throws IOException, FHIRException {
loadDefinitionItem(file, new CSFileInputStream(file), loader, null, null);
}
private void loadFromStream(InputStream stream, IContextResourceLoader loader) throws IOException, FHIRException {
ZipInputStream zip = new ZipInputStream(stream);
ZipEntry ze;
while ((ze = zip.getNextEntry()) != null) {
loadDefinitionItem(ze.getName(), zip, loader, null, null);
zip.closeEntry();
}
zip.close();
}
private void readVersionInfo(InputStream stream) throws IOException, DefinitionException {
byte[] bytes = IOUtils.toByteArray(stream);
binaries.put("version.info", bytes);
String[] vi = new String(bytes).split("\\r?\\n");
for (String s : vi) {
if (s.startsWith("version=")) {
if (version == null)
version = s.substring(8);
else if (!version.equals(s.substring(8)))
throw new DefinitionException(formatMessage(I18nConstants.VERSION_MISMATCH_THE_CONTEXT_HAS_VERSION__LOADED_AND_THE_NEW_CONTENT_BEING_LOADED_IS_VERSION_, version, s.substring(8)));
}
if (s.startsWith("revision="))
revision = s.substring(9);
if (s.startsWith("date="))
date = s.substring(5);
}
}
private void loadBytes(String name, InputStream stream) throws IOException {
byte[] bytes = IOUtils.toByteArray(stream);
binaries.put(name, bytes);
}
@Override
public IParser getParser(ParserType type) {
switch (type) {
case JSON: return newJsonParser();
case XML: return newXmlParser();
default:
throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString()));
}
}
@Override
public IParser getParser(String type) {
if (type.equalsIgnoreCase("JSON"))
return new JsonParser();
if (type.equalsIgnoreCase("XML"))
return new XmlParser();
throw new Error(formatMessage(I18nConstants.PARSER_TYPE__NOT_SUPPORTED, type.toString()));
}
@Override
public IParser newJsonParser() {
return new JsonParser();
}
@Override
public IParser newXmlParser() {
return new XmlParser();
}
@Override
public IResourceValidator newValidator() throws FHIRException {
if (validatorFactory == null)
throw new Error(formatMessage(I18nConstants.NO_VALIDATOR_CONFIGURED));
return validatorFactory.makeValidator(this, xverManager);
}
@Override
public List<String> getResourceNames() {
List<String> result = new ArrayList<String>();
for (StructureDefinition sd : listStructures()) {
if (sd.getKind() == StructureDefinitionKind.RESOURCE && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
result.add(sd.getName());
}
Collections.sort(result);
return result;
}
@Override
public List<String> getTypeNames() {
List<String> result = new ArrayList<String>();
for (StructureDefinition sd : listStructures()) {
if (sd.getKind() != StructureDefinitionKind.LOGICAL && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION)
result.add(sd.getName());
}
Collections.sort(result);
return result;
}
@Override
public String getAbbreviation(String name) {
return "xxx";
}
@Override
public boolean isDatatype(String typeSimple) {
// TODO Auto-generated method stub
return false;
}
@Override
public boolean isResource(String t) {
StructureDefinition sd;
try {
sd = fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t);
} catch (Exception e) {
return false;
}
if (sd == null)
return false;
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT)
return false;
return sd.getKind() == StructureDefinitionKind.RESOURCE;
}
@Override
public boolean hasLinkFor(String typeSimple) {
return false;
}
@Override
public String getLinkFor(String corePath, String typeSimple) {
return null;
}
@Override
public BindingResolution resolveBinding(StructureDefinition profile, ElementDefinitionBindingComponent binding, String path) {
return null;
}
@Override
public BindingResolution resolveBinding(StructureDefinition profile, String url, String path) {
return null;
}
@Override
public String getLinkForProfile(StructureDefinition profile, String url) {
return null;
}
public Questionnaire getQuestionnaire() {
return questionnaire;
}
public void setQuestionnaire(Questionnaire questionnaire) {
this.questionnaire = questionnaire;
}
@Override
public List<StructureDefinition> allStructures() {
List<StructureDefinition> result = new ArrayList<StructureDefinition>();
Set<StructureDefinition> set = new HashSet<StructureDefinition>();
for (StructureDefinition sd : listStructures()) {
if (!set.contains(sd)) {
try {
generateSnapshot(sd);
// new XmlParser().setOutputStyle(OutputStyle.PRETTY).compose(new FileOutputStream(Utilities.path("[tmp]", "snapshot", tail(sd.getUrl())+".xml")), sd);
} catch (Exception e) {
System.out.println("Unable to generate snapshot for "+tail(sd.getUrl()) +" from "+tail(sd.getBaseDefinition())+" because "+e.getMessage());
if (true) {
e.printStackTrace();
}
}
result.add(sd);
set.add(sd);
}
}
return result;
}
public void loadBinariesFromFolder(String folder) throws FileNotFoundException, Exception {
for (String n : new File(folder).list()) {
loadBytes(n, new FileInputStream(Utilities.path(folder, n)));
}
}
public void loadBinariesFromFolder(NpmPackage pi) throws FileNotFoundException, Exception {
for (String n : pi.list("other")) {
loadBytes(n, pi.load("other", n));
}
}
public void loadFromFolder(String folder) throws FileNotFoundException, Exception {
for (String n : new File(folder).list()) {
if (n.endsWith(".json"))
loadFromFile(Utilities.path(folder, n), new JsonParser());
else if (n.endsWith(".xml"))
loadFromFile(Utilities.path(folder, n), new XmlParser());
}
}
private void loadFromFile(String filename, IParser p) throws FileNotFoundException, Exception {
Resource r;
try {
r = p.parse(new FileInputStream(filename));
if (r.getResourceType() == ResourceType.Bundle) {
for (BundleEntryComponent e : ((Bundle) r).getEntry()) {
cacheResource(e.getResource());
}
} else {
cacheResource(r);
}
} catch (Exception e) {
return;
}
}
@Override
public boolean prependLinks() {
return false;
}
@Override
public boolean hasCache() {
return true;
}
@Override
public String getVersion() {
return version;
}
public List<StructureMap> findTransformsforSource(String url) {
List<StructureMap> res = new ArrayList<StructureMap>();
for (StructureMap map : listTransforms()) {
boolean match = false;
boolean ok = true;
for (StructureMapStructureComponent t : map.getStructure()) {
if (t.getMode() == StructureMapModelMode.SOURCE) {
match = match || t.getUrl().equals(url);
ok = ok && t.getUrl().equals(url);
}
}
if (match && ok)
res.add(map);
}
return res;
}
public IValidatorFactory getValidatorFactory() {
return validatorFactory;
}
public void setValidatorFactory(IValidatorFactory validatorFactory) {
this.validatorFactory = validatorFactory;
}
@Override
public <T extends Resource> T fetchResource(Class<T> class_, String uri) {
T r = super.fetchResource(class_, uri);
if (r instanceof StructureDefinition) {
StructureDefinition p = (StructureDefinition)r;
try {
generateSnapshot(p);
} catch (Exception e) {
// not sure what to do in this case?
System.out.println("Unable to generate snapshot for "+uri+": "+e.getMessage());
}
}
return r;
}
@Override
public StructureDefinition fetchRawProfile(String uri) {
StructureDefinition r = super.fetchResource(StructureDefinition.class, uri);
return r;
}
@Override
public void generateSnapshot(StructureDefinition p) throws DefinitionException, FHIRException {
generateSnapshot(p, false);
}
@Override
public void generateSnapshot(StructureDefinition p, boolean logical) throws DefinitionException, FHIRException {
if (!p.hasSnapshot() && (logical || p.getKind() != StructureDefinitionKind.LOGICAL)) {
if (!p.hasBaseDefinition())
throw new DefinitionException(formatMessage(I18nConstants.PROFILE___HAS_NO_BASE_AND_NO_SNAPSHOT, p.getName(), p.getUrl()));
StructureDefinition sd = fetchResource(StructureDefinition.class, p.getBaseDefinition());
if (sd == null && "http://hl7.org/fhir/StructureDefinition/Base".equals(p.getBaseDefinition())) {
sd = ProfileUtilities.makeBaseDefinition(p.getFhirVersion());
}
if (sd == null) {
throw new DefinitionException(formatMessage(I18nConstants.PROFILE___BASE__COULD_NOT_BE_RESOLVED, p.getName(), p.getUrl(), p.getBaseDefinition()));
}
List<ValidationMessage> msgs = new ArrayList<ValidationMessage>();
List<String> errors = new ArrayList<String>();
ProfileUtilities pu = new ProfileUtilities(this, msgs, this);
pu.setAutoFixSliceNames(true);
pu.setThrowException(false);
if (xverManager == null) {
xverManager = new XVerExtensionManager(this);
}
pu.setXver(xverManager);
if (sd.getDerivation() == TypeDerivationRule.CONSTRAINT) {
pu.sortDifferential(sd, p, p.getUrl(), errors, true);
}
pu.setDebug(false);
for (String err : errors)
msgs.add(new ValidationMessage(Source.ProfileValidator, IssueType.EXCEPTION, p.getUserString("path"), "Error sorting Differential: "+err, ValidationMessage.IssueSeverity.ERROR));
pu.generateSnapshot(sd, p, p.getUrl(), Utilities.extractBaseUrl(sd.getUserString("path")), p.getName());
for (ValidationMessage msg : msgs) {
if ((!ignoreProfileErrors && msg.getLevel() == ValidationMessage.IssueSeverity.ERROR) || msg.getLevel() == ValidationMessage.IssueSeverity.FATAL)
throw new DefinitionException(formatMessage(I18nConstants.PROFILE___ELEMENT__ERROR_GENERATING_SNAPSHOT_, p.getName(), p.getUrl(), msg.getLocation(), msg.getMessage()));
}
if (!p.hasSnapshot())
throw new FHIRException(formatMessage(I18nConstants.PROFILE___ERROR_GENERATING_SNAPSHOT, p.getName(), p.getUrl()));
pu = null;
}
}
public boolean isIgnoreProfileErrors() {
return ignoreProfileErrors;
}
public void setIgnoreProfileErrors(boolean ignoreProfileErrors) {
this.ignoreProfileErrors = ignoreProfileErrors;
}
public String listMapUrls() {
return Utilities.listCanonicalUrls(transforms.keys());
}
public boolean isProgress() {
return progress;
}
public void setProgress(boolean progress) {
this.progress = progress;
}
@Override
public boolean hasPackage(String id, String ver) {
return loadedPackages.contains(id+"#"+ver);
}
public boolean hasPackage(String idAndver) {
return loadedPackages.contains(idAndver);
}
public void setClock(TimeTracker tt) {
clock = tt;
}
public boolean isCanNoTS() {
return canNoTS;
}
public void setCanNoTS(boolean canNoTS) {
this.canNoTS = canNoTS;
}
public XVerExtensionManager getXVer() {
if (xverManager == null) {
xverManager = new XVerExtensionManager(this);
}
return xverManager;
}
<<<<<<< HEAD
}
=======
}
>>>>>>> 45530c0f5d95f642c9eaf68e48650d3a281d6736

View File

@ -0,0 +1,535 @@
package org.hl7.fhir.r4b.context;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.formats.JsonParser;
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4b.model.CodeableConcept;
import org.hl7.fhir.r4b.model.Coding;
import org.hl7.fhir.r4b.model.UriType;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
/**
* This implements a two level cache.
* - a temporary cache for remmbering previous local operations
* - a persistent cache for rembering tx server operations
*
* the cache is a series of pairs: a map, and a list. the map is the loaded cache, the list is the persiistent cache, carefully maintained in order for version control consistency
*
* @author graha
*
*/
public class TerminologyCache {
public static final boolean TRANSIENT = false;
public static final boolean PERMANENT = true;
private static final String NAME_FOR_NO_SYSTEM = "all-systems";
private static final String ENTRY_MARKER = "-------------------------------------------------------------------------------------";
private static final String BREAK = "####";
public class CacheToken {
private String name;
private String key;
private String request;
public void setName(String n) {
if (name == null)
name = n;
else if (!n.equals(name))
name = NAME_FOR_NO_SYSTEM;
}
}
private class CacheEntry {
private String request;
private boolean persistent;
private ValidationResult v;
private ValueSetExpansionOutcome e;
}
private class NamedCache {
private String name;
private List<CacheEntry> list = new ArrayList<CacheEntry>(); // persistent entries
private Map<String, CacheEntry> map = new HashMap<String, CacheEntry>();
}
private Object lock;
private String folder;
private Map<String, NamedCache> caches = new HashMap<String, NamedCache>();
private static boolean noCaching;
// use lock from the context
public TerminologyCache(Object lock, String folder) throws FileNotFoundException, IOException, FHIRException {
super();
this.lock = lock;
this.folder = folder;
if (folder != null)
load();
}
public void clear() {
caches.clear();
}
public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) {
CacheToken ct = new CacheToken();
if (code.hasSystem())
ct.name = getNameForSystem(code.getSystem());
else
ct.name = NAME_FOR_NO_SYSTEM;
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY);
ValueSet vsc = getVSEssense(vs);
try {
ct.request = "{\"code\" : "+json.composeString(code, "code")+", \"valueSet\" :"+(vsc == null ? "null" : extracted(json, vsc))+(options == null ? "" : ", "+options.toJson())+"}";
} catch (IOException e) {
throw new Error(e);
}
ct.key = String.valueOf(hashNWS(ct.request));
return ct;
}
public String extracted(JsonParser json, ValueSet vsc) throws IOException {
String s = null;
if (vsc.getExpansion().getContains().size() > 1000 || vsc.getCompose().getIncludeFirstRep().getConcept().size() > 1000) {
s = Integer.toString(vsc.hashCode()); // turn caching off - hack efficiency optimisation
} else {
s = json.composeString(vsc);
}
return s;
}
public CacheToken generateValidationToken(ValidationOptions options, CodeableConcept code, ValueSet vs) {
CacheToken ct = new CacheToken();
for (Coding c : code.getCoding()) {
if (c.hasSystem())
ct.setName(getNameForSystem(c.getSystem()));
}
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY);
ValueSet vsc = getVSEssense(vs);
try {
ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"valueSet\" :"+extracted(json, vsc)+(options == null ? "" : ", "+options.toJson())+"}";
} catch (IOException e) {
throw new Error(e);
}
ct.key = String.valueOf(hashNWS(ct.request));
return ct;
}
public ValueSet getVSEssense(ValueSet vs) {
if (vs == null)
return null;
ValueSet vsc = new ValueSet();
vsc.setCompose(vs.getCompose());
if (vs.hasExpansion()) {
vsc.getExpansion().getParameter().addAll(vs.getExpansion().getParameter());
vsc.getExpansion().getContains().addAll(vs.getExpansion().getContains());
}
return vsc;
}
public CacheToken generateExpandToken(ValueSet vs, boolean heirarchical) {
CacheToken ct = new CacheToken();
ValueSet vsc = getVSEssense(vs);
for (ConceptSetComponent inc : vs.getCompose().getInclude())
if (inc.hasSystem())
ct.setName(getNameForSystem(inc.getSystem()));
for (ConceptSetComponent inc : vs.getCompose().getExclude())
if (inc.hasSystem())
ct.setName(getNameForSystem(inc.getSystem()));
for (ValueSetExpansionContainsComponent inc : vs.getExpansion().getContains())
if (inc.hasSystem())
ct.setName(getNameForSystem(inc.getSystem()));
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY);
try {
ct.request = "{\"hierarchical\" : "+(heirarchical ? "true" : "false")+", \"valueSet\" :"+extracted(json, vsc)+"}\r\n";
} catch (IOException e) {
throw new Error(e);
}
ct.key = String.valueOf(hashNWS(ct.request));
return ct;
}
private String getNameForSystem(String system) {
if (system.equals("http://snomed.info/sct"))
return "snomed";
if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm"))
return "rxnorm";
if (system.equals("http://loinc.org"))
return "loinc";
if (system.equals("http://unitsofmeasure.org"))
return "ucum";
if (system.startsWith("http://hl7.org/fhir/sid/"))
return system.substring(24).replace("/", "");
if (system.startsWith("urn:iso:std:iso:"))
return "iso"+system.substring(16).replace(":", "");
if (system.startsWith("http://terminology.hl7.org/CodeSystem/"))
return system.substring(38).replace("/", "");
if (system.startsWith("http://hl7.org/fhir/"))
return system.substring(20).replace("/", "");
if (system.equals("urn:ietf:bcp:47"))
return "lang";
if (system.equals("urn:ietf:bcp:13"))
return "mimetypes";
if (system.equals("urn:iso:std:iso:11073:10101"))
return "11073";
if (system.equals("http://dicom.nema.org/resources/ontology/DCM"))
return "dicom";
return system.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X");
}
public NamedCache getNamedCache(CacheToken cacheToken) {
NamedCache nc = caches.get(cacheToken.name);
if (nc == null) {
nc = new NamedCache();
nc.name = cacheToken.name;
caches.put(nc.name, nc);
}
return nc;
}
public ValueSetExpansionOutcome getExpansion(CacheToken cacheToken) {
synchronized (lock) {
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = nc.map.get(cacheToken.key);
if (e == null)
return null;
else
return e.e;
}
}
public void cacheExpansion(CacheToken cacheToken, ValueSetExpansionOutcome res, boolean persistent) {
synchronized (lock) {
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = new CacheEntry();
e.request = cacheToken.request;
e.persistent = persistent;
e.e = res;
store(cacheToken, persistent, nc, e);
}
}
public void store(CacheToken cacheToken, boolean persistent, NamedCache nc, CacheEntry e) {
if (noCaching) {
return;
}
boolean n = nc.map.containsKey(cacheToken.key);
nc.map.put(cacheToken.key, e);
if (persistent) {
if (n) {
for (int i = nc.list.size()- 1; i>= 0; i--) {
if (nc.list.get(i).request.equals(e.request)) {
nc.list.remove(i);
}
}
}
nc.list.add(e);
save(nc);
}
}
public ValidationResult getValidation(CacheToken cacheToken) {
if (cacheToken.key == null) {
return null;
}
synchronized (lock) {
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = nc.map.get(cacheToken.key);
if (e == null)
return null;
else
return e.v;
}
}
public void cacheValidation(CacheToken cacheToken, ValidationResult res, boolean persistent) {
if (cacheToken.key != null) {
synchronized (lock) {
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = new CacheEntry();
e.request = cacheToken.request;
e.persistent = persistent;
e.v = res;
store(cacheToken, persistent, nc, e);
}
}
}
// persistence
public void save() {
}
private void save(NamedCache nc) {
if (folder == null)
return;
try {
OutputStreamWriter sw = new OutputStreamWriter(new FileOutputStream(Utilities.path(folder, nc.name+".cache")), "UTF-8");
sw.write(ENTRY_MARKER+"\r\n");
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY);
for (CacheEntry ce : nc.list) {
sw.write(ce.request.trim());
sw.write(BREAK+"\r\n");
if (ce.e != null) {
sw.write("e: {\r\n");
if (ce.e.getValueset() != null)
sw.write(" \"valueSet\" : "+json.composeString(ce.e.getValueset()).trim()+",\r\n");
sw.write(" \"error\" : \""+Utilities.escapeJson(ce.e.getError()).trim()+"\"\r\n}\r\n");
} else {
sw.write("v: {\r\n");
boolean first = true;
if (ce.v.getDisplay() != null) {
if (first) first = false; else sw.write(",\r\n");
sw.write(" \"display\" : \""+Utilities.escapeJson(ce.v.getDisplay()).trim()+"\"");
}
if (ce.v.getCode() != null) {
if (first) first = false; else sw.write(",\r\n");
sw.write(" \"code\" : \""+Utilities.escapeJson(ce.v.getCode()).trim()+"\"");
}
if (ce.v.getSystem() != null) {
if (first) first = false; else sw.write(",\r\n");
sw.write(" \"system\" : \""+Utilities.escapeJson(ce.v.getSystem()).trim()+"\"");
}
if (ce.v.getSeverity() != null) {
if (first) first = false; else sw.write(",\r\n");
sw.write(" \"severity\" : "+"\""+ce.v.getSeverity().toCode().trim()+"\""+"");
}
if (ce.v.getMessage() != null) {
if (first) first = false; else sw.write(",\r\n");
sw.write(" \"error\" : \""+Utilities.escapeJson(ce.v.getMessage()).trim()+"\"");
}
if (ce.v.getErrorClass() != null) {
if (first) first = false; else sw.write(",\r\n");
sw.write(" \"class\" : \""+Utilities.escapeJson(ce.v.getErrorClass().toString())+"\"");
}
if (ce.v.getDefinition() != null) {
if (first) first = false; else sw.write(",\r\n");
sw.write(" \"definition\" : \""+Utilities.escapeJson(ce.v.getDefinition()).trim()+"\"");
}
sw.write("\r\n}\r\n");
}
sw.write(ENTRY_MARKER+"\r\n");
}
sw.close();
} catch (Exception e) {
System.out.println("error saving "+nc.name+": "+e.getMessage());
}
}
private void load() throws FHIRException {
for (String fn : new File(folder).list()) {
if (fn.endsWith(".cache") && !fn.equals("validation.cache")) {
int c = 0;
try {
String title = fn.substring(0, fn.lastIndexOf("."));
NamedCache nc = new NamedCache();
nc.name = title;
caches.put(title, nc);
String src = TextFile.fileToString(Utilities.path(folder, fn));
if (src.startsWith("?"))
src = src.substring(1);
int i = src.indexOf(ENTRY_MARKER);
while (i > -1) {
c++;
String s = src.substring(0, i);
src = src.substring(i+ENTRY_MARKER.length()+1);
i = src.indexOf(ENTRY_MARKER);
if (!Utilities.noString(s)) {
int j = s.indexOf(BREAK);
String q = s.substring(0, j);
String p = s.substring(j+BREAK.length()+1).trim();
CacheEntry ce = new CacheEntry();
ce.persistent = true;
ce.request = q;
boolean e = p.charAt(0) == 'e';
p = p.substring(3);
JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(p);
String error = loadJS(o.get("error"));
if (e) {
if (o.has("valueSet"))
ce.e = new ValueSetExpansionOutcome((ValueSet) new JsonParser().parse(o.getAsJsonObject("valueSet")), error, TerminologyServiceErrorClass.UNKNOWN);
else
ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN);
} else {
String t = loadJS(o.get("severity"));
IssueSeverity severity = t == null ? null : IssueSeverity.fromCode(t);
String display = loadJS(o.get("display"));
String code = loadJS(o.get("code"));
String system = loadJS(o.get("system"));
String definition = loadJS(o.get("definition"));
t = loadJS(o.get("class"));
TerminologyServiceErrorClass errorClass = t == null ? null : TerminologyServiceErrorClass.valueOf(t) ;
ce.v = new ValidationResult(severity, error, system, new ConceptDefinitionComponent().setDisplay(display).setDefinition(definition).setCode(code)).setErrorClass(errorClass);
}
nc.map.put(String.valueOf(hashNWS(ce.request)), ce);
nc.list.add(ce);
}
}
} catch (Exception e) {
throw new FHIRException("Error loading "+fn+": "+e.getMessage()+" entry "+c, e);
}
}
}
}
private String loadJS(JsonElement e) {
if (e == null)
return null;
if (!(e instanceof JsonPrimitive))
return null;
String s = e.getAsString();
if ("".equals(s))
return null;
return s;
}
private String hashNWS(String s) {
s = StringUtils.remove(s, ' ');
s = StringUtils.remove(s, '\n');
s = StringUtils.remove(s, '\r');
return String.valueOf(s.hashCode());
}
// management
public TerminologyCache copy() {
// TODO Auto-generated method stub
return null;
}
public String summary(ValueSet vs) {
if (vs == null)
return "null";
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (ConceptSetComponent cc : vs.getCompose().getInclude())
b.append("Include "+getIncSummary(cc));
for (ConceptSetComponent cc : vs.getCompose().getExclude())
b.append("Exclude "+getIncSummary(cc));
return b.toString();
}
private String getIncSummary(ConceptSetComponent cc) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (UriType vs : cc.getValueSet())
b.append(vs.asStringValue());
String vsd = b.length() > 0 ? " where the codes are in the value sets ("+b.toString()+")" : "";
String system = cc.getSystem();
if (cc.hasConcept())
return Integer.toString(cc.getConcept().size())+" codes from "+system+vsd;
if (cc.hasFilter()) {
String s = "";
for (ConceptSetFilterComponent f : cc.getFilter()) {
if (!Utilities.noString(s))
s = s + " & ";
s = s + f.getProperty()+" "+(f.hasOp() ? f.getOp().toCode() : "?")+" "+f.getValue();
}
return "from "+system+" where "+s+vsd;
}
return "All codes from "+system+vsd;
}
public String summary(Coding code) {
return code.getSystem()+"#"+code.getCode()+": \""+code.getDisplay()+"\"";
}
public String summary(CodeableConcept code) {
StringBuilder b = new StringBuilder();
b.append("{");
boolean first = true;
for (Coding c : code.getCoding()) {
if (first) first = false; else b.append(",");
b.append(summary(c));
}
b.append("}: \"");
b.append(code.getText());
b.append("\"");
return b.toString();
}
public static boolean isNoCaching() {
return noCaching;
}
public static void setNoCaching(boolean noCaching) {
TerminologyCache.noCaching = noCaching;
}
public void removeCS(String url) {
synchronized (lock) {
String name = getNameForSystem(url);
if (caches.containsKey(name)) {
caches.remove(name);
}
}
}
public String getFolder() {
return folder;
}
}

View File

@ -0,0 +1,506 @@
package org.hl7.fhir.r4b.context;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.context.IWorkerContext.ValidationResult;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.formats.JsonParser;
import org.hl7.fhir.r4b.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r4b.model.CodeableConcept;
import org.hl7.fhir.r4b.model.Coding;
import org.hl7.fhir.r4b.model.UriType;
import org.hl7.fhir.r4b.model.ValueSet;
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetComponent;
import org.hl7.fhir.r4b.model.ValueSet.ConceptSetFilterComponent;
import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.TerminologyServiceErrorClass;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationOptions;
import com.google.gson.JsonElement;
import com.google.gson.JsonNull;
import com.google.gson.JsonObject;
import com.google.gson.JsonPrimitive;
/**
* This implements a two level cache.
* - a temporary cache for remmbering previous local operations
* - a persistent cache for rembering tx server operations
*
* the cache is a series of pairs: a map, and a list. the map is the loaded cache, the list is the persiistent cache, carefully maintained in order for version control consistency
*
* @author graha
*
*/
public class TerminologyCache {
public static final boolean TRANSIENT = false;
public static final boolean PERMANENT = true;
private static final String NAME_FOR_NO_SYSTEM = "all-systems";
private static final String ENTRY_MARKER = "-------------------------------------------------------------------------------------";
private static final String BREAK = "####";
public class CacheToken {
private String name;
private String key;
private String request;
public void setName(String n) {
if (name == null)
name = n;
else if (!n.equals(name))
name = NAME_FOR_NO_SYSTEM;
}
}
private class CacheEntry {
private String request;
private boolean persistent;
private ValidationResult v;
private ValueSetExpansionOutcome e;
}
private class NamedCache {
private String name;
private List<CacheEntry> list = new ArrayList<CacheEntry>(); // persistent entries
private Map<String, CacheEntry> map = new HashMap<String, CacheEntry>();
}
private Object lock;
private String folder;
private Map<String, NamedCache> caches = new HashMap<String, NamedCache>();
private static boolean noCaching;
// use lock from the context
public TerminologyCache(Object lock, String folder) throws FileNotFoundException, IOException, FHIRException {
super();
this.lock = lock;
this.folder = folder;
if (folder != null)
load();
}
public CacheToken generateValidationToken(ValidationOptions options, Coding code, ValueSet vs) {
CacheToken ct = new CacheToken();
if (code.hasSystem())
ct.name = getNameForSystem(code.getSystem());
else
ct.name = NAME_FOR_NO_SYSTEM;
// JsonParser json = new JsonParser();
// json.setOutputStyle(OutputStyle.PRETTY);
// ValueSet vsc = getVSEssense(vs);
// try {
// ct.request = "{\"code\" : "+json.composeString(code, "code")+", \"valueSet\" :"+(vsc == null ? "null" : extracted(json, vsc))+(options == null ? "" : ", "+options.toJson())+"}";
// } catch (IOException e) {
// throw new Error(e);
// }
// ct.key = String.valueOf(hashNWS(ct.request));
if (vs != null && vs.hasUrl()) {
ct.key = vs.getUrl()+"#"+vs.getVersion();
} else {
ct.key = null;
}
return ct;
}
public String extracted(JsonParser json, ValueSet vsc) throws IOException {
String s = null;
if (vsc.getExpansion().getContains().size() > 1000 || vsc.getCompose().getIncludeFirstRep().getConcept().size() > 1000) {
s = Integer.toString(vsc.hashCode()); // turn caching off - hack efficiency optimisation
} else {
s = json.composeString(vsc);
}
return s;
}
public CacheToken generateValidationToken(ValidationOptions options, CodeableConcept code, ValueSet vs) {
CacheToken ct = new CacheToken();
for (Coding c : code.getCoding()) {
if (c.hasSystem())
ct.setName(getNameForSystem(c.getSystem()));
}
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY);
ValueSet vsc = getVSEssense(vs);
try {
ct.request = "{\"code\" : "+json.composeString(code, "codeableConcept")+", \"valueSet\" :"+extracted(json, vsc)+(options == null ? "" : ", "+options.toJson())+"}";
} catch (IOException e) {
throw new Error(e);
}
ct.key = String.valueOf(hashNWS(ct.request));
return ct;
}
public ValueSet getVSEssense(ValueSet vs) {
if (vs == null)
return null;
ValueSet vsc = new ValueSet();
vsc.setCompose(vs.getCompose());
if (vs.hasExpansion()) {
vsc.getExpansion().getParameter().addAll(vs.getExpansion().getParameter());
vsc.getExpansion().getContains().addAll(vs.getExpansion().getContains());
}
return vsc;
}
public CacheToken generateExpandToken(ValueSet vs, boolean heirarchical) {
CacheToken ct = new CacheToken();
ValueSet vsc = getVSEssense(vs);
for (ConceptSetComponent inc : vs.getCompose().getInclude())
if (inc.hasSystem())
ct.setName(getNameForSystem(inc.getSystem()));
for (ConceptSetComponent inc : vs.getCompose().getExclude())
if (inc.hasSystem())
ct.setName(getNameForSystem(inc.getSystem()));
for (ValueSetExpansionContainsComponent inc : vs.getExpansion().getContains())
if (inc.hasSystem())
ct.setName(getNameForSystem(inc.getSystem()));
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY);
try {
ct.request = "{\"hierarchical\" : "+(heirarchical ? "true" : "false")+", \"valueSet\" :"+extracted(json, vsc)+"}\r\n";
} catch (IOException e) {
throw new Error(e);
}
ct.key = String.valueOf(hashNWS(ct.request));
return ct;
}
private String getNameForSystem(String system) {
if (system.equals("http://snomed.info/sct"))
return "snomed";
if (system.equals("http://www.nlm.nih.gov/research/umls/rxnorm"))
return "rxnorm";
if (system.equals("http://loinc.org"))
return "loinc";
if (system.equals("http://unitsofmeasure.org"))
return "ucum";
if (system.startsWith("http://hl7.org/fhir/sid/"))
return system.substring(24).replace("/", "");
if (system.startsWith("urn:iso:std:iso:"))
return "iso"+system.substring(16).replace(":", "");
if (system.startsWith("http://terminology.hl7.org/CodeSystem/"))
return system.substring(38).replace("/", "");
if (system.startsWith("http://hl7.org/fhir/"))
return system.substring(20).replace("/", "");
if (system.equals("urn:ietf:bcp:47"))
return "lang";
if (system.equals("urn:ietf:bcp:13"))
return "mimetypes";
if (system.equals("urn:iso:std:iso:11073:10101"))
return "11073";
if (system.equals("http://dicom.nema.org/resources/ontology/DCM"))
return "dicom";
return system.replace("/", "_").replace(":", "_").replace("?", "X").replace("#", "X");
}
public NamedCache getNamedCache(CacheToken cacheToken) {
NamedCache nc = caches.get(cacheToken.name);
if (nc == null) {
nc = new NamedCache();
nc.name = cacheToken.name;
caches.put(nc.name, nc);
}
return nc;
}
public ValueSetExpansionOutcome getExpansion(CacheToken cacheToken) {
synchronized (lock) {
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = nc.map.get(cacheToken.key);
if (e == null)
return null;
else
return e.e;
}
}
public void cacheExpansion(CacheToken cacheToken, ValueSetExpansionOutcome res, boolean persistent) {
synchronized (lock) {
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = new CacheEntry();
e.request = cacheToken.request;
e.persistent = persistent;
e.e = res;
store(cacheToken, persistent, nc, e);
}
}
public void store(CacheToken cacheToken, boolean persistent, NamedCache nc, CacheEntry e) {
if (noCaching) {
return;
}
boolean n = nc.map.containsKey(cacheToken.key);
nc.map.put(cacheToken.key, e);
if (persistent) {
if (n) {
for (int i = nc.list.size()- 1; i>= 0; i--) {
if (nc.list.get(i).request.equals(e.request)) {
nc.list.remove(i);
}
}
}
nc.list.add(e);
save(nc);
}
}
public ValidationResult getValidation(CacheToken cacheToken) {
if (cacheToken.key == null) {
return null;
}
synchronized (lock) {
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = nc.map.get(cacheToken.key);
if (e == null)
return null;
else
return e.v;
}
}
public void cacheValidation(CacheToken cacheToken, ValidationResult res, boolean persistent) {
if (cacheToken.key != null) {
synchronized (lock) {
NamedCache nc = getNamedCache(cacheToken);
CacheEntry e = new CacheEntry();
e.request = cacheToken.request;
e.persistent = persistent;
e.v = res;
store(cacheToken, persistent, nc, e);
}
}
}
// persistence
public void save() {
}
private void save(NamedCache nc) {
if (folder == null)
return;
try {
OutputStreamWriter sw = new OutputStreamWriter(new FileOutputStream(Utilities.path(folder, nc.name+".cache")), "UTF-8");
sw.write(ENTRY_MARKER+"\r\n");
JsonParser json = new JsonParser();
json.setOutputStyle(OutputStyle.PRETTY);
for (CacheEntry ce : nc.list) {
sw.write(ce.request.trim());
sw.write(BREAK+"\r\n");
if (ce.e != null) {
sw.write("e: {\r\n");
if (ce.e.getValueset() != null)
sw.write(" \"valueSet\" : "+json.composeString(ce.e.getValueset()).trim()+",\r\n");
sw.write(" \"error\" : \""+Utilities.escapeJson(ce.e.getError()).trim()+"\"\r\n}\r\n");
} else {
sw.write("v: {\r\n");
sw.write(" \"display\" : \""+Utilities.escapeJson(ce.v.getDisplay()).trim()+"\",\r\n");
sw.write(" \"code\" : \""+Utilities.escapeJson(ce.v.getCode()).trim()+"\",\r\n");
sw.write(" \"system\" : \""+Utilities.escapeJson(ce.v.getSystem()).trim()+"\",\r\n");
sw.write(" \"severity\" : "+(ce.v.getSeverity() == null ? "null" : "\""+ce.v.getSeverity().toCode().trim()+"\"")+",\r\n");
sw.write(" \"error\" : \""+Utilities.escapeJson(ce.v.getMessage()).trim()+"\"\r\n}\r\n");
}
sw.write(ENTRY_MARKER+"\r\n");
}
sw.close();
} catch (Exception e) {
System.out.println("error saving "+nc.name+": "+e.getMessage());
}
}
private void load() throws FHIRException {
for (String fn : new File(folder).list()) {
if (fn.endsWith(".cache") && !fn.equals("validation.cache")) {
try {
String title = fn.substring(0, fn.lastIndexOf("."));
NamedCache nc = new NamedCache();
nc.name = title;
caches.put(title, nc);
String src = TextFile.fileToString(Utilities.path(folder, fn));
if (src.startsWith("?"))
src = src.substring(1);
int i = src.indexOf(ENTRY_MARKER);
while (i > -1) {
String s = src.substring(0, i);
src = src.substring(i+ENTRY_MARKER.length()+1);
i = src.indexOf(ENTRY_MARKER);
if (!Utilities.noString(s)) {
int j = s.indexOf(BREAK);
String q = s.substring(0, j);
String p = s.substring(j+BREAK.length()+1).trim();
CacheEntry ce = new CacheEntry();
ce.persistent = true;
ce.request = q;
boolean e = p.charAt(0) == 'e';
p = p.substring(3);
JsonObject o = (JsonObject) new com.google.gson.JsonParser().parse(p);
String error = loadJS(o.get("error"));
if (e) {
if (o.has("valueSet"))
ce.e = new ValueSetExpansionOutcome((ValueSet) new JsonParser().parse(o.getAsJsonObject("valueSet")), error, TerminologyServiceErrorClass.UNKNOWN);
else
ce.e = new ValueSetExpansionOutcome(error, TerminologyServiceErrorClass.UNKNOWN);
} else {
IssueSeverity severity = o.get("severity") instanceof JsonNull ? null : IssueSeverity.fromCode(o.get("severity").getAsString());
String display = loadJS(o.get("display"));
String code = loadJS(o.get("code"));
String system = loadJS(o.get("system"));
ce.v = new ValidationResult(severity, error, system, new ConceptDefinitionComponent().setDisplay(display).setCode(code));
}
nc.map.put(String.valueOf(hashNWS(ce.request)), ce);
nc.list.add(ce);
}
}
} catch (Exception e) {
throw new FHIRException("Error loading "+fn+": "+e.getMessage(), e);
}
}
}
}
private String loadJS(JsonElement e) {
if (e == null)
return null;
if (!(e instanceof JsonPrimitive))
return null;
String s = e.getAsString();
if ("".equals(s))
return null;
return s;
}
private String hashNWS(String s) {
s = StringUtils.remove(s, ' ');
s = StringUtils.remove(s, '\n');
s = StringUtils.remove(s, '\r');
return String.valueOf(s.hashCode());
}
// management
public TerminologyCache copy() {
// TODO Auto-generated method stub
return null;
}
public String summary(ValueSet vs) {
if (vs == null)
return "null";
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (ConceptSetComponent cc : vs.getCompose().getInclude())
b.append("Include "+getIncSummary(cc));
for (ConceptSetComponent cc : vs.getCompose().getExclude())
b.append("Exclude "+getIncSummary(cc));
return b.toString();
}
private String getIncSummary(ConceptSetComponent cc) {
CommaSeparatedStringBuilder b = new CommaSeparatedStringBuilder();
for (UriType vs : cc.getValueSet())
b.append(vs.asStringValue());
String vsd = b.length() > 0 ? " where the codes are in the value sets ("+b.toString()+")" : "";
String system = cc.getSystem();
if (cc.hasConcept())
return Integer.toString(cc.getConcept().size())+" codes from "+system+vsd;
if (cc.hasFilter()) {
String s = "";
for (ConceptSetFilterComponent f : cc.getFilter()) {
if (!Utilities.noString(s))
s = s + " & ";
s = s + f.getProperty()+" "+(f.hasOp() ? f.getOp().toCode() : "?")+" "+f.getValue();
}
return "from "+system+" where "+s+vsd;
}
return "All codes from "+system+vsd;
}
public String summary(Coding code) {
return code.getSystem()+"#"+code.getCode()+": \""+code.getDisplay()+"\"";
}
public String summary(CodeableConcept code) {
StringBuilder b = new StringBuilder();
b.append("{");
boolean first = true;
for (Coding c : code.getCoding()) {
if (first) first = false; else b.append(",");
b.append(summary(c));
}
b.append("}: \"");
b.append(code.getText());
b.append("\"");
return b.toString();
}
public static boolean isNoCaching() {
return noCaching;
}
public static void setNoCaching(boolean noCaching) {
TerminologyCache.noCaching = noCaching;
}
public void removeCS(String url) {
synchronized (lock) {
String name = getNameForSystem(url);
if (caches.containsKey(name)) {
caches.remove(name);
}
}
}
public String getFolder() {
return folder;
}
}

View File

@ -0,0 +1,91 @@
package org.hl7.fhir.r4b.context;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.io.UnsupportedEncodingException;
import java.util.List;
import org.hl7.fhir.utilities.ToolingClientLogger;
import org.hl7.fhir.utilities.Utilities;
public class TextClientLogger extends BaseLogger implements ToolingClientLogger {
private PrintStream file;
public TextClientLogger(String log) {
if (log != null) {
try {
file = new PrintStream(new FileOutputStream(log));
} catch (FileNotFoundException e) {
}
}
}
@Override
public void logRequest(String method, String url, List<String> headers, byte[] body) {
if (file == null)
return;
String id = nextId();
file.println("\r\n--- "+id+" -----------------\r\nRequest: \r\n");
file.println(method+" "+url+" HTTP/1.0");
for (String s : headers)
file.println(Utilities.escapeXml(s));
if (body != null) {
file.println("");
try {
file.println(Utilities.escapeXml(new String(body, "UTF-8")));
} catch (UnsupportedEncodingException e) {
}
}
}
@Override
public void logResponse(String outcome, List<String> headers, byte[] body) {
if (file == null)
return;
file.println("\r\n\r\nResponse: \r\n");
file.println(outcome);
for (String s : headers)
file.println(Utilities.escapeXml(s));
if (body != null) {
file.println("");
try {
file.println(Utilities.escapeXml(new String(body, "UTF-8")));
} catch (UnsupportedEncodingException e) {
}
}
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,978 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.model.Base;
import org.hl7.fhir.r4b.model.DataType;
import org.hl7.fhir.r4b.model.ElementDefinition;
import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r4b.model.Enumerations.BindingStrength;
import org.hl7.fhir.r4b.model.ICoding;
import org.hl7.fhir.r4b.model.StringType;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.TypeConvertor;
import org.hl7.fhir.r4b.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r4b.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.utilities.ElementDecoration;
import org.hl7.fhir.utilities.ElementDecoration.DecorationType;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
/**
* This class represents the underlying reference model of FHIR
*
* A resource is nothing but a set of elements, where every element has a
* name, maybe a stated type, maybe an id, and either a value or child elements
* (one or the other, but not both or neither)
*
* @author Grahame Grieve
*
*/
public class Element extends Base {
public enum SpecialElement {
CONTAINED, BUNDLE_ENTRY, BUNDLE_OUTCOME, PARAMETER;
public static SpecialElement fromProperty(Property property) {
if (property.getStructure().getType().equals("Parameters"))
return PARAMETER;
if (property.getStructure().getType().equals("Bundle") && property.getName().equals("resource"))
return BUNDLE_ENTRY;
if (property.getStructure().getType().equals("Bundle") && property.getName().equals("outcome"))
return BUNDLE_OUTCOME;
if (property.getName().equals("contained"))
return CONTAINED;
throw new FHIRException("Unknown resource containing a native resource: "+property.getDefinition().getId());
}
}
private List<String> comments;// not relevant for production, but useful in documentation
private String name;
private String type;
private String value;
private int index = -1;
private List<Element> children;
private Property property;
private Property elementProperty; // this is used when special is set to true - it tracks the underlying element property which is used in a few places
private int line;
private int col;
private SpecialElement special;
private XhtmlNode xhtml; // if this is populated, then value will also hold the string representation
private String explicitType; // for xsi:type attribute
private Element parentForValidator;
private boolean hasParentForValidator;
private String path;
private List<ValidationMessage> messages;
public Element(String name) {
super();
this.name = name;
}
public Element(Element other) {
super();
name = other.name;
type = other.type;
property = other.property;
elementProperty = other.elementProperty;
special = other.special;
}
public Element(String name, Property property) {
super();
this.name = name;
this.property = property;
}
public Element(String name, Property property, String type, String value) {
super();
this.name = name;
this.property = property;
this.type = type;
this.value = value;
}
public void updateProperty(Property property, SpecialElement special, Property elementProperty) {
this.property = property;
this.elementProperty = elementProperty;
this.special = special;
}
public SpecialElement getSpecial() {
return special;
}
public String getName() {
return name;
}
public String getType() {
if (type == null)
return property.getType(name);
else
return type;
}
public String getValue() {
return value;
}
public boolean parseSingle() {
return !(children == null || children.isEmpty());
}
public List<Element> getChildren() {
if (children == null)
children = new ArrayList<Element>();
return children;
}
public boolean hasComments() {
return !(comments == null || comments.isEmpty());
}
public List<String> getComments() {
if (comments == null)
comments = new ArrayList<String>();
return comments;
}
public Property getProperty() {
return property;
}
public void setValue(String value) {
this.value = value;
}
public void setType(String type) {
this.type = type;
}
public boolean hasValue() {
return value != null;
}
public List<Element> getChildrenByName(String name) {
List<Element> res = new ArrayList<Element>();
if (hasChildren()) {
for (Element child : children)
if (name.equals(child.getName()))
res.add(child);
}
return res;
}
public void numberChildren() {
if (children == null)
return;
String last = "";
int index = 0;
for (Element child : children) {
if (child.getProperty().isList()) {
if (last.equals(child.getName())) {
index++;
} else {
last = child.getName();
index = 0;
}
child.index = index;
} else {
child.index = -1;
}
child.numberChildren();
}
}
public int getIndex() {
return index;
}
public boolean hasIndex() {
return index > -1;
}
public void setIndex(int index) {
this.index = index;
}
public String getChildValue(String name) {
if (children == null)
return null;
for (Element child : children) {
if (name.equals(child.getName()))
return child.getValue();
}
return null;
}
public void setChildValue(String name, String value) {
if (children == null)
children = new ArrayList<Element>();
for (Element child : children) {
if (name.equals(child.getName())) {
if (!child.isPrimitive())
throw new Error("Cannot set a value of a non-primitive type ("+name+" on "+this.getName()+")");
child.setValue(value);
}
}
try {
setProperty(name.hashCode(), name, new StringType(value));
} catch (FHIRException e) {
throw new Error(e);
}
}
public List<Element> getChildren(String name) {
List<Element> res = new ArrayList<Element>();
if (children != null)
for (Element child : children) {
if (name.equals(child.getName()))
res.add(child);
}
return res;
}
public boolean hasType() {
if (type == null)
return property.hasType(name);
else
return true;
}
@Override
public String fhirType() {
return getType();
}
@Override
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
if (isPrimitive() && (hash == "value".hashCode()) && !Utilities.noString(value)) {
// String tn = getType();
// throw new Error(tn+" not done yet");
Base[] b = new Base[1];
b[0] = new StringType(value);
return b;
}
List<Base> result = new ArrayList<Base>();
if (children != null) {
for (Element child : children) {
if (child.getName().equals(name))
result.add(child);
if (child.getName().startsWith(name) && child.getProperty().isChoice() && child.getProperty().getName().equals(name+"[x]"))
result.add(child);
}
}
if (result.isEmpty() && checkValid) {
// throw new FHIRException("not determined yet");
}
return result.toArray(new Base[result.size()]);
}
@Override
protected void listChildren(List<org.hl7.fhir.r4b.model.Property> childProps) {
if (children != null) {
Map<String, org.hl7.fhir.r4b.model.Property> map = new HashMap<String, org.hl7.fhir.r4b.model.Property>();
for (Element c : children) {
org.hl7.fhir.r4b.model.Property p = map.get(c.getName());
if (p == null) {
p = new org.hl7.fhir.r4b.model.Property(c.getName(), c.fhirType(), c.getProperty().getDefinition().getDefinition(), c.getProperty().getDefinition().getMin(), maxToInt(c.getProperty().getDefinition().getMax()), c);
childProps.add(p);
map.put(c.getName(), p);
} else
p.getValues().add(c);
}
}
}
@Override
public Base setProperty(int hash, String name, Base value) throws FHIRException {
if ("xhtml".equals(getType()) && (hash == "value".hashCode())) {
this.xhtml = TypeConvertor.castToXhtml(value);
this.value = TypeConvertor.castToXhtmlString(value);
return this;
}
if (isPrimitive() && (hash == "value".hashCode())) {
this.value = TypeConvertor.castToString(value).asStringValue();
return this;
}
if (!value.isPrimitive() && !(value instanceof Element)) {
if (isDataType(value))
value = convertToElement(property.getChild(name), value);
else
throw new FHIRException("Cannot set property "+name+" on "+this.name+" - value is not a primitive type ("+value.fhirType()+") or an ElementModel type");
}
if (children == null)
children = new ArrayList<Element>();
Element childForValue = null;
// look through existing children
for (Element child : children) {
if (child.getName().equals(name)) {
if (!child.isList()) {
childForValue = child;
break;
} else {
Element ne = new Element(child);
children.add(ne);
numberChildren();
childForValue = ne;
break;
}
}
}
int i = 0;
if (childForValue == null)
for (Property p : property.getChildProperties(this.name, type)) {
int t = -1;
for (int c =0; c < children.size(); c++) {
Element e = children.get(c);
if (p.getName().equals(e.getName()))
t = c;
}
if (t >= i)
i = t+1;
if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
Element ne = new Element(name, p);
children.add(i, ne);
childForValue = ne;
break;
}
}
if (childForValue == null)
throw new Error("Cannot set property "+name+" on "+this.name);
else if (value.isPrimitive()) {
if (childForValue.property.getName().endsWith("[x]"))
childForValue.name = name+Utilities.capitalize(value.fhirType());
childForValue.setValue(value.primitiveValue());
} else {
Element ve = (Element) value;
childForValue.type = ve.getType();
if (childForValue.property.getName().endsWith("[x]"))
childForValue.name = name+Utilities.capitalize(childForValue.type);
else if (value.isResource()) {
if (childForValue.elementProperty == null)
childForValue.elementProperty = childForValue.property;
childForValue.property = ve.property;
childForValue.special = SpecialElement.BUNDLE_ENTRY;
}
if (ve.children != null) {
if (childForValue.children == null)
childForValue.children = new ArrayList<Element>();
else
childForValue.children.clear();
childForValue.children.addAll(ve.children);
}
}
return childForValue;
}
private Base convertToElement(Property prop, Base v) throws FHIRException {
return new ObjectConverter(property.getContext()).convert(prop, (DataType) v);
}
private boolean isDataType(Base v) {
return v instanceof DataType && property.getContext().getTypeNames().contains(v.fhirType());
}
@Override
public Base makeProperty(int hash, String name) throws FHIRException {
if (isPrimitive() && (hash == "value".hashCode())) {
return new StringType(value);
}
if (children == null)
children = new ArrayList<Element>();
// look through existing children
for (Element child : children) {
if (child.getName().equals(name)) {
if (!child.isList()) {
return child;
} else {
Element ne = new Element(child);
children.add(ne);
numberChildren();
return ne;
}
}
}
for (Property p : property.getChildProperties(this.name, type)) {
if (p.getName().equals(name)) {
Element ne = new Element(name, p);
children.add(ne);
return ne;
}
}
throw new Error("Unrecognised name "+name+" on "+this.name);
}
private int maxToInt(String max) {
if (max.equals("*"))
return Integer.MAX_VALUE;
else
return Integer.parseInt(max);
}
@Override
public boolean isPrimitive() {
return type != null ? property.isPrimitive(type) : property.isPrimitive(property.getType(name));
}
@Override
public boolean isBooleanPrimitive() {
return isPrimitive() && ("boolean".equals(type) || "boolean".equals(property.getType(name)));
}
@Override
public boolean isResource() {
return property.isResource();
}
@Override
public boolean hasPrimitiveValue() {
return property.isPrimitiveName(name) || property.IsLogicalAndHasPrimitiveValue(name);
}
@Override
public String primitiveValue() {
if (isPrimitive())
return value;
else {
if (hasPrimitiveValue() && children != null) {
for (Element c : children) {
if (c.getName().equals("value"))
return c.primitiveValue();
}
}
return null;
}
}
// for the validator
public int line() {
return line;
}
public int col() {
return col;
}
public Element markLocation(int line, int col) {
this.line = line;
this.col = col;
return this;
}
public void clearDecorations() {
clearUserData("fhir.decorations");
for (Element e : children)
e.clearDecorations();
}
public void markValidation(StructureDefinition profile, ElementDefinition definition) {
@SuppressWarnings("unchecked")
List<ElementDecoration> decorations = (List<ElementDecoration>) getUserData("fhir.decorations");
if (decorations == null) {
decorations = new ArrayList<>();
setUserData("fhir.decorations", decorations);
}
decorations.add(new ElementDecoration(DecorationType.TYPE, profile.getUserString("path"), definition.getPath()));
if (definition.getId() != null && tail(definition.getId()).contains(":")) {
String[] details = tail(definition.getId()).split(":");
decorations.add(new ElementDecoration(DecorationType.SLICE, null, details[1]));
}
}
private String tail(String id) {
return id.contains(".") ? id.substring(id.lastIndexOf(".")+1) : id;
}
public Element getNamedChild(String name) {
if (children == null)
return null;
Element result = null;
for (Element child : children) {
if (child.getName() != null && name != null && child.getProperty() != null && child.getProperty().getDefinition() != null && child.fhirType() != null) {
if (child.getName().equals(name) || (child.getName().length() > child.fhirType().length() && child.getName().substring(0, child.getName().length() - child.fhirType().length()).equals(name) && child.getProperty().getDefinition().isChoice())) {
if (result == null)
result = child;
else
throw new Error("Attempt to read a single element when there is more than one present ("+name+")");
}
}
}
return result;
}
public void getNamedChildren(String name, List<Element> list) {
if (children != null)
for (Element child : children)
if (child.getName().equals(name))
list.add(child);
}
public String getNamedChildValue(String name) {
Element child = getNamedChild(name);
return child == null ? null : child.value;
}
public void getNamedChildrenWithWildcard(String string, List<Element> values) {
Validate.isTrue(string.endsWith("[x]"));
String start = string.substring(0, string.length() - 3);
if (children != null) {
for (Element child : children) {
if (child.getName().startsWith(start)) {
values.add(child);
}
}
}
}
public XhtmlNode getXhtml() {
return xhtml;
}
public Element setXhtml(XhtmlNode xhtml) {
this.xhtml = xhtml;
return this;
}
@Override
public boolean isEmpty() {
// GG: this used to also test !"".equals(value).
// the condition where "" is empty and there are no children is an error, and so this really only manifested as an issue in corner cases technical testing of the validator / FHIRPath.
// it should not cause any problems in real life.
if (value != null) {
return false;
}
for (Element next : getChildren()) {
if (!next.isEmpty()) {
return false;
}
}
return true;
}
public Property getElementProperty() {
return elementProperty;
}
public boolean hasElementProperty() {
return elementProperty != null;
}
public boolean hasChild(String name) {
return getNamedChild(name) != null;
}
public boolean hasChildren(String name) {
if (children != null)
for (Element child : children)
if (child.getName().equals(name))
return true;
return false;
}
@Override
public String toString() {
return name+"="+fhirType() + "["+(children == null || hasValue() ? value : Integer.toString(children.size())+" children")+"]";
}
@Override
public String getIdBase() {
return getChildValue("id");
}
@Override
public void setIdBase(String value) {
setChildValue("id", value);
}
@Override
public boolean equalsDeep(Base other) {
if (!super.equalsDeep(other))
return false;
if (isPrimitive() && other.isPrimitive())
return primitiveValue().equals(other.primitiveValue());
if (isPrimitive() || other.isPrimitive())
return false;
Set<String> processed = new HashSet<String>();
for (org.hl7.fhir.r4b.model.Property p : children()) {
String name = p.getName();
processed.add(name);
org.hl7.fhir.r4b.model.Property o = other.getChildByName(name);
if (!equalsDeep(p, o))
return false;
}
for (org.hl7.fhir.r4b.model.Property p : children()) {
String name = p.getName();
if (!processed.contains(name)) {
org.hl7.fhir.r4b.model.Property o = other.getChildByName(name);
if (!equalsDeep(p, o))
return false;
}
}
return true;
}
private boolean equalsDeep(org.hl7.fhir.r4b.model.Property p, org.hl7.fhir.r4b.model.Property o) {
if (o == null || p == null)
return false;
if (p.getValues().size() != o.getValues().size())
return false;
for (int i = 0; i < p.getValues().size(); i++)
if (!Base.compareDeep(p.getValues().get(i), o.getValues().get(i), true))
return false;
return true;
}
@Override
public boolean equalsShallow(Base other) {
if (!super.equalsShallow(other))
return false;
if (isPrimitive() && other.isPrimitive())
return primitiveValue().equals(other.primitiveValue());
if (isPrimitive() || other.isPrimitive())
return false;
return true; //?
}
public DataType asType() throws FHIRException {
return new ObjectConverter(property.getContext()).convertToType(this);
}
@Override
public boolean isMetadataBased() {
return true;
}
public boolean isList() {
if (elementProperty != null)
return elementProperty.isList();
else
return property.isList();
}
public boolean isBaseList() {
if (elementProperty != null)
return elementProperty.isBaseList();
else
return property.isBaseList();
}
@Override
public String[] getTypesForProperty(int hash, String name) throws FHIRException {
Property p = property.getChildSimpleName(this.name, name);
if (p != null) {
Set<String> types = new HashSet<String>();
for (TypeRefComponent tr : p.getDefinition().getType()) {
types.add(tr.getCode());
}
return types.toArray(new String[]{});
}
return super.getTypesForProperty(hash, name);
}
public void sort() {
if (children != null) {
List<Element> remove = new ArrayList<Element>();
for (Element child : children) {
child.sort();
if (child.isEmpty())
remove.add(child);
}
children.removeAll(remove);
Collections.sort(children, new ElementSortComparator(this, this.property));
}
}
public class ElementSortComparator implements Comparator<Element> {
private List<ElementDefinition> children;
public ElementSortComparator(Element e, Property property) {
String tn = e.getType();
StructureDefinition sd = property.getContext().fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, property.getContext().getOverrideVersionNs()));
if (sd != null && !sd.getAbstract())
children = sd.getSnapshot().getElement();
else
children = property.getStructure().getSnapshot().getElement();
}
@Override
public int compare(Element e0, Element e1) {
int i0 = find(e0);
int i1 = find(e1);
return Integer.compare(i0, i1);
}
private int find(Element e0) {
int i = e0.elementProperty != null ? children.indexOf(e0.elementProperty.getDefinition()) : children.indexOf(e0.property.getDefinition());
return i;
}
}
public class ICodingImpl implements ICoding {
private String system;
private String version;
private String code;
private String display;
private boolean doesSystem;
private boolean doesVersion;
private boolean doesCode;
private boolean doesDisplay;
public ICodingImpl(boolean doesCode, boolean doesSystem, boolean doesVersion, boolean doesDisplay) {
super();
this.doesCode = doesCode;
this.doesSystem = doesSystem;
this.doesVersion = doesVersion;
this.doesDisplay = doesDisplay;
}
public String getSystem() {
return system;
}
public String getVersion() {
return version;
}
public String getCode() {
return code;
}
public String getDisplay() {
return display;
}
public boolean hasSystem() {
return !Utilities.noString(system);
}
public boolean hasVersion() {
return !Utilities.noString(version);
}
public boolean hasCode() {
return !Utilities.noString(code);
}
public boolean hasDisplay() {
return !Utilities.noString(display);
}
public boolean supportsSystem() {
return doesSystem;
}
public boolean supportsVersion() {
return doesVersion;
}
public boolean supportsCode() {
return doesCode;
}
public boolean supportsDisplay() {
return doesDisplay;
}
}
public ICoding getAsICoding() throws FHIRException {
if ("code".equals(fhirType())) {
if (property.getDefinition().getBinding().getStrength() != BindingStrength.REQUIRED)
return null;
ICodingImpl c = new ICodingImpl(true, true, false, false);
c.code = primitiveValue();
ValueSetExpansionOutcome vse = property.getContext().expandVS(property.getDefinition().getBinding(), true, false);
if (vse.getValueset() == null)
return null;
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
if (cc.getCode().equals(c.code)) {
c.system = cc.getSystem();
if (cc.hasVersion()) {
c.doesVersion = true;
c.version = cc.getVersion();
}
if (cc.hasDisplay()) {
c.doesDisplay = true;
c.display = cc.getDisplay();
}
}
}
if (c.system == null)
return null;
return c;
} else if ("Coding".equals(fhirType())) {
ICodingImpl c = new ICodingImpl(true, true, true, true);
c.system = getNamedChildValue("system");
c.code = getNamedChildValue("code");
c.display = getNamedChildValue("display");
c.version = getNamedChildValue("version");
return c;
} else if ("Quantity".equals(fhirType())) {
ICodingImpl c = new ICodingImpl(true, true, false, false);
c.system = getNamedChildValue("system");
c.code = getNamedChildValue("code");
return c;
} else
return null;
}
public String getExplicitType() {
return explicitType;
}
public void setExplicitType(String explicitType) {
this.explicitType = explicitType;
}
public boolean hasDescendant(Element element) {
if (children != null) {
for (Element child : children) {
if (element == child || child.hasDescendant(element)) {
return true;
}
}
}
return false;
}
public Element getExtension(String url) {
if (children != null) {
for (Element child : children) {
if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
String u = child.getChildValue("url");
if (url.equals(u)) {
return child;
}
}
}
}
return null;
}
public Base getExtensionValue(String url) {
if (children != null) {
for (Element child : children) {
if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
String u = child.getChildValue("url");
if (url.equals(u)) {
return child.getNamedChild("value");
}
}
}
}
return null;
}
public boolean hasExtension(String url) {
if (children != null) {
for (Element child : children) {
if (Utilities.existsInList(child.getName(), "extension", "modifierExtension")) {
String u = child.getChildValue("url");
if (url.equals(u)) {
return true;
}
}
}
}
return false;
}
/**
* this is set by the instance validator. There's no reason to maintain this when working with an element tree, and so it should be ignored outside the validator
*/
public Element getParentForValidator() {
if (!hasParentForValidator) {
throw new Error("Parent not set");
}
return parentForValidator;
}
public void setParentForValidator(Element parentForValidator) {
this.parentForValidator = parentForValidator;
this.hasParentForValidator = true;
}
public boolean hasParentForValidator() {
return hasParentForValidator;
}
public void clear() {
comments = null;
children.clear();;
property = null;
elementProperty = null;
xhtml = null;
path = null;
}
public String getPath() {
return path;
}
public void setPath(String path) {
this.path = path;
}
public void addMessage(ValidationMessage vm) {
if (messages == null) {
messages = new ArrayList<>();
}
messages.add(vm);
}
public boolean hasMessages() {
return messages != null && !messages.isEmpty();
}
public List<ValidationMessage> getMessages() {
return messages;
}
}

View File

@ -0,0 +1,588 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.formats.JsonCreator;
import org.hl7.fhir.r4b.formats.JsonCreatorCanonical;
import org.hl7.fhir.r4b.formats.JsonCreatorGson;
import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r4b.utils.FHIRPathEngine;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.json.JsonTrackingParser.LocationData;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
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;
public class JsonParser extends ParserBase {
private JsonCreator json;
private Map<JsonElement, LocationData> map;
private boolean allowComments;
private ProfileUtilities profileUtilities;
public JsonParser(IWorkerContext context, ProfileUtilities utilities) {
super(context);
this.profileUtilities = utilities;
}
public JsonParser(IWorkerContext context) {
super(context);
this.profileUtilities = new ProfileUtilities(this.context, null, null, new FHIRPathEngine(context));
}
public Element parse(String source, String type) throws Exception {
JsonObject obj = (JsonObject) new com.google.gson.JsonParser().parse(source);
String path = "/"+type;
StructureDefinition sd = getDefinition(-1, -1, type);
if (sd == null)
return null;
Element result = new Element(type, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities));
result.setPath(type);
checkObject(obj, path);
result.setType(type);
parseChildren(path, obj, result, true);
result.numberChildren();
return result;
}
@Override
public List<NamedElement> parse(InputStream stream) throws IOException, FHIRException {
// if we're parsing at this point, then we're going to use the custom parser
List<NamedElement> res = new ArrayList<>();
map = new IdentityHashMap<JsonElement, LocationData>();
String source = TextFile.streamToString(stream);
if (policy == ValidationPolicy.EVERYTHING) {
JsonObject obj = null;
try {
obj = JsonTrackingParser.parse(source, map, false, allowComments);
} catch (Exception e) {
logError(-1, -1,context.formatMessage(I18nConstants.DOCUMENT), IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_, e.getMessage()), IssueSeverity.FATAL);
return null;
}
assert (map.containsKey(obj));
Element e = parse(obj);
if (e != null) {
res.add(new NamedElement(null, e));
}
} else {
JsonObject obj = JsonTrackingParser.parse(source, null); // (JsonObject) new com.google.gson.JsonParser().parse(source);
// assert (map.containsKey(obj));
Element e = parse(obj);
if (e != null) {
res.add(new NamedElement(null, e));
}
}
return res;
}
public Element parse(JsonObject object, Map<JsonElement, LocationData> map) throws FHIRException {
this.map = map;
return parse(object);
}
public Element parse(JsonObject object) throws FHIRException {
JsonElement rt = object.get("resourceType");
if (rt == null) {
logError(line(object), col(object), "$", IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
return null;
} else {
String name = rt.getAsString();
String path = name;
StructureDefinition sd = getDefinition(line(object), col(object), name);
if (sd == null)
return null;
Element result = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities));
checkObject(object, path);
result.markLocation(line(object), col(object));
result.setType(name);
result.setPath(result.fhirType());
parseChildren(path, object, result, true);
result.numberChildren();
return result;
}
}
private void checkObject(JsonObject object, String path) throws FHIRFormatError {
if (policy == ValidationPolicy.EVERYTHING) {
boolean found = false;
for (Entry<String, JsonElement> e : object.entrySet()) {
// if (!e.getKey().equals("fhir_comments")) {
found = true;
break;
// }
}
if (!found)
logError(line(object), col(object), path, IssueType.INVALID, context.formatMessage(I18nConstants.OBJECT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR);
}
}
private void parseChildren(String path, JsonObject object, Element element, boolean hasResourceType) throws FHIRException {
reapComments(object, element);
List<Property> properties = element.getProperty().getChildProperties(element.getName(), null);
Set<String> processed = new HashSet<String>();
if (hasResourceType)
processed.add("resourceType");
// note that we do not trouble ourselves to maintain the wire format order here - we don't even know what it was anyway
// first pass: process the properties
for (Property property : properties) {
parseChildItem(path, object, element, processed, property);
}
// second pass: check for things not processed
if (policy != ValidationPolicy.NONE) {
for (Entry<String, JsonElement> e : object.entrySet()) {
if (!processed.contains(e.getKey())) {
logError(line(e.getValue()), col(e.getValue()), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_, e.getKey()), IssueSeverity.ERROR);
}
}
}
}
public void parseChildItem(String path, JsonObject object, Element context, Set<String> processed, Property property) {
if (property.isChoice() || property.getDefinition().getPath().endsWith("data[x]")) {
for (TypeRefComponent type : property.getDefinition().getType()) {
String eName = property.getName().substring(0, property.getName().length()-3) + Utilities.capitalize(type.getWorkingCode());
if (!isPrimitive(type.getWorkingCode()) && object.has(eName)) {
parseChildComplex(path, object, context, processed, property, eName);
break;
} else if (isPrimitive(type.getWorkingCode()) && (object.has(eName) || object.has("_"+eName))) {
parseChildPrimitive(object, context, processed, property, path, eName);
break;
}
}
} else if (property.isPrimitive(property.getType(null))) {
parseChildPrimitive(object, context, processed, property, path, property.getName());
} else if (object.has(property.getName())) {
parseChildComplex(path, object, context, processed, property, property.getName());
}
}
private void parseChildComplex(String path, JsonObject object, Element element, Set<String> processed, Property property, String name) throws FHIRException {
processed.add(name);
String npath = path+"."+property.getName();
String fpath = element.getPath()+"."+property.getName();
JsonElement e = object.get(name);
if (property.isList() && (e instanceof JsonArray)) {
JsonArray arr = (JsonArray) e;
if (arr.size() == 0) {
logError(line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ARRAY_CANNOT_BE_EMPTY), IssueSeverity.ERROR);
}
int c = 0;
for (JsonElement am : arr) {
parseChildComplexInstance(npath+"["+c+"]", fpath+"["+c+"]", object, element, property, name, am);
c++;
}
} else {
if (property.isList()) {
logError(line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describeType(e), name, path), IssueSeverity.ERROR);
}
parseChildComplexInstance(npath, fpath, object, element, property, name, e);
}
}
private String describeType(JsonElement e) {
if (e.isJsonArray())
return "an Array";
if (e.isJsonObject())
return "an Object";
if (e.isJsonPrimitive())
return "a primitive property";
if (e.isJsonNull())
return "a Null";
return null;
}
private void parseChildComplexInstance(String npath, String fpath, JsonObject object, Element element, Property property, String name, JsonElement e) throws FHIRException {
if (e instanceof JsonObject) {
JsonObject child = (JsonObject) e;
Element n = new Element(name, property).markLocation(line(child), col(child));
n.setPath(fpath);
checkObject(child, npath);
element.getChildren().add(n);
if (property.isResource())
parseResource(npath, child, n, property);
else
parseChildren(npath, child, n, false);
} else
logError(line(e), col(e), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE__NOT_, (property.isList() ? "an Array" : "an Object"), describe(e), name, npath), IssueSeverity.ERROR);
}
private String describe(JsonElement e) {
if (e instanceof JsonArray) {
return "an array";
}
if (e instanceof JsonObject) {
return "an object";
}
if (e instanceof JsonNull) {
return "null";
}
return "a primitive property";
}
private void parseChildPrimitive(JsonObject object, Element element, Set<String> processed, Property property, String path, String name) throws FHIRException {
String npath = path+"."+property.getName();
String fpath = element.getPath()+"."+property.getName();
processed.add(name);
processed.add("_"+name);
JsonElement main = object.has(name) ? object.get(name) : null;
JsonElement fork = object.has("_"+name) ? object.get("_"+name) : null;
if (main != null || fork != null) {
if (property.isList()) {
boolean ok = true;
if (!(main == null || main instanceof JsonArray)) {
logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main), name, path), IssueSeverity.ERROR);
ok = false;
}
if (!(fork == null || fork instanceof JsonArray)) {
logError(line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_BASE_PROPERTY_MUST_BE_AN_ARRAY_NOT_, describe(main), name, path), IssueSeverity.ERROR);
ok = false;
}
if (ok) {
JsonArray arr1 = (JsonArray) main;
JsonArray arr2 = (JsonArray) fork;
for (int i = 0; i < Math.max(arrC(arr1), arrC(arr2)); i++) {
JsonElement m = arrI(arr1, i);
JsonElement f = arrI(arr2, i);
parseChildPrimitiveInstance(element, property, name, npath, fpath, m, f);
}
}
} else {
parseChildPrimitiveInstance(element, property, name, npath, fpath, main, fork);
}
}
}
private JsonElement arrI(JsonArray arr, int i) {
return arr == null || i >= arr.size() || arr.get(i) instanceof JsonNull ? null : arr.get(i);
}
private int arrC(JsonArray arr) {
return arr == null ? 0 : arr.size();
}
private void parseChildPrimitiveInstance(Element element, Property property, String name, String npath, String fpath, JsonElement main, JsonElement fork) throws FHIRException {
if (main != null && !(main instanceof JsonPrimitive))
logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(
I18nConstants.THIS_PROPERTY_MUST_BE_AN_SIMPLE_VALUE_NOT_, describe(main), name, npath), IssueSeverity.ERROR);
else if (fork != null && !(fork instanceof JsonObject))
logError(line(fork), col(fork), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_AN_OBJECT_NOT_, describe(fork), name, npath), IssueSeverity.ERROR);
else {
Element n = new Element(name, property).markLocation(line(main != null ? main : fork), col(main != null ? main : fork));
n.setPath(fpath);
element.getChildren().add(n);
if (main != null) {
JsonPrimitive p = (JsonPrimitive) main;
if (p.isNumber() && p.getAsNumber() instanceof JsonTrackingParser.PresentedBigDecimal) {
String rawValue = ((JsonTrackingParser.PresentedBigDecimal) p.getAsNumber()).getPresentation();
n.setValue(rawValue);
} else {
n.setValue(p.getAsString());
}
if (!n.getProperty().isChoice() && n.getType().equals("xhtml")) {
try {
n.setXhtml(new XhtmlParser().setValidatorMode(policy == ValidationPolicy.EVERYTHING).parse(n.getValue(), null).getDocumentElement());
} catch (Exception e) {
logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_XHTML_, e.getMessage()), IssueSeverity.ERROR);
}
}
if (policy == ValidationPolicy.EVERYTHING) {
// now we cross-check the primitive format against the stated type
if (Utilities.existsInList(n.getType(), "boolean")) {
if (!p.isBoolean())
logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_BOOLEAN), IssueSeverity.ERROR);
} else if (Utilities.existsInList(n.getType(), "integer", "unsignedInt", "positiveInt", "decimal")) {
if (!p.isNumber())
logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_NUMBER), IssueSeverity.ERROR);
} else if (!p.isString())
logError(line(main), col(main), npath, IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_JSON_THE_PRIMITIVE_VALUE_MUST_BE_A_STRING), IssueSeverity.ERROR);
}
}
if (fork != null) {
JsonObject child = (JsonObject) fork;
checkObject(child, npath);
parseChildren(npath, child, n, false);
}
}
}
private void parseResource(String npath, JsonObject res, Element parent, Property elementProperty) throws FHIRException {
JsonElement rt = res.get("resourceType");
if (rt == null) {
logError(line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNABLE_TO_FIND_RESOURCETYPE_PROPERTY), IssueSeverity.FATAL);
} else {
String name = rt.getAsString();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, context.getOverrideVersionNs()));
if (sd == null) {
logError(line(res), col(res), npath, IssueType.INVALID, context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL);
} else {
parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd, this.profileUtilities), SpecialElement.fromProperty(parent.getProperty()), elementProperty);
parent.setType(name);
parseChildren(npath, res, parent, true);
}
}
}
private void reapComments(JsonObject object, Element context) {
if (object.has("fhir_comments")) {
JsonArray arr = object.getAsJsonArray("fhir_comments");
for (JsonElement e : arr) {
context.getComments().add(e.getAsString());
}
}
}
private int line(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getLine();
}
private int col(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getCol();
}
protected void prop(String name, String value, String link) throws IOException {
json.link(link);
if (name != null)
json.name(name);
json.value(value);
}
protected void open(String name, String link) throws IOException {
json.link(link);
if (name != null)
json.name(name);
json.beginObject();
}
protected void close() throws IOException {
json.endObject();
}
protected void openArray(String name, String link) throws IOException {
json.link(link);
if (name != null)
json.name(name);
json.beginArray();
}
protected void closeArray() throws IOException {
json.endArray();
}
@Override
public void compose(Element e, OutputStream stream, OutputStyle style, String identity) throws FHIRException, IOException {
OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
if (style == OutputStyle.CANONICAL)
json = new JsonCreatorCanonical(osw);
else
json = new JsonCreatorGson(osw);
json.setIndent(style == OutputStyle.PRETTY ? " " : "");
json.beginObject();
prop("resourceType", e.getType(), null);
Set<String> done = new HashSet<String>();
for (Element child : e.getChildren()) {
compose(e.getName(), e, done, child);
}
json.endObject();
json.finish();
osw.flush();
}
public void compose(Element e, JsonCreator json) throws Exception {
this.json = json;
json.beginObject();
prop("resourceType", e.getType(), linkResolver == null ? null : linkResolver.resolveProperty(e.getProperty()));
Set<String> done = new HashSet<String>();
for (Element child : e.getChildren()) {
compose(e.getName(), e, done, child);
}
json.endObject();
json.finish();
}
private void compose(String path, Element e, Set<String> done, Element child) throws IOException {
boolean isList = child.hasElementProperty() ? child.getElementProperty().isList() : child.getProperty().isList();
if (!isList) {// for specials, ignore the cardinality of the stated type
compose(path, child);
} else if (!done.contains(child.getName())) {
done.add(child.getName());
List<Element> list = e.getChildrenByName(child.getName());
composeList(path, list);
}
}
private void composeList(String path, List<Element> list) throws IOException {
// there will be at least one element
String name = list.get(0).getName();
boolean complex = true;
if (list.get(0).isPrimitive()) {
boolean prim = false;
complex = false;
for (Element item : list) {
if (item.hasValue())
prim = true;
if (item.hasChildren())
complex = true;
}
if (prim) {
openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty()));
for (Element item : list) {
if (item.hasValue())
primitiveValue(null, item);
else
json.nullValue();
}
closeArray();
}
name = "_"+name;
}
if (complex) {
openArray(name, linkResolver == null ? null : linkResolver.resolveProperty(list.get(0).getProperty()));
for (Element item : list) {
if (item.hasChildren()) {
open(null,null);
if (item.getProperty().isResource()) {
prop("resourceType", item.getType(), linkResolver == null ? null : linkResolver.resolveType(item.getType()));
}
Set<String> done = new HashSet<String>();
for (Element child : item.getChildren()) {
compose(path+"."+name+"[]", item, done, child);
}
close();
} else
json.nullValue();
}
closeArray();
}
}
private void primitiveValue(String name, Element item) throws IOException {
if (name != null) {
if (linkResolver != null)
json.link(linkResolver.resolveProperty(item.getProperty()));
json.name(name);
}
String type = item.getType();
if (Utilities.existsInList(type, "boolean"))
json.value(item.getValue().trim().equals("true") ? new Boolean(true) : new Boolean(false));
else if (Utilities.existsInList(type, "integer", "unsignedInt", "positiveInt"))
json.value(new Integer(item.getValue()));
else if (Utilities.existsInList(type, "decimal"))
try {
json.value(new BigDecimal(item.getValue()));
} catch (Exception e) {
throw new NumberFormatException(context.formatMessage(I18nConstants.ERROR_WRITING_NUMBER__TO_JSON, item.getValue()));
}
else
json.value(item.getValue());
}
private void compose(String path, Element element) throws IOException {
String name = element.getName();
if (element.isPrimitive() || isPrimitive(element.getType())) {
if (element.hasValue())
primitiveValue(name, element);
name = "_"+name;
if (element.getType().equals("xhtml"))
json.anchor("end-xhtml");
}
if (element.hasChildren()) {
open(name, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()));
if (element.getProperty().isResource()) {
prop("resourceType", element.getType(), linkResolver == null ? null : linkResolver.resolveType(element.getType()));
}
Set<String> done = new HashSet<String>();
for (Element child : element.getChildren()) {
compose(path+"."+element.getName(), element, done, child);
}
close();
}
}
public boolean isAllowComments() {
return allowComments;
}
public JsonParser setAllowComments(boolean allowComments) {
this.allowComments = allowComments;
return this;
}
}

View File

@ -0,0 +1,121 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.model.StructureDefinition;
public class Manager {
//TODO use EnumMap
public enum FhirFormat { XML, JSON, TURTLE, TEXT, VBAR, SHC;
// SHC = smart health cards, including as text versions of QR codes
public String getExtension() {
switch (this) {
case JSON:
return "json";
case TURTLE:
return "ttl";
case XML:
return "xml";
case TEXT:
return "txt";
case VBAR:
return "hl7";
case SHC:
return "shc";
}
return null;
}
public static FhirFormat getFhirFormat(String code) {
switch (code) {
case "json":
return JSON;
case "ttl":
return TURTLE;
case "xml":
return XML;
case "txt":
return TEXT;
case "hl7":
return VBAR;
}
return null;
}
}
public static List<NamedElement> parse(IWorkerContext context, InputStream source, FhirFormat inputFormat) throws FHIRFormatError, DefinitionException, IOException, FHIRException {
return makeParser(context, inputFormat).parse(source);
}
public static Element parseSingle(IWorkerContext context, InputStream source, FhirFormat inputFormat) throws FHIRFormatError, DefinitionException, IOException, FHIRException {
return makeParser(context, inputFormat).parseSingle(source);
}
public static void compose(IWorkerContext context, Element e, OutputStream destination, FhirFormat outputFormat, OutputStyle style, String base) throws FHIRException, IOException {
makeParser(context, outputFormat).compose(e, destination, style, base);
}
public static ParserBase makeParser(IWorkerContext context, FhirFormat format) {
switch (format) {
case JSON : return new JsonParser(context);
case XML : return new XmlParser(context);
case TURTLE : return new TurtleParser(context);
case VBAR : return new VerticalBarParser(context);
case SHC : return new SHCParser(context);
case TEXT : throw new Error("Programming logic error: do not call makeParser for a text resource");
}
return null;
}
public static Element build(IWorkerContext context, StructureDefinition sd) {
Property p = new Property(context, sd.getSnapshot().getElementFirstRep(), sd);
Element e = new Element(null, p);
return e;
}
}

View File

@ -0,0 +1,189 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.model.Base;
import org.hl7.fhir.r4b.model.CodeableConcept;
import org.hl7.fhir.r4b.model.Coding;
import org.hl7.fhir.r4b.model.DataType;
import org.hl7.fhir.r4b.model.ElementDefinition;
import org.hl7.fhir.r4b.model.Factory;
import org.hl7.fhir.r4b.model.Identifier;
import org.hl7.fhir.r4b.model.PrimitiveType;
import org.hl7.fhir.r4b.model.Reference;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
public class ObjectConverter {
private IWorkerContext context;
private ProfileUtilities profileUtilities;
public ObjectConverter(IWorkerContext context) {
this.context = context;
profileUtilities = new ProfileUtilities(context, null, null);
}
public Element convert(Resource ig) throws IOException, FHIRException {
if (ig == null)
return null;
ByteArrayOutputStream bs = new ByteArrayOutputStream();
org.hl7.fhir.r4b.formats.JsonParser jp = new org.hl7.fhir.r4b.formats.JsonParser();
jp.compose(bs, ig);
ByteArrayInputStream bi = new ByteArrayInputStream(bs.toByteArray());
List<NamedElement> list = new JsonParser(context).parse(bi);
if (list.size() != 1) {
throw new FHIRException("Unable to convert because the source contains multieple resources");
}
return list.get(0).getElement();
}
public Element convert(Property property, DataType type) throws FHIRException {
return convertElement(property, type);
}
private Element convertElement(Property property, Base base) throws FHIRException {
if (base == null)
return null;
String tn = base.fhirType();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(tn, context.getOverrideVersionNs()));
if (sd == null)
throw new FHIRException("Unable to find definition for type "+tn);
Element res = new Element(property.getName(), property);
if (sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE)
res.setValue(((PrimitiveType) base).asStringValue());
List<ElementDefinition> children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElementFirstRep());
for (ElementDefinition child : children) {
String n = tail(child.getPath());
if (sd.getKind() != StructureDefinitionKind.PRIMITIVETYPE || !"value".equals(n)) {
Base[] values = base.getProperty(n.hashCode(), n, false);
if (values != null)
for (Base value : values) {
res.getChildren().add(convertElement(new Property(context, child, sd), value));
}
}
}
return res;
}
private String tail(String path) {
if (path.contains("."))
return path.substring(path.lastIndexOf('.')+1);
else
return path;
}
public DataType convertToType(Element element) throws FHIRException {
DataType b = new Factory().create(element.fhirType());
if (b instanceof PrimitiveType) {
((PrimitiveType) b).setValueAsString(element.primitiveValue());
} else {
for (Element child : element.getChildren()) {
b.setProperty(child.getName(), convertToType(child));
}
}
return b;
}
public Resource convert(Element element) throws FHIRException {
ByteArrayOutputStream bo = new ByteArrayOutputStream();
try {
new JsonParser(context).compose(element, bo, OutputStyle.NORMAL, null);
// TextFile.bytesToFile(bo.toByteArray(), "c:\\temp\\json.json");
return new org.hl7.fhir.r4b.formats.JsonParser().parse(bo.toByteArray());
} catch (IOException e) {
// won't happen
throw new FHIRException(e);
}
}
public static CodeableConcept readAsCodeableConcept(Element element) {
if (element == null) {
return null;
}
CodeableConcept cc = new CodeableConcept();
List<Element> list = new ArrayList<Element>();
element.getNamedChildren("coding", list);
for (Element item : list)
cc.addCoding(readAsCoding(item));
cc.setText(element.getNamedChildValue("text"));
return cc;
}
public static Coding readAsCoding(Element item) {
Coding c = new Coding();
c.setSystem(item.getNamedChildValue("system"));
c.setVersion(item.getNamedChildValue("version"));
c.setCode(item.getNamedChildValue("code"));
c.setDisplay(item.getNamedChildValue("display"));
return c;
}
public static Identifier readAsIdentifier(Element item) {
Identifier r = new Identifier();
r.setSystem(item.getNamedChildValue("system"));
r.setValue(item.getNamedChildValue("value"));
return r;
}
public static Reference readAsReference(Element item) {
Reference r = new Reference();
r.setDisplay(item.getNamedChildValue("display"));
r.setReference(item.getNamedChildValue("reference"));
r.setType(item.getNamedChildValue("type"));
if (!r.hasType()) {
Element ext = item.getExtension("http://hl7.org/fhir/4.0/StructureDefinition/extension-Reference.type");
if (ext != null) {
r.setType(ext.getChildValue("valueUri"));
}
}
List<Element> identifier = item.getChildrenByName("identifier");
if (identifier.isEmpty() == false) {
r.setIdentifier(readAsIdentifier(identifier.get(0)));
}
return r;
}
}

View File

@ -0,0 +1,197 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r4b.formats.FormatUtilities;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r4b.utils.ToolingExtensions;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
public abstract class ParserBase {
public class NamedElement {
private String name;
private Element element;
public NamedElement(String name, Element element) {
super();
this.name = name;
this.element = element;
}
public String getName() {
return name;
}
public Element getElement() {
return element;
}
}
public interface ILinkResolver {
String resolveType(String type);
String resolveProperty(Property property);
String resolvePage(String string);
}
public enum ValidationPolicy { NONE, QUICK, EVERYTHING }
public boolean isPrimitive(String code) {
return Utilities.existsInList(code, "boolean", "integer", "integer64", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "xhtml", "url", "canonical");
// StructureDefinition sd = context.fetchTypeDefinition(code);
// return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
}
protected IWorkerContext context;
protected ValidationPolicy policy;
protected List<ValidationMessage> errors;
protected ILinkResolver linkResolver;
protected boolean showDecorations;
public ParserBase(IWorkerContext context) {
super();
this.context = context;
policy = ValidationPolicy.NONE;
}
public void setupValidation(ValidationPolicy policy, List<ValidationMessage> errors) {
this.policy = policy;
this.errors = errors;
}
public abstract List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException;
public Element parseSingle(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
List<NamedElement> res = parse(stream);
if (res.size() != 1) {
throw new FHIRException("Parsing FHIR content returned multiple elements in a context where only one element is allowed");
}
return res.get(0).getElement();
}
public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException;
//FIXME: i18n should be done here
public void logError(int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError {
if (errors != null) {
if (policy == ValidationPolicy.EVERYTHING) {
ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level);
errors.add(msg);
} else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK))
throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col));
}
}
protected StructureDefinition getDefinition(int line, int col, String ns, String name) throws FHIRFormatError {
if (ns == null) {
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS__CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAMESPACE, name), IssueSeverity.FATAL);
return null;
}
if (name == null) {
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL);
return null;
}
for (StructureDefinition sd : context.allStructures()) {
if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/de-")) {
if(name.equals(sd.getType()) && (ns == null || ns.equals(FormatUtilities.FHIR_NS)) && !ToolingExtensions.hasExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
return sd;
String sns = ToolingExtensions.readStringExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
if ((name.equals(sd.getType()) || name.equals(sd.getName())) && ns != null && ns.equals(sns))
return sd;
}
}
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAMESPACENAME_, ns, name), IssueSeverity.FATAL);
return null;
}
protected StructureDefinition getDefinition(int line, int col, String name) throws FHIRFormatError {
if (name == null) {
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL);
return null;
}
// first pass: only look at base definitions
for (StructureDefinition sd : context.getStructures()) {
if (sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/"+name)) {
context.generateSnapshot(sd);
return sd;
}
}
for (StructureDefinition sd : context.getStructures()) {
if (name.equals(sd.getType()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
context.generateSnapshot(sd);
return sd;
}
}
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL);
return null;
}
public ILinkResolver getLinkResolver() {
return linkResolver;
}
public ParserBase setLinkResolver(ILinkResolver linkResolver) {
this.linkResolver = linkResolver;
return this;
}
public boolean isShowDecorations() {
return showDecorations;
}
public void setShowDecorations(boolean showDecorations) {
this.showDecorations = showDecorations;
}
public String getImpliedProfile() {
return null;
}
}

View File

@ -0,0 +1,169 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.formats.FormatUtilities;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.StructureDefinition.TypeDerivationRule;
import org.hl7.fhir.r4b.utils.ToolingExtensions;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.validation.ValidationMessage.Source;
public abstract class ParserBase {
public interface ILinkResolver {
String resolveType(String type);
String resolveProperty(Property property);
String resolvePage(String string);
}
public enum ValidationPolicy { NONE, QUICK, EVERYTHING }
public boolean isPrimitive(String code) {
return Utilities.existsInList(code, "boolean", "integer", "integer64", "string", "decimal", "uri", "base64Binary", "instant", "date", "dateTime", "time", "code", "oid", "id", "markdown", "unsignedInt", "positiveInt", "xhtml", "url", "canonical");
// StructureDefinition sd = context.fetchTypeDefinition(code);
// return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
}
protected IWorkerContext context;
protected ValidationPolicy policy;
protected List<ValidationMessage> errors;
protected ILinkResolver linkResolver;
protected boolean showDecorations;
public ParserBase(IWorkerContext context) {
super();
this.context = context;
policy = ValidationPolicy.NONE;
}
public void setupValidation(ValidationPolicy policy, List<ValidationMessage> errors) {
this.policy = policy;
this.errors = errors;
}
public abstract Element parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException;
public abstract void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException;
//FIXME: i18n should be done here
public void logError(int line, int col, String path, IssueType type, String message, IssueSeverity level) throws FHIRFormatError {
if (policy == ValidationPolicy.EVERYTHING) {
ValidationMessage msg = new ValidationMessage(Source.InstanceValidator, type, line, col, path, message, level);
errors.add(msg);
} else if (level == IssueSeverity.FATAL || (level == IssueSeverity.ERROR && policy == ValidationPolicy.QUICK))
throw new FHIRFormatError(message+String.format(" at line %d col %d", line, col));
}
protected StructureDefinition getDefinition(int line, int col, String ns, String name) throws FHIRFormatError {
if (ns == null) {
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS__CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAMESPACE, name), IssueSeverity.FATAL);
return null;
}
if (name == null) {
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL);
return null;
}
for (StructureDefinition sd : context.allStructures()) {
if (sd.getDerivation() == TypeDerivationRule.SPECIALIZATION && !sd.getUrl().startsWith("http://hl7.org/fhir/StructureDefinition/de-")) {
if(name.equals(sd.getType()) && (ns == null || ns.equals(FormatUtilities.FHIR_NS)) && !ToolingExtensions.hasExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace"))
return sd;
String sns = ToolingExtensions.readStringExtension(sd, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
if ((name.equals(sd.getType()) || name.equals(tail(sd.getType())) ) && ns != null && ns.equals(sns))
return sd;
}
}
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAMESPACENAME_, ns, name), IssueSeverity.FATAL);
return null;
}
private String tail(String type) {
return type.contains("/") ? type.substring(type.lastIndexOf("/")+1) : type;
}
protected StructureDefinition getDefinition(int line, int col, String name) throws FHIRFormatError {
if (name == null) {
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_CANNOT_BE_PARSED_AS_A_FHIR_OBJECT_NO_NAME), IssueSeverity.FATAL);
return null;
}
// first pass: only look at base definitions
for (StructureDefinition sd : context.getStructures()) {
if (sd.getUrl().equals("http://hl7.org/fhir/StructureDefinition/"+name)) {
context.generateSnapshot(sd);
return sd;
}
}
for (StructureDefinition sd : context.getStructures()) {
if (name.equals(sd.getType()) && sd.getDerivation() == TypeDerivationRule.SPECIALIZATION) {
context.generateSnapshot(sd);
return sd;
}
}
logError(line, col, name, IssueType.STRUCTURE, context.formatMessage(I18nConstants.THIS_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, name), IssueSeverity.FATAL);
return null;
}
public ILinkResolver getLinkResolver() {
return linkResolver;
}
public ParserBase setLinkResolver(ILinkResolver linkResolver) {
this.linkResolver = linkResolver;
return this;
}
public boolean isShowDecorations() {
return showDecorations;
}
public void setShowDecorations(boolean showDecorations) {
this.showDecorations = showDecorations;
}
}

View File

@ -0,0 +1,464 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.formats.FormatUtilities;
import org.hl7.fhir.r4b.model.ElementDefinition;
import org.hl7.fhir.r4b.model.ElementDefinition.PropertyRepresentation;
import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4b.model.TypeDetails;
import org.hl7.fhir.r4b.utils.ToolingExtensions;
import org.hl7.fhir.r4b.utils.TypesUtilities;
import org.hl7.fhir.utilities.Utilities;
public class Property {
private IWorkerContext context;
private ElementDefinition definition;
private StructureDefinition structure;
private Boolean canBePrimitive;
private ProfileUtilities profileUtilities;
public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure, ProfileUtilities profileUtilities) {
this.context = context;
this.definition = definition;
this.structure = structure;
this.profileUtilities = profileUtilities;
}
public Property(IWorkerContext context, ElementDefinition definition, StructureDefinition structure) {
this(context, definition, structure, new ProfileUtilities(context, null, null));
}
public String getName() {
return definition.getPath().substring(definition.getPath().lastIndexOf(".")+1);
}
public String getXmlName() {
if (definition.hasExtension("http://hl7.org/fhir/StructureDefinition/elementdefinition-xml-name")) {
return ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-xml-name");
} else {
return getName();
}
}
public String getXmlNamespace() {
if (ToolingExtensions.hasExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
return ToolingExtensions.readStringExtension(definition, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
} else if (ToolingExtensions.hasExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace")) {
return ToolingExtensions.readStringExtension(structure, "http://hl7.org/fhir/StructureDefinition/elementdefinition-namespace");
} else {
return FormatUtilities.FHIR_NS;
}
}
public ElementDefinition getDefinition() {
return definition;
}
public String getType() {
if (definition.getType().size() == 0)
return null;
else if (definition.getType().size() > 1) {
String tn = definition.getType().get(0).getWorkingCode();
for (int i = 1; i < definition.getType().size(); i++) {
if (!tn.equals(definition.getType().get(i).getWorkingCode()))
throw new Error("logic error, gettype when types > 1");
}
return tn;
} else
return definition.getType().get(0).getWorkingCode();
}
public String getType(String elementName) {
if (!definition.getPath().contains("."))
return definition.getPath();
ElementDefinition ed = definition;
if (definition.hasContentReference()) {
String url = null;
String path = definition.getContentReference();
if (!path.startsWith("#")) {
if (path.contains("#")) {
url = path.substring(0, path.indexOf("#"));
path = path.substring(path.indexOf("#")+1);
} else {
throw new Error("Illegal content reference '"+path+"'");
}
} else {
path = path.substring(1);
}
StructureDefinition sd = (url == null || url.equals(structure.getUrl())) ? structure : context.fetchResource(StructureDefinition.class, url, structure);
if (sd == null) {
throw new Error("Unknown Type in content reference '"+path+"'");
}
boolean found = false;
for (ElementDefinition d : sd.getSnapshot().getElement()) {
if (d.hasId() && d.getId().equals(path)) {
found = true;
ed = d;
}
}
if (!found)
throw new Error("Unable to resolve "+definition.getContentReference()+" at "+definition.getPath()+" on "+sd.getUrl());
}
if (ed.getType().size() == 0)
return null;
else if (ed.getType().size() > 1) {
String t = ed.getType().get(0).getCode();
boolean all = true;
for (TypeRefComponent tr : ed.getType()) {
if (!t.equals(tr.getCode()))
all = false;
}
if (all)
return t;
String tail = ed.getPath().substring(ed.getPath().lastIndexOf(".")+1);
if (tail.endsWith("[x]") && elementName != null && elementName.startsWith(tail.substring(0, tail.length()-3))) {
String name = elementName.substring(tail.length()-3);
return isPrimitive(lowFirst(name)) ? lowFirst(name) : name;
} else {
if (ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"))
return ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
throw new Error("logic error, gettype when types > 1, name mismatch for "+elementName+" on at "+ed.getPath());
}
} else if (ed.getType().get(0).getCode() == null) {
if (Utilities.existsInList(ed.getId(), "Element.id", "Extension.url"))
return "string";
else
return structure.getId();
} else
return ed.getType().get(0).getWorkingCode();
}
public boolean hasType(String elementName) {
if (definition.getType().size() == 0)
return false;
else if (definition.getType().size() > 1) {
String t = definition.getType().get(0).getCode();
boolean all = true;
for (TypeRefComponent tr : definition.getType()) {
if (!t.equals(tr.getCode()))
all = false;
}
if (all)
return true;
String tail = definition.getPath().substring(definition.getPath().lastIndexOf(".")+1);
if (tail.endsWith("[x]") && elementName.startsWith(tail.substring(0, tail.length()-3))) {
String name = elementName.substring(tail.length()-3);
return true;
} else
return false;
} else
return true;
}
public StructureDefinition getStructure() {
return structure;
}
/**
* Is the given name a primitive
*
* @param E.g. "Observation.status"
*/
public boolean isPrimitiveName(String name) {
String code = getType(name);
return isPrimitive(code);
}
/**
* Is the given type a primitive
*
* @param E.g. "integer"
*/
public boolean isPrimitive(String code) {
return TypesUtilities.isPrimitive(code);
// was this... but this can be very inefficient compared to hard coding the list
// StructureDefinition sd = context.fetchTypeDefinition(code);
// return sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
}
private String lowFirst(String t) {
return t.substring(0, 1).toLowerCase()+t.substring(1);
}
public boolean isResource() {
if (definition.getType().size() > 0)
return definition.getType().size() == 1 && ("Resource".equals(definition.getType().get(0).getCode()) || "DomainResource".equals(definition.getType().get(0).getCode()));
else
return !definition.getPath().contains(".") && (structure.getKind() == StructureDefinitionKind.RESOURCE);
}
public boolean isList() {
return !"1".equals(definition.getMax());
}
public boolean isBaseList() {
return !"1".equals(definition.getBase().getMax());
}
public String getScopedPropertyName() {
return definition.getBase().getPath();
}
private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) {
boolean result = false;
if (!ed.getType().isEmpty()) {
result = true;
for (final ElementDefinition ele : children) {
if (!ele.getPath().contains("extension")) {
result = false;
break;
}
}
}
return result;
}
public boolean IsLogicalAndHasPrimitiveValue(String name) {
// if (canBePrimitive!= null)
// return canBePrimitive;
canBePrimitive = false;
if (structure.getKind() != StructureDefinitionKind.LOGICAL)
return false;
if (!hasType(name))
return false;
StructureDefinition sd = context.fetchResource(StructureDefinition.class, structure.getUrl().substring(0, structure.getUrl().lastIndexOf("/")+1)+getType(name));
if (sd == null)
sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(getType(name), context.getOverrideVersionNs()));
if (sd != null && sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE)
return true;
if (sd == null || sd.getKind() != StructureDefinitionKind.LOGICAL)
return false;
for (ElementDefinition ed : sd.getSnapshot().getElement()) {
if (ed.getPath().equals(sd.getId()+".value") && ed.getType().size() == 1 && isPrimitive(ed.getType().get(0).getCode())) {
canBePrimitive = true;
return true;
}
}
return false;
}
public boolean isChoice() {
if (definition.getType().size() <= 1)
return false;
String tn = definition.getType().get(0).getCode();
for (int i = 1; i < definition.getType().size(); i++)
if (!definition.getType().get(i).getCode().equals(tn))
return true;
return false;
}
protected List<Property> getChildProperties(String elementName, String statedType) throws FHIRException {
ElementDefinition ed = definition;
StructureDefinition sd = structure;
List<ElementDefinition> children = profileUtilities.getChildMap(sd, ed);
String url = null;
if (children.isEmpty() || isElementWithOnlyExtension(ed, children)) {
// ok, find the right definitions
String t = null;
if (ed.getType().size() == 1)
t = ed.getType().get(0).getWorkingCode();
else if (ed.getType().size() == 0)
throw new Error("types == 0, and no children found on "+getDefinition().getPath());
else {
t = ed.getType().get(0).getWorkingCode();
boolean all = true;
for (TypeRefComponent tr : ed.getType()) {
if (!tr.getWorkingCode().equals(t)) {
all = false;
break;
}
}
if (!all) {
// ok, it's polymorphic
if (ed.hasRepresentation(PropertyRepresentation.TYPEATTR)) {
t = statedType;
if (t == null && ToolingExtensions.hasExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype"))
t = ToolingExtensions.readStringExtension(ed, "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
boolean ok = false;
for (TypeRefComponent tr : ed.getType()) {
if (tr.getWorkingCode().equals(t))
ok = true;
if (Utilities.isAbsoluteUrl(tr.getWorkingCode())) {
StructureDefinition sdt = context.fetchResource(StructureDefinition.class, tr.getWorkingCode());
if (sdt != null && sdt.getType().equals(t)) {
url = tr.getWorkingCode();
ok = true;
}
}
if (ok)
break;
}
if (!ok)
throw new DefinitionException("Type '"+t+"' is not an acceptable type for '"+elementName+"' on property "+definition.getPath());
} else {
t = elementName.substring(tail(ed.getPath()).length() - 3);
if (isPrimitive(lowFirst(t)))
t = lowFirst(t);
}
}
}
if (!"xhtml".equals(t)) {
for (TypeRefComponent aType: ed.getType()) {
if (aType.getWorkingCode().equals(t)) {
if (aType.hasProfile()) {
assert aType.getProfile().size() == 1;
url = aType.getProfile().get(0).getValue();
} else {
url = ProfileUtilities.sdNs(t, context.getOverrideVersionNs());
}
break;
}
}
if (url==null)
throw new FHIRException("Unable to find type " + t + " for element " + elementName + " with path " + ed.getPath());
sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null)
throw new DefinitionException("Unable to find type '"+t+"' for name '"+elementName+"' on property "+definition.getPath());
children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
}
}
List<Property> properties = new ArrayList<Property>();
for (ElementDefinition child : children) {
properties.add(new Property(context, child, sd, this.profileUtilities));
}
return properties;
}
protected List<Property> getChildProperties(TypeDetails type) throws DefinitionException {
ElementDefinition ed = definition;
StructureDefinition sd = structure;
List<ElementDefinition> children = profileUtilities.getChildMap(sd, ed);
if (children.isEmpty()) {
// ok, find the right definitions
String t = null;
if (ed.getType().size() == 1)
t = ed.getType().get(0).getCode();
else if (ed.getType().size() == 0)
throw new Error("types == 0, and no children found");
else {
t = ed.getType().get(0).getCode();
boolean all = true;
for (TypeRefComponent tr : ed.getType()) {
if (!tr.getCode().equals(t)) {
all = false;
break;
}
}
if (!all) {
// ok, it's polymorphic
t = type.getType();
}
}
if (!"xhtml".equals(t)) {
sd = context.fetchResource(StructureDefinition.class, t);
if (sd == null)
throw new DefinitionException("Unable to find class '"+t+"' for name '"+ed.getPath()+"' on property "+definition.getPath());
children = profileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
}
}
List<Property> properties = new ArrayList<Property>();
for (ElementDefinition child : children) {
properties.add(new Property(context, child, sd, this.profileUtilities));
}
return properties;
}
private String tail(String path) {
return path.contains(".") ? path.substring(path.lastIndexOf(".")+1) : path;
}
public Property getChild(String elementName, String childName) throws FHIRException {
List<Property> children = getChildProperties(elementName, null);
for (Property p : children) {
if (p.getName().equals(childName)) {
return p;
}
}
return null;
}
public Property getChild(String name, TypeDetails type) throws DefinitionException {
List<Property> children = getChildProperties(type);
for (Property p : children) {
if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
return p;
}
}
return null;
}
public Property getChild(String name) throws FHIRException {
List<Property> children = getChildProperties(name, null);
for (Property p : children) {
if (p.getName().equals(name)) {
return p;
}
}
return null;
}
public Property getChildSimpleName(String elementName, String name) throws FHIRException {
List<Property> children = getChildProperties(elementName, null);
for (Property p : children) {
if (p.getName().equals(name) || p.getName().equals(name+"[x]")) {
return p;
}
}
return null;
}
public IWorkerContext getContext() {
return context;
}
@Override
public String toString() {
return definition.getPath();
}
}

View File

@ -0,0 +1,315 @@
package org.hl7.fhir.r4b.elementmodel;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.zip.DataFormatException;
import java.util.zip.Inflater;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.VersionUtilities;
import org.hl7.fhir.utilities.json.JSONUtil;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.json.JsonTrackingParser.LocationData;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
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;
/**
* this class is actually a smart health cards validator.
* It's going to parse the JWT and assume that it contains
* a smart health card, which has a nested bundle in it, and
* then validate the bundle.
*
* See https://spec.smarthealth.cards/#health-cards-are-encoded-as-compact-serialization-json-web-signatures-jws
*
* This parser dose the JWT work, and then passes the JsonObject through to the underlying JsonParser
*
* Error locations are in the decoded payload
*
* @author grahame
*
*/
public class SHCParser extends ParserBase {
private JsonParser jsonParser;
private Map<JsonElement, LocationData> map;
private List<String> types = new ArrayList<>();
public SHCParser(IWorkerContext context) {
super(context);
jsonParser = new JsonParser(context);
}
public List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
List<NamedElement> res = new ArrayList<>();
String src = TextFile.streamToString(stream).trim();
List<String> list = new ArrayList<>();
String pfx = null;
if (src.startsWith("{")) {
JsonObject json = JsonTrackingParser.parseJson(src);
if (checkProperty(json, "$", "verifiableCredential", true, "Array")) {
pfx = "verifiableCredential";
JsonArray arr = json.getAsJsonArray("verifiableCredential");
int i = 0;
for (JsonElement e : arr) {
if (!(e instanceof JsonPrimitive)) {
logError(line(e), col(e), "$.verifiableCredential["+i+"]", IssueType.STRUCTURE, "Wrong Property verifiableCredential in JSON Payload. Expected : String but found "+JSONUtil.type(e), IssueSeverity.ERROR);
} else {
list.add(e.getAsString());
}
i++;
}
} else {
return res;
}
} else {
list.add(src);
}
int c = 0;
for (String ssrc : list) {
String prefix = pfx == null ? "" : pfx+"["+Integer.toString(c)+"].";
c++;
JWT jwt = null;
try {
jwt = decodeJWT(ssrc);
} catch (Exception e) {
logError(1, 1, prefix+"JWT", IssueType.INVALID, "Unable to decode JWT token", IssueSeverity.ERROR);
return res;
}
map = jwt.map;
checkNamedProperties(jwt.getPayload(), prefix+"payload", "iss", "nbf", "vc");
checkProperty(jwt.getPayload(), prefix+"payload", "iss", true, "String");
logError(1, 1, prefix+"JWT", IssueType.INFORMATIONAL, "The FHIR Validator does not check the JWT signature "+
"(see https://demo-portals.smarthealth.cards/VerifierPortal.html or https://github.com/smart-on-fhir/health-cards-dev-tools) (Issuer = '"+jwt.getPayload().get("iss").getAsString()+"')", IssueSeverity.INFORMATION);
checkProperty(jwt.getPayload(), prefix+"payload", "nbf", true, "Number");
JsonObject vc = jwt.getPayload().getAsJsonObject("vc");
if (vc == null) {
logError(1, 1, "JWT", IssueType.STRUCTURE, "Unable to find property 'vc' in the payload", IssueSeverity.ERROR);
return res;
}
String path = prefix+"payload.vc";
checkNamedProperties(vc, path, "type", "credentialSubject");
if (!checkProperty(vc, path, "type", true, "Array")) {
return res;
}
JsonArray type = vc.getAsJsonArray("type");
int i = 0;
for (JsonElement e : type) {
if (!(e instanceof JsonPrimitive)) {
logError(line(e), col(e), path+".type["+i+"]", IssueType.STRUCTURE, "Wrong Property Type in JSON Payload. Expected : String but found "+JSONUtil.type(e), IssueSeverity.ERROR);
} else {
types.add(e.getAsString());
}
i++;
}
if (!types.contains("https://smarthealth.cards#health-card")) {
logError(line(vc), col(vc), path, IssueType.STRUCTURE, "Card does not claim to be of type https://smarthealth.cards#health-card, cannot validate", IssueSeverity.ERROR);
return res;
}
if (!checkProperty(vc, path, "credentialSubject", true, "Object")) {
return res;
}
JsonObject cs = vc.getAsJsonObject("credentialSubject");
path = path+".credentialSubject";
if (!checkProperty(cs, path, "fhirVersion", true, "String")) {
return res;
}
JsonElement fv = cs.get("fhirVersion");
if (!VersionUtilities.versionsCompatible(context.getVersion(), fv.getAsString())) {
logError(line(fv), col(fv), path+".fhirVersion", IssueType.STRUCTURE, "Card claims to be of version "+fv.getAsString()+", cannot be validated against version "+context.getVersion(), IssueSeverity.ERROR);
return res;
}
if (!checkProperty(cs, path, "fhirBundle", true, "Object")) {
return res;
}
// ok. all checks passed, we can now validate the bundle
Element e = jsonParser.parse(cs.getAsJsonObject("fhirBundle"), map);
if (e != null) {
res.add(new NamedElement(path, e));
}
}
return res;
}
@Override
public String getImpliedProfile() {
if (types.contains("https://smarthealth.cards#covid19") && types.contains("https://smarthealth.cards#immunization")) {
return "http://hl7.org/fhir/uv/shc-vaccination/StructureDefinition/shc-vaccination-bundle-dm";
}
if (types.contains("https://smarthealth.cards#covid19") && types.contains("https://smarthealth.cards#laboratory")) {
return "http://hl7.org/fhir/uv/shc-vaccination/StructureDefinition/shc-covid19-laboratory-bundle-dm";
}
if (types.contains("https://smarthealth.cards#laboratory")) {
return "http://hl7.org/fhir/uv/shc-vaccination/StructureDefinition/shc-infectious-disease-laboratory-bundle-dm";
}
return null;
}
private boolean checkProperty(JsonObject obj, String path, String name, boolean required, String type) {
JsonElement e = obj.get(name);
if (e != null) {
String t = JSONUtil.type(e);
if (!type.equals(t)) {
logError(line(e), col(e), path+"."+name, IssueType.STRUCTURE, "Wrong Property Type in JSON Payload. Expected : "+type+" but found "+t, IssueSeverity.ERROR);
} else {
return true;
}
} else if (required) {
logError(line(obj), col(obj), path, IssueType.STRUCTURE, "Missing Property in JSON Payload: "+name, IssueSeverity.ERROR);
} else {
return true;
}
return false;
}
private void checkNamedProperties(JsonObject obj, String path, String... names) {
for (Entry<String, JsonElement> e : obj.entrySet()) {
if (!Utilities.existsInList(e.getKey(), names)) {
logError(line(e.getValue()), col(e.getValue()), path+"."+e.getKey(), IssueType.STRUCTURE, "Unknown Property in JSON Payload", IssueSeverity.WARNING);
}
}
}
private int line(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getLine();
}
private int col(JsonElement e) {
if (map == null|| !map.containsKey(e))
return -1;
else
return map.get(e).getCol();
}
public void compose(Element e, OutputStream destination, OutputStyle style, String base) throws FHIRException, IOException {
throw new FHIRFormatError("Writing resources is not supported for the SHC format");
// because then we'd have to try to sign, and we're just not going to be doing that from the element model
}
public static class JWT {
private JsonObject header;
private JsonObject payload;
public Map<JsonElement, LocationData> map = new HashMap<>();
public JsonObject getHeader() {
return header;
}
public void setHeader(JsonObject header) {
this.header = header;
}
public JsonObject getPayload() {
return payload;
}
public void setPayload(JsonObject payload) {
this.payload = payload;
}
}
private static final int BUFFER_SIZE = 1024;
public static final String CURRENT_PACKAGE = "hl7.fhir.uv.shc-vaccination#0.6.2";
private static final int MAX_ALLOWED_SHC_LENGTH = 1195;
// todo: deal with chunking
public static String decodeQRCode(String src) {
StringBuilder b = new StringBuilder();
if (!src.startsWith("shc:/")) {
throw new FHIRException("Unable to process smart health card (didn't start with shc:/)");
}
for (int i = 5; i < src.length(); i = i + 2) {
String s = src.substring(i, i+2);
byte v = Byte.parseByte(s);
char c = (char) (45+v);
b.append(c);
}
return b.toString();
}
public JWT decodeJWT(String jwt) throws IOException, DataFormatException {
if (jwt.startsWith("shc:/")) {
jwt = decodeQRCode(jwt);
}
if (jwt.length() > MAX_ALLOWED_SHC_LENGTH) {
logError(-1, -1, "jwt", IssueType.TOOLONG, "JWT Payload limit length is "+MAX_ALLOWED_SHC_LENGTH+" bytes for a single image - this has "+jwt.length()+" bytes", IssueSeverity.ERROR);
}
String[] parts = splitToken(jwt);
byte[] headerJson;
byte[] payloadJson;
try {
headerJson = Base64.getUrlDecoder().decode(parts[0]);
payloadJson = Base64.getUrlDecoder().decode(parts[1]);
} catch (NullPointerException e) {
throw new FHIRException("The UTF-8 Charset isn't initialized.", e);
} catch (IllegalArgumentException e){
throw new FHIRException("The input is not a valid base 64 encoded string.", e);
}
JWT res = new JWT();
res.header = JsonTrackingParser.parseJson(headerJson);
if ("DEF".equals(JSONUtil.str(res.header, "zip"))) {
payloadJson = inflate(payloadJson);
}
res.payload = JsonTrackingParser.parse(TextFile.bytesToString(payloadJson), res.map, true);
return res;
}
static String[] splitToken(String token) {
String[] parts = token.split("\\.");
if (parts.length == 2 && token.endsWith(".")) {
//Tokens with alg='none' have empty String as Signature.
parts = new String[]{parts[0], parts[1], ""};
}
if (parts.length != 3) {
throw new FHIRException(String.format("The token was expected to have 3 parts, but got %s.", parts.length));
}
return parts;
}
public static final byte[] inflate(byte[] data) throws IOException, DataFormatException {
final Inflater inflater = new Inflater(true);
inflater.setInput(data);
try (final ByteArrayOutputStream outputStream = new ByteArrayOutputStream(data.length))
{
byte[] buffer = new byte[BUFFER_SIZE];
while (!inflater.finished())
{
final int count = inflater.inflate(buffer);
outputStream.write(buffer, 0, count);
}
return outputStream.toByteArray();
}
}
}

View File

@ -0,0 +1,117 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.List;
import java.util.Map.Entry;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.context.SimpleWorkerContext;
import org.hl7.fhir.r4b.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r4b.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
public class Tester {
public static void main(String[] args) throws Exception {
IWorkerContext context = SimpleWorkerContext.fromPack(Utilities.path("C:\\work\\org.hl7.fhir\\build\\publish", "validation-min.xml.zip"));
int t = 0;
int ok = 0;
for (String f : new File("C:\\work\\org.hl7.fhir\\build\\publish").list()) {
if (f.endsWith(".xml") && !f.endsWith(".canonical.xml") && !f.contains("profile") && !f.contains("questionnaire") && new File("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".ttl")).exists()) {
// if (f.equals("account-questionnaire.xml")) {
System.out.print("convert "+f);
// Manager.convert(context, new FileInputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+f), FhirFormat.XML,
// new FileOutputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.json")), FhirFormat.JSON, OutputStyle.PRETTY);
// String src = normalise(TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.json")));
// String tgt = normalise(TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".json")));
Element e = Manager.parseSingle(context, new FileInputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+f), FhirFormat.XML);
Manager.compose(context, e, new FileOutputStream("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.ttl")), FhirFormat.TURTLE, OutputStyle.PRETTY, null);
Manager.compose(context, e, new FileOutputStream("C:\\temp\\resource.xml"), FhirFormat.XML, OutputStyle.PRETTY, null);
String src = TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".mm.ttl"));
String tgt = TextFile.fileToString("C:\\work\\org.hl7.fhir\\build\\publish\\"+Utilities.changeFileExt(f, ".ttl"));
t++;
if (src.equals(tgt)) {
System.out.println(".. ok");
ok++;
} else
System.out.println(".. fail");
}
// }
}
System.out.println("done - "+Integer.toString(t)+" files, "+Integer.toString(ok)+" ok");
}
private static com.google.gson.JsonParser parser = new com.google.gson.JsonParser();
private static String normalise(String s) {
JsonObject json = parser.parse(s).getAsJsonObject();
JsonElement txt = json.get("text");
if (txt != null) {
if (((JsonObject) txt).has("div"))
((JsonObject) txt).remove("div");
}
removeComments(json);
return json.toString();
}
private static void removeComments(JsonArray arr) {
for (JsonElement i : arr) {
if (i instanceof JsonObject)
removeComments((JsonObject) i);
if (i instanceof JsonArray)
removeComments((JsonArray) i);
}
}
private static void removeComments(JsonObject json) {
if (json.has("fhir_comments"))
json.remove("fhir_comments");
for (Entry<String, JsonElement> p : json.entrySet()) {
if (p.getValue() instanceof JsonObject)
removeComments((JsonObject) p.getValue());
if (p.getValue() instanceof JsonArray)
removeComments((JsonArray) p.getValue());
}
}
}

View File

@ -0,0 +1,585 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r4b.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.utils.SnomedExpressions;
import org.hl7.fhir.r4b.utils.SnomedExpressions.Expression;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.turtle.Turtle;
import org.hl7.fhir.utilities.turtle.Turtle.Complex;
import org.hl7.fhir.utilities.turtle.Turtle.Section;
import org.hl7.fhir.utilities.turtle.Turtle.Subject;
import org.hl7.fhir.utilities.turtle.Turtle.TTLComplex;
import org.hl7.fhir.utilities.turtle.Turtle.TTLList;
import org.hl7.fhir.utilities.turtle.Turtle.TTLLiteral;
import org.hl7.fhir.utilities.turtle.Turtle.TTLObject;
import org.hl7.fhir.utilities.turtle.Turtle.TTLURL;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
public class TurtleParser extends ParserBase {
private String base;
public static String FHIR_URI_BASE = "http://hl7.org/fhir/";
public static String FHIR_VERSION_BASE = "http://build.fhir.org/";
public TurtleParser(IWorkerContext context) {
super(context);
}
@Override
public List<NamedElement> parse(InputStream input) throws IOException, FHIRException {
List<NamedElement> res = new ArrayList<>();
Turtle src = new Turtle();
if (policy == ValidationPolicy.EVERYTHING) {
try {
src.parse(TextFile.streamToString(input));
} catch (Exception e) {
logError(-1, -1, "(document)", IssueType.INVALID, context.formatMessage(I18nConstants.ERROR_PARSING_TURTLE_, e.getMessage()), IssueSeverity.FATAL);
return null;
}
Element e = parse(src);
if (e != null) {
res.add(new NamedElement(null, e));
}
} else {
src.parse(TextFile.streamToString(input));
Element e = parse(src);
if (e != null) {
res.add(new NamedElement(null, e));
}
}
return res;
}
private Element parse(Turtle src) throws FHIRException {
// we actually ignore the stated URL here
for (TTLComplex cmp : src.getObjects().values()) {
for (String p : cmp.getPredicates().keySet()) {
if ((FHIR_URI_BASE + "nodeRole").equals(p) && cmp.getPredicates().get(p).hasValue(FHIR_URI_BASE + "treeRoot")) {
return parse(src, cmp);
}
}
}
// still here: well, we didn't find a start point
String msg = "Error parsing Turtle: unable to find any node maked as the entry point (where " + FHIR_URI_BASE + "nodeRole = " + FHIR_URI_BASE + "treeRoot)";
if (policy == ValidationPolicy.EVERYTHING) {
logError(-1, -1, "(document)", IssueType.INVALID, msg, IssueSeverity.FATAL);
return null;
} else {
throw new FHIRFormatError(msg);
}
}
private Element parse(Turtle src, TTLComplex cmp) throws FHIRException {
TTLObject type = cmp.getPredicates().get("http://www.w3.org/2000/01/rdf-schema#type");
if (type == null) {
logError(cmp.getLine(), cmp.getCol(), "(document)", IssueType.INVALID, context.formatMessage(I18nConstants.UNKNOWN_RESOURCE_TYPE_MISSING_RDFSTYPE), IssueSeverity.FATAL);
return null;
}
if (type instanceof TTLList) {
// this is actually broken - really we have to look through the structure definitions at this point
for (TTLObject obj : ((TTLList) type).getList()) {
if (obj instanceof TTLURL && ((TTLURL) obj).getUri().startsWith(FHIR_URI_BASE)) {
type = obj;
break;
}
}
}
if (!(type instanceof TTLURL)) {
logError(cmp.getLine(), cmp.getCol(), "(document)", IssueType.INVALID, context.formatMessage(I18nConstants.UNEXPECTED_DATATYPE_FOR_RDFSTYPE), IssueSeverity.FATAL);
return null;
}
String name = ((TTLURL) type).getUri();
String ns = name.substring(0, name.lastIndexOf("/"));
name = name.substring(name.lastIndexOf("/")+1);
String path = "/"+name;
StructureDefinition sd = getDefinition(cmp.getLine(), cmp.getCol(), ns, name);
if (sd == null)
return null;
Element result = new Element(name, new Property(context, sd.getSnapshot().getElement().get(0), sd));
result.markLocation(cmp.getLine(), cmp.getCol());
result.setType(name);
parseChildren(src, path, cmp, result, false);
result.numberChildren();
return result;
}
private void parseChildren(Turtle src, String path, TTLComplex object, Element element, boolean primitive) throws FHIRException {
List<Property> properties = element.getProperty().getChildProperties(element.getName(), null);
Set<String> processed = new HashSet<String>();
if (primitive)
processed.add(FHIR_URI_BASE + "value");
// note that we do not trouble ourselves to maintain the wire format order here - we don't even know what it was anyway
// first pass: process the properties
for (Property property : properties) {
if (property.isChoice()) {
for (TypeRefComponent type : property.getDefinition().getType()) {
String eName = property.getName().substring(0, property.getName().length()-3) + Utilities.capitalize(type.getCode());
parseChild(src, object, element, processed, property, path, getFormalName(property, eName));
}
} else {
parseChild(src, object, element, processed, property, path, getFormalName(property));
}
}
// second pass: check for things not processed
if (policy != ValidationPolicy.NONE) {
for (String u : object.getPredicates().keySet()) {
if (!processed.contains(u)) {
TTLObject n = object.getPredicates().get(u);
logError(n.getLine(), n.getCol(), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNRECOGNISED_PREDICATE_, u), IssueSeverity.ERROR);
}
}
}
}
private void parseChild(Turtle src, TTLComplex object, Element context, Set<String> processed, Property property, String path, String name) throws FHIRException {
processed.add(name);
String npath = path+"/"+property.getName();
TTLObject e = object.getPredicates().get(FHIR_URI_BASE + name);
if (e == null)
return;
if (property.isList() && (e instanceof TTLList)) {
TTLList arr = (TTLList) e;
for (TTLObject am : arr.getList()) {
parseChildInstance(src, npath, object, context, property, name, am);
}
} else {
parseChildInstance(src, npath, object, context, property, name, e);
}
}
private void parseChildInstance(Turtle src, String npath, TTLComplex object, Element element, Property property, String name, TTLObject e) throws FHIRException {
if (property.isResource())
parseResource(src, npath, object, element, property, name, e);
else if (e instanceof TTLComplex) {
TTLComplex child = (TTLComplex) e;
Element n = new Element(tail(name), property).markLocation(e.getLine(), e.getCol());
element.getChildren().add(n);
if (property.isPrimitive(property.getType(tail(name)))) {
parseChildren(src, npath, child, n, true);
TTLObject val = child.getPredicates().get(FHIR_URI_BASE + "value");
if (val != null) {
if (val instanceof TTLLiteral) {
String value = ((TTLLiteral) val).getValue();
String type = ((TTLLiteral) val).getType();
// todo: check type
n.setValue(value);
} else
logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_A_LITERAL_NOT_, "a "+e.getClass().getName()), IssueSeverity.ERROR);
}
} else
parseChildren(src, npath, child, n, false);
} else
logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.THIS_PROPERTY_MUST_BE_A_URI_OR_BNODE_NOT_, "a "+e.getClass().getName()), IssueSeverity.ERROR);
}
private String tail(String name) {
return name.substring(name.lastIndexOf(".")+1);
}
private void parseResource(Turtle src, String npath, TTLComplex object, Element element, Property property, String name, TTLObject e) throws FHIRException {
TTLComplex obj;
if (e instanceof TTLComplex)
obj = (TTLComplex) e;
else if (e instanceof TTLURL) {
String url = ((TTLURL) e).getUri();
obj = src.getObject(url);
if (obj == null) {
logError(e.getLine(), e.getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.REFERENCE_TO__CANNOT_BE_RESOLVED, url), IssueSeverity.FATAL);
return;
}
} else
throw new FHIRFormatError(context.formatMessage(I18nConstants.WRONG_TYPE_FOR_RESOURCE));
TTLObject type = obj.getPredicates().get("http://www.w3.org/2000/01/rdf-schema#type");
if (type == null) {
logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNKNOWN_RESOURCE_TYPE_MISSING_RDFSTYPE), IssueSeverity.FATAL);
return;
}
if (type instanceof TTLList) {
// this is actually broken - really we have to look through the structure definitions at this point
for (TTLObject tobj : ((TTLList) type).getList()) {
if (tobj instanceof TTLURL && ((TTLURL) tobj).getUri().startsWith(FHIR_URI_BASE)) {
type = tobj;
break;
}
}
}
if (!(type instanceof TTLURL)) {
logError(object.getLine(), object.getCol(), npath, IssueType.INVALID, context.formatMessage(I18nConstants.UNEXPECTED_DATATYPE_FOR_RDFSTYPE), IssueSeverity.FATAL);
return;
}
String rt = ((TTLURL) type).getUri();
String ns = rt.substring(0, rt.lastIndexOf("/"));
rt = rt.substring(rt.lastIndexOf("/")+1);
StructureDefinition sd = getDefinition(object.getLine(), object.getCol(), ns, rt);
if (sd == null)
return;
Element n = new Element(tail(name), property).markLocation(object.getLine(), object.getCol());
element.getChildren().add(n);
n.updateProperty(new Property(this.context, sd.getSnapshot().getElement().get(0), sd), SpecialElement.fromProperty(n.getProperty()), property);
n.setType(rt);
parseChildren(src, npath, obj, n, false);
}
private String getFormalName(Property property) {
String en = property.getDefinition().getBase().getPath();
if (en == null)
en = property.getDefinition().getPath();
// boolean doType = false;
// if (en.endsWith("[x]")) {
// en = en.substring(0, en.length()-3);
// doType = true;
// }
// if (doType || (element.getProperty().getDefinition().getType().size() > 1 && !allReference(element.getProperty().getDefinition().getType())))
// en = en + Utilities.capitalize(element.getType());
return en;
}
private String getFormalName(Property property, String elementName) {
String en = property.getDefinition().getBase().getPath();
if (en == null)
en = property.getDefinition().getPath();
if (!en.endsWith("[x]"))
throw new Error(context.formatMessage(I18nConstants.ATTEMPT_TO_REPLACE_ELEMENT_NAME_FOR_A_NONCHOICE_TYPE));
return en.substring(0, en.lastIndexOf(".")+1)+elementName;
}
@Override
public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException, FHIRException {
this.base = base;
Turtle ttl = new Turtle();
compose(e, ttl, base);
ttl.commit(stream, false);
}
public void compose(Element e, Turtle ttl, String base) throws FHIRException {
ttl.prefix("fhir", FHIR_URI_BASE);
ttl.prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
ttl.prefix("owl", "http://www.w3.org/2002/07/owl#");
ttl.prefix("xsd", "http://www.w3.org/2001/XMLSchema#");
Section section = ttl.section("resource");
String subjId = genSubjectId(e);
String ontologyId = subjId.replace(">", ".ttl>");
Section ontology = ttl.section("ontology header");
ontology.triple(ontologyId, "a", "owl:Ontology");
ontology.triple(ontologyId, "owl:imports", "fhir:fhir.ttl");
if(ontologyId.startsWith("<" + FHIR_URI_BASE))
ontology.triple(ontologyId, "owl:versionIRI", ontologyId.replace(FHIR_URI_BASE, FHIR_VERSION_BASE));
Subject subject = section.triple(subjId, "a", "fhir:" + e.getType());
subject.linkedPredicate("fhir:nodeRole", "fhir:treeRoot", linkResolver == null ? null : linkResolver.resolvePage("rdf.html#tree-root"));
for (Element child : e.getChildren()) {
composeElement(section, subject, child, null);
}
}
protected String getURIType(String uri) {
if(uri.startsWith("<" + FHIR_URI_BASE))
if(uri.substring(FHIR_URI_BASE.length() + 1).contains("/"))
return uri.substring(FHIR_URI_BASE.length() + 1, uri.indexOf('/', FHIR_URI_BASE.length() + 1));
return null;
}
protected String getReferenceURI(String ref) {
if (ref != null && (ref.startsWith("http://") || ref.startsWith("https://")))
return "<" + ref + ">";
else if (base != null && ref != null && ref.contains("/"))
return "<" + Utilities.appendForwardSlash(base) + ref + ">";
else
return null;
}
protected void decorateReference(Complex t, Element coding) {
String refURI = getReferenceURI(coding.getChildValue("reference"));
if(refURI != null)
t.linkedPredicate("fhir:link", refURI, linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference"));
}
protected void decorateCanonical(Complex t, Element canonical) {
String refURI = getReferenceURI(canonical.primitiveValue());
if(refURI != null)
t.linkedPredicate("fhir:link", refURI, linkResolver == null ? null : linkResolver.resolvePage("rdf.html#reference"));
}
private String genSubjectId(Element e) {
String id = e.getChildValue("id");
if (base == null || id == null)
return "";
else if (base.endsWith("#"))
return "<" + base + e.getType() + "-" + id + ">";
else
return "<" + Utilities.pathURL(base, e.getType(), id) + ">";
}
private String urlescape(String s) {
StringBuilder b = new StringBuilder();
for (char ch : s.toCharArray()) {
if (Utilities.charInSet(ch, ':', ';', '=', ','))
b.append("%"+Integer.toHexString(ch));
else
b.append(ch);
}
return b.toString();
}
private void composeElement(Section section, Complex ctxt, Element element, Element parent) throws FHIRException {
// "Extension".equals(element.getType())?
// (element.getProperty().getDefinition().getIsModifier()? "modifierExtension" : "extension") ;
String en = getFormalName(element);
Complex t;
if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY && parent != null && parent.getNamedChildValue("fullUrl") != null) {
String url = "<"+parent.getNamedChildValue("fullUrl")+">";
ctxt.linkedPredicate("fhir:"+en, url, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()));
t = section.subject(url);
} else {
t = ctxt.linkedPredicate("fhir:"+en, linkResolver == null ? null : linkResolver.resolveProperty(element.getProperty()));
}
if (element.getSpecial() != null)
t.linkedPredicate("a", "fhir:"+element.fhirType(), linkResolver == null ? null : linkResolver.resolveType(element.fhirType()));
if (element.hasValue())
t.linkedPredicate("fhir:value", ttlLiteral(element.getValue(), element.getType()), linkResolver == null ? null : linkResolver.resolveType(element.getType()));
if (element.getProperty().isList() && (!element.isResource() || element.getSpecial() == SpecialElement.CONTAINED))
t.linkedPredicate("fhir:index", Integer.toString(element.getIndex()), linkResolver == null ? null : linkResolver.resolvePage("rdf.html#index"));
if ("Coding".equals(element.getType()))
decorateCoding(t, element, section);
if (Utilities.existsInList(element.getType(), "Reference"))
decorateReference(t, element);
else if (Utilities.existsInList(element.getType(), "canonical"))
decorateCanonical(t, element);
if("canonical".equals(element.getType())) {
String refURI = element.primitiveValue();
if (refURI != null) {
String uriType = getURIType(refURI);
if(uriType != null && !section.hasSubject(refURI))
section.triple(refURI, "a", "fhir:" + uriType);
}
}
if("Reference".equals(element.getType())) {
String refURI = getReferenceURI(element.getChildValue("reference"));
if (refURI != null) {
String uriType = getURIType(refURI);
if(uriType != null && !section.hasSubject(refURI))
section.triple(refURI, "a", "fhir:" + uriType);
}
}
for (Element child : element.getChildren()) {
if ("xhtml".equals(child.getType())) {
String childfn = getFormalName(child);
t.predicate("fhir:" + childfn, ttlLiteral(child.getValue(), child.getType()));
} else
composeElement(section, t, child, element);
}
}
private String getFormalName(Element element) {
String en = null;
if (element.getSpecial() == null) {
if (element.getProperty().getDefinition().hasBase())
en = element.getProperty().getDefinition().getBase().getPath();
}
else if (element.getSpecial() == SpecialElement.BUNDLE_ENTRY)
en = "Bundle.entry.resource";
else if (element.getSpecial() == SpecialElement.BUNDLE_OUTCOME)
en = "Bundle.entry.response.outcome";
else if (element.getSpecial() == SpecialElement.PARAMETER)
en = element.getElementProperty().getDefinition().getPath();
else // CONTAINED
en = "DomainResource.contained";
if (en == null)
en = element.getProperty().getDefinition().getPath();
boolean doType = false;
if (en.endsWith("[x]")) {
en = en.substring(0, en.length()-3);
doType = true;
}
if (doType || (element.getProperty().getDefinition().getType().size() > 1 && !allReference(element.getProperty().getDefinition().getType())))
en = en + Utilities.capitalize(element.getType());
return en;
}
private boolean allReference(List<TypeRefComponent> types) {
for (TypeRefComponent t : types) {
if (!t.getCode().equals("Reference"))
return false;
}
return true;
}
static public String ttlLiteral(String value, String type) {
String xst = "";
if (type.equals("boolean"))
xst = "^^xsd:boolean";
else if (type.equals("integer"))
xst = "^^xsd:integer";
else if (type.equals("integer64"))
xst = "^^xsd:long";
else if (type.equals("unsignedInt"))
xst = "^^xsd:nonNegativeInteger";
else if (type.equals("positiveInt"))
xst = "^^xsd:positiveInteger";
else if (type.equals("decimal"))
xst = "^^xsd:decimal";
else if (type.equals("base64Binary"))
xst = "^^xsd:base64Binary";
else if (type.equals("instant"))
xst = "^^xsd:dateTime";
else if (type.equals("time"))
xst = "^^xsd:time";
else if (type.equals("date") || type.equals("dateTime") ) {
String v = value;
if (v.length() > 10) {
int i = value.substring(10).indexOf("-");
if (i == -1)
i = value.substring(10).indexOf("+");
v = i == -1 ? value : value.substring(0, 10+i);
}
if (v.length() > 10)
xst = "^^xsd:dateTime";
else if (v.length() == 10)
xst = "^^xsd:date";
else if (v.length() == 7)
xst = "^^xsd:gYearMonth";
else if (v.length() == 4)
xst = "^^xsd:gYear";
}
return "\"" +Turtle.escape(value, true) + "\""+xst;
}
protected void decorateCoding(Complex t, Element coding, Section section) throws FHIRException {
String system = coding.getChildValue("system");
String code = coding.getChildValue("code");
if (system == null || code == null)
return;
if ("http://snomed.info/sct".equals(system)) {
t.prefix("sct", "http://snomed.info/id/");
if (code.contains(":") || code.contains("="))
generateLinkedPredicate(t, code);
else
t.linkedPredicate("a", "sct:" + urlescape(code), null);
} else if ("http://loinc.org".equals(system)) {
t.prefix("loinc", "http://loinc.org/rdf#");
t.linkedPredicate("a", "loinc:"+urlescape(code).toUpperCase(), null);
}
}
private void generateLinkedPredicate(Complex t, String code) throws FHIRException {
Expression expression = SnomedExpressions.parse(code);
}
// 128045006|cellulitis (disorder)|:{363698007|finding site|=56459004|foot structure|}
// Grahame Grieve: or
//
// 64572001|disease|:{116676008|associated morphology|=72704001|fracture|,363698007|finding site|=(12611008|bone structure of tibia|:272741003|laterality|=7771000|left|)}
// Harold Solbrig:
// a sct:128045006,
// rdfs:subClassOf [
// a owl:Restriction;
// owl:onProperty sct:609096000 ;
// owl:someValuesFrom [
// a owl:Restriction;
// owl:onProperty sct:363698007 ;
// owl:someValuesFrom sct:56459004 ] ] ;
// and
//
// a sct:64572001,
// rdfs:subclassOf [
// a owl:Restriction ;
// owl:onProperty sct:60909600 ;
// owl:someValuesFrom [
// a owl:Class ;
// owl:intersectionOf ( [
// a owl:Restriction;
// owl:onProperty sct:116676008;
// owl:someValuesFrom sct:72704001 ]
// [ a owl:Restriction;
// owl:onProperty sct:363698007
// owl:someValuesFrom [
// a owl:Class ;
// owl:intersectionOf(
// sct:12611008
// owl:someValuesFrom [
// a owl:Restriction;
// owl:onProperty sct:272741003;
// owl:someValuesFrom sct:7771000
// ] ) ] ] ) ] ]
// (an approximation -- I'll have to feed it into a translator to be sure I've got it 100% right)
//
}

View File

@ -0,0 +1,528 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.model.StructureDefinition;
/**
* This class provides special support for parsing v2 by the v2 logical model
* For the logical model, see the FHIRPath spec
*
* @author Grahame Grieve
*
*/
public class VerticalBarParser extends ParserBase {
/**
* Delimiters for a message. Note that the application rarely needs to concern
* itself with this information; it mainly exists for internal use. However if
* a message is being written to a spec that calls for non-standard delimiters,
* the application can set them here.
*
* @author Grahame
*
*/
public class Delimiters {
/**
* Hl7 defined default delimiter for a field
*/
public final static char DEFAULT_DELIMITER_FIELD = '|';
/**
* Hl7 defined default delimiter for a component
*/
public final static char DEFAULT_DELIMITER_COMPONENT = '^';
/**
* Hl7 defined default delimiter for a subcomponent
*/
public final static char DEFAULT_DELIMITER_SUBCOMPONENT = '&';
/**
* Hl7 defined default delimiter for a repeat
*/
public final static char DEFAULT_DELIMITER_REPETITION = '~';
/**
* Hl7 defined default delimiter for an escape
*/
public final static char DEFAULT_CHARACTER_ESCAPE = '\\';
/**
* defined escape character for this message
*/
private char escapeCharacter;
/**
* defined repetition character for this message
*/
private char repetitionDelimiter;
/**
* defined field character for this message
*/
private char fieldDelimiter;
/**
* defined subComponent character for this message
*/
private char subComponentDelimiter;
/**
* defined component character for this message
*/
private char componentDelimiter;
/**
* create
*
*/
public Delimiters() {
super();
reset();
}
public boolean matches(Delimiters other) {
return escapeCharacter == other.escapeCharacter &&
repetitionDelimiter == other.repetitionDelimiter &&
fieldDelimiter == other.fieldDelimiter &&
subComponentDelimiter == other.subComponentDelimiter &&
componentDelimiter == other.componentDelimiter;
}
/**
* get defined component character for this message
* @return
*/
public char getComponentDelimiter() {
return componentDelimiter;
}
/**
* set defined component character for this message
* @param componentDelimiter
*/
public void setComponentDelimiter(char componentDelimiter) {
this.componentDelimiter = componentDelimiter;
}
/**
* get defined escape character for this message
* @return
*/
public char getEscapeCharacter() {
return escapeCharacter;
}
/**
* set defined escape character for this message
* @param escapeCharacter
*/
public void setEscapeCharacter(char escapeCharacter) {
this.escapeCharacter = escapeCharacter;
}
/**
* get defined field character for this message
* @return
*/
public char getFieldDelimiter() {
return fieldDelimiter;
}
/**
* set defined field character for this message
* @param fieldDelimiter
*/
public void setFieldDelimiter(char fieldDelimiter) {
this.fieldDelimiter = fieldDelimiter;
}
/**
* get repeat field character for this message
* @return
*/
public char getRepetitionDelimiter() {
return repetitionDelimiter;
}
/**
* set repeat field character for this message
* @param repetitionDelimiter
*/
public void setRepetitionDelimiter(char repetitionDelimiter) {
this.repetitionDelimiter = repetitionDelimiter;
}
/**
* get sub-component field character for this message
* @return
*/
public char getSubComponentDelimiter() {
return subComponentDelimiter;
}
/**
* set sub-component field character for this message
* @param subComponentDelimiter
*/
public void setSubComponentDelimiter(char subComponentDelimiter) {
this.subComponentDelimiter = subComponentDelimiter;
}
/**
* reset to default HL7 values
*
*/
public void reset () {
fieldDelimiter = DEFAULT_DELIMITER_FIELD;
componentDelimiter = DEFAULT_DELIMITER_COMPONENT;
subComponentDelimiter = DEFAULT_DELIMITER_SUBCOMPONENT;
repetitionDelimiter = DEFAULT_DELIMITER_REPETITION;
escapeCharacter = DEFAULT_CHARACTER_ESCAPE;
}
/**
* check that the delimiters are valid
*
* @throws FHIRException
*/
public void check() throws FHIRException {
rule(componentDelimiter != fieldDelimiter, "Delimiter Error: \""+componentDelimiter+"\" is used for both CPComponent and CPField");
rule(subComponentDelimiter != fieldDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPField");
rule(subComponentDelimiter != componentDelimiter, "Delimiter Error: \""+subComponentDelimiter+"\" is used for both CPSubComponent and CPComponent");
rule(repetitionDelimiter != fieldDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPField");
rule(repetitionDelimiter != componentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPComponent");
rule(repetitionDelimiter != subComponentDelimiter, "Delimiter Error: \""+repetitionDelimiter+"\" is used for both Repetition and CPSubComponent");
rule(escapeCharacter != fieldDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPField");
rule(escapeCharacter != componentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPComponent");
rule(escapeCharacter != subComponentDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and CPSubComponent");
rule(escapeCharacter != repetitionDelimiter, "Delimiter Error: \""+escapeCharacter+"\" is used for both Escape and Repetition");
}
/**
* check to see whether ch is a delimiter character (vertical bar parser support)
* @param ch
* @return
*/
public boolean isDelimiter(char ch) {
return ch == escapeCharacter || ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter || ch == componentDelimiter;
}
/**
* check to see whether ch is a cell delimiter char (vertical bar parser support)
* @param ch
* @return
*/
public boolean isCellDelimiter(char ch) {
return ch == repetitionDelimiter || ch == fieldDelimiter || ch == subComponentDelimiter || ch == componentDelimiter;
}
/**
* get the escape for a character
* @param ch
* @return
*/
public String getEscape(char ch) {
if (ch == escapeCharacter)
return escapeCharacter + "E" + escapeCharacter;
else if (ch == fieldDelimiter)
return escapeCharacter + "F" + escapeCharacter;
else if (ch == componentDelimiter)
return escapeCharacter + "S" + escapeCharacter;
else if (ch == subComponentDelimiter)
return escapeCharacter + "T" + escapeCharacter;
else if (ch == repetitionDelimiter)
return escapeCharacter + "R" + escapeCharacter;
else
return null;
}
/**
* build the MSH-2 content
* @return
*/
public String forMSH2() {
return "" + componentDelimiter + repetitionDelimiter + escapeCharacter + subComponentDelimiter;
}
/**
* check to see whether ch represents a delimiter escape
* @param ch
* @return
*/
public boolean isDelimiterEscape(char ch) {
return ch == 'F' || ch == 'S' || ch == 'E' || ch == 'T' || ch == 'R';
}
/**
* get escape for ch in an escape
* @param ch
* @return
* @throws DefinitionException
* @throws FHIRException
*/
public char getDelimiterEscapeChar(char ch) throws DefinitionException {
if (ch == 'E')
return escapeCharacter;
else if (ch == 'F')
return fieldDelimiter;
else if (ch == 'S')
return componentDelimiter;
else if (ch == 'T')
return subComponentDelimiter;
else if (ch == 'R')
return repetitionDelimiter;
else
throw new DefinitionException("internal error in getDelimiterEscapeChar");
}
}
public class VerticalBarParserReader {
private BufferedInputStream stream;
private String charsetName;
private InputStreamReader reader = null;
private boolean finished;
private char peeked;
private char lastValue;
private int offset;
private int lineNumber;
public VerticalBarParserReader(BufferedInputStream stream, String charsetName) throws FHIRException {
super();
setStream(stream);
setCharsetName(charsetName);
open();
}
public String getCharsetName() {
return charsetName;
}
public void setCharsetName(String charsetName) {
this.charsetName = charsetName;
}
public BufferedInputStream getStream() {
return stream;
}
public void setStream(BufferedInputStream stream) {
this.stream = stream;
}
private void open() throws FHIRException {
try {
stream.mark(2048);
reader = new InputStreamReader(stream, charsetName);
offset = 0;
lineNumber = 0;
lastValue = ' ';
next();
} catch (Exception e) {
throw new FHIRException(e);
}
}
private void next() throws IOException, FHIRException {
finished = !reader.ready();
if (!finished) {
char[] temp = new char[1];
rule(reader.read(temp, 0, 1) == 1, "unable to read 1 character from the stream");
peeked = temp[0];
}
}
public String read(int charCount) throws FHIRException {
String value = "";
for (int i = 0; i < charCount; i++)
value = value + read();
return value;
}
public void skipEOL () throws FHIRException {
while (!finished && (peek() == '\r' || peek() == '\n'))
read();
}
public char read () throws FHIRException {
rule(!finished, "No more content to read");
char value = peek();
offset++;
if (value == '\r' || value == '\n') {
if (lastValue != '\r' || value != '\n')
lineNumber++;
}
lastValue = value;
try {
next();
} catch (Exception e) {
throw new FHIRException(e);
}
return value;
}
public boolean isFinished () {
return finished;
}
public char peek() throws FHIRException {
rule(!finished, "Cannot peek");
return peeked;
}
public void mark() {
stream.mark(2048);
}
public void reset() throws FHIRException {
try {
stream.reset();
} catch (IOException e) {
throw new FHIRException(e);
}
open();
}
public boolean IsEOL() throws FHIRException {
return peek() == '\r' || peek() == '\n';
}
public int getLineNumber() {
return lineNumber;
}
public int getOffset() {
return offset;
}
}
public VerticalBarParser(IWorkerContext context) {
super(context);
}
private String charset = "ASCII";
private Delimiters delimiters = new Delimiters();
@Override
public List<NamedElement> parse(InputStream stream) throws IOException, FHIRFormatError, DefinitionException, FHIRException {
StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/v2/StructureDefinition/Message");
Element message = new Element("Message", new Property(context, sd.getSnapshot().getElementFirstRep(), sd));
VerticalBarParserReader reader = new VerticalBarParserReader(new BufferedInputStream(stream), charset);
preDecode(reader);
while (!reader.isFinished()) // && (getOptions().getSegmentLimit() == 0 || getOptions().getSegmentLimit() > message.getSegments().size()))
readSegment(message, reader);
List<NamedElement> res = new ArrayList<>();
res.add(new NamedElement(null, message));
return res;
}
private void preDecode(VerticalBarParserReader reader) throws FHIRException {
reader.skipEOL();
String temp = reader.read(3);
rule(temp.equals("MSH") || temp.equals("FHS"), "Found '" + temp + "' looking for 'MSH' or 'FHS'");
readDelimiters(reader);
// readVersion(message); - probably don't need to do that?
// readCharacterSet();
reader.reset(); // ready to read message now
}
private void rule(boolean test, String msg) throws FHIRException {
if (!test)
throw new FHIRException(msg);
}
private void readDelimiters(VerticalBarParserReader reader) throws FHIRException {
delimiters.setFieldDelimiter(reader.read());
if (!(reader.peek() == delimiters.getFieldDelimiter()))
delimiters.setComponentDelimiter(reader.read());
if (!(reader.peek() == delimiters.getFieldDelimiter()))
delimiters.setRepetitionDelimiter(reader.read());
if (!(reader.peek() == delimiters.getFieldDelimiter()))
delimiters.setEscapeCharacter(reader.read());
if (!(reader.peek() == delimiters.getFieldDelimiter()))
delimiters.setSubComponentDelimiter(reader.read());
delimiters.check();
}
private void readSegment(Element message, VerticalBarParserReader reader) throws FHIRException {
Element segment = new Element("segment", message.getProperty().getChild("segment"));
message.getChildren().add(segment);
Element segmentCode = new Element("code", segment.getProperty().getChild("code"));
segment.getChildren().add(segmentCode);
segmentCode.setValue(reader.read(3));
int index = 0;
while (!reader.isFinished() && !reader.IsEOL()) {
index++;
readField(reader, segment, index);
if (!reader.isFinished() && !reader.IsEOL())
rule(reader.read() == delimiters.getFieldDelimiter(), "Expected to find field delimiter");
}
if (!reader.isFinished())
reader.skipEOL();
}
private void readField(VerticalBarParserReader reader, Element segment, int index) {
// TODO Auto-generated method stub
}
@Override
public void compose(Element e, OutputStream destination, OutputStyle style, String base) {
// TODO Auto-generated method stub
}
}

View File

@ -0,0 +1,792 @@
package org.hl7.fhir.r4b.elementmodel;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMResult;
import javax.xml.transform.sax.SAXSource;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.conformance.ProfileUtilities;
import org.hl7.fhir.r4b.context.IWorkerContext;
import org.hl7.fhir.r4b.elementmodel.Element.SpecialElement;
import org.hl7.fhir.r4b.elementmodel.ParserBase.NamedElement;
import org.hl7.fhir.r4b.formats.FormatUtilities;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
import org.hl7.fhir.r4b.model.DateTimeType;
import org.hl7.fhir.r4b.model.ElementDefinition.PropertyRepresentation;
import org.hl7.fhir.r4b.model.Enumeration;
import org.hl7.fhir.r4b.model.StructureDefinition;
import org.hl7.fhir.r4b.utils.ToolingExtensions;
import org.hl7.fhir.r4b.utils.formats.XmlLocationAnnotator;
import org.hl7.fhir.r4b.utils.formats.XmlLocationData;
import org.hl7.fhir.utilities.ElementDecoration;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.i18n.I18nConstants;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueType;
import org.hl7.fhir.utilities.xhtml.CDANarrativeFormat;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
import org.hl7.fhir.utilities.xml.IXMLWriter;
import org.hl7.fhir.utilities.xml.XMLUtil;
import org.hl7.fhir.utilities.xml.XMLWriter;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
public class XmlParser extends ParserBase {
private boolean allowXsiLocation;
private String version;
public XmlParser(IWorkerContext context) {
super(context);
}
private String schemaPath;
public String getSchemaPath() {
return schemaPath;
}
public void setSchemaPath(String schemaPath) {
this.schemaPath = schemaPath;
}
public boolean isAllowXsiLocation() {
return allowXsiLocation;
}
public void setAllowXsiLocation(boolean allowXsiLocation) {
this.allowXsiLocation = allowXsiLocation;
}
public List<NamedElement> parse(InputStream stream) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
List<NamedElement> res = new ArrayList<>();
Document doc = null;
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
// xxe protection
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);
if (policy == ValidationPolicy.EVERYTHING) {
// The SAX interface appears to not work when reporting the correct version/encoding.
// if we can, we'll inspect the header/encoding ourselves
if (stream.markSupported()) {
stream.mark(1024);
version = checkHeader(stream);
stream.reset();
}
// use a slower parser that keeps location data
TransformerFactory transformerFactory = TransformerFactory.newInstance();
Transformer nullTransformer = transformerFactory.newTransformer();
DocumentBuilder docBuilder = factory.newDocumentBuilder();
doc = docBuilder.newDocument();
DOMResult domResult = new DOMResult(doc);
SAXParserFactory spf = SAXParserFactory.newInstance();
spf.setNamespaceAware(true);
spf.setValidating(false);
// xxe protection
spf.setFeature("http://xml.org/sax/features/external-general-entities", false);
spf.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
SAXParser saxParser = spf.newSAXParser();
XMLReader xmlReader = saxParser.getXMLReader();
// xxe protection
xmlReader.setFeature("http://xml.org/sax/features/external-general-entities", false);
xmlReader.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
XmlLocationAnnotator locationAnnotator = new XmlLocationAnnotator(xmlReader, doc);
InputSource inputSource = new InputSource(stream);
SAXSource saxSource = new SAXSource(locationAnnotator, inputSource);
nullTransformer.transform(saxSource, domResult);
} else {
DocumentBuilder builder = factory.newDocumentBuilder();
doc = builder.parse(stream);
}
} catch (Exception e) {
logError(0, 0, "(syntax)", IssueType.INVALID, e.getMessage(), IssueSeverity.FATAL);
doc = null;
}
if (doc != null) {
Element e = parse(doc);
if (e != null) {
res.add(new NamedElement(null, e));
}
}
return res;
}
private void checkForProcessingInstruction(Document document) throws FHIRFormatError {
if (policy == ValidationPolicy.EVERYTHING && FormatUtilities.FHIR_NS.equals(document.getDocumentElement().getNamespaceURI())) {
Node node = document.getFirstChild();
while (node != null) {
if (node.getNodeType() == Node.PROCESSING_INSTRUCTION_NODE)
logError(line(document), col(document), "(document)", IssueType.INVALID, context.formatMessage(
I18nConstants.NO_PROCESSING_INSTRUCTIONS_ALLOWED_IN_RESOURCES), IssueSeverity.ERROR);
node = node.getNextSibling();
}
}
}
private int line(Node node) {
XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY);
return loc == null ? 0 : loc.getStartLine();
}
private int col(Node node) {
XmlLocationData loc = node == null ? null : (XmlLocationData) node.getUserData(XmlLocationData.LOCATION_DATA_KEY);
return loc == null ? 0 : loc.getStartColumn();
}
public Element parse(Document doc) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
checkForProcessingInstruction(doc);
org.w3c.dom.Element element = doc.getDocumentElement();
return parse(element);
}
public Element parse(org.w3c.dom.Element element) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
String ns = element.getNamespaceURI();
String name = element.getLocalName();
String path = "/"+pathPrefix(ns)+name;
StructureDefinition sd = getDefinition(line(element), col(element), (ns == null ? "noNamespace" : ns), name);
if (sd == null)
return null;
Element result = new Element(element.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd));
result.setPath(element.getLocalName());
checkElement(element, path, result.getProperty());
result.markLocation(line(element), col(element));
result.setType(element.getLocalName());
parseChildren(path, element, result);
result.numberChildren();
return result;
}
private String pathPrefix(String ns) {
if (Utilities.noString(ns))
return "";
if (ns.equals(FormatUtilities.FHIR_NS))
return "f:";
if (ns.equals(FormatUtilities.XHTML_NS))
return "h:";
if (ns.equals("urn:hl7-org:v3"))
return "v3:";
if (ns.equals("urn:hl7-org:sdtc"))
return "sdtc:";
if (ns.equals("urn:ihe:pharm"))
return "pharm:";
return "?:";
}
private boolean empty(org.w3c.dom.Element element) {
for (int i = 0; i < element.getAttributes().getLength(); i++) {
String n = element.getAttributes().item(i).getNodeName();
if (!n.equals("xmlns") && !n.startsWith("xmlns:"))
return false;
}
if (!Utilities.noString(element.getTextContent().trim()))
return false;
Node n = element.getFirstChild();
while (n != null) {
if (n.getNodeType() == Node.ELEMENT_NODE)
return false;
n = n.getNextSibling();
}
return true;
}
private void checkElement(org.w3c.dom.Element element, String path, Property prop) throws FHIRFormatError {
if (policy == ValidationPolicy.EVERYTHING) {
if (empty(element) && FormatUtilities.FHIR_NS.equals(element.getNamespaceURI())) // this rule only applies to FHIR Content
logError(line(element), col(element), path, IssueType.INVALID, context.formatMessage(I18nConstants.ELEMENT_MUST_HAVE_SOME_CONTENT), IssueSeverity.ERROR);
String ns = prop.getXmlNamespace();
String elementNs = element.getNamespaceURI();
if (elementNs == null) {
elementNs = "noNamespace";
}
if (!elementNs.equals(ns))
logError(line(element), col(element), path, IssueType.INVALID, context.formatMessage(I18nConstants.WRONG_NAMESPACE__EXPECTED_, ns), IssueSeverity.ERROR);
}
}
public Element parse(org.w3c.dom.Element base, String type) throws Exception {
StructureDefinition sd = getDefinition(0, 0, FormatUtilities.FHIR_NS, type);
Element result = new Element(base.getLocalName(), new Property(context, sd.getSnapshot().getElement().get(0), sd));
result.setPath(base.getLocalName());
String path = "/"+pathPrefix(base.getNamespaceURI())+base.getLocalName();
checkElement(base, path, result.getProperty());
result.setType(base.getLocalName());
parseChildren(path, base, result);
result.numberChildren();
return result;
}
private void parseChildren(String path, org.w3c.dom.Element node, Element element) throws FHIRFormatError, FHIRException, IOException, DefinitionException {
// this parsing routine retains the original order in a the XML file, to support validation
reapComments(node, element);
List<Property> properties = element.getProperty().getChildProperties(element.getName(), XMLUtil.getXsiType(node));
String text = XMLUtil.getDirectText(node).trim();
int line = line(node);
int col = col(node);
if (!Utilities.noString(text)) {
Property property = getTextProp(properties);
if (property != null) {
if ("ED.data[x]".equals(property.getDefinition().getId()) || (property.getDefinition()!=null && property.getDefinition().getBase()!=null && "ED.data[x]".equals(property.getDefinition().getBase().getPath()))) {
if ("B64".equals(node.getAttribute("representation"))) {
Element n = new Element("dataBase64Binary", property, "base64Binary", text).markLocation(line, col);
n.setPath(element.getPath()+"."+property.getName());
element.getChildren().add(n);
} else {
Element n = new Element("dataString", property, "string", text).markLocation(line, col);
n.setPath(element.getPath()+"."+property.getName());
element.getChildren().add(n);
}
} else {
Element n = new Element(property.getName(), property, property.getType(), text).markLocation(line, col);
n.setPath(element.getPath()+"."+property.getName());
element.getChildren().add(n);
}
}
else {
Node n = node.getFirstChild();
while (n != null) {
if (n.getNodeType() == Node.TEXT_NODE && !Utilities.noString(n.getTextContent().trim())) {
while (n.getNextSibling() != null && n.getNodeType() != Node.ELEMENT_NODE) {
n = n.getNextSibling();
}
while (n.getPreviousSibling() != null && n.getNodeType() != Node.ELEMENT_NODE) {
n = n.getPreviousSibling();
}
line = line(n);
col = col(n);
logError(line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.TEXT_SHOULD_NOT_BE_PRESENT, text), IssueSeverity.ERROR);
}
n = n.getNextSibling();
}
}
}
for (int i = 0; i < node.getAttributes().getLength(); i++) {
Node attr = node.getAttributes().item(i);
String value = attr.getNodeValue();
if (!validAttrValue(value)) {
logError(line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.XML_ATTR_VALUE_INVALID, attr.getNodeName()), IssueSeverity.ERROR);
}
if (!(attr.getNodeName().equals("xmlns") || attr.getNodeName().startsWith("xmlns:"))) {
Property property = getAttrProp(properties, attr.getLocalName(), attr.getNamespaceURI());
if (property != null) {
String av = attr.getNodeValue();
if (ToolingExtensions.hasExtension(property.getDefinition(), "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat"))
av = convertForDateFormatFromExternal(ToolingExtensions.readStringExtension(property.getDefinition(), "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat"), av);
if (property.getName().equals("value") && element.isPrimitive())
element.setValue(av);
else {
Element n = new Element(property.getName(), property, property.getType(), av).markLocation(line, col);
n.setPath(element.getPath()+"."+property.getName());
element.getChildren().add(n);
}
} else {
boolean ok = false;
if (FormatUtilities.FHIR_NS.equals(node.getNamespaceURI())) {
if (attr.getLocalName().equals("schemaLocation") && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())) {
ok = ok || allowXsiLocation;
}
} else
ok = ok || (attr.getLocalName().equals("schemaLocation")); // xsi:schemalocation allowed for non FHIR content
ok = ok || (hasTypeAttr(element) && attr.getLocalName().equals("type") && FormatUtilities.NS_XSI.equals(attr.getNamespaceURI())); // xsi:type allowed if element says so
if (!ok)
logError(line, col, path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ATTRIBUTE__ON__FOR_TYPE__PROPERTIES__, attr.getNodeName(), node.getNodeName(), element.fhirType(), properties), IssueSeverity.ERROR);
}
}
}
String lastName = null;
int repeatCount = 0;
Node child = node.getFirstChild();
while (child != null) {
if (child.getNodeType() == Node.ELEMENT_NODE) {
Property property = getElementProp(properties, child.getLocalName(), child.getNamespaceURI());
if (property != null) {
if (property.getName().equals(lastName)) {
repeatCount++;
} else {
lastName = property.getName();
repeatCount = 0;
}
if (!property.isChoice() && "xhtml".equals(property.getType())) {
XhtmlNode xhtml;
if (property.getDefinition().hasRepresentation(PropertyRepresentation.CDATEXT))
xhtml = new CDANarrativeFormat().convert((org.w3c.dom.Element) child);
else
xhtml = new XhtmlParser().setValidatorMode(true).parseHtmlNode((org.w3c.dom.Element) child);
Element n = new Element(property.getName(), property, "xhtml", new XhtmlComposer(XhtmlComposer.XML, false).compose(xhtml)).setXhtml(xhtml).markLocation(line(child), col(child));
n.setPath(element.getPath()+"."+property.getName());
element.getChildren().add(n);
} else {
String npath = path+"/"+pathPrefix(child.getNamespaceURI())+child.getLocalName();
Element n = new Element(child.getLocalName(), property).markLocation(line(child), col(child));
if (property.isList()) {
n.setPath(element.getPath()+"."+property.getName()+"["+repeatCount+"]");
} else {
n.setPath(element.getPath()+"."+property.getName());
}
checkElement((org.w3c.dom.Element) child, npath, n.getProperty());
boolean ok = true;
if (property.isChoice()) {
if (property.getDefinition().hasRepresentation(PropertyRepresentation.TYPEATTR)) {
String xsiType = ((org.w3c.dom.Element) child).getAttributeNS(FormatUtilities.NS_XSI, "type");
if (Utilities.noString(xsiType)) {
if (ToolingExtensions.hasExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype")) {
xsiType = ToolingExtensions.readStringExtension(property.getDefinition(), "http://hl7.org/fhir/StructureDefinition/elementdefinition-defaulttype");
n.setType(xsiType);
} else {
logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NO_TYPE_FOUND_ON_, child.getLocalName()), IssueSeverity.ERROR);
ok = false;
}
} else {
if (xsiType.contains(":"))
xsiType = xsiType.substring(xsiType.indexOf(":")+1);
n.setType(xsiType);
n.setExplicitType(xsiType);
}
} else
n.setType(n.getType());
}
element.getChildren().add(n);
if (ok) {
if (property.isResource())
parseResource(npath, (org.w3c.dom.Element) child, n, property);
else
parseChildren(npath, (org.w3c.dom.Element) child, n);
}
}
} else
logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.UNDEFINED_ELEMENT_, child.getLocalName()), IssueSeverity.ERROR);
} else if (child.getNodeType() == Node.CDATA_SECTION_NODE){
logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.CDATA_IS_NOT_ALLOWED), IssueSeverity.ERROR);
} else if (!Utilities.existsInList(child.getNodeType(), 3, 8)) {
logError(line(child), col(child), path, IssueType.STRUCTURE, context.formatMessage(I18nConstants.NODE_TYPE__IS_NOT_ALLOWED, Integer.toString(child.getNodeType())), IssueSeverity.ERROR);
}
child = child.getNextSibling();
}
}
private boolean validAttrValue(String value) {
if (version == null) {
return true;
}
if (version.equals("1.0")) {
boolean ok = true;
for (char ch : value.toCharArray()) {
if (ch <= 0x1F && !Utilities.existsInList(ch, '\r', '\n', '\t')) {
ok = false;
}
}
return ok;
} else
return true;
}
private Property getElementProp(List<Property> properties, String nodeName, String namespace) {
List<Property> propsSortedByLongestFirst = new ArrayList<Property>(properties);
// sort properties according to their name longest first, so .requestOrganizationReference comes first before .request[x]
// and therefore the longer property names get evaluated first
Collections.sort(propsSortedByLongestFirst, new Comparator<Property>() {
@Override
public int compare(Property o1, Property o2) {
return o2.getName().length() - o1.getName().length();
}
});
// first scan, by namespace
for (Property p : propsSortedByLongestFirst) {
if (!p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && !p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) {
if (p.getXmlName().equals(nodeName) && p.getXmlNamespace().equals(namespace))
return p;
}
}
for (Property p : propsSortedByLongestFirst) {
if (!p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && !p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT)) {
if (p.getXmlName().equals(nodeName))
return p;
if (p.getName().endsWith("[x]") && nodeName.length() > p.getName().length()-3 && p.getName().substring(0, p.getName().length()-3).equals(nodeName.substring(0, p.getName().length()-3)))
return p;
}
}
return null;
}
private Property getAttrProp(List<Property> properties, String nodeName, String namespace) {
for (Property p : properties) {
if (p.getXmlName().equals(nodeName) && p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR) && p.getXmlNamespace().equals(namespace)) {
return p;
}
}
if (namespace == null) {
for (Property p : properties) {
if (p.getXmlName().equals(nodeName) && p.getDefinition().hasRepresentation(PropertyRepresentation.XMLATTR)) {
return p;
}
}
}
return null;
}
private Property getTextProp(List<Property> properties) {
for (Property p : properties)
if (p.getDefinition().hasRepresentation(PropertyRepresentation.XMLTEXT))
return p;
return null;
}
private String convertForDateFormatFromExternal(String fmt, String av) throws FHIRException {
if ("v3".equals(fmt)) {
try {
DateTimeType d = DateTimeType.parseV3(av);
return d.asStringValue();
} catch (Exception e) {
return av; // not at all clear what to do in this case.
}
} else
throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATA_FORMAT_, fmt));
}
private String convertForDateFormatToExternal(String fmt, String av) throws FHIRException {
if ("v3".equals(fmt)) {
DateTimeType d = new DateTimeType(av);
return d.getAsV3();
} else
throw new FHIRException(context.formatMessage(I18nConstants.UNKNOWN_DATE_FORMAT_, fmt));
}
private void parseResource(String string, org.w3c.dom.Element container, Element parent, Property elementProperty) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
org.w3c.dom.Element res = XMLUtil.getFirstChild(container);
String name = res.getLocalName();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, ProfileUtilities.sdNs(name, context.getOverrideVersionNs()));
if (sd == null)
throw new FHIRFormatError(context.formatMessage(I18nConstants.CONTAINED_RESOURCE_DOES_NOT_APPEAR_TO_BE_A_FHIR_RESOURCE_UNKNOWN_NAME_, res.getLocalName()));
parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd), SpecialElement.fromProperty(parent.getProperty()), elementProperty);
parent.setType(name);
parseChildren(res.getLocalName(), res, parent);
}
private void reapComments(org.w3c.dom.Element element, Element context) {
Node node = element.getPreviousSibling();
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
if (node.getNodeType() == Node.COMMENT_NODE)
context.getComments().add(0, node.getTextContent());
node = node.getPreviousSibling();
}
node = element.getLastChild();
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {
node = node.getPreviousSibling();
}
while (node != null) {
if (node.getNodeType() == Node.COMMENT_NODE)
context.getComments().add(node.getTextContent());
node = node.getNextSibling();
}
}
private boolean isAttr(Property property) {
for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
if (r.getValue() == PropertyRepresentation.XMLATTR) {
return true;
}
}
return false;
}
private boolean isCdaText(Property property) {
for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
if (r.getValue() == PropertyRepresentation.CDATEXT) {
return true;
}
}
return false;
}
private boolean isTypeAttr(Property property) {
for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
if (r.getValue() == PropertyRepresentation.TYPEATTR) {
return true;
}
}
return false;
}
private boolean isText(Property property) {
for (Enumeration<PropertyRepresentation> r : property.getDefinition().getRepresentation()) {
if (r.getValue() == PropertyRepresentation.XMLTEXT) {
return true;
}
}
return false;
}
@Override
public void compose(Element e, OutputStream stream, OutputStyle style, String base) throws IOException, FHIRException {
XMLWriter xml = new XMLWriter(stream, "UTF-8");
xml.setSortAttributes(false);
xml.setPretty(style == OutputStyle.PRETTY);
xml.start();
String ns = e.getProperty().getXmlNamespace();
if (ns!=null && !"noNamespace".equals(ns)) {
xml.setDefaultNamespace(ns);
}
if (hasTypeAttr(e))
xml.namespace("http://www.w3.org/2001/XMLSchema-instance", "xsi");
addNamespaces(xml, e);
composeElement(xml, e, e.getType(), true);
xml.end();
}
private void addNamespaces(IXMLWriter xml, Element e) throws IOException {
String ns = e.getProperty().getXmlNamespace();
if (ns!=null && xml.getDefaultNamespace()!=null && !xml.getDefaultNamespace().equals(ns)){
if (!xml.namespaceDefined(ns)) {
String prefix = pathPrefix(ns);
if (prefix.endsWith(":")) {
prefix = prefix.substring(0, prefix.length()-1);
}
if ("?".equals(prefix)) {
xml.namespace(ns);
} else {
xml.namespace(ns, prefix);
}
}
}
for (Element c : e.getChildren()) {
addNamespaces(xml, c);
}
}
private boolean hasTypeAttr(Element e) {
if (isTypeAttr(e.getProperty()))
return true;
for (Element c : e.getChildren()) {
if (hasTypeAttr(c))
return true;
}
return false;
}
private void setXsiTypeIfIsTypeAttr(IXMLWriter xml, Element element) throws IOException, FHIRException {
if (isTypeAttr(element.getProperty()) && !Utilities.noString(element.getType())) {
String type = element.getType();
if (Utilities.isAbsoluteUrl(type)) {
type = type.substring(type.lastIndexOf("/")+1);
}
xml.attribute("xsi:type",type);
}
}
public void compose(Element e, IXMLWriter xml) throws Exception {
xml.start();
xml.setDefaultNamespace(e.getProperty().getXmlNamespace());
if (schemaPath != null) {
xml.setSchemaLocation(FormatUtilities.FHIR_NS, Utilities.pathURL(schemaPath, e.fhirType()+".xsd"));
}
composeElement(xml, e, e.getType(), true);
xml.end();
}
private void composeElement(IXMLWriter xml, Element element, String elementName, boolean root) throws IOException, FHIRException {
if (showDecorations) {
@SuppressWarnings("unchecked")
List<ElementDecoration> decorations = (List<ElementDecoration>) element.getUserData("fhir.decorations");
if (decorations != null)
for (ElementDecoration d : decorations)
xml.decorate(d);
}
for (String s : element.getComments()) {
xml.comment(s, true);
}
if (isText(element.getProperty())) {
if (linkResolver != null)
xml.link(linkResolver.resolveProperty(element.getProperty()));
xml.enter(element.getProperty().getXmlNamespace(),elementName);
xml.text(element.getValue());
xml.exit(element.getProperty().getXmlNamespace(),elementName);
} else if (!element.hasChildren() && !element.hasValue()) {
if (element.getExplicitType() != null)
xml.attribute("xsi:type", element.getExplicitType());
xml.element(elementName);
} else if (element.isPrimitive() || (element.hasType() && isPrimitive(element.getType()))) {
if (element.getType().equals("xhtml")) {
String rawXhtml = element.getValue();
if (isCdaText(element.getProperty())) {
new CDANarrativeFormat().convert(xml, new XhtmlParser().parseFragment(rawXhtml));
} else {
xml.escapedText(rawXhtml);
xml.anchor("end-xhtml");
}
} else if (isText(element.getProperty())) {
if (linkResolver != null)
xml.link(linkResolver.resolveProperty(element.getProperty()));
xml.text(element.getValue());
} else {
setXsiTypeIfIsTypeAttr(xml, element);
if (element.hasValue()) {
if (linkResolver != null)
xml.link(linkResolver.resolveType(element.getType()));
xml.attribute("value", element.getValue());
}
if (linkResolver != null)
xml.link(linkResolver.resolveProperty(element.getProperty()));
if (element.hasChildren()) {
xml.enter(element.getProperty().getXmlNamespace(), elementName);
for (Element child : element.getChildren())
composeElement(xml, child, child.getName(), false);
xml.exit(element.getProperty().getXmlNamespace(),elementName);
} else
xml.element(elementName);
}
} else {
setXsiTypeIfIsTypeAttr(xml, element);
for (Element child : element.getChildren()) {
if (isAttr(child.getProperty())) {
if (linkResolver != null)
xml.link(linkResolver.resolveType(child.getType()));
String av = child.getValue();
if (ToolingExtensions.hasExtension(child.getProperty().getDefinition(), "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat"))
av = convertForDateFormatToExternal(ToolingExtensions.readStringExtension(child.getProperty().getDefinition(), "http://www.healthintersections.com.au/fhir/StructureDefinition/elementdefinition-dateformat"), av);
xml.attribute(child.getProperty().getXmlNamespace(),child.getProperty().getXmlName(), av);
}
}
if (linkResolver != null)
xml.link(linkResolver.resolveProperty(element.getProperty()));
xml.enter(element.getProperty().getXmlNamespace(),elementName);
if (!root && element.getSpecial() != null) {
if (linkResolver != null)
xml.link(linkResolver.resolveProperty(element.getProperty()));
xml.enter(element.getProperty().getXmlNamespace(),element.getType());
}
for (Element child : element.getChildren()) {
if (isText(child.getProperty())) {
if (linkResolver != null)
xml.link(linkResolver.resolveProperty(element.getProperty()));
xml.text(child.getValue());
} else if (!isAttr(child.getProperty()))
composeElement(xml, child, child.getName(), false);
}
if (!root && element.getSpecial() != null)
xml.exit(element.getProperty().getXmlNamespace(),element.getType());
xml.exit(element.getProperty().getXmlNamespace(),elementName);
}
}
private String checkHeader(InputStream stream) throws IOException {
try {
// the stream will either start with the UTF-8 BOF or with <xml
int i0 = stream.read();
int i1 = stream.read();
int i2 = stream.read();
StringBuilder b = new StringBuilder();
if (i0 == 0xEF && i1 == 0xBB && i2 == 0xBF) {
// ok, it's UTF-8
} else if (i0 == 0x3C && i1 == 0x3F && i2 == 0x78) { // <xm
b.append((char) i0);
b.append((char) i1);
b.append((char) i2);
} else if (i0 == 60) { // just plain old XML with no header
return "1.0";
} else {
throw new Exception(context.formatMessage(I18nConstants.XML_ENCODING_INVALID));
}
int i = stream.read();
do {
b.append((char) i);
i = stream.read();
} while (i != 0x3E);
String header = b.toString();
String e = null;
i = header.indexOf("encoding=\"");
if (i > -1) {
e = header.substring(i+10, i+15);
} else {
i = header.indexOf("encoding='");
if (i > -1) {
e = header.substring(i+10, i+15);
}
}
if (e != null && !"UTF-8".equalsIgnoreCase(e)) {
logError(0, 0, "XML", IssueType.INVALID, context.formatMessage(I18nConstants.XML_ENCODING_INVALID), IssueSeverity.ERROR);
}
i = header.indexOf("version=\"");
if (i > -1) {
return header.substring(i+9, i+12);
} else {
i = header.indexOf("version='");
if (i > -1) {
return header.substring(i+9, i+12);
}
}
return "?xml-p1?";
} catch (Exception e) {
// suppress this error
logError(0, 0, "XML", IssueType.INVALID, e.getMessage(), IssueSeverity.ERROR);
}
return "?xml-p2?";
}
}

View File

@ -0,0 +1,172 @@
package org.hl7.fhir.r4b.formats;
import java.io.FileNotFoundException;
import java.io.IOException;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/*
Copyright (c) 2011+, HL7, Inc
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.math.BigDecimal;
import java.net.URI;
import org.apache.commons.codec.binary.Base64;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.r4b.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.utilities.TextFile;
public abstract class FormatUtilities {
public static final String ID_REGEX = "[A-Za-z0-9\\-\\.]{1,64}";
public static final String FHIR_NS = "http://hl7.org/fhir";
public static final String XHTML_NS = "http://www.w3.org/1999/xhtml";
public static final String NS_XSI = "http://www.w3.org/2001/XMLSchema-instance";
private static final int MAX_SCAN_LENGTH = 1000; // how many characters to scan into content when autodetermining format
protected String toString(String value) {
return value;
}
protected String toString(int value) {
return java.lang.Integer.toString(value);
}
protected String toString(boolean value) {
return java.lang.Boolean.toString(value);
}
protected String toString(BigDecimal value) {
return value.toString();
}
protected String toString(URI value) {
return value.toString();
}
public static String toString(byte[] value) {
byte[] encodeBase64 = Base64.encodeBase64(value);
return new String(encodeBase64);
}
public static boolean isValidId(String tail) {
return tail.matches(ID_REGEX);
}
public static String makeId(String candidate) {
StringBuilder b = new StringBuilder();
for (char c : candidate.toCharArray())
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '.' || c == '-')
b.append(c);
return b.toString();
}
public static ParserBase makeParser(FhirFormat format) {
switch (format) {
case XML : return new XmlParser();
case JSON : return new JsonParser();
case TURTLE : throw new Error("unsupported Format "+format.toString()); // return new TurtleParser();
case VBAR : throw new Error("unsupported Format "+format.toString()); //
case TEXT : throw new Error("unsupported Format "+format.toString()); //
}
throw new Error("unsupported Format "+format.toString());
}
public static ParserBase makeParser(String format) {
if ("XML".equalsIgnoreCase(format)) return new XmlParser();
if ("JSON".equalsIgnoreCase(format)) return new JsonParser();
if ("TURTLE".equalsIgnoreCase(format)) throw new Error("unsupported Format "+format.toString()); // return new TurtleParser();
if ("JSONLD".equalsIgnoreCase(format)) throw new Error("unsupported Format "+format.toString()); // return new JsonLdParser();
if ("VBAR".equalsIgnoreCase(format)) throw new Error("unsupported Format "+format.toString()); //
if ("TEXT".equalsIgnoreCase(format)) throw new Error("unsupported Format "+format.toString()); //
throw new Error("unsupported Format "+format);
}
public static FhirFormat determineFormat(byte[] source) throws FHIRException {
return determineFormat(source, MAX_SCAN_LENGTH);
}
public static FhirFormat determineFormat(byte[] source, int scanLength) throws FHIRException {
if (scanLength == -1)
scanLength = source.length;
int lt = firstIndexOf(source, '<', scanLength);
int ps = firstIndexOf(source, '{', scanLength);
int at = firstIndexOf(source, '@', scanLength);
if (at < ps && at < lt) return FhirFormat.TURTLE;
if (ps < lt) return FhirFormat.JSON;
if (lt < ps) return FhirFormat.XML;
throw new FHIRException("unable to determine format");
}
private static int firstIndexOf(byte[] source, char c, int scanLength) {
for (int i = 0; i < Math.min(source.length, scanLength); i++) {
if (source[i] == c)
return i;
}
return Integer.MAX_VALUE;
}
public static Resource loadFile(String path) throws FileNotFoundException, IOException, FHIRException {
byte[] src = TextFile.fileToBytes(path);
FhirFormat fmt = determineFormat(src);
ParserBase parser = makeParser(fmt);
return parser.parse(src);
}
}

View File

@ -0,0 +1,230 @@
package org.hl7.fhir.r4b.formats;
import java.io.FileInputStream;
import java.io.IOException;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.model.DataType;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.SearchParameter;
import org.xmlpull.v1.XmlPullParserException;
/**
* General interface - either an XML or JSON parser: read or write instances
*
* Defined to allow a factory to create a parser of the right type
*/
public interface IParser {
/**
* check what kind of parser this is
*
* @return what kind of parser this is
*/
public ParserType getType();
// -- Parser Configuration ----------------------------------
/**
* Whether to parse or ignore comments - either reading or writing
*/
public boolean getHandleComments();
public IParser setHandleComments(boolean value);
/**
* @param allowUnknownContent Whether to throw an exception if unknown content is found (or just skip it) when parsing
*/
public boolean isAllowUnknownContent();
public IParser setAllowUnknownContent(boolean value);
public enum OutputStyle {
/**
* Produce normal output - no whitespace, except in HTML where whitespace is untouched
*/
NORMAL,
/**
* Produce pretty output - human readable whitespace, HTML whitespace untouched
*/
PRETTY,
/**
* Produce canonical output - no comments, no whitspace, HTML whitespace normlised, JSON attributes sorted alphabetically (slightly slower)
*/
CANONICAL,
}
/**
* Writing:
*/
public OutputStyle getOutputStyle();
public IParser setOutputStyle(OutputStyle value);
/**
* This method is used by the publication tooling to stop the xhrtml narrative being generated.
* It is not valid to use in production use. The tooling uses it to generate json/xml representations in html that are not cluttered by escaped html representations of the html representation
*/
public IParser setSuppressXhtml(String message);
// -- Reading methods ----------------------------------------
/**
* parse content that is known to be a resource
* @throws XmlPullParserException
* @throws FHIRFormatError
* @throws IOException
*/
public Resource parse(InputStream input) throws IOException, FHIRFormatError;
public Resource parseAndClose(InputStream input) throws IOException, FHIRFormatError;
/**
* parse content that is known to be a resource
* @throws UnsupportedEncodingException
* @throws IOException
* @throws FHIRFormatError
*/
public Resource parse(String input) throws UnsupportedEncodingException, FHIRFormatError, IOException;
/**
* parse content that is known to be a resource
* @throws IOException
* @throws FHIRFormatError
*/
public Resource parse(byte[] bytes) throws FHIRFormatError, IOException;
/**
* This is used to parse a type - a fragment of a resource.
* There's no reason to use this in production - it's used
* in the build tools
*
* Not supported by all implementations
*
* @param input
* @param knownType. if this is blank, the parser may try to infer the type (xml only)
* @return
* @throws XmlPullParserException
* @throws FHIRFormatError
* @throws IOException
*/
public DataType parseType(InputStream input, String knownType) throws IOException, FHIRFormatError;
public DataType parseAnyType(InputStream input, String knownType) throws IOException, FHIRFormatError;
/**
* This is used to parse a type - a fragment of a resource.
* There's no reason to use this in production - it's used
* in the build tools
*
* Not supported by all implementations
*
* @param input
* @param knownType. if this is blank, the parser may try to infer the type (xml only)
* @return
* @throws UnsupportedEncodingException
* @throws IOException
* @throws FHIRFormatError
*/
public DataType parseType(String input, String knownType) throws UnsupportedEncodingException, FHIRFormatError, IOException;
/**
* This is used to parse a type - a fragment of a resource.
* There's no reason to use this in production - it's used
* in the build tools
*
* Not supported by all implementations
*
* @param input
* @param knownType. if this is blank, the parser may try to infer the type (xml only)
* @return
* @throws IOException
* @throws FHIRFormatError
*/
public DataType parseType(byte[] bytes, String knownType) throws FHIRFormatError, IOException;
// -- Writing methods ----------------------------------------
/**
* Compose a resource to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
* @throws IOException
*/
public void compose(OutputStream stream, Resource resource) throws IOException;
/**
* Compose a resource to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
* @throws IOException
*/
public String composeString(Resource resource) throws IOException;
/**
* Compose a resource to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
* @throws IOException
*/
public byte[] composeBytes(Resource resource) throws IOException;
/**
* Compose a type to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
*
* Not supported by all implementations. rootName is ignored in the JSON format
* @throws XmlPullParserException
* @throws FHIRFormatError
* @throws IOException
*/
public void compose(OutputStream stream, DataType type, String rootName) throws IOException;
/**
* Compose a type to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
*
* Not supported by all implementations. rootName is ignored in the JSON format
* @throws IOException
*/
public String composeString(DataType type, String rootName) throws IOException;
/**
* Compose a type to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
*
* Not supported by all implementations. rootName is ignored in the JSON format
* @throws IOException
*/
public byte[] composeBytes(DataType type, String rootName) throws IOException;
}

View File

@ -0,0 +1,73 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.math.BigDecimal;
/**
* Facade to GSON writer, or something that imposes property ordering first
*
* @author Grahame
*
*/
public interface JsonCreator {
void setIndent(String string);
void beginObject() throws IOException;
void endObject() throws IOException;
void nullValue() throws IOException;
void name(String name) throws IOException;
void value(String value) throws IOException;
void value(Boolean value) throws IOException;
void value(BigDecimal value) throws IOException;
void valueNum(String value) throws IOException; // allow full control of representation
void value(Integer value) throws IOException;
void beginArray() throws IOException;
void endArray() throws IOException;
void finish() throws IOException;
// only used by an creator that actually produces xhtml
void link(String href);
void anchor(String string);
}

View File

@ -0,0 +1,281 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
public class JsonCreatorCanonical implements JsonCreator {
public class JsonCanValue {
String name;
private JsonCanValue(String name) {
this.name = name;
}
}
private class JsonCanNumberValue extends JsonCanValue {
private BigDecimal value;
private JsonCanNumberValue(String name, BigDecimal value) {
super(name);
this.value = value;
}
}
private class JsonCanPresentedNumberValue extends JsonCanValue {
private String value;
private JsonCanPresentedNumberValue(String name, String value) {
super(name);
this.value = value;
}
}
private class JsonCanIntegerValue extends JsonCanValue {
private Integer value;
private JsonCanIntegerValue(String name, Integer value) {
super(name);
this.value = value;
}
}
private class JsonCanBooleanValue extends JsonCanValue {
private Boolean value;
private JsonCanBooleanValue(String name, Boolean value) {
super(name);
this.value = value;
}
}
private class JsonCanStringValue extends JsonCanValue {
private String value;
private JsonCanStringValue(String name, String value) {
super(name);
this.value = value;
}
}
private class JsonCanNullValue extends JsonCanValue {
private JsonCanNullValue(String name) {
super(name);
}
}
public class JsonCanObject extends JsonCanValue {
boolean array;
List<JsonCanValue> children = new ArrayList<JsonCanValue>();
public JsonCanObject(String name, boolean array) {
super(name);
this.array = array;
}
public void addProp(JsonCanValue obj) {
children.add(obj);
}
}
Stack<JsonCanObject> stack;
JsonCanObject root;
JsonCreatorDirect jj;
String name;
public JsonCreatorCanonical(OutputStreamWriter osw) {
stack = new Stack<JsonCreatorCanonical.JsonCanObject>();
jj = new JsonCreatorDirect(osw);
name = null;
}
private String takeName() {
String res = name;
name = null;
return res;
}
@Override
public void setIndent(String indent) {
if (!indent.equals(""))
throw new Error("do not use pretty when canonical is set");
jj.setIndent(indent);
}
@Override
public void beginObject() throws IOException {
JsonCanObject obj = new JsonCanObject(takeName(), false);
if (stack.isEmpty())
root = obj;
else
stack.peek().addProp(obj);
stack.push(obj);
}
@Override
public void endObject() throws IOException {
stack.pop();
}
@Override
public void nullValue() throws IOException {
stack.peek().addProp(new JsonCanNullValue(takeName()));
}
@Override
public void name(String name) throws IOException {
this.name = name;
}
@Override
public void value(String value) throws IOException {
stack.peek().addProp(new JsonCanStringValue(takeName(), value));
}
@Override
public void value(Boolean value) throws IOException {
stack.peek().addProp(new JsonCanBooleanValue(takeName(), value));
}
@Override
public void value(BigDecimal value) throws IOException {
stack.peek().addProp(new JsonCanNumberValue(takeName(), value));
}
@Override
public void valueNum(String value) throws IOException {
stack.peek().addProp(new JsonCanPresentedNumberValue(takeName(), value));
}
@Override
public void value(Integer value) throws IOException {
stack.peek().addProp(new JsonCanIntegerValue(takeName(), value));
}
@Override
public void beginArray() throws IOException {
JsonCanObject obj = new JsonCanObject(takeName(), true);
if (!stack.isEmpty())
stack.peek().addProp(obj);
stack.push(obj);
}
@Override
public void endArray() throws IOException {
stack.pop();
}
@Override
public void finish() throws IOException {
writeObject(root);
}
private void writeObject(JsonCanObject obj) throws IOException {
jj.beginObject();
List<String> names = new ArrayList<String>();
for (JsonCanValue v : obj.children)
names.add(v.name);
Collections.sort(names);
for (String n : names) {
jj.name(n);
JsonCanValue v = getPropForName(n, obj.children);
if (v instanceof JsonCanNumberValue)
jj.value(((JsonCanNumberValue) v).value);
else if (v instanceof JsonCanPresentedNumberValue)
jj.valueNum(((JsonCanPresentedNumberValue) v).value);
else if (v instanceof JsonCanIntegerValue)
jj.value(((JsonCanIntegerValue) v).value);
else if (v instanceof JsonCanBooleanValue)
jj.value(((JsonCanBooleanValue) v).value);
else if (v instanceof JsonCanStringValue)
jj.value(((JsonCanStringValue) v).value);
else if (v instanceof JsonCanNullValue)
jj.nullValue();
else if (v instanceof JsonCanObject) {
JsonCanObject o = (JsonCanObject) v;
if (o.array)
writeArray(o);
else
writeObject(o);
} else
throw new Error("not possible");
}
jj.endObject();
}
private JsonCanValue getPropForName(String name, List<JsonCanValue> children) {
for (JsonCanValue child : children)
if (child.name.equals(name))
return child;
return null;
}
private void writeArray(JsonCanObject arr) throws IOException {
jj.beginArray();
for (JsonCanValue v : arr.children) {
if (v instanceof JsonCanNumberValue)
jj.value(((JsonCanNumberValue) v).value);
else if (v instanceof JsonCanIntegerValue)
jj.value(((JsonCanIntegerValue) v).value);
else if (v instanceof JsonCanBooleanValue)
jj.value(((JsonCanBooleanValue) v).value);
else if (v instanceof JsonCanStringValue)
jj.value(((JsonCanStringValue) v).value);
else if (v instanceof JsonCanNullValue)
jj.nullValue();
else if (v instanceof JsonCanObject) {
JsonCanObject o = (JsonCanObject) v;
if (o.array)
writeArray(o);
else
writeObject(o);
} else
throw new Error("not possible");
}
jj.endArray();
}
@Override
public void link(String href) {
// not used
}
@Override
public void anchor(String name) {
// not used
}
}

View File

@ -0,0 +1,220 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.Writer;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.utilities.Utilities;
/**
* A little implementation of a json write to replace Gson .... because Gson screws up decimal values, and *we care*
*
* @author Grahame Grieve
*
*/
public class JsonCreatorDirect implements JsonCreator {
private Writer writer;
private boolean pretty;
private boolean named;
private List<Boolean> valued = new ArrayList<Boolean>();
private int indent;
public JsonCreatorDirect(Writer writer) {
super();
this.writer = writer;
}
@Override
public void setIndent(String indent) {
this.pretty = !Utilities.noString(indent);
}
@Override
public void beginObject() throws IOException {
checkState();
writer.write("{");
stepIn();
if (!valued.isEmpty()) {
valued.set(0, true);
}
valued.add(0, false);
}
public void stepIn() throws IOException {
if (pretty) {
indent++;
writer.write("\r\n");
for (int i = 0; i < indent; i++) {
writer.write(" ");
}
}
}
public void stepOut() throws IOException {
if (pretty) {
indent--;
writer.write("\r\n");
for (int i = 0; i < indent; i++) {
writer.write(" ");
}
}
}
private void checkState() throws IOException {
if (named) {
if (pretty)
writer.write(" : ");
else
writer.write(":");
named = false;
}
if (!valued.isEmpty() && valued.get(0)) {
writer.write(",");
if (pretty) {
writer.write("\r\n");
for (int i = 0; i < indent; i++) {
writer.write(" ");
}
}
valued.set(0, false);
}
}
@Override
public void endObject() throws IOException {
stepOut();
writer.write("}");
valued.remove(0);
}
@Override
public void nullValue() throws IOException {
checkState();
writer.write("null");
valued.set(0, true);
}
@Override
public void name(String name) throws IOException {
checkState();
writer.write("\""+name+"\"");
named = true;
}
@Override
public void value(String value) throws IOException {
checkState();
writer.write("\""+Utilities.escapeJson(value)+"\"");
valued.set(0, true);
}
@Override
public void value(Boolean value) throws IOException {
checkState();
if (value == null)
writer.write("null");
else if (value.booleanValue())
writer.write("true");
else
writer.write("false");
valued.set(0, true);
}
@Override
public void value(BigDecimal value) throws IOException {
checkState();
if (value == null)
writer.write("null");
else
writer.write(value.toString());
valued.set(0, true);
}
@Override
public void valueNum(String value) throws IOException {
checkState();
if (value == null)
writer.write("null");
else
writer.write(value);
valued.set(0, true);
}
@Override
public void value(Integer value) throws IOException {
checkState();
if (value == null)
writer.write("null");
else
writer.write(value.toString());
valued.set(0, true);
}
@Override
public void beginArray() throws IOException {
checkState();
writer.write("[");
if (!valued.isEmpty()) {
valued.set(0, true);
}
valued.add(0, false);
}
@Override
public void endArray() throws IOException {
writer.write("]");
valued.remove(0);
}
@Override
public void finish() throws IOException {
writer.flush();
}
@Override
public void link(String href) {
// not used
}
@Override
public void anchor(String name) {
// not used
}
}

View File

@ -0,0 +1,124 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import com.google.gson.stream.JsonWriter;
public class JsonCreatorGson implements JsonCreator {
JsonWriter gson;
public JsonCreatorGson(OutputStreamWriter osw) {
gson = new JsonWriter(osw);
}
@Override
public void setIndent(String indent) {
gson.setIndent(indent);
}
@Override
public void beginObject() throws IOException {
gson.beginObject();
}
@Override
public void endObject() throws IOException {
gson.endObject();
}
@Override
public void nullValue() throws IOException {
gson.nullValue();
}
@Override
public void name(String name) throws IOException {
gson.name(name);
}
@Override
public void value(String value) throws IOException {
gson.value(value);
}
@Override
public void value(Boolean value) throws IOException {
gson.value(value);
}
@Override
public void value(BigDecimal value) throws IOException {
gson.value(value);
}
@Override
public void value(Integer value) throws IOException {
gson.value(value);
}
@Override
public void beginArray() throws IOException {
gson.beginArray();
}
@Override
public void endArray() throws IOException {
gson.endArray();
}
@Override
public void finish() {
// nothing to do here
}
@Override
public void link(String href) {
// not used
}
@Override
public void valueNum(String value) throws IOException {
value(new BigDecimal(value));
}
@Override
public void anchor(String name) {
// not used
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,389 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/*
Copyright (c) 2011+, HL7, Inc
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.math.BigDecimal;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4b.model.DataType;
import org.hl7.fhir.r4b.model.DomainResource;
import org.hl7.fhir.r4b.model.Element;
import org.hl7.fhir.r4b.model.IdType;
import org.hl7.fhir.r4b.model.PrimitiveType;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.StringType;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.json.JsonTrackingParser;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
import com.google.gson.JsonArray;
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import com.google.gson.JsonSyntaxException;
/**
* General parser for JSON content. You instantiate an JsonParser of these, but you
* actually use parse or parseGeneral defined on this class
*
* The two classes are separated to keep generated and manually maintained code apart.
*/
public abstract class JsonParserBase extends ParserBase implements IParser {
@Override
public ParserType getType() {
return ParserType.JSON;
}
// private static com.google.gson.JsonParser parser = new com.google.gson.JsonParser();
// -- in descendent generated code --------------------------------------
abstract protected Resource parseResource(JsonObject json) throws IOException, FHIRFormatError;
abstract protected DataType parseType(JsonObject json, String type) throws IOException, FHIRFormatError;
abstract protected DataType parseAnyType(JsonObject json, String type) throws IOException, FHIRFormatError;
abstract protected DataType parseType(String prefix, JsonObject json) throws IOException, FHIRFormatError;
abstract protected boolean hasTypeName(JsonObject json, String prefix);
abstract protected void composeResource(Resource resource) throws IOException;
abstract protected void composeTypeInner(DataType type) throws IOException;
/* -- entry points --------------------------------------------------- */
/**
* @throws FHIRFormatError
* Parse content that is known to be a resource
* @throws IOException
* @throws
*/
@Override
public Resource parse(InputStream input) throws IOException, FHIRFormatError {
JsonObject json = loadJson(input);
return parseResource(json);
}
/**
* parse xml that is known to be a resource, and that has already been read into a JSON object
* @throws IOException
* @throws FHIRFormatError
*/
public Resource parse(JsonObject json) throws FHIRFormatError, IOException {
return parseResource(json);
}
@Override
public DataType parseType(InputStream input, String type) throws IOException, FHIRFormatError {
JsonObject json = loadJson(input);
return parseType(json, type);
}
@Override
public DataType parseAnyType(InputStream input, String type) throws IOException, FHIRFormatError {
JsonObject json = loadJson(input);
return parseAnyType(json, type);
}
protected JsonObject getJObject(JsonObject parent, String name) throws IOException {
JsonElement j = parent.get(name);
if (j == null) {
return null;
}
if (!(j instanceof JsonObject)) {
throw new IOException("property "+name+" is a "+j.getClass()+" looking for an object");
}
return (JsonObject) j;
}
protected JsonArray getJArray(JsonObject parent, String name) throws IOException {
JsonElement j = parent.get(name);
if (j == null) {
return null;
}
if (!(j instanceof JsonArray)) {
throw new IOException("property "+name+" is a "+j.getClass()+" looking for an Array");
}
return (JsonArray) j;
}
/**
* Compose a resource to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
* @throws IOException
*/
@Override
public void compose(OutputStream stream, Resource resource) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
if (style == OutputStyle.CANONICAL)
json = new JsonCreatorCanonical(osw);
else
json = new JsonCreatorDirect(osw); // use this instead of Gson because this preserves decimal formatting
json.setIndent(style == OutputStyle.PRETTY ? " " : "");
json.beginObject();
composeResource(resource);
json.endObject();
json.finish();
osw.flush();
}
/**
* Compose a resource using a pre-existing JsonWriter
* @throws IOException
*/
public void compose(JsonCreator writer, Resource resource) throws IOException {
json = writer;
composeResource(resource);
}
@Override
public void compose(OutputStream stream, DataType type, String rootName) throws IOException {
OutputStreamWriter osw = new OutputStreamWriter(stream, "UTF-8");
if (style == OutputStyle.CANONICAL)
json = new JsonCreatorCanonical(osw);
else
json = new JsonCreatorDirect(osw);// use this instead of Gson because this preserves decimal formatting
json.setIndent(style == OutputStyle.PRETTY ? " " : "");
json.beginObject();
composeTypeInner(type);
json.endObject();
json.finish();
osw.flush();
}
/* -- json routines --------------------------------------------------- */
protected JsonCreator json;
private boolean htmlPretty;
private JsonObject loadJson(InputStream input) throws JsonSyntaxException, IOException {
return JsonTrackingParser.parse(TextFile.streamToString(input), null, allowUnknownContent, allowComments);
// return parser.parse(TextFile.streamToString(input)).getAsJsonObject();
}
// private JsonObject loadJson(String input) {
// return parser.parse(input).getAsJsonObject();
// }
//
protected void parseElementProperties(JsonObject json, Element e) throws IOException, FHIRFormatError {
if (json != null && json.has("id"))
e.setId(json.get("id").getAsString());
if (!Utilities.noString(e.getId()))
idMap.put(e.getId(), e);
if (json.has("fhir_comments") && handleComments) {
JsonArray array = json.getAsJsonArray("fhir_comments");
for (int i = 0; i < array.size(); i++) {
e.getFormatCommentsPre().add(array.get(i).getAsString());
}
}
}
protected XhtmlNode parseXhtml(String value) throws IOException, FHIRFormatError {
XhtmlParser prsr = new XhtmlParser();
try {
return prsr.parse(value, "div").getChildNodes().get(0);
} catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
throw new FHIRFormatError(e.getMessage(), e);
}
}
protected DomainResource parseDomainResource(JsonObject json) throws FHIRFormatError, IOException {
return (DomainResource) parseResource(json);
}
protected void writeNull(String name) throws IOException {
json.nullValue();
}
protected void prop(String name, String value) throws IOException {
if (name != null)
json.name(name);
json.value(value);
}
protected void prop(String name, java.lang.Boolean value) throws IOException {
if (name != null)
json.name(name);
json.value(value);
}
protected void prop(String name, BigDecimal value) throws IOException {
if (name != null)
json.name(name);
json.value(value);
}
protected void propNum(String name, String value) throws IOException {
if (name != null)
json.name(name);
json.valueNum(value);
}
protected void prop(String name, java.lang.Integer value) throws IOException {
if (name != null)
json.name(name);
json.value(value);
}
protected void composeXhtml(String name, XhtmlNode html) throws IOException {
if (!Utilities.noString(xhtmlMessage)) {
prop(name, "<div>!-- "+xhtmlMessage+" --></div>");
} else {
XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty);
prop(name, comp.compose(html));
}
}
protected void open(String name) throws IOException {
if (name != null)
json.name(name);
json.beginObject();
}
protected void close() throws IOException {
json.endObject();
}
protected void openArray(String name) throws IOException {
if (name != null)
json.name(name);
json.beginArray();
}
protected void closeArray() throws IOException {
json.endArray();
}
protected void openObject(String name) throws IOException {
if (name != null)
json.name(name);
json.beginObject();
}
protected void closeObject() throws IOException {
json.endObject();
}
// protected void composeBinary(String name, Binary element) {
// if (element != null) {
// prop("resourceType", "Binary");
// if (element.getXmlId() != null)
// prop("id", element.getXmlId());
// prop("contentType", element.getContentType());
// prop("content", toString(element.getContent()));
// }
//
// }
protected boolean anyHasExtras(List<? extends Element> list) {
for (Element e : list) {
if (e.hasExtension() || !Utilities.noString(e.getId()))
return true;
}
return false;
}
protected boolean anyHasValue(List<? extends PrimitiveType> list) {
for (PrimitiveType e : list) {
if (e.hasValue())
return true;
}
return false;
}
protected boolean makeComments(Element element) {
return handleComments && (style != OutputStyle.CANONICAL) && !(element.getFormatCommentsPre().isEmpty() && element.getFormatCommentsPost().isEmpty());
}
protected void composeDomainResource(String name, DomainResource e) throws IOException {
openObject(name);
composeResource(e);
close();
}
protected abstract void composeType(String prefix, DataType type) throws IOException;
abstract void composeStringCore(String name, StringType value, boolean inArray) throws IOException;
protected void composeStringCore(String name, IIdType value, boolean inArray) throws IOException {
composeStringCore(name, new StringType(value.getValue()), inArray);
}
abstract void composeStringExtras(String name, StringType value, boolean inArray) throws IOException;
protected void composeStringExtras(String name, IIdType value, boolean inArray) throws IOException {
composeStringExtras(name, new StringType(value.getValue()), inArray);
}
protected void parseElementProperties(JsonObject theAsJsonObject, IIdType theReferenceElement) throws FHIRFormatError, IOException {
parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
}
protected void parseElementProperties(JsonObject theAsJsonObject, IdType theReferenceElement) throws FHIRFormatError, IOException {
parseElementProperties(theAsJsonObject, (Element)theReferenceElement);
}
}

View File

@ -0,0 +1,250 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.math.BigDecimal;
import java.text.ParseException;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.codec.binary.Base64;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.model.DataType;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.SearchParameter;
import org.hl7.fhir.utilities.Utilities;
public abstract class ParserBase extends FormatUtilities implements IParser {
// -- implementation of variant type methods from the interface --------------------------------
public Resource parse(String input) throws FHIRFormatError, IOException {
return parse(input.getBytes("UTF-8"));
}
public Resource parse(byte[] bytes) throws FHIRFormatError, IOException {
ByteArrayInputStream bi = new ByteArrayInputStream(bytes);
return parse(bi);
}
public DataType parseType(String input, String typeName) throws FHIRFormatError, IOException {
return parseType(input.getBytes("UTF-8"), typeName);
}
public DataType parseAnyType(String input, String typeName) throws FHIRFormatError, IOException {
return parseAnyType(input.getBytes("UTF-8"), typeName);
}
public DataType parseType(byte[] bytes, String typeName) throws FHIRFormatError, IOException {
ByteArrayInputStream bi = new ByteArrayInputStream(bytes);
return parseType(bi, typeName);
}
public DataType parseAnyType(byte[] bytes, String typeName) throws FHIRFormatError, IOException {
ByteArrayInputStream bi = new ByteArrayInputStream(bytes);
return parseAnyType(bi, typeName);
}
public String composeString(Resource resource) throws IOException {
return new String(composeBytes(resource), "UTF-8");
}
public byte[] composeBytes(Resource resource) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
compose(bytes, resource);
bytes.close();
return bytes.toByteArray();
}
public String composeString(DataType type, String typeName) throws IOException {
return new String(composeBytes(type, typeName));
}
public byte[] composeBytes(DataType type, String typeName) throws IOException {
ByteArrayOutputStream bytes = new ByteArrayOutputStream();
compose(bytes, type, typeName);
bytes.close();
return bytes.toByteArray();
}
// -- Parser Configuration --------------------------------
protected String xhtmlMessage;
@Override
public IParser setSuppressXhtml(String message) {
xhtmlMessage = message;
return this;
}
protected boolean handleComments = false;
public boolean getHandleComments() {
return handleComments;
}
public IParser setHandleComments(boolean value) {
this.handleComments = value;
return this;
}
/**
* Whether to throw an exception if unknown content is found (or just skip it)
*/
protected boolean allowUnknownContent;
/**
* whether to allow comments in the json (special case for IG publisher source)
*/
protected boolean allowComments;
/**
* @return Whether to throw an exception if unknown content is found (or just skip it)
*/
public boolean isAllowUnknownContent() {
return allowUnknownContent;
}
/**
* @param allowUnknownContent Whether to throw an exception if unknown content is found (or just skip it)
*/
public IParser setAllowUnknownContent(boolean allowUnknownContent) {
this.allowUnknownContent = allowUnknownContent;
return this;
}
public boolean isAllowComments() {
return allowComments;
}
public void setAllowComments(boolean allowComments) {
this.allowComments = allowComments;
}
protected OutputStyle style = OutputStyle.NORMAL;
public OutputStyle getOutputStyle() {
return style;
}
public IParser setOutputStyle(OutputStyle style) {
this.style = style;
return this;
}
// -- Parser Utilities --------------------------------
protected Map<String, Object> idMap = new HashMap<String, Object>();
protected int parseIntegerPrimitive(String value) {
if (value.startsWith("+") && Utilities.isInteger(value.substring(1)))
value = value.substring(1);
return java.lang.Integer.parseInt(value);
}
protected int parseIntegerPrimitive(java.lang.Long value) {
if (value < java.lang.Integer.MIN_VALUE || value > java.lang.Integer.MAX_VALUE) {
throw new IllegalArgumentException
(value + " cannot be cast to int without changing its value.");
}
return value.intValue();
}
protected String parseCodePrimitive(String value) {
return value;
}
protected String parseTimePrimitive(String value) throws ParseException {
return value;
}
protected BigDecimal parseDecimalPrimitive(BigDecimal value) {
return value;
}
protected BigDecimal parseDecimalPrimitive(String value) {
return new BigDecimal(value);
}
protected String parseUriPrimitive(String value) {
return value;
}
protected byte[] parseBase64BinaryPrimitive(String value) {
return Base64.decodeBase64(value.getBytes());
}
protected String parseOidPrimitive(String value) {
return value;
}
protected Boolean parseBooleanPrimitive(String value) {
return java.lang.Boolean.valueOf(value);
}
protected Boolean parseBooleanPrimitive(Boolean value) {
return java.lang.Boolean.valueOf(value);
}
protected String parseIdPrimitive(String value) {
return value;
}
protected String parseStringPrimitive(String value) {
return value;
}
protected String parseUuidPrimitive(String value) {
return value;
}
@Override
public Resource parseAndClose(InputStream input) throws IOException, FHIRFormatError {
try {
return parse(input);
} finally {
input.close();
}
}
}

View File

@ -0,0 +1,59 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import org.hl7.fhir.r4b.elementmodel.Manager.FhirFormat;
import org.hl7.fhir.r4b.formats.IParser.OutputStyle;
public class ParserFactory {
public static IParser parser(FhirFormat format) {
switch (format) {
case JSON : return new JsonParser();
case XML : return new XmlParser();
case TURTLE : return new RdfParser();
default:
throw new Error("Not supported at this time");
}
}
public static IParser parser(FhirFormat format, OutputStyle style) {
switch (format) {
case JSON : return new JsonParser().setOutputStyle(style);
case XML : return new XmlParser().setOutputStyle(style);
case TURTLE : return new RdfParser().setOutputStyle(style);
default:
throw new Error("Not supported at this time");
}
}
}

View File

@ -0,0 +1,61 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/**
* Used in factory methods for parsers, for requesting a parser of a particular type
* (see IWorkerContext)
*
* @author Grahame
*
*/
public enum ParserType {
/**
* XML as specified in specification
*/
XML,
/**
* JSON as specified in the specification
*/
JSON,
/**
* XHTML - write narrative (generate if necessary). No read
*/
XHTML,
/**
* RDF is not supported yet
*/
RDF_TURTLE
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,138 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r4b.model.Base;
import org.hl7.fhir.r4b.model.CodeType;
import org.hl7.fhir.r4b.model.CodeableConcept;
import org.hl7.fhir.r4b.model.Coding;
import org.hl7.fhir.r4b.model.DataType;
import org.hl7.fhir.r4b.model.Enumeration;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.utilities.turtle.Turtle;
import org.hl7.fhir.utilities.turtle.Turtle.Complex;
import org.hl7.fhir.utilities.turtle.Turtle.Section;
import org.hl7.fhir.utilities.turtle.Turtle.Subject;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public abstract class RdfParserBase extends ParserBase implements IParser {
protected abstract void composeResource(Complex complex, Resource resource) throws IOException;
@Override
public ParserType getType() {
return ParserType.RDF_TURTLE;
}
@Override
public Resource parse(InputStream input) throws IOException, FHIRFormatError {
throw new Error("Parsing not implemented yet");
}
@Override
public DataType parseType(InputStream input, String knownType) throws IOException, FHIRFormatError {
throw new Error("Parsing not implemented yet");
}
@Override
public DataType parseAnyType(InputStream input, String knownType) throws IOException, FHIRFormatError {
throw new Error("Parsing not implemented yet");
}
private String url;
@Override
public void compose(OutputStream stream, Resource resource) throws IOException {
Turtle ttl = new Turtle();
// ttl.setFormat(FFormat);
ttl.prefix("fhir", "http://hl7.org/fhir/");
ttl.prefix("rdfs", "http://www.w3.org/2000/01/rdf-schema#");
Section section = ttl.section("resource");
Subject subject;
if (url != null)
subject = section.triple("<"+url+">", "a", "fhir:"+resource.getResourceType().toString());
else
subject = section.triple("[]", "a", "fhir:"+resource.getResourceType().toString());
composeResource(subject, resource);
try {
ttl.commit(stream, false);
} catch (Exception e) {
throw new IOException(e);
}
}
protected void composeBase(Complex t, String parentType, String name, Base element, int index) {
}
@Override
public void compose(OutputStream stream, DataType type, String rootName) throws IOException {
throw new Error("Not supported in RDF");
}
protected String ttlLiteral(String value) {
return "\"" +Turtle.escape(value, true) + "\"";
}
protected void composeXhtmlNode(Complex t, String string, String string2, XhtmlNode div, int i) {
}
protected void decorateCode(Complex t, Enumeration<? extends Enum> value) {
}
protected void decorateCode(Complex t, CodeType value) {
}
protected void decorateCoding(Complex t, Coding element) {
if (!element.hasSystem())
return;
if ("http://snomed.info/sct".equals(element.getSystem())) {
t.prefix("sct", "http://snomed.info/sct/");
t.predicate("a", "sct:"+element.getCode());
} else if ("http://snomed.info/sct".equals(element.getSystem())) {
t.prefix("loinc", "http://loinc.org/rdf#");
t.predicate("a", "loinc:"+element.getCode());
}
}
protected void decorateCodeableConcept(Complex t, CodeableConcept element) {
for (Coding c : element.getCoding())
decorateCoding(t, c);
}
}

View File

@ -0,0 +1,140 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
public class TurtleLexer {
public enum TurtleTokenType {
NULL,
TOKEN, SPECIAL, LITERAL
}
private String source;
private int cursor;
private String token;
private TurtleTokenType type;
public TurtleLexer(String source) throws Exception {
this.source = source;
cursor = 0;
readNext();
}
private void readNext() throws Exception {
if (cursor >= source.length()) {
token = null;
type = TurtleTokenType.NULL;
} else if (source.charAt(cursor) == '"')
readLiteral();
else if (source.charAt(cursor) == '[' || source.charAt(cursor) == ']')
readDelimiter();
else if (source.charAt(cursor) == '(')
throw new Exception("not supported yet");
else if (source.charAt(cursor) == ';' || source.charAt(cursor) == '.' || source.charAt(cursor) == ',')
readDelimiter();
else if (Character.isLetter(source.charAt(cursor)))
readToken();
}
private void readLiteral() {
StringBuilder b = new StringBuilder();
cursor++; // skip "
while (cursor < source.length() && source.charAt(cursor) != '"') {
if (source.charAt(cursor) == '\\') {
b.append(source.charAt(cursor));
cursor++;
}
b.append(source.charAt(cursor));
cursor++;
}
token = "\""+b.toString()+"\"";
type = TurtleTokenType.LITERAL;
cursor++; // skip "
while (cursor < source.length() && Character.isWhitespace(source.charAt(cursor)))
cursor++;
}
private void readDelimiter() {
StringBuilder b = new StringBuilder();
b.append(source.charAt(cursor));
cursor++;
token = b.toString();
type = TurtleTokenType.SPECIAL;
while (cursor < source.length() && Character.isWhitespace(source.charAt(cursor)))
cursor++;
}
private void readToken() {
StringBuilder b = new StringBuilder();
while (cursor < source.length() && isValidTokenChar(source.charAt(cursor))) {
if (source.charAt(cursor) == '\\') {
b.append(source.charAt(cursor));
cursor++;
}
b.append(source.charAt(cursor));
cursor++;
}
token = b.toString();
type = TurtleTokenType.TOKEN;
if (token.endsWith(".")) {
cursor--;
token = token.substring(0, token.length()-1);
}
while (cursor < source.length() && Character.isWhitespace(source.charAt(cursor)))
cursor++;
}
private boolean isValidTokenChar(char c) {
return Character.isLetter(c) || Character.isDigit(c) || c == ':' || c == '\\' || c == '.';
}
public boolean done() {
return type == TurtleTokenType.NULL;
}
public String next() throws Exception {
String res = token;
readNext();
return res;
}
public String peek() throws Exception {
return token;
}
public TurtleTokenType peekType() {
return type;
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,464 @@
package org.hl7.fhir.r4b.formats;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
/*
Copyright (c) 2011+, HL7, Inc
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
*/
import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.util.ArrayList;
import java.util.List;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4b.model.Base;
import org.hl7.fhir.r4b.model.DataType;
import org.hl7.fhir.r4b.model.DomainResource;
import org.hl7.fhir.r4b.model.Element;
import org.hl7.fhir.r4b.model.Resource;
import org.hl7.fhir.r4b.model.StringType;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.xhtml.NodeType;
import org.hl7.fhir.utilities.xhtml.XhtmlComposer;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.hl7.fhir.utilities.xhtml.XhtmlParser;
import org.hl7.fhir.utilities.xml.IXMLWriter;
import org.hl7.fhir.utilities.xml.XMLWriter;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlPullParserFactory;
/**
* General parser for XML content. You instantiate an XmlParser of these, but you
* actually use parse or parseGeneral defined on this class
*
* The two classes are separated to keep generated and manually maintained code apart.
*/
public abstract class XmlParserBase extends ParserBase implements IParser {
@Override
public ParserType getType() {
return ParserType.XML;
}
// -- in descendent generated code --------------------------------------
abstract protected Resource parseResource(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError ;
abstract protected DataType parseType(XmlPullParser xml, String type) throws XmlPullParserException, IOException, FHIRFormatError ;
abstract protected DataType parseAnyType(XmlPullParser xml, String type) throws XmlPullParserException, IOException, FHIRFormatError ;
abstract protected void composeType(String prefix, DataType type) throws IOException ;
/* -- entry points --------------------------------------------------- */
/**
* Parse content that is known to be a resource
* @
*/
@Override
public Resource parse(InputStream input) throws IOException, FHIRFormatError {
try {
XmlPullParser xpp = loadXml(input);
return parse(xpp);
} catch (XmlPullParserException e) {
throw new FHIRFormatError(e.getMessage(), e);
}
}
/**
* parse xml that is known to be a resource, and that is already being read by an XML Pull Parser
* This is if a resource is in a bigger piece of XML.
* @
*/
public Resource parse(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException {
if (xpp.getNamespace() == null)
throw new FHIRFormatError("This does not appear to be a FHIR resource (no namespace '"+xpp.getNamespace()+"') (@ /) "+Integer.toString(xpp.getEventType()));
if (!xpp.getNamespace().equals(FHIR_NS))
throw new FHIRFormatError("This does not appear to be a FHIR resource (wrong namespace '"+xpp.getNamespace()+"') (@ /)");
return parseResource(xpp);
}
@Override
public DataType parseType(InputStream input, String knownType) throws IOException, FHIRFormatError {
try {
XmlPullParser xml = loadXml(input);
return parseType(xml, knownType);
} catch (XmlPullParserException e) {
throw new FHIRFormatError(e.getMessage(), e);
}
}
@Override
public DataType parseAnyType(InputStream input, String knownType) throws IOException, FHIRFormatError {
try {
XmlPullParser xml = loadXml(input);
return parseAnyType(xml, knownType);
} catch (XmlPullParserException e) {
throw new FHIRFormatError(e.getMessage(), e);
}
}
/**
* Compose a resource to a stream, possibly using pretty presentation for a human reader (used in the spec, for example, but not normally in production)
* @
*/
@Override
public void compose(OutputStream stream, Resource resource) throws IOException {
XMLWriter writer = new XMLWriter(stream, "UTF-8");
writer.setPretty(style == OutputStyle.PRETTY);
writer.start();
compose(writer, resource, writer.isPretty());
writer.end();
}
/**
* Compose a resource to a stream, possibly using pretty presentation for a human reader, and maybe a different choice in the xhtml narrative (used in the spec in one place, but should not be used in production)
* @
*/
public void compose(OutputStream stream, Resource resource, boolean htmlPretty) throws IOException {
XMLWriter writer = new XMLWriter(stream, "UTF-8");
writer.setPretty(style == OutputStyle.PRETTY);
writer.start();
compose(writer, resource, htmlPretty);
writer.end();
}
/**
* Compose a type to a stream (used in the spec, for example, but not normally in production)
* @
*/
public void compose(OutputStream stream, String rootName, DataType type) throws IOException {
xml = new XMLWriter(stream, "UTF-8");
xml.setPretty(style == OutputStyle.PRETTY);
xml.start();
xml.setDefaultNamespace(FHIR_NS);
composeType(Utilities.noString(rootName) ? "value" : rootName, type);
xml.end();
}
@Override
public void compose(OutputStream stream, DataType type, String rootName) throws IOException {
xml = new XMLWriter(stream, "UTF-8");
xml.setPretty(style == OutputStyle.PRETTY);
xml.start();
xml.setDefaultNamespace(FHIR_NS);
composeType(Utilities.noString(rootName) ? "value" : rootName, type);
xml.end();
}
/* -- xml routines --------------------------------------------------- */
protected XmlPullParser loadXml(String source) throws UnsupportedEncodingException, XmlPullParserException, IOException {
return loadXml(new ByteArrayInputStream(source.getBytes("UTF-8")));
}
protected XmlPullParser loadXml(InputStream stream) throws XmlPullParserException, IOException {
BufferedInputStream input = new BufferedInputStream(stream);
XmlPullParserFactory factory = XmlPullParserFactory.newInstance(System.getProperty(XmlPullParserFactory.PROPERTY_NAME), null);
factory.setNamespaceAware(true);
factory.setFeature(XmlPullParser.FEATURE_PROCESS_DOCDECL, false);
XmlPullParser xpp = factory.newPullParser();
xpp.setInput(input, "UTF-8");
next(xpp);
nextNoWhitespace(xpp);
return xpp;
}
protected int next(XmlPullParser xpp) throws XmlPullParserException, IOException {
if (handleComments)
return xpp.nextToken();
else
return xpp.next();
}
protected List<String> comments = new ArrayList<String>();
protected int nextNoWhitespace(XmlPullParser xpp) throws XmlPullParserException, IOException {
int eventType = xpp.getEventType();
while ((eventType == XmlPullParser.TEXT && xpp.isWhitespace()) || (eventType == XmlPullParser.COMMENT)
|| (eventType == XmlPullParser.CDSECT) || (eventType == XmlPullParser.IGNORABLE_WHITESPACE)
|| (eventType == XmlPullParser.PROCESSING_INSTRUCTION) || (eventType == XmlPullParser.DOCDECL)) {
if (eventType == XmlPullParser.COMMENT) {
comments.add(xpp.getText());
} else if (eventType == XmlPullParser.DOCDECL) {
throw new XmlPullParserException("DTD declarations are not allowed");
}
eventType = next(xpp);
}
return eventType;
}
protected void skipElementWithContent(XmlPullParser xpp) throws XmlPullParserException, IOException {
// when this is called, we are pointing an element that may have content
while (xpp.getEventType() != XmlPullParser.END_TAG) {
next(xpp);
if (xpp.getEventType() == XmlPullParser.START_TAG)
skipElementWithContent(xpp);
}
next(xpp);
}
protected void skipEmptyElement(XmlPullParser xpp) throws XmlPullParserException, IOException {
while (xpp.getEventType() != XmlPullParser.END_TAG)
next(xpp);
next(xpp);
}
protected IXMLWriter xml;
protected boolean htmlPretty;
private String schemaPath;
public String getSchemaPath() {
return schemaPath;
}
public void setSchemaPath(String schemaPath) {
this.schemaPath = schemaPath;
}
/* -- worker routines --------------------------------------------------- */
protected void parseTypeAttributes(XmlPullParser xpp, DataType t) {
parseElementAttributes(xpp, t);
}
protected void parseElementAttributes(XmlPullParser xpp, Element e) {
if (xpp.getAttributeValue(null, "id") != null) {
e.setId(xpp.getAttributeValue(null, "id"));
idMap.put(e.getId(), e);
}
if (!comments.isEmpty()) {
e.getFormatCommentsPre().addAll(comments);
comments.clear();
}
}
protected void parseElementClose(Base e) {
if (!comments.isEmpty()) {
e.getFormatCommentsPost().addAll(comments);
comments.clear();
}
}
protected void parseBackboneAttributes(XmlPullParser xpp, Element e) {
parseElementAttributes(xpp, e);
}
protected void parseResourceAttributes(XmlPullParser xpp, Resource r) {
}
private String pathForLocation(XmlPullParser xpp) {
return xpp.getPositionDescription();
}
protected void unknownContent(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException {
if (!isAllowUnknownContent())
throw new FHIRFormatError("Unknown Content "+xpp.getName()+" @ "+pathForLocation(xpp));
// otherwise, read over whatever element this is
int count = 1;
do {
xpp.next();
if (xpp.getEventType() == XmlPullParser.END_TAG)
count--;
if (xpp.getEventType() == XmlPullParser.START_TAG)
count++;
} while (count > 0);
xpp.next();
}
protected XhtmlNode parseXhtml(XmlPullParser xpp) throws XmlPullParserException, IOException, FHIRFormatError {
XhtmlParser prsr = new XhtmlParser();
try {
return prsr.parseHtmlNode(xpp);
} catch (org.hl7.fhir.exceptions.FHIRFormatError e) {
throw new FHIRFormatError(e.getMessage(), e);
}
}
private String parseString(XmlPullParser xpp) throws XmlPullParserException, FHIRFormatError, IOException {
StringBuilder res = new StringBuilder();
next(xpp);
while (xpp.getEventType() == XmlPullParser.TEXT || xpp.getEventType() == XmlPullParser.IGNORABLE_WHITESPACE || xpp.getEventType() == XmlPullParser.ENTITY_REF) {
res.append(xpp.getText());
next(xpp);
}
if (xpp.getEventType() != XmlPullParser.END_TAG)
throw new FHIRFormatError("Bad String Structure - parsed "+res.toString()+" now found "+Integer.toString(xpp.getEventType()));
next(xpp);
return res.length() == 0 ? null : res.toString();
}
private int parseInt(XmlPullParser xpp) throws FHIRFormatError, XmlPullParserException, IOException {
int res = -1;
String textNode = parseString(xpp);
res = java.lang.Integer.parseInt(textNode);
return res;
}
protected DomainResource parseDomainResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException {
next(xpp);
int eventType = nextNoWhitespace(xpp);
if (eventType == XmlPullParser.START_TAG) {
DomainResource dr = (DomainResource) parseResource(xpp);
nextNoWhitespace(xpp);
next(xpp);
return dr;
} else {
unknownContent(xpp);
return null;
}
}
protected Resource parseResourceContained(XmlPullParser xpp) throws IOException, FHIRFormatError, XmlPullParserException {
next(xpp);
int eventType = nextNoWhitespace(xpp);
if (eventType == XmlPullParser.START_TAG) {
Resource r = (Resource) parseResource(xpp);
nextNoWhitespace(xpp);
next(xpp);
return r;
} else {
unknownContent(xpp);
return null;
}
}
public void compose(IXMLWriter writer, Resource resource, boolean htmlPretty) throws IOException {
this.htmlPretty = htmlPretty;
xml = writer;
xml.setDefaultNamespace(FHIR_NS);
if (schemaPath != null) {
xml.setSchemaLocation(FHIR_NS, Utilities.pathURL(schemaPath, resource.fhirType()+".xsd"));
}
composeResource(resource);
}
protected abstract void composeResource(Resource resource) throws IOException ;
protected void composeElementAttributes(Element element) throws IOException {
if (style != OutputStyle.CANONICAL)
for (String comment : element.getFormatCommentsPre())
xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY);
if (element.getId() != null)
xml.attribute("id", element.getId());
}
protected void composeElementClose(Base base) throws IOException {
if (style != OutputStyle.CANONICAL)
for (String comment : base.getFormatCommentsPost())
xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY);
}
protected void composeResourceAttributes(Resource element) throws IOException {
if (style != OutputStyle.CANONICAL)
for (String comment : element.getFormatCommentsPre())
xml.comment(comment, getOutputStyle() == OutputStyle.PRETTY);
}
protected void composeTypeAttributes(DataType type) throws IOException {
composeElementAttributes(type);
}
protected void composeXhtml(String name, XhtmlNode html) throws IOException {
if (!Utilities.noString(xhtmlMessage)) {
xml.enter(XhtmlComposer.XHTML_NS, name);
xml.comment(xhtmlMessage, false);
xml.exit(XhtmlComposer.XHTML_NS, name);
} else {
XhtmlComposer comp = new XhtmlComposer(XhtmlComposer.XML, htmlPretty);
// name is also found in the html and should the same
// ? check that
boolean oldPretty = xml.isPretty();
xml.setPretty(htmlPretty);
if (html.getNodeType() != NodeType.Text && html.getNsDecl() == null)
xml.namespace(XhtmlComposer.XHTML_NS, null);
comp.compose(xml, html);
xml.setPretty(oldPretty);
}
}
protected void composeBaseElements(Base element) throws IOException {
// nothing
}
abstract protected void composeString(String name, StringType value) throws IOException ;
protected void composeString(String name, IIdType value) throws IOException {
composeString(name, new StringType(value.getValue()));
}
protected void composeDomainResource(String name, DomainResource res) throws IOException {
xml.enter(FHIR_NS, name);
composeResource(res.getResourceType().toString(), res);
xml.exit(FHIR_NS, name);
}
protected abstract void composeResource(String name, Resource res) throws IOException ;
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,165 @@
package org.hl7.fhir.r4b.model;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, \
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this \
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, \
this list of conditions and the following disclaimer in the documentation \
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \
POSSIBILITY OF SUCH DAMAGE.
*/
// Generated on Fri, Dec 31, 2021 05:58+1100 for FHIR v4.3.0-snapshot1
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.r4b.model.Enumerations.*;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.r4b.model.Age;
import org.hl7.fhir.r4b.model.Base;
import org.hl7.fhir.r4b.model.Property;
import org.hl7.fhir.r4b.model.Quantity;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Block;
/**
* Base StructureDefinition for Age Type: A duration of time during which an organism (or a process) has existed.
*/
@DatatypeDef(name="Age")
public class Age extends Quantity implements ICompositeType {
private static final long serialVersionUID = 0L;
/**
* Constructor
*/
public Age() {
super();
}
protected void listChildren(List<Property> children) {
super.listChildren(children);
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
@Override
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
switch (hash) {
default: return super.getProperty(hash, name, checkValid);
}
}
@Override
public Base setProperty(int hash, String name, Base value) throws FHIRException {
switch (hash) {
default: return super.setProperty(hash, name, value);
}
}
@Override
public Base setProperty(String name, Base value) throws FHIRException {
return super.setProperty(name, value);
}
@Override
public Base makeProperty(int hash, String name) throws FHIRException {
switch (hash) {
default: return super.makeProperty(hash, name);
}
}
@Override
public String[] getTypesForProperty(int hash, String name) throws FHIRException {
switch (hash) {
default: return super.getTypesForProperty(hash, name);
}
}
@Override
public Base addChild(String name) throws FHIRException {
return super.addChild(name);
}
public String fhirType() {
return "Age";
}
public Age copy() {
Age dst = new Age();
copyValues(dst);
return dst;
}
public void copyValues(Age dst) {
super.copyValues(dst);
}
protected Age typedCopy() {
return copy();
}
@Override
public boolean equalsDeep(Base other_) {
if (!super.equalsDeep(other_))
return false;
if (!(other_ instanceof Age))
return false;
Age o = (Age) other_;
return true;
}
@Override
public boolean equalsShallow(Base other_) {
if (!super.equalsShallow(other_))
return false;
if (!(other_ instanceof Age))
return false;
Age o = (Age) other_;
return true;
}
public boolean isEmpty() {
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty();
}
}

File diff suppressed because one or more lines are too long

View File

@ -0,0 +1,390 @@
package org.hl7.fhir.r4b.model;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, \
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this \
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, \
this list of conditions and the following disclaimer in the documentation \
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \
POSSIBILITY OF SUCH DAMAGE.
*/
// Generated on Fri, Dec 31, 2021 05:58+1100 for FHIR v4.3.0-snapshot1
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.r4b.model.Enumerations.*;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.ICompositeType;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Block;
/**
* Base StructureDefinition for Annotation Type: A text note which also contains information about who made the statement and when.
*/
@DatatypeDef(name="Annotation")
public class Annotation extends DataType implements ICompositeType {
/**
* The individual responsible for making the annotation.
*/
@Child(name = "author", type = {Practitioner.class, Patient.class, RelatedPerson.class, Organization.class, StringType.class}, order=0, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Individual responsible for the annotation", formalDefinition="The individual responsible for making the annotation." )
protected DataType author;
/**
* Indicates when this particular annotation was made.
*/
@Child(name = "time", type = {DateTimeType.class}, order=1, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="When the annotation was made", formalDefinition="Indicates when this particular annotation was made." )
protected DateTimeType time;
/**
* The text of the annotation in markdown format.
*/
@Child(name = "text", type = {MarkdownType.class}, order=2, min=1, max=1, modifier=false, summary=true)
@Description(shortDefinition="The annotation - text content (as markdown)", formalDefinition="The text of the annotation in markdown format." )
protected MarkdownType text;
private static final long serialVersionUID = 1108562171L;
/**
* Constructor
*/
public Annotation() {
super();
}
/**
* Constructor
*/
public Annotation(String text) {
super();
this.setText(text);
}
/**
* @return {@link #author} (The individual responsible for making the annotation.)
*/
public DataType getAuthor() {
return this.author;
}
/**
* @return {@link #author} (The individual responsible for making the annotation.)
*/
public Reference getAuthorReference() throws FHIRException {
if (this.author == null)
this.author = new Reference();
if (!(this.author instanceof Reference))
throw new FHIRException("Type mismatch: the type Reference was expected, but "+this.author.getClass().getName()+" was encountered");
return (Reference) this.author;
}
public boolean hasAuthorReference() {
return this != null && this.author instanceof Reference;
}
/**
* @return {@link #author} (The individual responsible for making the annotation.)
*/
public StringType getAuthorStringType() throws FHIRException {
if (this.author == null)
this.author = new StringType();
if (!(this.author instanceof StringType))
throw new FHIRException("Type mismatch: the type StringType was expected, but "+this.author.getClass().getName()+" was encountered");
return (StringType) this.author;
}
public boolean hasAuthorStringType() {
return this != null && this.author instanceof StringType;
}
public boolean hasAuthor() {
return this.author != null && !this.author.isEmpty();
}
/**
* @param value {@link #author} (The individual responsible for making the annotation.)
*/
public Annotation setAuthor(DataType value) {
if (value != null && !(value instanceof Reference || value instanceof StringType))
throw new Error("Not the right type for Annotation.author[x]: "+value.fhirType());
this.author = value;
return this;
}
/**
* @return {@link #time} (Indicates when this particular annotation was made.). This is the underlying object with id, value and extensions. The accessor "getTime" gives direct access to the value
*/
public DateTimeType getTimeElement() {
if (this.time == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Annotation.time");
else if (Configuration.doAutoCreate())
this.time = new DateTimeType(); // bb
return this.time;
}
public boolean hasTimeElement() {
return this.time != null && !this.time.isEmpty();
}
public boolean hasTime() {
return this.time != null && !this.time.isEmpty();
}
/**
* @param value {@link #time} (Indicates when this particular annotation was made.). This is the underlying object with id, value and extensions. The accessor "getTime" gives direct access to the value
*/
public Annotation setTimeElement(DateTimeType value) {
this.time = value;
return this;
}
/**
* @return Indicates when this particular annotation was made.
*/
public Date getTime() {
return this.time == null ? null : this.time.getValue();
}
/**
* @param value Indicates when this particular annotation was made.
*/
public Annotation setTime(Date value) {
if (value == null)
this.time = null;
else {
if (this.time == null)
this.time = new DateTimeType();
this.time.setValue(value);
}
return this;
}
/**
* @return {@link #text} (The text of the annotation in markdown format.). This is the underlying object with id, value and extensions. The accessor "getText" gives direct access to the value
*/
public MarkdownType getTextElement() {
if (this.text == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Annotation.text");
else if (Configuration.doAutoCreate())
this.text = new MarkdownType(); // bb
return this.text;
}
public boolean hasTextElement() {
return this.text != null && !this.text.isEmpty();
}
public boolean hasText() {
return this.text != null && !this.text.isEmpty();
}
/**
* @param value {@link #text} (The text of the annotation in markdown format.). This is the underlying object with id, value and extensions. The accessor "getText" gives direct access to the value
*/
public Annotation setTextElement(MarkdownType value) {
this.text = value;
return this;
}
/**
* @return The text of the annotation in markdown format.
*/
public String getText() {
return this.text == null ? null : this.text.getValue();
}
/**
* @param value The text of the annotation in markdown format.
*/
public Annotation setText(String value) {
if (this.text == null)
this.text = new MarkdownType();
this.text.setValue(value);
return this;
}
protected void listChildren(List<Property> children) {
super.listChildren(children);
children.add(new Property("author[x]", "Reference(Practitioner|Patient|RelatedPerson|Organization)|string", "The individual responsible for making the annotation.", 0, 1, author));
children.add(new Property("time", "dateTime", "Indicates when this particular annotation was made.", 0, 1, time));
children.add(new Property("text", "markdown", "The text of the annotation in markdown format.", 0, 1, text));
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case 1475597077: /*author[x]*/ return new Property("author[x]", "Reference(Practitioner|Patient|RelatedPerson|Organization)|string", "The individual responsible for making the annotation.", 0, 1, author);
case -1406328437: /*author*/ return new Property("author[x]", "Reference(Practitioner|Patient|RelatedPerson|Organization)|string", "The individual responsible for making the annotation.", 0, 1, author);
case 305515008: /*authorReference*/ return new Property("author[x]", "Reference(Practitioner|Patient|RelatedPerson|Organization)", "The individual responsible for making the annotation.", 0, 1, author);
case 290249084: /*authorString*/ return new Property("author[x]", "string", "The individual responsible for making the annotation.", 0, 1, author);
case 3560141: /*time*/ return new Property("time", "dateTime", "Indicates when this particular annotation was made.", 0, 1, time);
case 3556653: /*text*/ return new Property("text", "markdown", "The text of the annotation in markdown format.", 0, 1, text);
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
@Override
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
switch (hash) {
case -1406328437: /*author*/ return this.author == null ? new Base[0] : new Base[] {this.author}; // DataType
case 3560141: /*time*/ return this.time == null ? new Base[0] : new Base[] {this.time}; // DateTimeType
case 3556653: /*text*/ return this.text == null ? new Base[0] : new Base[] {this.text}; // MarkdownType
default: return super.getProperty(hash, name, checkValid);
}
}
@Override
public Base setProperty(int hash, String name, Base value) throws FHIRException {
switch (hash) {
case -1406328437: // author
this.author = TypeConvertor.castToType(value); // DataType
return value;
case 3560141: // time
this.time = TypeConvertor.castToDateTime(value); // DateTimeType
return value;
case 3556653: // text
this.text = TypeConvertor.castToMarkdown(value); // MarkdownType
return value;
default: return super.setProperty(hash, name, value);
}
}
@Override
public Base setProperty(String name, Base value) throws FHIRException {
if (name.equals("author[x]")) {
this.author = TypeConvertor.castToType(value); // DataType
} else if (name.equals("time")) {
this.time = TypeConvertor.castToDateTime(value); // DateTimeType
} else if (name.equals("text")) {
this.text = TypeConvertor.castToMarkdown(value); // MarkdownType
} else
return super.setProperty(name, value);
return value;
}
@Override
public Base makeProperty(int hash, String name) throws FHIRException {
switch (hash) {
case 1475597077: return getAuthor();
case -1406328437: return getAuthor();
case 3560141: return getTimeElement();
case 3556653: return getTextElement();
default: return super.makeProperty(hash, name);
}
}
@Override
public String[] getTypesForProperty(int hash, String name) throws FHIRException {
switch (hash) {
case -1406328437: /*author*/ return new String[] {"Reference", "string"};
case 3560141: /*time*/ return new String[] {"dateTime"};
case 3556653: /*text*/ return new String[] {"markdown"};
default: return super.getTypesForProperty(hash, name);
}
}
@Override
public Base addChild(String name) throws FHIRException {
if (name.equals("authorReference")) {
this.author = new Reference();
return this.author;
}
else if (name.equals("authorString")) {
this.author = new StringType();
return this.author;
}
else if (name.equals("time")) {
throw new FHIRException("Cannot call addChild on a primitive type Annotation.time");
}
else if (name.equals("text")) {
throw new FHIRException("Cannot call addChild on a primitive type Annotation.text");
}
else
return super.addChild(name);
}
public String fhirType() {
return "Annotation";
}
public Annotation copy() {
Annotation dst = new Annotation();
copyValues(dst);
return dst;
}
public void copyValues(Annotation dst) {
super.copyValues(dst);
dst.author = author == null ? null : author.copy();
dst.time = time == null ? null : time.copy();
dst.text = text == null ? null : text.copy();
}
protected Annotation typedCopy() {
return copy();
}
@Override
public boolean equalsDeep(Base other_) {
if (!super.equalsDeep(other_))
return false;
if (!(other_ instanceof Annotation))
return false;
Annotation o = (Annotation) other_;
return compareDeep(author, o.author, true) && compareDeep(time, o.time, true) && compareDeep(text, o.text, true)
;
}
@Override
public boolean equalsShallow(Base other_) {
if (!super.equalsShallow(other_))
return false;
if (!(other_ instanceof Annotation))
return false;
Annotation o = (Annotation) other_;
return compareValues(time, o.time, true) && compareValues(text, o.text, true);
}
public boolean isEmpty() {
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty(author, time, text);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,884 @@
package org.hl7.fhir.r4b.model;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, \
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this \
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, \
this list of conditions and the following disclaimer in the documentation \
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \
POSSIBILITY OF SUCH DAMAGE.
*/
// Generated on Fri, Dec 31, 2021 05:58+1100 for FHIR v4.3.0-snapshot1
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.r4b.model.Enumerations.*;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.ICompositeType;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.api.annotation.SearchParamDefinition;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Block;
/**
* A reply to an appointment request for a patient and/or practitioner(s), such as a confirmation or rejection.
*/
@ResourceDef(name="AppointmentResponse", profile="http://hl7.org/fhir/StructureDefinition/AppointmentResponse")
public class AppointmentResponse extends DomainResource {
/**
* This records identifiers associated with this appointment response concern that are defined by business processes and/ or used to refer to it when a direct URL reference to the resource itself is not appropriate.
*/
@Child(name = "identifier", type = {Identifier.class}, order=0, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=true)
@Description(shortDefinition="External Ids for this item", formalDefinition="This records identifiers associated with this appointment response concern that are defined by business processes and/ or used to refer to it when a direct URL reference to the resource itself is not appropriate." )
protected List<Identifier> identifier;
/**
* Appointment that this response is replying to.
*/
@Child(name = "appointment", type = {Appointment.class}, order=1, min=1, max=1, modifier=false, summary=true)
@Description(shortDefinition="Appointment this response relates to", formalDefinition="Appointment that this response is replying to." )
protected Reference appointment;
/**
* Date/Time that the appointment is to take place, or requested new start time.
*/
@Child(name = "start", type = {InstantType.class}, order=2, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Time from appointment, or requested new start time", formalDefinition="Date/Time that the appointment is to take place, or requested new start time." )
protected InstantType start;
/**
* This may be either the same as the appointment request to confirm the details of the appointment, or alternately a new time to request a re-negotiation of the end time.
*/
@Child(name = "end", type = {InstantType.class}, order=3, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Time from appointment, or requested new end time", formalDefinition="This may be either the same as the appointment request to confirm the details of the appointment, or alternately a new time to request a re-negotiation of the end time." )
protected InstantType end;
/**
* Role of participant in the appointment.
*/
@Child(name = "participantType", type = {CodeableConcept.class}, order=4, min=0, max=Child.MAX_UNLIMITED, modifier=false, summary=true)
@Description(shortDefinition="Role of participant in the appointment", formalDefinition="Role of participant in the appointment." )
@ca.uhn.fhir.model.api.annotation.Binding(valueSet="http://hl7.org/fhir/ValueSet/encounter-participant-type")
protected List<CodeableConcept> participantType;
/**
* A Person, Location, HealthcareService, or Device that is participating in the appointment.
*/
@Child(name = "actor", type = {Patient.class, Practitioner.class, PractitionerRole.class, RelatedPerson.class, Device.class, HealthcareService.class, Location.class}, order=5, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Person, Location, HealthcareService, or Device", formalDefinition="A Person, Location, HealthcareService, or Device that is participating in the appointment." )
protected Reference actor;
/**
* Participation status of the participant. When the status is declined or tentative if the start/end times are different to the appointment, then these times should be interpreted as a requested time change. When the status is accepted, the times can either be the time of the appointment (as a confirmation of the time) or can be empty.
*/
@Child(name = "participantStatus", type = {CodeType.class}, order=6, min=1, max=1, modifier=true, summary=true)
@Description(shortDefinition="accepted | declined | tentative | needs-action", formalDefinition="Participation status of the participant. When the status is declined or tentative if the start/end times are different to the appointment, then these times should be interpreted as a requested time change. When the status is accepted, the times can either be the time of the appointment (as a confirmation of the time) or can be empty." )
@ca.uhn.fhir.model.api.annotation.Binding(valueSet="http://hl7.org/fhir/ValueSet/participationstatus")
protected Enumeration<ParticipationStatus> participantStatus;
/**
* Additional comments about the appointment.
*/
@Child(name = "comment", type = {StringType.class}, order=7, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Additional comments", formalDefinition="Additional comments about the appointment." )
protected StringType comment;
private static final long serialVersionUID = -1779591264L;
/**
* Constructor
*/
public AppointmentResponse() {
super();
}
/**
* Constructor
*/
public AppointmentResponse(Reference appointment, ParticipationStatus participantStatus) {
super();
this.setAppointment(appointment);
this.setParticipantStatus(participantStatus);
}
/**
* @return {@link #identifier} (This records identifiers associated with this appointment response concern that are defined by business processes and/ or used to refer to it when a direct URL reference to the resource itself is not appropriate.)
*/
public List<Identifier> getIdentifier() {
if (this.identifier == null)
this.identifier = new ArrayList<Identifier>();
return this.identifier;
}
/**
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public AppointmentResponse setIdentifier(List<Identifier> theIdentifier) {
this.identifier = theIdentifier;
return this;
}
public boolean hasIdentifier() {
if (this.identifier == null)
return false;
for (Identifier item : this.identifier)
if (!item.isEmpty())
return true;
return false;
}
public Identifier addIdentifier() { //3
Identifier t = new Identifier();
if (this.identifier == null)
this.identifier = new ArrayList<Identifier>();
this.identifier.add(t);
return t;
}
public AppointmentResponse addIdentifier(Identifier t) { //3
if (t == null)
return this;
if (this.identifier == null)
this.identifier = new ArrayList<Identifier>();
this.identifier.add(t);
return this;
}
/**
* @return The first repetition of repeating field {@link #identifier}, creating it if it does not already exist {3}
*/
public Identifier getIdentifierFirstRep() {
if (getIdentifier().isEmpty()) {
addIdentifier();
}
return getIdentifier().get(0);
}
/**
* @return {@link #appointment} (Appointment that this response is replying to.)
*/
public Reference getAppointment() {
if (this.appointment == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create AppointmentResponse.appointment");
else if (Configuration.doAutoCreate())
this.appointment = new Reference(); // cc
return this.appointment;
}
public boolean hasAppointment() {
return this.appointment != null && !this.appointment.isEmpty();
}
/**
* @param value {@link #appointment} (Appointment that this response is replying to.)
*/
public AppointmentResponse setAppointment(Reference value) {
this.appointment = value;
return this;
}
/**
* @return {@link #start} (Date/Time that the appointment is to take place, or requested new start time.). This is the underlying object with id, value and extensions. The accessor "getStart" gives direct access to the value
*/
public InstantType getStartElement() {
if (this.start == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create AppointmentResponse.start");
else if (Configuration.doAutoCreate())
this.start = new InstantType(); // bb
return this.start;
}
public boolean hasStartElement() {
return this.start != null && !this.start.isEmpty();
}
public boolean hasStart() {
return this.start != null && !this.start.isEmpty();
}
/**
* @param value {@link #start} (Date/Time that the appointment is to take place, or requested new start time.). This is the underlying object with id, value and extensions. The accessor "getStart" gives direct access to the value
*/
public AppointmentResponse setStartElement(InstantType value) {
this.start = value;
return this;
}
/**
* @return Date/Time that the appointment is to take place, or requested new start time.
*/
public Date getStart() {
return this.start == null ? null : this.start.getValue();
}
/**
* @param value Date/Time that the appointment is to take place, or requested new start time.
*/
public AppointmentResponse setStart(Date value) {
if (value == null)
this.start = null;
else {
if (this.start == null)
this.start = new InstantType();
this.start.setValue(value);
}
return this;
}
/**
* @return {@link #end} (This may be either the same as the appointment request to confirm the details of the appointment, or alternately a new time to request a re-negotiation of the end time.). This is the underlying object with id, value and extensions. The accessor "getEnd" gives direct access to the value
*/
public InstantType getEndElement() {
if (this.end == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create AppointmentResponse.end");
else if (Configuration.doAutoCreate())
this.end = new InstantType(); // bb
return this.end;
}
public boolean hasEndElement() {
return this.end != null && !this.end.isEmpty();
}
public boolean hasEnd() {
return this.end != null && !this.end.isEmpty();
}
/**
* @param value {@link #end} (This may be either the same as the appointment request to confirm the details of the appointment, or alternately a new time to request a re-negotiation of the end time.). This is the underlying object with id, value and extensions. The accessor "getEnd" gives direct access to the value
*/
public AppointmentResponse setEndElement(InstantType value) {
this.end = value;
return this;
}
/**
* @return This may be either the same as the appointment request to confirm the details of the appointment, or alternately a new time to request a re-negotiation of the end time.
*/
public Date getEnd() {
return this.end == null ? null : this.end.getValue();
}
/**
* @param value This may be either the same as the appointment request to confirm the details of the appointment, or alternately a new time to request a re-negotiation of the end time.
*/
public AppointmentResponse setEnd(Date value) {
if (value == null)
this.end = null;
else {
if (this.end == null)
this.end = new InstantType();
this.end.setValue(value);
}
return this;
}
/**
* @return {@link #participantType} (Role of participant in the appointment.)
*/
public List<CodeableConcept> getParticipantType() {
if (this.participantType == null)
this.participantType = new ArrayList<CodeableConcept>();
return this.participantType;
}
/**
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public AppointmentResponse setParticipantType(List<CodeableConcept> theParticipantType) {
this.participantType = theParticipantType;
return this;
}
public boolean hasParticipantType() {
if (this.participantType == null)
return false;
for (CodeableConcept item : this.participantType)
if (!item.isEmpty())
return true;
return false;
}
public CodeableConcept addParticipantType() { //3
CodeableConcept t = new CodeableConcept();
if (this.participantType == null)
this.participantType = new ArrayList<CodeableConcept>();
this.participantType.add(t);
return t;
}
public AppointmentResponse addParticipantType(CodeableConcept t) { //3
if (t == null)
return this;
if (this.participantType == null)
this.participantType = new ArrayList<CodeableConcept>();
this.participantType.add(t);
return this;
}
/**
* @return The first repetition of repeating field {@link #participantType}, creating it if it does not already exist {3}
*/
public CodeableConcept getParticipantTypeFirstRep() {
if (getParticipantType().isEmpty()) {
addParticipantType();
}
return getParticipantType().get(0);
}
/**
* @return {@link #actor} (A Person, Location, HealthcareService, or Device that is participating in the appointment.)
*/
public Reference getActor() {
if (this.actor == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create AppointmentResponse.actor");
else if (Configuration.doAutoCreate())
this.actor = new Reference(); // cc
return this.actor;
}
public boolean hasActor() {
return this.actor != null && !this.actor.isEmpty();
}
/**
* @param value {@link #actor} (A Person, Location, HealthcareService, or Device that is participating in the appointment.)
*/
public AppointmentResponse setActor(Reference value) {
this.actor = value;
return this;
}
/**
* @return {@link #participantStatus} (Participation status of the participant. When the status is declined or tentative if the start/end times are different to the appointment, then these times should be interpreted as a requested time change. When the status is accepted, the times can either be the time of the appointment (as a confirmation of the time) or can be empty.). This is the underlying object with id, value and extensions. The accessor "getParticipantStatus" gives direct access to the value
*/
public Enumeration<ParticipationStatus> getParticipantStatusElement() {
if (this.participantStatus == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create AppointmentResponse.participantStatus");
else if (Configuration.doAutoCreate())
this.participantStatus = new Enumeration<ParticipationStatus>(new ParticipationStatusEnumFactory()); // bb
return this.participantStatus;
}
public boolean hasParticipantStatusElement() {
return this.participantStatus != null && !this.participantStatus.isEmpty();
}
public boolean hasParticipantStatus() {
return this.participantStatus != null && !this.participantStatus.isEmpty();
}
/**
* @param value {@link #participantStatus} (Participation status of the participant. When the status is declined or tentative if the start/end times are different to the appointment, then these times should be interpreted as a requested time change. When the status is accepted, the times can either be the time of the appointment (as a confirmation of the time) or can be empty.). This is the underlying object with id, value and extensions. The accessor "getParticipantStatus" gives direct access to the value
*/
public AppointmentResponse setParticipantStatusElement(Enumeration<ParticipationStatus> value) {
this.participantStatus = value;
return this;
}
/**
* @return Participation status of the participant. When the status is declined or tentative if the start/end times are different to the appointment, then these times should be interpreted as a requested time change. When the status is accepted, the times can either be the time of the appointment (as a confirmation of the time) or can be empty.
*/
public ParticipationStatus getParticipantStatus() {
return this.participantStatus == null ? null : this.participantStatus.getValue();
}
/**
* @param value Participation status of the participant. When the status is declined or tentative if the start/end times are different to the appointment, then these times should be interpreted as a requested time change. When the status is accepted, the times can either be the time of the appointment (as a confirmation of the time) or can be empty.
*/
public AppointmentResponse setParticipantStatus(ParticipationStatus value) {
if (this.participantStatus == null)
this.participantStatus = new Enumeration<ParticipationStatus>(new ParticipationStatusEnumFactory());
this.participantStatus.setValue(value);
return this;
}
/**
* @return {@link #comment} (Additional comments about the appointment.). This is the underlying object with id, value and extensions. The accessor "getComment" gives direct access to the value
*/
public StringType getCommentElement() {
if (this.comment == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create AppointmentResponse.comment");
else if (Configuration.doAutoCreate())
this.comment = new StringType(); // bb
return this.comment;
}
public boolean hasCommentElement() {
return this.comment != null && !this.comment.isEmpty();
}
public boolean hasComment() {
return this.comment != null && !this.comment.isEmpty();
}
/**
* @param value {@link #comment} (Additional comments about the appointment.). This is the underlying object with id, value and extensions. The accessor "getComment" gives direct access to the value
*/
public AppointmentResponse setCommentElement(StringType value) {
this.comment = value;
return this;
}
/**
* @return Additional comments about the appointment.
*/
public String getComment() {
return this.comment == null ? null : this.comment.getValue();
}
/**
* @param value Additional comments about the appointment.
*/
public AppointmentResponse setComment(String value) {
if (Utilities.noString(value))
this.comment = null;
else {
if (this.comment == null)
this.comment = new StringType();
this.comment.setValue(value);
}
return this;
}
protected void listChildren(List<Property> children) {
super.listChildren(children);
children.add(new Property("identifier", "Identifier", "This records identifiers associated with this appointment response concern that are defined by business processes and/ or used to refer to it when a direct URL reference to the resource itself is not appropriate.", 0, java.lang.Integer.MAX_VALUE, identifier));
children.add(new Property("appointment", "Reference(Appointment)", "Appointment that this response is replying to.", 0, 1, appointment));
children.add(new Property("start", "instant", "Date/Time that the appointment is to take place, or requested new start time.", 0, 1, start));
children.add(new Property("end", "instant", "This may be either the same as the appointment request to confirm the details of the appointment, or alternately a new time to request a re-negotiation of the end time.", 0, 1, end));
children.add(new Property("participantType", "CodeableConcept", "Role of participant in the appointment.", 0, java.lang.Integer.MAX_VALUE, participantType));
children.add(new Property("actor", "Reference(Patient|Practitioner|PractitionerRole|RelatedPerson|Device|HealthcareService|Location)", "A Person, Location, HealthcareService, or Device that is participating in the appointment.", 0, 1, actor));
children.add(new Property("participantStatus", "code", "Participation status of the participant. When the status is declined or tentative if the start/end times are different to the appointment, then these times should be interpreted as a requested time change. When the status is accepted, the times can either be the time of the appointment (as a confirmation of the time) or can be empty.", 0, 1, participantStatus));
children.add(new Property("comment", "string", "Additional comments about the appointment.", 0, 1, comment));
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case -1618432855: /*identifier*/ return new Property("identifier", "Identifier", "This records identifiers associated with this appointment response concern that are defined by business processes and/ or used to refer to it when a direct URL reference to the resource itself is not appropriate.", 0, java.lang.Integer.MAX_VALUE, identifier);
case -1474995297: /*appointment*/ return new Property("appointment", "Reference(Appointment)", "Appointment that this response is replying to.", 0, 1, appointment);
case 109757538: /*start*/ return new Property("start", "instant", "Date/Time that the appointment is to take place, or requested new start time.", 0, 1, start);
case 100571: /*end*/ return new Property("end", "instant", "This may be either the same as the appointment request to confirm the details of the appointment, or alternately a new time to request a re-negotiation of the end time.", 0, 1, end);
case 841294093: /*participantType*/ return new Property("participantType", "CodeableConcept", "Role of participant in the appointment.", 0, java.lang.Integer.MAX_VALUE, participantType);
case 92645877: /*actor*/ return new Property("actor", "Reference(Patient|Practitioner|PractitionerRole|RelatedPerson|Device|HealthcareService|Location)", "A Person, Location, HealthcareService, or Device that is participating in the appointment.", 0, 1, actor);
case 996096261: /*participantStatus*/ return new Property("participantStatus", "code", "Participation status of the participant. When the status is declined or tentative if the start/end times are different to the appointment, then these times should be interpreted as a requested time change. When the status is accepted, the times can either be the time of the appointment (as a confirmation of the time) or can be empty.", 0, 1, participantStatus);
case 950398559: /*comment*/ return new Property("comment", "string", "Additional comments about the appointment.", 0, 1, comment);
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
@Override
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
switch (hash) {
case -1618432855: /*identifier*/ return this.identifier == null ? new Base[0] : this.identifier.toArray(new Base[this.identifier.size()]); // Identifier
case -1474995297: /*appointment*/ return this.appointment == null ? new Base[0] : new Base[] {this.appointment}; // Reference
case 109757538: /*start*/ return this.start == null ? new Base[0] : new Base[] {this.start}; // InstantType
case 100571: /*end*/ return this.end == null ? new Base[0] : new Base[] {this.end}; // InstantType
case 841294093: /*participantType*/ return this.participantType == null ? new Base[0] : this.participantType.toArray(new Base[this.participantType.size()]); // CodeableConcept
case 92645877: /*actor*/ return this.actor == null ? new Base[0] : new Base[] {this.actor}; // Reference
case 996096261: /*participantStatus*/ return this.participantStatus == null ? new Base[0] : new Base[] {this.participantStatus}; // Enumeration<ParticipationStatus>
case 950398559: /*comment*/ return this.comment == null ? new Base[0] : new Base[] {this.comment}; // StringType
default: return super.getProperty(hash, name, checkValid);
}
}
@Override
public Base setProperty(int hash, String name, Base value) throws FHIRException {
switch (hash) {
case -1618432855: // identifier
this.getIdentifier().add(TypeConvertor.castToIdentifier(value)); // Identifier
return value;
case -1474995297: // appointment
this.appointment = TypeConvertor.castToReference(value); // Reference
return value;
case 109757538: // start
this.start = TypeConvertor.castToInstant(value); // InstantType
return value;
case 100571: // end
this.end = TypeConvertor.castToInstant(value); // InstantType
return value;
case 841294093: // participantType
this.getParticipantType().add(TypeConvertor.castToCodeableConcept(value)); // CodeableConcept
return value;
case 92645877: // actor
this.actor = TypeConvertor.castToReference(value); // Reference
return value;
case 996096261: // participantStatus
value = new ParticipationStatusEnumFactory().fromType(TypeConvertor.castToCode(value));
this.participantStatus = (Enumeration) value; // Enumeration<ParticipationStatus>
return value;
case 950398559: // comment
this.comment = TypeConvertor.castToString(value); // StringType
return value;
default: return super.setProperty(hash, name, value);
}
}
@Override
public Base setProperty(String name, Base value) throws FHIRException {
if (name.equals("identifier")) {
this.getIdentifier().add(TypeConvertor.castToIdentifier(value));
} else if (name.equals("appointment")) {
this.appointment = TypeConvertor.castToReference(value); // Reference
} else if (name.equals("start")) {
this.start = TypeConvertor.castToInstant(value); // InstantType
} else if (name.equals("end")) {
this.end = TypeConvertor.castToInstant(value); // InstantType
} else if (name.equals("participantType")) {
this.getParticipantType().add(TypeConvertor.castToCodeableConcept(value));
} else if (name.equals("actor")) {
this.actor = TypeConvertor.castToReference(value); // Reference
} else if (name.equals("participantStatus")) {
value = new ParticipationStatusEnumFactory().fromType(TypeConvertor.castToCode(value));
this.participantStatus = (Enumeration) value; // Enumeration<ParticipationStatus>
} else if (name.equals("comment")) {
this.comment = TypeConvertor.castToString(value); // StringType
} else
return super.setProperty(name, value);
return value;
}
@Override
public Base makeProperty(int hash, String name) throws FHIRException {
switch (hash) {
case -1618432855: return addIdentifier();
case -1474995297: return getAppointment();
case 109757538: return getStartElement();
case 100571: return getEndElement();
case 841294093: return addParticipantType();
case 92645877: return getActor();
case 996096261: return getParticipantStatusElement();
case 950398559: return getCommentElement();
default: return super.makeProperty(hash, name);
}
}
@Override
public String[] getTypesForProperty(int hash, String name) throws FHIRException {
switch (hash) {
case -1618432855: /*identifier*/ return new String[] {"Identifier"};
case -1474995297: /*appointment*/ return new String[] {"Reference"};
case 109757538: /*start*/ return new String[] {"instant"};
case 100571: /*end*/ return new String[] {"instant"};
case 841294093: /*participantType*/ return new String[] {"CodeableConcept"};
case 92645877: /*actor*/ return new String[] {"Reference"};
case 996096261: /*participantStatus*/ return new String[] {"code"};
case 950398559: /*comment*/ return new String[] {"string"};
default: return super.getTypesForProperty(hash, name);
}
}
@Override
public Base addChild(String name) throws FHIRException {
if (name.equals("identifier")) {
return addIdentifier();
}
else if (name.equals("appointment")) {
this.appointment = new Reference();
return this.appointment;
}
else if (name.equals("start")) {
throw new FHIRException("Cannot call addChild on a primitive type AppointmentResponse.start");
}
else if (name.equals("end")) {
throw new FHIRException("Cannot call addChild on a primitive type AppointmentResponse.end");
}
else if (name.equals("participantType")) {
return addParticipantType();
}
else if (name.equals("actor")) {
this.actor = new Reference();
return this.actor;
}
else if (name.equals("participantStatus")) {
throw new FHIRException("Cannot call addChild on a primitive type AppointmentResponse.participantStatus");
}
else if (name.equals("comment")) {
throw new FHIRException("Cannot call addChild on a primitive type AppointmentResponse.comment");
}
else
return super.addChild(name);
}
public String fhirType() {
return "AppointmentResponse";
}
public AppointmentResponse copy() {
AppointmentResponse dst = new AppointmentResponse();
copyValues(dst);
return dst;
}
public void copyValues(AppointmentResponse dst) {
super.copyValues(dst);
if (identifier != null) {
dst.identifier = new ArrayList<Identifier>();
for (Identifier i : identifier)
dst.identifier.add(i.copy());
};
dst.appointment = appointment == null ? null : appointment.copy();
dst.start = start == null ? null : start.copy();
dst.end = end == null ? null : end.copy();
if (participantType != null) {
dst.participantType = new ArrayList<CodeableConcept>();
for (CodeableConcept i : participantType)
dst.participantType.add(i.copy());
};
dst.actor = actor == null ? null : actor.copy();
dst.participantStatus = participantStatus == null ? null : participantStatus.copy();
dst.comment = comment == null ? null : comment.copy();
}
protected AppointmentResponse typedCopy() {
return copy();
}
@Override
public boolean equalsDeep(Base other_) {
if (!super.equalsDeep(other_))
return false;
if (!(other_ instanceof AppointmentResponse))
return false;
AppointmentResponse o = (AppointmentResponse) other_;
return compareDeep(identifier, o.identifier, true) && compareDeep(appointment, o.appointment, true)
&& compareDeep(start, o.start, true) && compareDeep(end, o.end, true) && compareDeep(participantType, o.participantType, true)
&& compareDeep(actor, o.actor, true) && compareDeep(participantStatus, o.participantStatus, true)
&& compareDeep(comment, o.comment, true);
}
@Override
public boolean equalsShallow(Base other_) {
if (!super.equalsShallow(other_))
return false;
if (!(other_ instanceof AppointmentResponse))
return false;
AppointmentResponse o = (AppointmentResponse) other_;
return compareValues(start, o.start, true) && compareValues(end, o.end, true) && compareValues(participantStatus, o.participantStatus, true)
&& compareValues(comment, o.comment, true);
}
public boolean isEmpty() {
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty(identifier, appointment, start
, end, participantType, actor, participantStatus, comment);
}
@Override
public ResourceType getResourceType() {
return ResourceType.AppointmentResponse;
}
/**
* Search parameter: <b>actor</b>
* <p>
* Description: <b>The Person, Location/HealthcareService or Device that this appointment response replies for</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.actor</b><br>
* </p>
*/
@SearchParamDefinition(name="actor", path="AppointmentResponse.actor", description="The Person, Location/HealthcareService or Device that this appointment response replies for", type="reference", providesMembershipIn={ @ca.uhn.fhir.model.api.annotation.Compartment(name="Base FHIR compartment definition for Device"), @ca.uhn.fhir.model.api.annotation.Compartment(name="Base FHIR compartment definition for Patient"), @ca.uhn.fhir.model.api.annotation.Compartment(name="Base FHIR compartment definition for Practitioner"), @ca.uhn.fhir.model.api.annotation.Compartment(name="Base FHIR compartment definition for RelatedPerson") }, target={Device.class, HealthcareService.class, Location.class, Patient.class, Practitioner.class, PractitionerRole.class, RelatedPerson.class } )
public static final String SP_ACTOR = "actor";
/**
* <b>Fluent Client</b> search parameter constant for <b>actor</b>
* <p>
* Description: <b>The Person, Location/HealthcareService or Device that this appointment response replies for</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.actor</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.ReferenceClientParam ACTOR = new ca.uhn.fhir.rest.gclient.ReferenceClientParam(SP_ACTOR);
/**
* Constant for fluent queries to be used to add include statements. Specifies
* the path value of "<b>AppointmentResponse:actor</b>".
*/
public static final ca.uhn.fhir.model.api.Include INCLUDE_ACTOR = new ca.uhn.fhir.model.api.Include("AppointmentResponse:actor").toLocked();
/**
* Search parameter: <b>appointment</b>
* <p>
* Description: <b>The appointment that the response is attached to</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.appointment</b><br>
* </p>
*/
@SearchParamDefinition(name="appointment", path="AppointmentResponse.appointment", description="The appointment that the response is attached to", type="reference", target={Appointment.class } )
public static final String SP_APPOINTMENT = "appointment";
/**
* <b>Fluent Client</b> search parameter constant for <b>appointment</b>
* <p>
* Description: <b>The appointment that the response is attached to</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.appointment</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.ReferenceClientParam APPOINTMENT = new ca.uhn.fhir.rest.gclient.ReferenceClientParam(SP_APPOINTMENT);
/**
* Constant for fluent queries to be used to add include statements. Specifies
* the path value of "<b>AppointmentResponse:appointment</b>".
*/
public static final ca.uhn.fhir.model.api.Include INCLUDE_APPOINTMENT = new ca.uhn.fhir.model.api.Include("AppointmentResponse:appointment").toLocked();
/**
* Search parameter: <b>identifier</b>
* <p>
* Description: <b>An Identifier in this appointment response</b><br>
* Type: <b>token</b><br>
* Path: <b>AppointmentResponse.identifier</b><br>
* </p>
*/
@SearchParamDefinition(name="identifier", path="AppointmentResponse.identifier", description="An Identifier in this appointment response", type="token" )
public static final String SP_IDENTIFIER = "identifier";
/**
* <b>Fluent Client</b> search parameter constant for <b>identifier</b>
* <p>
* Description: <b>An Identifier in this appointment response</b><br>
* Type: <b>token</b><br>
* Path: <b>AppointmentResponse.identifier</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.TokenClientParam IDENTIFIER = new ca.uhn.fhir.rest.gclient.TokenClientParam(SP_IDENTIFIER);
/**
* Search parameter: <b>location</b>
* <p>
* Description: <b>This Response is for this Location</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.actor.where(resolve() is Location)</b><br>
* </p>
*/
@SearchParamDefinition(name="location", path="AppointmentResponse.actor.where(resolve() is Location)", description="This Response is for this Location", type="reference", target={Device.class, HealthcareService.class, Location.class, Patient.class, Practitioner.class, PractitionerRole.class, RelatedPerson.class } )
public static final String SP_LOCATION = "location";
/**
* <b>Fluent Client</b> search parameter constant for <b>location</b>
* <p>
* Description: <b>This Response is for this Location</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.actor.where(resolve() is Location)</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.ReferenceClientParam LOCATION = new ca.uhn.fhir.rest.gclient.ReferenceClientParam(SP_LOCATION);
/**
* Constant for fluent queries to be used to add include statements. Specifies
* the path value of "<b>AppointmentResponse:location</b>".
*/
public static final ca.uhn.fhir.model.api.Include INCLUDE_LOCATION = new ca.uhn.fhir.model.api.Include("AppointmentResponse:location").toLocked();
/**
* Search parameter: <b>part-status</b>
* <p>
* Description: <b>The participants acceptance status for this appointment</b><br>
* Type: <b>token</b><br>
* Path: <b>AppointmentResponse.participantStatus</b><br>
* </p>
*/
@SearchParamDefinition(name="part-status", path="AppointmentResponse.participantStatus", description="The participants acceptance status for this appointment", type="token" )
public static final String SP_PART_STATUS = "part-status";
/**
* <b>Fluent Client</b> search parameter constant for <b>part-status</b>
* <p>
* Description: <b>The participants acceptance status for this appointment</b><br>
* Type: <b>token</b><br>
* Path: <b>AppointmentResponse.participantStatus</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.TokenClientParam PART_STATUS = new ca.uhn.fhir.rest.gclient.TokenClientParam(SP_PART_STATUS);
/**
* Search parameter: <b>patient</b>
* <p>
* Description: <b>This Response is for this Patient</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.actor.where(resolve() is Patient)</b><br>
* </p>
*/
@SearchParamDefinition(name="patient", path="AppointmentResponse.actor.where(resolve() is Patient)", description="This Response is for this Patient", type="reference", target={Device.class, HealthcareService.class, Location.class, Patient.class, Practitioner.class, PractitionerRole.class, RelatedPerson.class } )
public static final String SP_PATIENT = "patient";
/**
* <b>Fluent Client</b> search parameter constant for <b>patient</b>
* <p>
* Description: <b>This Response is for this Patient</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.actor.where(resolve() is Patient)</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.ReferenceClientParam PATIENT = new ca.uhn.fhir.rest.gclient.ReferenceClientParam(SP_PATIENT);
/**
* Constant for fluent queries to be used to add include statements. Specifies
* the path value of "<b>AppointmentResponse:patient</b>".
*/
public static final ca.uhn.fhir.model.api.Include INCLUDE_PATIENT = new ca.uhn.fhir.model.api.Include("AppointmentResponse:patient").toLocked();
/**
* Search parameter: <b>practitioner</b>
* <p>
* Description: <b>This Response is for this Practitioner</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.actor.where(resolve() is Practitioner)</b><br>
* </p>
*/
@SearchParamDefinition(name="practitioner", path="AppointmentResponse.actor.where(resolve() is Practitioner)", description="This Response is for this Practitioner", type="reference", target={Device.class, HealthcareService.class, Location.class, Patient.class, Practitioner.class, PractitionerRole.class, RelatedPerson.class } )
public static final String SP_PRACTITIONER = "practitioner";
/**
* <b>Fluent Client</b> search parameter constant for <b>practitioner</b>
* <p>
* Description: <b>This Response is for this Practitioner</b><br>
* Type: <b>reference</b><br>
* Path: <b>AppointmentResponse.actor.where(resolve() is Practitioner)</b><br>
* </p>
*/
public static final ca.uhn.fhir.rest.gclient.ReferenceClientParam PRACTITIONER = new ca.uhn.fhir.rest.gclient.ReferenceClientParam(SP_PRACTITIONER);
/**
* Constant for fluent queries to be used to add include statements. Specifies
* the path value of "<b>AppointmentResponse:practitioner</b>".
*/
public static final ca.uhn.fhir.model.api.Include INCLUDE_PRACTITIONER = new ca.uhn.fhir.model.api.Include("AppointmentResponse:practitioner").toLocked();
}

View File

@ -0,0 +1,730 @@
package org.hl7.fhir.r4b.model;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, \
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this \
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, \
this list of conditions and the following disclaimer in the documentation \
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \
POSSIBILITY OF SUCH DAMAGE.
*/
// Generated on Fri, Dec 31, 2021 05:58+1100 for FHIR v4.3.0-snapshot1
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.r4b.model.Enumerations.*;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.ICompositeType;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Block;
/**
* Base StructureDefinition for Attachment Type: For referring to data content defined in other formats.
*/
@DatatypeDef(name="Attachment")
public class Attachment extends DataType implements ICompositeType {
/**
* Identifies the type of the data in the attachment and allows a method to be chosen to interpret or render the data. Includes mime type parameters such as charset where appropriate.
*/
@Child(name = "contentType", type = {CodeType.class}, order=0, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Mime type of the content, with charset etc.", formalDefinition="Identifies the type of the data in the attachment and allows a method to be chosen to interpret or render the data. Includes mime type parameters such as charset where appropriate." )
@ca.uhn.fhir.model.api.annotation.Binding(valueSet="http://hl7.org/fhir/ValueSet/mimetypes")
protected CodeType contentType;
/**
* The human language of the content. The value can be any valid value according to BCP 47.
*/
@Child(name = "language", type = {CodeType.class}, order=1, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Human language of the content (BCP-47)", formalDefinition="The human language of the content. The value can be any valid value according to BCP 47." )
@ca.uhn.fhir.model.api.annotation.Binding(valueSet="http://hl7.org/fhir/ValueSet/languages")
protected CodeType language;
/**
* The actual data of the attachment - a sequence of bytes, base64 encoded.
*/
@Child(name = "data", type = {Base64BinaryType.class}, order=2, min=0, max=1, modifier=false, summary=false)
@Description(shortDefinition="Data inline, base64ed", formalDefinition="The actual data of the attachment - a sequence of bytes, base64 encoded." )
protected Base64BinaryType data;
/**
* A location where the data can be accessed.
*/
@Child(name = "url", type = {UrlType.class}, order=3, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Uri where the data can be found", formalDefinition="A location where the data can be accessed." )
protected UrlType url;
/**
* The number of bytes of data that make up this attachment (before base64 encoding, if that is done).
*/
@Child(name = "size", type = {UnsignedIntType.class}, order=4, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Number of bytes of content (if url provided)", formalDefinition="The number of bytes of data that make up this attachment (before base64 encoding, if that is done)." )
protected UnsignedIntType size;
/**
* The calculated hash of the data using SHA-1. Represented using base64.
*/
@Child(name = "hash", type = {Base64BinaryType.class}, order=5, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Hash of the data (sha-1, base64ed)", formalDefinition="The calculated hash of the data using SHA-1. Represented using base64." )
protected Base64BinaryType hash;
/**
* A label or set of text to display in place of the data.
*/
@Child(name = "title", type = {StringType.class}, order=6, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Label to display in place of the data", formalDefinition="A label or set of text to display in place of the data." )
protected StringType title;
/**
* The date that the attachment was first created.
*/
@Child(name = "creation", type = {DateTimeType.class}, order=7, min=0, max=1, modifier=false, summary=true)
@Description(shortDefinition="Date attachment was first created", formalDefinition="The date that the attachment was first created." )
protected DateTimeType creation;
private static final long serialVersionUID = -564352571L;
/**
* Constructor
*/
public Attachment() {
super();
}
/**
* @return {@link #contentType} (Identifies the type of the data in the attachment and allows a method to be chosen to interpret or render the data. Includes mime type parameters such as charset where appropriate.). This is the underlying object with id, value and extensions. The accessor "getContentType" gives direct access to the value
*/
public CodeType getContentTypeElement() {
if (this.contentType == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Attachment.contentType");
else if (Configuration.doAutoCreate())
this.contentType = new CodeType(); // bb
return this.contentType;
}
public boolean hasContentTypeElement() {
return this.contentType != null && !this.contentType.isEmpty();
}
public boolean hasContentType() {
return this.contentType != null && !this.contentType.isEmpty();
}
/**
* @param value {@link #contentType} (Identifies the type of the data in the attachment and allows a method to be chosen to interpret or render the data. Includes mime type parameters such as charset where appropriate.). This is the underlying object with id, value and extensions. The accessor "getContentType" gives direct access to the value
*/
public Attachment setContentTypeElement(CodeType value) {
this.contentType = value;
return this;
}
/**
* @return Identifies the type of the data in the attachment and allows a method to be chosen to interpret or render the data. Includes mime type parameters such as charset where appropriate.
*/
public String getContentType() {
return this.contentType == null ? null : this.contentType.getValue();
}
/**
* @param value Identifies the type of the data in the attachment and allows a method to be chosen to interpret or render the data. Includes mime type parameters such as charset where appropriate.
*/
public Attachment setContentType(String value) {
if (Utilities.noString(value))
this.contentType = null;
else {
if (this.contentType == null)
this.contentType = new CodeType();
this.contentType.setValue(value);
}
return this;
}
/**
* @return {@link #language} (The human language of the content. The value can be any valid value according to BCP 47.). This is the underlying object with id, value and extensions. The accessor "getLanguage" gives direct access to the value
*/
public CodeType getLanguageElement() {
if (this.language == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Attachment.language");
else if (Configuration.doAutoCreate())
this.language = new CodeType(); // bb
return this.language;
}
public boolean hasLanguageElement() {
return this.language != null && !this.language.isEmpty();
}
public boolean hasLanguage() {
return this.language != null && !this.language.isEmpty();
}
/**
* @param value {@link #language} (The human language of the content. The value can be any valid value according to BCP 47.). This is the underlying object with id, value and extensions. The accessor "getLanguage" gives direct access to the value
*/
public Attachment setLanguageElement(CodeType value) {
this.language = value;
return this;
}
/**
* @return The human language of the content. The value can be any valid value according to BCP 47.
*/
public String getLanguage() {
return this.language == null ? null : this.language.getValue();
}
/**
* @param value The human language of the content. The value can be any valid value according to BCP 47.
*/
public Attachment setLanguage(String value) {
if (Utilities.noString(value))
this.language = null;
else {
if (this.language == null)
this.language = new CodeType();
this.language.setValue(value);
}
return this;
}
/**
* @return {@link #data} (The actual data of the attachment - a sequence of bytes, base64 encoded.). This is the underlying object with id, value and extensions. The accessor "getData" gives direct access to the value
*/
public Base64BinaryType getDataElement() {
if (this.data == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Attachment.data");
else if (Configuration.doAutoCreate())
this.data = new Base64BinaryType(); // bb
return this.data;
}
public boolean hasDataElement() {
return this.data != null && !this.data.isEmpty();
}
public boolean hasData() {
return this.data != null && !this.data.isEmpty();
}
/**
* @param value {@link #data} (The actual data of the attachment - a sequence of bytes, base64 encoded.). This is the underlying object with id, value and extensions. The accessor "getData" gives direct access to the value
*/
public Attachment setDataElement(Base64BinaryType value) {
this.data = value;
return this;
}
/**
* @return The actual data of the attachment - a sequence of bytes, base64 encoded.
*/
public byte[] getData() {
return this.data == null ? null : this.data.getValue();
}
/**
* @param value The actual data of the attachment - a sequence of bytes, base64 encoded.
*/
public Attachment setData(byte[] value) {
if (value == null)
this.data = null;
else {
if (this.data == null)
this.data = new Base64BinaryType();
this.data.setValue(value);
}
return this;
}
/**
* @return {@link #url} (A location where the data can be accessed.). This is the underlying object with id, value and extensions. The accessor "getUrl" gives direct access to the value
*/
public UrlType getUrlElement() {
if (this.url == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Attachment.url");
else if (Configuration.doAutoCreate())
this.url = new UrlType(); // bb
return this.url;
}
public boolean hasUrlElement() {
return this.url != null && !this.url.isEmpty();
}
public boolean hasUrl() {
return this.url != null && !this.url.isEmpty();
}
/**
* @param value {@link #url} (A location where the data can be accessed.). This is the underlying object with id, value and extensions. The accessor "getUrl" gives direct access to the value
*/
public Attachment setUrlElement(UrlType value) {
this.url = value;
return this;
}
/**
* @return A location where the data can be accessed.
*/
public String getUrl() {
return this.url == null ? null : this.url.getValue();
}
/**
* @param value A location where the data can be accessed.
*/
public Attachment setUrl(String value) {
if (Utilities.noString(value))
this.url = null;
else {
if (this.url == null)
this.url = new UrlType();
this.url.setValue(value);
}
return this;
}
/**
* @return {@link #size} (The number of bytes of data that make up this attachment (before base64 encoding, if that is done).). This is the underlying object with id, value and extensions. The accessor "getSize" gives direct access to the value
*/
public UnsignedIntType getSizeElement() {
if (this.size == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Attachment.size");
else if (Configuration.doAutoCreate())
this.size = new UnsignedIntType(); // bb
return this.size;
}
public boolean hasSizeElement() {
return this.size != null && !this.size.isEmpty();
}
public boolean hasSize() {
return this.size != null && !this.size.isEmpty();
}
/**
* @param value {@link #size} (The number of bytes of data that make up this attachment (before base64 encoding, if that is done).). This is the underlying object with id, value and extensions. The accessor "getSize" gives direct access to the value
*/
public Attachment setSizeElement(UnsignedIntType value) {
this.size = value;
return this;
}
/**
* @return The number of bytes of data that make up this attachment (before base64 encoding, if that is done).
*/
public int getSize() {
return this.size == null || this.size.isEmpty() ? 0 : this.size.getValue();
}
/**
* @param value The number of bytes of data that make up this attachment (before base64 encoding, if that is done).
*/
public Attachment setSize(int value) {
if (this.size == null)
this.size = new UnsignedIntType();
this.size.setValue(value);
return this;
}
/**
* @return {@link #hash} (The calculated hash of the data using SHA-1. Represented using base64.). This is the underlying object with id, value and extensions. The accessor "getHash" gives direct access to the value
*/
public Base64BinaryType getHashElement() {
if (this.hash == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Attachment.hash");
else if (Configuration.doAutoCreate())
this.hash = new Base64BinaryType(); // bb
return this.hash;
}
public boolean hasHashElement() {
return this.hash != null && !this.hash.isEmpty();
}
public boolean hasHash() {
return this.hash != null && !this.hash.isEmpty();
}
/**
* @param value {@link #hash} (The calculated hash of the data using SHA-1. Represented using base64.). This is the underlying object with id, value and extensions. The accessor "getHash" gives direct access to the value
*/
public Attachment setHashElement(Base64BinaryType value) {
this.hash = value;
return this;
}
/**
* @return The calculated hash of the data using SHA-1. Represented using base64.
*/
public byte[] getHash() {
return this.hash == null ? null : this.hash.getValue();
}
/**
* @param value The calculated hash of the data using SHA-1. Represented using base64.
*/
public Attachment setHash(byte[] value) {
if (value == null)
this.hash = null;
else {
if (this.hash == null)
this.hash = new Base64BinaryType();
this.hash.setValue(value);
}
return this;
}
/**
* @return {@link #title} (A label or set of text to display in place of the data.). This is the underlying object with id, value and extensions. The accessor "getTitle" gives direct access to the value
*/
public StringType getTitleElement() {
if (this.title == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Attachment.title");
else if (Configuration.doAutoCreate())
this.title = new StringType(); // bb
return this.title;
}
public boolean hasTitleElement() {
return this.title != null && !this.title.isEmpty();
}
public boolean hasTitle() {
return this.title != null && !this.title.isEmpty();
}
/**
* @param value {@link #title} (A label or set of text to display in place of the data.). This is the underlying object with id, value and extensions. The accessor "getTitle" gives direct access to the value
*/
public Attachment setTitleElement(StringType value) {
this.title = value;
return this;
}
/**
* @return A label or set of text to display in place of the data.
*/
public String getTitle() {
return this.title == null ? null : this.title.getValue();
}
/**
* @param value A label or set of text to display in place of the data.
*/
public Attachment setTitle(String value) {
if (Utilities.noString(value))
this.title = null;
else {
if (this.title == null)
this.title = new StringType();
this.title.setValue(value);
}
return this;
}
/**
* @return {@link #creation} (The date that the attachment was first created.). This is the underlying object with id, value and extensions. The accessor "getCreation" gives direct access to the value
*/
public DateTimeType getCreationElement() {
if (this.creation == null)
if (Configuration.errorOnAutoCreate())
throw new Error("Attempt to auto-create Attachment.creation");
else if (Configuration.doAutoCreate())
this.creation = new DateTimeType(); // bb
return this.creation;
}
public boolean hasCreationElement() {
return this.creation != null && !this.creation.isEmpty();
}
public boolean hasCreation() {
return this.creation != null && !this.creation.isEmpty();
}
/**
* @param value {@link #creation} (The date that the attachment was first created.). This is the underlying object with id, value and extensions. The accessor "getCreation" gives direct access to the value
*/
public Attachment setCreationElement(DateTimeType value) {
this.creation = value;
return this;
}
/**
* @return The date that the attachment was first created.
*/
public Date getCreation() {
return this.creation == null ? null : this.creation.getValue();
}
/**
* @param value The date that the attachment was first created.
*/
public Attachment setCreation(Date value) {
if (value == null)
this.creation = null;
else {
if (this.creation == null)
this.creation = new DateTimeType();
this.creation.setValue(value);
}
return this;
}
protected void listChildren(List<Property> children) {
super.listChildren(children);
children.add(new Property("contentType", "code", "Identifies the type of the data in the attachment and allows a method to be chosen to interpret or render the data. Includes mime type parameters such as charset where appropriate.", 0, 1, contentType));
children.add(new Property("language", "code", "The human language of the content. The value can be any valid value according to BCP 47.", 0, 1, language));
children.add(new Property("data", "base64Binary", "The actual data of the attachment - a sequence of bytes, base64 encoded.", 0, 1, data));
children.add(new Property("url", "url", "A location where the data can be accessed.", 0, 1, url));
children.add(new Property("size", "unsignedInt", "The number of bytes of data that make up this attachment (before base64 encoding, if that is done).", 0, 1, size));
children.add(new Property("hash", "base64Binary", "The calculated hash of the data using SHA-1. Represented using base64.", 0, 1, hash));
children.add(new Property("title", "string", "A label or set of text to display in place of the data.", 0, 1, title));
children.add(new Property("creation", "dateTime", "The date that the attachment was first created.", 0, 1, creation));
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case -389131437: /*contentType*/ return new Property("contentType", "code", "Identifies the type of the data in the attachment and allows a method to be chosen to interpret or render the data. Includes mime type parameters such as charset where appropriate.", 0, 1, contentType);
case -1613589672: /*language*/ return new Property("language", "code", "The human language of the content. The value can be any valid value according to BCP 47.", 0, 1, language);
case 3076010: /*data*/ return new Property("data", "base64Binary", "The actual data of the attachment - a sequence of bytes, base64 encoded.", 0, 1, data);
case 116079: /*url*/ return new Property("url", "url", "A location where the data can be accessed.", 0, 1, url);
case 3530753: /*size*/ return new Property("size", "unsignedInt", "The number of bytes of data that make up this attachment (before base64 encoding, if that is done).", 0, 1, size);
case 3195150: /*hash*/ return new Property("hash", "base64Binary", "The calculated hash of the data using SHA-1. Represented using base64.", 0, 1, hash);
case 110371416: /*title*/ return new Property("title", "string", "A label or set of text to display in place of the data.", 0, 1, title);
case 1820421855: /*creation*/ return new Property("creation", "dateTime", "The date that the attachment was first created.", 0, 1, creation);
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
@Override
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
switch (hash) {
case -389131437: /*contentType*/ return this.contentType == null ? new Base[0] : new Base[] {this.contentType}; // CodeType
case -1613589672: /*language*/ return this.language == null ? new Base[0] : new Base[] {this.language}; // CodeType
case 3076010: /*data*/ return this.data == null ? new Base[0] : new Base[] {this.data}; // Base64BinaryType
case 116079: /*url*/ return this.url == null ? new Base[0] : new Base[] {this.url}; // UrlType
case 3530753: /*size*/ return this.size == null ? new Base[0] : new Base[] {this.size}; // UnsignedIntType
case 3195150: /*hash*/ return this.hash == null ? new Base[0] : new Base[] {this.hash}; // Base64BinaryType
case 110371416: /*title*/ return this.title == null ? new Base[0] : new Base[] {this.title}; // StringType
case 1820421855: /*creation*/ return this.creation == null ? new Base[0] : new Base[] {this.creation}; // DateTimeType
default: return super.getProperty(hash, name, checkValid);
}
}
@Override
public Base setProperty(int hash, String name, Base value) throws FHIRException {
switch (hash) {
case -389131437: // contentType
this.contentType = TypeConvertor.castToCode(value); // CodeType
return value;
case -1613589672: // language
this.language = TypeConvertor.castToCode(value); // CodeType
return value;
case 3076010: // data
this.data = TypeConvertor.castToBase64Binary(value); // Base64BinaryType
return value;
case 116079: // url
this.url = TypeConvertor.castToUrl(value); // UrlType
return value;
case 3530753: // size
this.size = TypeConvertor.castToUnsignedInt(value); // UnsignedIntType
return value;
case 3195150: // hash
this.hash = TypeConvertor.castToBase64Binary(value); // Base64BinaryType
return value;
case 110371416: // title
this.title = TypeConvertor.castToString(value); // StringType
return value;
case 1820421855: // creation
this.creation = TypeConvertor.castToDateTime(value); // DateTimeType
return value;
default: return super.setProperty(hash, name, value);
}
}
@Override
public Base setProperty(String name, Base value) throws FHIRException {
if (name.equals("contentType")) {
this.contentType = TypeConvertor.castToCode(value); // CodeType
} else if (name.equals("language")) {
this.language = TypeConvertor.castToCode(value); // CodeType
} else if (name.equals("data")) {
this.data = TypeConvertor.castToBase64Binary(value); // Base64BinaryType
} else if (name.equals("url")) {
this.url = TypeConvertor.castToUrl(value); // UrlType
} else if (name.equals("size")) {
this.size = TypeConvertor.castToUnsignedInt(value); // UnsignedIntType
} else if (name.equals("hash")) {
this.hash = TypeConvertor.castToBase64Binary(value); // Base64BinaryType
} else if (name.equals("title")) {
this.title = TypeConvertor.castToString(value); // StringType
} else if (name.equals("creation")) {
this.creation = TypeConvertor.castToDateTime(value); // DateTimeType
} else
return super.setProperty(name, value);
return value;
}
@Override
public Base makeProperty(int hash, String name) throws FHIRException {
switch (hash) {
case -389131437: return getContentTypeElement();
case -1613589672: return getLanguageElement();
case 3076010: return getDataElement();
case 116079: return getUrlElement();
case 3530753: return getSizeElement();
case 3195150: return getHashElement();
case 110371416: return getTitleElement();
case 1820421855: return getCreationElement();
default: return super.makeProperty(hash, name);
}
}
@Override
public String[] getTypesForProperty(int hash, String name) throws FHIRException {
switch (hash) {
case -389131437: /*contentType*/ return new String[] {"code"};
case -1613589672: /*language*/ return new String[] {"code"};
case 3076010: /*data*/ return new String[] {"base64Binary"};
case 116079: /*url*/ return new String[] {"url"};
case 3530753: /*size*/ return new String[] {"unsignedInt"};
case 3195150: /*hash*/ return new String[] {"base64Binary"};
case 110371416: /*title*/ return new String[] {"string"};
case 1820421855: /*creation*/ return new String[] {"dateTime"};
default: return super.getTypesForProperty(hash, name);
}
}
@Override
public Base addChild(String name) throws FHIRException {
if (name.equals("contentType")) {
throw new FHIRException("Cannot call addChild on a primitive type Attachment.contentType");
}
else if (name.equals("language")) {
throw new FHIRException("Cannot call addChild on a primitive type Attachment.language");
}
else if (name.equals("data")) {
throw new FHIRException("Cannot call addChild on a primitive type Attachment.data");
}
else if (name.equals("url")) {
throw new FHIRException("Cannot call addChild on a primitive type Attachment.url");
}
else if (name.equals("size")) {
throw new FHIRException("Cannot call addChild on a primitive type Attachment.size");
}
else if (name.equals("hash")) {
throw new FHIRException("Cannot call addChild on a primitive type Attachment.hash");
}
else if (name.equals("title")) {
throw new FHIRException("Cannot call addChild on a primitive type Attachment.title");
}
else if (name.equals("creation")) {
throw new FHIRException("Cannot call addChild on a primitive type Attachment.creation");
}
else
return super.addChild(name);
}
public String fhirType() {
return "Attachment";
}
public Attachment copy() {
Attachment dst = new Attachment();
copyValues(dst);
return dst;
}
public void copyValues(Attachment dst) {
super.copyValues(dst);
dst.contentType = contentType == null ? null : contentType.copy();
dst.language = language == null ? null : language.copy();
dst.data = data == null ? null : data.copy();
dst.url = url == null ? null : url.copy();
dst.size = size == null ? null : size.copy();
dst.hash = hash == null ? null : hash.copy();
dst.title = title == null ? null : title.copy();
dst.creation = creation == null ? null : creation.copy();
}
protected Attachment typedCopy() {
return copy();
}
@Override
public boolean equalsDeep(Base other_) {
if (!super.equalsDeep(other_))
return false;
if (!(other_ instanceof Attachment))
return false;
Attachment o = (Attachment) other_;
return compareDeep(contentType, o.contentType, true) && compareDeep(language, o.language, true)
&& compareDeep(data, o.data, true) && compareDeep(url, o.url, true) && compareDeep(size, o.size, true)
&& compareDeep(hash, o.hash, true) && compareDeep(title, o.title, true) && compareDeep(creation, o.creation, true)
;
}
@Override
public boolean equalsShallow(Base other_) {
if (!super.equalsShallow(other_))
return false;
if (!(other_ instanceof Attachment))
return false;
Attachment o = (Attachment) other_;
return compareValues(contentType, o.contentType, true) && compareValues(language, o.language, true)
&& compareValues(data, o.data, true) && compareValues(url, o.url, true) && compareValues(size, o.size, true)
&& compareValues(hash, o.hash, true) && compareValues(title, o.title, true) && compareValues(creation, o.creation, true)
;
}
public boolean isEmpty() {
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty(contentType, language, data
, url, size, hash, title, creation);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,310 @@
package org.hl7.fhir.r4b.model;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, \
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this \
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, \
this list of conditions and the following disclaimer in the documentation \
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \
POSSIBILITY OF SUCH DAMAGE.
*/
// Generated on Fri, Dec 31, 2021 05:58+1100 for FHIR v4.3.0-snapshot1
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.r4b.model.Enumerations.*;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Block;
/**
* Base StructureDefinition for BackboneElement Type: Base definition for all elements that are defined inside a resource - but not those in a data type.
*/
@DatatypeDef(name="BackboneElement")
public abstract class BackboneElement extends DataType implements IBaseBackboneElement {
/**
* May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.
Modifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).
*/
@Child(name = "modifierExtension", type = {Extension.class}, order=0, min=0, max=Child.MAX_UNLIMITED, modifier=true, summary=true)
@Description(shortDefinition="Extensions that cannot be ignored even if unrecognized", formalDefinition="May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself)." )
protected List<Extension> modifierExtension;
private static final long serialVersionUID = -1431673179L;
/**
* Constructor
*/
public BackboneElement() {
super();
}
/**
* @return {@link #modifierExtension} (May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.
Modifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).)
*/
public List<Extension> getModifierExtension() {
if (this.modifierExtension == null)
this.modifierExtension = new ArrayList<Extension>();
return this.modifierExtension;
}
/**
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public BackboneElement setModifierExtension(List<Extension> theModifierExtension) {
this.modifierExtension = theModifierExtension;
return this;
}
public boolean hasModifierExtension() {
if (this.modifierExtension == null)
return false;
for (Extension item : this.modifierExtension)
if (!item.isEmpty())
return true;
return false;
}
public Extension addModifierExtension() { //3
Extension t = new Extension();
if (this.modifierExtension == null)
this.modifierExtension = new ArrayList<Extension>();
this.modifierExtension.add(t);
return t;
}
public BackboneElement addModifierExtension(Extension t) { //3
if (t == null)
return this;
if (this.modifierExtension == null)
this.modifierExtension = new ArrayList<Extension>();
this.modifierExtension.add(t);
return this;
}
/**
* @return The first repetition of repeating field {@link #modifierExtension}, creating it if it does not already exist {3}
*/
public Extension getModifierExtensionFirstRep() {
if (getModifierExtension().isEmpty()) {
addModifierExtension();
}
return getModifierExtension().get(0);
}
protected void listChildren(List<Property> children) {
super.listChildren(children);
children.add(new Property("modifierExtension", "Extension", "May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).", 0, java.lang.Integer.MAX_VALUE, modifierExtension));
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case -298878168: /*modifierExtension*/ return new Property("modifierExtension", "Extension", "May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).", 0, java.lang.Integer.MAX_VALUE, modifierExtension);
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
@Override
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
switch (hash) {
case -298878168: /*modifierExtension*/ return this.modifierExtension == null ? new Base[0] : this.modifierExtension.toArray(new Base[this.modifierExtension.size()]); // Extension
default: return super.getProperty(hash, name, checkValid);
}
}
@Override
public Base setProperty(int hash, String name, Base value) throws FHIRException {
switch (hash) {
case -298878168: // modifierExtension
this.getModifierExtension().add(TypeConvertor.castToExtension(value)); // Extension
return value;
default: return super.setProperty(hash, name, value);
}
}
@Override
public Base setProperty(String name, Base value) throws FHIRException {
if (name.equals("modifierExtension")) {
this.getModifierExtension().add(TypeConvertor.castToExtension(value));
} else
return super.setProperty(name, value);
return value;
}
@Override
public Base makeProperty(int hash, String name) throws FHIRException {
switch (hash) {
case -298878168: return addModifierExtension();
default: return super.makeProperty(hash, name);
}
}
@Override
public String[] getTypesForProperty(int hash, String name) throws FHIRException {
switch (hash) {
case -298878168: /*modifierExtension*/ return new String[] {"Extension"};
default: return super.getTypesForProperty(hash, name);
}
}
@Override
public Base addChild(String name) throws FHIRException {
if (name.equals("modifierExtension")) {
return addModifierExtension();
}
else
return super.addChild(name);
}
public String fhirType() {
return "BackboneElement";
}
public abstract BackboneElement copy();
public void copyValues(BackboneElement dst) {
super.copyValues(dst);
if (modifierExtension != null) {
dst.modifierExtension = new ArrayList<Extension>();
for (Extension i : modifierExtension)
dst.modifierExtension.add(i.copy());
};
}
@Override
public boolean equalsDeep(Base other_) {
if (!super.equalsDeep(other_))
return false;
if (!(other_ instanceof BackboneElement))
return false;
BackboneElement o = (BackboneElement) other_;
return compareDeep(modifierExtension, o.modifierExtension, true);
}
@Override
public boolean equalsShallow(Base other_) {
if (!super.equalsShallow(other_))
return false;
if (!(other_ instanceof BackboneElement))
return false;
BackboneElement o = (BackboneElement) other_;
return true;
}
public boolean isEmpty() {
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty(modifierExtension);
}
// Manual code (from Configuration.txt):
public void checkNoModifiers(String noun, String verb) throws FHIRException {
if (hasModifierExtension()) {
throw new FHIRException("Found unknown Modifier Exceptions on "+noun+" doing "+verb);
}
}
public void addModifierExtension(String url, DataType value) {
if (isDisallowExtensions())
throw new Error("Extensions are not allowed in this context");
Extension ex = new Extension();
ex.setUrl(url);
ex.setValue(value);
getModifierExtension().add(ex);
}
@Override
public Extension getExtensionByUrl(String theUrl) {
org.apache.commons.lang3.Validate.notBlank(theUrl, "theUrl must not be blank or null");
ArrayList<Extension> retVal = new ArrayList<Extension>();
Extension res = super.getExtensionByUrl(theUrl);
if (res != null) {
retVal.add(res);
}
for (Extension next : getModifierExtension()) {
if (theUrl.equals(next.getUrl())) {
retVal.add(next);
}
}
if (retVal.size() == 0)
return null;
else {
org.apache.commons.lang3.Validate.isTrue(retVal.size() == 1, "Url "+theUrl+" must have only one match");
return retVal.get(0);
}
}
@Override
public void removeExtension(String theUrl) {
for (int i = getModifierExtension().size()-1; i >= 0; i--) {
if (theUrl.equals(getExtension().get(i).getUrl()))
getExtension().remove(i);
}
super.removeExtension(theUrl);
}
/**
* Returns an unmodifiable list containing all extensions on this element which
* match the given URL.
*
* @param theUrl The URL. Must not be blank or null.
* @return an unmodifiable list containing all extensions on this element which
* match the given URL
*/
@Override
public List<Extension> getExtensionsByUrl(String theUrl) {
org.apache.commons.lang3.Validate.notBlank(theUrl, "theUrl must not be blank or null");
ArrayList<Extension> retVal = new ArrayList<Extension>();
retVal.addAll(super.getExtensionsByUrl(theUrl));
for (Extension next : getModifierExtension()) {
if (theUrl.equals(next.getUrl())) {
retVal.add(next);
}
}
return java.util.Collections.unmodifiableList(retVal);
}
// end addition
}

View File

@ -0,0 +1,247 @@
package org.hl7.fhir.r4b.model;
/*
Copyright (c) 2011+, HL7, Inc.
All rights reserved.
Redistribution and use in source and binary forms, with or without modification, \
are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this \
list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, \
this list of conditions and the following disclaimer in the documentation \
and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific
prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS \"AS IS\" AND \
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED \
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. \
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, \
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT \
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR \
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, \
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) \
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE \
POSSIBILITY OF SUCH DAMAGE.
*/
// Generated on Tue, Dec 28, 2021 07:16+1100 for FHIR v5.0.0-snapshot1
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import org.hl7.fhir.r4b.model.Enumerations.*;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.ChildOrder;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Block;
/**
* Base StructureDefinition for BackboneType Type: Base definition for the few data types that are allowed to carry modifier extensions.
*/
@DatatypeDef(name="BackboneType")
public abstract class BackboneType extends DataType implements IBaseBackboneElement {
/**
* May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.
Modifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).
*/
@Child(name = "modifierExtension", type = {Extension.class}, order=0, min=0, max=Child.MAX_UNLIMITED, modifier=true, summary=true)
@Description(shortDefinition="Extensions that cannot be ignored even if unrecognized", formalDefinition="May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself)." )
protected List<Extension> modifierExtension;
private static final long serialVersionUID = -1431673179L;
/**
* Constructor
*/
public BackboneType() {
super();
}
/**
* @return {@link #modifierExtension} (May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.
Modifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).)
*/
public List<Extension> getModifierExtension() {
if (this.modifierExtension == null)
this.modifierExtension = new ArrayList<Extension>();
return this.modifierExtension;
}
/**
* @return Returns a reference to <code>this</code> for easy method chaining
*/
public BackboneType setModifierExtension(List<Extension> theModifierExtension) {
this.modifierExtension = theModifierExtension;
return this;
}
public boolean hasModifierExtension() {
if (this.modifierExtension == null)
return false;
for (Extension item : this.modifierExtension)
if (!item.isEmpty())
return true;
return false;
}
public Extension addModifierExtension() { //3
Extension t = new Extension();
if (this.modifierExtension == null)
this.modifierExtension = new ArrayList<Extension>();
this.modifierExtension.add(t);
return t;
}
public BackboneType addModifierExtension(Extension t) { //3
if (t == null)
return this;
if (this.modifierExtension == null)
this.modifierExtension = new ArrayList<Extension>();
this.modifierExtension.add(t);
return this;
}
/**
* @return The first repetition of repeating field {@link #modifierExtension}, creating it if it does not already exist {3}
*/
public Extension getModifierExtensionFirstRep() {
if (getModifierExtension().isEmpty()) {
addModifierExtension();
}
return getModifierExtension().get(0);
}
protected void listChildren(List<Property> children) {
super.listChildren(children);
children.add(new Property("modifierExtension", "Extension", "May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).", 0, java.lang.Integer.MAX_VALUE, modifierExtension));
}
@Override
public Property getNamedProperty(int _hash, String _name, boolean _checkValid) throws FHIRException {
switch (_hash) {
case -298878168: /*modifierExtension*/ return new Property("modifierExtension", "Extension", "May be used to represent additional information that is not part of the basic definition of the element and that modifies the understanding of the element in which it is contained and/or the understanding of the containing element's descendants. Usually modifier elements provide negation or qualification. To make the use of extensions safe and manageable, there is a strict set of governance applied to the definition and use of extensions. Though any implementer can define an extension, there is a set of requirements that SHALL be met as part of the definition of the extension. Applications processing a resource are required to check for modifier extensions.\n\nModifier extensions SHALL NOT change the meaning of any elements on Resource or DomainResource (including cannot change the meaning of modifierExtension itself).", 0, java.lang.Integer.MAX_VALUE, modifierExtension);
default: return super.getNamedProperty(_hash, _name, _checkValid);
}
}
@Override
public Base[] getProperty(int hash, String name, boolean checkValid) throws FHIRException {
switch (hash) {
case -298878168: /*modifierExtension*/ return this.modifierExtension == null ? new Base[0] : this.modifierExtension.toArray(new Base[this.modifierExtension.size()]); // Extension
default: return super.getProperty(hash, name, checkValid);
}
}
@Override
public Base setProperty(int hash, String name, Base value) throws FHIRException {
switch (hash) {
case -298878168: // modifierExtension
this.getModifierExtension().add(TypeConvertor.castToExtension(value)); // Extension
return value;
default: return super.setProperty(hash, name, value);
}
}
@Override
public Base setProperty(String name, Base value) throws FHIRException {
if (name.equals("modifierExtension")) {
this.getModifierExtension().add(TypeConvertor.castToExtension(value));
} else
return super.setProperty(name, value);
return value;
}
@Override
public Base makeProperty(int hash, String name) throws FHIRException {
switch (hash) {
case -298878168: return addModifierExtension();
default: return super.makeProperty(hash, name);
}
}
@Override
public String[] getTypesForProperty(int hash, String name) throws FHIRException {
switch (hash) {
case -298878168: /*modifierExtension*/ return new String[] {"Extension"};
default: return super.getTypesForProperty(hash, name);
}
}
@Override
public Base addChild(String name) throws FHIRException {
if (name.equals("modifierExtension")) {
return addModifierExtension();
}
else
return super.addChild(name);
}
public String fhirType() {
return "BackboneType";
}
public abstract BackboneType copy();
public void copyValues(BackboneType dst) {
super.copyValues(dst);
if (modifierExtension != null) {
dst.modifierExtension = new ArrayList<Extension>();
for (Extension i : modifierExtension)
dst.modifierExtension.add(i.copy());
};
}
@Override
public boolean equalsDeep(Base other_) {
if (!super.equalsDeep(other_))
return false;
if (!(other_ instanceof BackboneType))
return false;
BackboneType o = (BackboneType) other_;
return compareDeep(modifierExtension, o.modifierExtension, true);
}
@Override
public boolean equalsShallow(Base other_) {
if (!super.equalsShallow(other_))
return false;
if (!(other_ instanceof BackboneType))
return false;
BackboneType o = (BackboneType) other_;
return true;
}
public boolean isEmpty() {
return super.isEmpty() && ca.uhn.fhir.util.ElementUtil.isEmpty(modifierExtension);
}
// Manual code (from Configuration.txt):
public void checkNoModifiers(String noun, String verb) throws FHIRException {
if (hasModifierExtension()) {
throw new FHIRException("Found unknown Modifier Exceptions on "+noun+" doing "+verb);
}
}
// end addition
}

Some files were not shown because too many files have changed in this diff Show More