Merge pull request #1787 from hapifhir/2024-10-gg-tx-server-auth-1

2024 10 gg tx server auth 1
This commit is contained in:
Grahame Grieve 2024-10-24 21:09:22 +10:30 committed by GitHub
commit 78f6040ceb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
87 changed files with 1217 additions and 595 deletions

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -87,9 +87,9 @@ public class TerminologyClientR4 implements ITerminologyClient {
return (ValueSet) VersionConvertorFactory_40_50.convertResource(vs2);
} catch (org.hl7.fhir.r4.utils.client.EFhirClientException e) {
if (e.getServerErrors().size() > 0) {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage(), (org.hl7.fhir.r5.model.OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0)));
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), (org.hl7.fhir.r5.model.OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0)));
} else {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage());
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage());
}
}
}
@ -103,9 +103,9 @@ public class TerminologyClientR4 implements ITerminologyClient {
} catch (EFhirClientException e) {
if (e.getServerErrors().size() == 1) {
OperationOutcome op = (OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0));
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage(), op, e);
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), op, e);
} else {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage(), e);
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), e);
}
} catch (IOException e) {
throw new FHIRException(e);
@ -122,9 +122,9 @@ public class TerminologyClientR4 implements ITerminologyClient {
} catch (EFhirClientException e) {
if (e.getServerErrors().size() == 1) {
OperationOutcome op = (OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0));
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage(), op, e);
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), op, e);
} else {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage(), e);
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), e);
}
} catch (IOException e) {
throw new FHIRException(e);
@ -140,9 +140,9 @@ public class TerminologyClientR4 implements ITerminologyClient {
} catch (EFhirClientException e) {
if (e.getServerErrors().size() == 1) {
OperationOutcome op = (OperationOutcome) VersionConvertorFactory_40_50.convertResource(e.getServerErrors().get(0));
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage(), op, e);
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), op, e);
} else {
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getMessage(), e);
throw new org.hl7.fhir.r5.utils.client.EFhirClientException(e.getCode(), e.getMessage(), e);
}
} catch (IOException e) {
throw new FHIRException(e);

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -346,36 +346,41 @@ public class PEBuilder {
ElementDefinition defn = list.get(i);
if (!defn.getMax().equals("0") && (allFixed || include(defn))) {
if (passElementPropsCheck(defn) && !Utilities.existsInList(defn.getName(), omitList)) {
PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path());
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
pe.setMustHaveValue(definition.getMustHaveValue());
}
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
if (defn.hasSlicing()) {
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
res.add(pe);
pe.setSlicer(true);
}
if (defn.getType().size() > 1) {
// DebugUtilities.breakpoint();
i++;
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
StructureDefinition ext = getExtensionDefinition(list.get(i));
if (ext != null) {
res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path()));
} else if (isTypeSlicing(defn)) {
res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
} else {
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
} else {
res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
}
} else {
PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path());
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
pe.setMustHaveValue(definition.getMustHaveValue());
}
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
if (defn.hasSlicing()) {
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
res.add(pe);
pe.setSlicer(true);
}
i++;
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
StructureDefinition ext = getExtensionDefinition(list.get(i));
if (ext != null) {
res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path()));
} else if (isTypeSlicing(defn)) {
res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
} else {
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
} else {
res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
}
}
i++;
}
} else {
res.add(pe);
i++;
}
} else {
res.add(pe);
i++;
}
} else {
i++;
@ -528,7 +533,7 @@ public class PEBuilder {
protected void populateByProfile(Base base, PEDefinition definition) {
if (definition.types().size() == 1) {
for (PEDefinition pe : definition.directChildren(true)) {
if (pe.fixedValue()) {
if (pe.hasFixedValue()) {
if (pe.definition().hasPattern()) {
base.setProperty(pe.schemaName(), pe.definition().getPattern());
} else {

View File

@ -39,6 +39,7 @@ import org.hl7.fhir.r4.model.Base;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.ElementDefinition.TypeRefComponent;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.profilemodel.PEDefinition.PEDefinitionElementMode;
import org.hl7.fhir.utilities.Utilities;
@ -220,7 +221,7 @@ public abstract class PEDefinition {
if (types().size() == 1) {
return children(types.get(0).getUrl(), false);
} else {
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (element = "+path+", types = "+types()+")");
}
}
@ -228,17 +229,22 @@ public abstract class PEDefinition {
if (types().size() == 1) {
return children(types.get(0).getUrl(), allFixed);
} else {
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (element = "+path+", types = "+types()+")");
}
}
/**
* @return True if the element has a fixed value. This will always be false if fixedProps = false when the builder is created
*/
public boolean fixedValue() {
public boolean hasFixedValue() {
return definition.hasFixed() || definition.hasPattern();
}
public Type getFixedValue() {
return definition.hasFixed() ? definition.getFixed() : definition.getPattern();
}
protected abstract void makeChildren(String typeUrl, List<PEDefinition> children, boolean allFixed);
@Override
@ -289,7 +295,7 @@ public abstract class PEDefinition {
public boolean isList() {
return "*".equals(definition.getMax());
return "*".equals(definition.getMax()) || (Utilities.parseInt(definition.getMax(), 2) > 1);
}

View File

@ -46,6 +46,7 @@ import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.model.Address;
import org.hl7.fhir.r4.model.BackboneElement;
import org.hl7.fhir.r4.model.PrimitiveType;
import org.hl7.fhir.r4.model.Quantity;
import org.hl7.fhir.r4.model.Reference;
@ -225,6 +226,10 @@ public class PEInstance {
return (Type) data;
}
public BackboneElement asElement() {
return (BackboneElement) data;
}
public CodeableConcept asCodeableConcept() {
return (CodeableConcept) asDataType();
}
@ -293,18 +298,27 @@ public class PEInstance {
return builder.getContext();
}
public void addChild(String name, Type value) {
public Base addChild(String name, Type value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), value);
}
public void addChild(String name, String value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), new StringType(value));
return b;
}
public void addChild(String name, Date value) {
public Base addChild(String name, BackboneElement value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), value);
return b;
}
public Base addChild(String name, String value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), new StringType(value));
return b;
}
public Base addChild(String name, Date value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), new DateType(value));
return b;
}
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r4.profilemodel.gen;
public @interface BindingStrength {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r4.profilemodel.gen;
public @interface Definition {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r4.profilemodel.gen;
public @interface Doco {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r4.profilemodel.gen;
public @interface Label {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r4.profilemodel.gen;
public @interface Max {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r4.profilemodel.gen;
public @interface Min {
String value();
}

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r4.profilemodel.gen;
public @interface MustSupport {
boolean value();
}

View File

@ -33,25 +33,56 @@ import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.TimeZone;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import org.hl7.fhir.r4.context.IWorkerContext;
import org.hl7.fhir.r4.model.CodeableConcept;
import org.hl7.fhir.r4.model.ElementDefinition;
import org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionBindingComponent;
import org.hl7.fhir.r4.model.Identifier;
import org.hl7.fhir.r4.model.Observation;
import org.hl7.fhir.r4.model.StructureDefinition;
import org.hl7.fhir.r4.model.StructureDefinition.StructureDefinitionKind;
import org.hl7.fhir.r4.model.Type;
import org.hl7.fhir.r4.model.ValueSet.ValueSetExpansionContainsComponent;
import org.hl7.fhir.r4.profilemodel.PEBuilder;
import org.hl7.fhir.r4.profilemodel.PEBuilder.PEElementPropertiesPolicy;
import org.hl7.fhir.r4.profilemodel.gen.PECodeGenerator.ExtensionPolicy;
import org.hl7.fhir.r4.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r4.profilemodel.PEDefinition;
import org.hl7.fhir.r4.profilemodel.PEInstance;
import org.hl7.fhir.r4.profilemodel.PEType;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
/**
*
* The easiest way to generate code is to use the FHIR Validator, which can generate java classes for profiles
* using this code. Parameters:
*
* -codegen -version r4 -ig hl7.fhir.dk.core#3.2.0 -profiles http://hl7.dk/fhir/core/StructureDefinition/dk-core-gln-identifier,http://hl7.dk/fhir/core/StructureDefinition/dk-core-patient -output /Users/grahamegrieve/temp/codegen -package-name org.hl7.fhir.test
*
* Parameter Documentation:
* -codegen: tells the validator to generate code
* -version {r4|5}: which version to generate for
* -ig {name}: loads an IG (and it's dependencies) - see -ig documentation for the validator
* -profiles {list}: a comma separated list of profile URLs to generate code for
* -output {folder}: the folder where to generate the output java class source code
* -package-name {name}: the name of the java package to generate in
*
* options
* -option {name}: a code generation option, one of:
*
* narrative: generate code for the resource narrative (recommended: don't - leave that for the native resource level)
* meta: generate code the what's in meta
* contained: generate code for contained resources
* all-elements: generate code for all elements, not just the key elements (makes the code verbose)
*/
public class PECodeGenerator {
@ -73,7 +104,12 @@ public class PECodeGenerator {
private String doco;
private String url;
private boolean isResource;
private Set<String> unfixed = new HashSet<>();
private Set<String> enumNames = new HashSet<>();
private StringBuilder inits = new StringBuilder();
private StringBuilder fields = new StringBuilder();
private StringBuilder enums = new StringBuilder();
private StringBuilder load = new StringBuilder();
private StringBuilder save = new StringBuilder();
private StringBuilder clear = new StringBuilder();
@ -82,10 +118,10 @@ public class PECodeGenerator {
private StringBuilder hash = new StringBuilder();
public void genId() {
if (isResource) {
genField(true, "id", "String", "id", "", false, "");
genAccessors(true, false, "id", "String", "", "String", "String", "Id", "Ids", false, "", false);
genLoad(true, false, "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, null);
genSave(true, false, "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, false, null);
genField(true, "id", "String", "id", "", false, "", 0, 1, null);
genAccessors(true, false, "id", "String", "", "String", "String", "Id", "Ids", false, "", false, false);
genLoad(true, false, "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, null, false);
genSave(true, false, "id", "id", "IdType", "", "String", "String", "Id", "Ids", false, false, false, null, false);
genClear(false, "id");
}
}
@ -106,15 +142,27 @@ public class PECodeGenerator {
w(b, " private static final String CANONICAL_URL = \""+url+"\";");
w(b);
}
if (enums.length() > 0) {
w(b, enums.toString());
}
w(b, fields.toString());
jdoc(b, "Parameter-less constructor. If you use this, the fixed values won't be filled out - they'll be missing. They'll be filled in if/when you call build, so they won't be missing from the resource, only from this particular object model", 2, true);
if (unfixed.isEmpty()) {
jdoc(b, "Parameter-less constructor.", 2, true);
} else {
jdoc(b, "Parameter-less constructor. If you use this, the fixed values on "+CommaSeparatedStringBuilder.join(",", unfixed)+" won't be filled out - they'll be missing. They'll be filled in if/when you call build, so they won't be missing from the resource, only from this particular object model", 2, true);
}
w(b, " public "+name+"() {");
w(b, " // todo");
if (inits.length() > 0) {
w(b, " initFixedValues();");
}
w(b, " }");
w(b);
if (isResource) {
jdoc(b, "Construct an instance of the object, and fill out all the fixed values ", 2, true);
w(b, " public "+name+"(IWorkerContext context) {");
if (inits.length() > 0) {
w(b, " initFixedValues();");
}
w(b, " workerContext = context;");
w(b, " PEBuilder builder = new PEBuilder(context, PEElementPropertiesPolicy.EXTENSION, true);");
w(b, " PEInstance src = builder.buildPEInstance(CANONICAL_URL, builder.createResource(CANONICAL_URL, false));");
@ -171,6 +219,12 @@ public class PECodeGenerator {
w(b, save.toString());
w(b, " }");
w(b);
if (inits.length() > 0) {
w(b, " private void initFixedValues() {");
w(b, inits.toString());
w(b, " }");
w(b);
}
w(b, accessors.toString());
w(b);
w(b, " public void clear() {");
@ -180,18 +234,127 @@ public class PECodeGenerator {
w(b, "}");
}
private String generateEnum(PEDefinition source, PEDefinition field) {
if (field.definition().hasBinding() && !field.hasFixedValue()) {
ElementDefinitionBindingComponent binding = field.definition().getBinding();
if (binding.getStrength() == org.hl7.fhir.r4.model.Enumerations.BindingStrength.REQUIRED && binding.hasValueSet()) {
org.hl7.fhir.r4.model.ValueSet vs = workerContext.fetchResource(org.hl7.fhir.r4.model.ValueSet.class, binding.getValueSet());
if (vs != null) {
ValueSetExpansionOutcome vse = workerContext.expandVS(vs, false, false);
if (vse.isOk()) {
String baseName = Utilities.nmtokenize(Utilities.singularise(vs.getName()));
String name = baseName;
int c = 0;
while (enumNames.contains(name)) {
c++;
name = baseName+c;
}
w(enums, " public enum "+name+" {");
for (int i = 0; i < vse.getValueset().getExpansion().getContains().size(); i++) {
ValueSetExpansionContainsComponent cc = vse.getValueset().getExpansion().getContains().get(i);
String code = Utilities.javaTokenize(cc.getCode(), true).toUpperCase();
if (cc.getAbstract()) {
code = "_"+code;
}
cc.setUserData("java.code", code);
w(enums, " "+code+(i < vse.getValueset().getExpansion().getContains().size() - 1 ? "," : ";")+" // \""+cc.getDisplay()+"\" = "+cc.getSystem()+"#"+cc.getCode());
}
w(enums, "");
w(enums, " public static "+name+" fromCode(String s) {");
w(enums, " switch (s) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
w(enums, " case \""+cc.getCode()+"\": return "+cc.getUserString("java.code")+";");
}
w(enums, " default: return null;");
w(enums, " }");
w(enums, " }");
w(enums, "");
w(enums, " public static "+name+" fromCoding(Coding c) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
if (cc.hasVersion()) {
w(enums, " if (\""+cc.getSystem()+"\".equals(c.getSystem()) && \""+cc.getCode()+"\".equals(c.getCode()) && (!c.hasVersion() || \""+cc.getVersion()+"\".equals(c.getVersion()))) {");
} else {
w(enums, " if (\""+cc.getSystem()+"\".equals(c.getSystem()) && \""+cc.getCode()+"\".equals(c.getCode())) {");
}
w(enums, " return "+cc.getUserString("java.code")+";");
w(enums, " }");
}
w(enums, " return null;");
w(enums, " }");
w(enums, "");
w(enums, " public static "+name+" fromCodeableConcept(CodeableConcept cc) {");
w(enums, " for (Coding c : cc.getCoding()) {");
w(enums, " "+name+" v = fromCoding(c);");
w(enums, " if (v != null) {");
w(enums, " return v;");
w(enums, " }");
w(enums, " }");
w(enums, " return null;");
w(enums, " }");
w(enums, "");
w(enums, " public String toDisplay() {");
w(enums, " switch (this) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
w(enums, " case "+cc.getUserString("java.code")+": return \""+Utilities.escapeJava(cc.getDisplay())+"\";");
}
w(enums, " default: return null;");
w(enums, " }");
w(enums, " }");
w(enums, "");
w(enums, " public String toCode() {");
w(enums, " switch (this) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
w(enums, " case "+cc.getUserString("java.code")+": return \""+cc.getCode()+"\";");
}
w(enums, " default: return null;");
w(enums, " }");
w(enums, " }");
w(enums, "");
w(enums, " public Coding toCoding() {");
w(enums, " switch (this) {");
for (ValueSetExpansionContainsComponent cc : vse.getValueset().getExpansion().getContains()) {
if (cc.hasVersion()) {
w(enums, " case "+cc.getUserString("java.code")+": return new Coding().setSystem(\""+cc.getSystem()+"\").setVersion(\""+cc.getVersion()+"\").setCode()\""+cc.getCode()+"\";");
} else {
w(enums, " case "+cc.getUserString("java.code")+": return new Coding().setSystem(\""+cc.getSystem()+"\").setCode(\""+cc.getCode()+"\");");
}
}
w(enums, " default: return null;");
w(enums, " }");
w(enums, " }");
w(enums, "");
w(enums, " public CodeableConcept toCodeableConcept() {");
w(enums, " Coding c = toCoding();");
w(enums, " return c == null ? null : new CodeableConcept().addCoding(c);");
w(enums, " }");
w(enums, " }");
return name;
}
}
}
}
return null;
}
private void defineField(PEDefinition source, PEDefinition field) {
if (field.types().size() == 1) {
StructureDefinition sd = workerContext.fetchTypeDefinition(field.types().get(0).getUrl());
if (sd != null) {
String enumName = generateEnum(source, field);
boolean isPrim = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
boolean isAbstract = sd.getAbstract();
String name = field.name().replace("[x]", "");
String name = Utilities.javaTokenize(field.name().replace("[x]", ""), false);
String sname = name;
String type = null;
String init = "";
String ptype = type;
if (isPrim) {
boolean isEnum = false;
if (enumName != null) {
type = enumName;
ptype = enumName;
isEnum = true;
} else if (isPrim) {
// todo: are we extension-less?
type = Utilities.capitalize(field.types().get(0).getName()+"Type");
ptype = getPrimitiveType(sd);
@ -210,18 +373,20 @@ public class PECodeGenerator {
String csname = Utilities.capitalize(sname);
String nn = field.min() == 1 ? "// @NotNull" : "";
boolean isExtension = field.isExtension();
genField(isPrim, name, ptype, ltype, nn, field.isList(), field.shortDocumentation());
genAccessors(isPrim, isAbstract, name, type, init, ptype, ltype, cname, csname, field.isList(), field.documentation(), field.fixedValue());
genLoad(isPrim, isAbstract, name, sname, type, init, ptype, ltype, cname, csname, field.isList(), field.fixedValue(), field.types().get(0));
genSave(isPrim, isAbstract, name, sname, type, init, ptype, ltype, cname, csname, field.isList(), field.fixedValue(), isExtension, field.types().get(0));
genField(isPrim, name, ptype, ltype, nn, field.isList(), field.shortDocumentation(), field.min(), field.max(), field.definition());
if (isPrim && field.hasFixedValue()) {
genFixed(name, ptype, field.getFixedValue());
}
genAccessors(isPrim, isAbstract, name, type, init, ptype, ltype, cname, csname, field.isList(), field.documentation(), field.hasFixedValue(), isEnum);
genLoad(isPrim, isAbstract, name, sname, type, init, ptype, ltype, cname, csname, field.isList(), field.hasFixedValue(), field.types().get(0), isEnum);
genSave(isPrim, isAbstract, name, sname, type, init, ptype, ltype, cname, csname, field.isList(), field.hasFixedValue(), isExtension, field.types().get(0), isEnum);
genClear(field.isList(), name);
}
} else {
// ignoring polymorphics for now
}
}
private void genClear(boolean list, String name) {
if (list) {
w(clear, " "+name+".clear();");
@ -230,11 +395,26 @@ public class PECodeGenerator {
}
}
private void genLoad(boolean isPrim, boolean isAbstract, String name, String sname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, PEType typeInfo) {
private void genLoad(boolean isPrim, boolean isAbstract, String name, String sname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, PEType typeInfo, boolean isEnum) {
if (isList) {
w(load, " for (PEInstance item : src.children(\""+sname+"\")) {");
w(load, " "+name+".add(("+type+") item.asDataType());");
if ("BackboneElement".equals(type)) {
w(load, " "+name+".add(("+type+") item.asElement());");
} else {
w(load, " "+name+".add(("+type+") item.asDataType());");
}
w(load, " }");
} else if (isEnum) {
w(load, " if (src.hasChild(\""+name+"\")) {");
if ("CodeableConcept".equals(typeInfo.getName())) {
w(load, " "+name+" = "+type+".fromCodeableConcept((CodeableConcept) src.child(\""+name+"\").asDataType());");
} else if ("Coding".equals(typeInfo.getName())) {
w(load, " "+name+" = "+type+".fromCoding((Coding) src.child(\""+name+"\").asDataType());");
} else {
w(load, " "+name+" = "+type+".fromCode(src.child(\""+name+"\").asDataType().primitiveValue());");
}
w(load, " }");
} else if (isPrim) {
w(load, " if (src.hasChild(\""+name+"\")) {");
if ("CodeType".equals(type)) {
@ -249,13 +429,17 @@ public class PECodeGenerator {
w(load, " "+name+" = "+type+".fromSource(src.child(\""+name+"\"));");
w(load, " }");
} else {
w(load, " if (src.hasChild(\""+name+"\")) {");
w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asDataType();");
w(load, " if (src.hasChild(\""+name+"\")) {");
if ("BackboneElement".equals(type)) {
w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asElement();");
} else {
w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asDataType();");
}
w(load, " }");
}
}
private void genSave(boolean isPrim, boolean isAbstract, String name, String sname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, boolean isExtension, PEType typeInfo) {
private void genSave(boolean isPrim, boolean isAbstract, String name, String sname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, boolean isExtension, PEType typeInfo, boolean isEnum) {
w(save, " tgt.clear(\""+sname+"\");");
if (isList) {
w(save, " for ("+type+" item : "+name+") {");
@ -265,6 +449,16 @@ public class PECodeGenerator {
w(save, " tgt.addChild(\""+sname+"\", item);");
}
w(save, " }");
} else if (isEnum) {
w(save, " if ("+name+" != null) {");
if ("CodeableConcept".equals(typeInfo.getName())) {
w(save, " tgt.addChild(\""+sname+"\", "+name+".toCodeableConcept());");
} else if ("Coding".equals(typeInfo.getName())) {
w(save, " tgt.addChild(\""+sname+"\", "+name+".toCoding());");
} else {
w(save, " tgt.addChild(\""+sname+"\", "+name+".toCode());");
}
w(save, " }");
} else if (isPrim) {
w(save, " if ("+name+" != null) {");
if (isExtension) {
@ -290,21 +484,27 @@ public class PECodeGenerator {
}
}
private void genAccessors(boolean isPrim, boolean isAbstract, String name, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, String shortDoco, boolean isFixed) {
private void genAccessors(boolean isPrim, boolean isAbstract, String name, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, String shortDoco, boolean isFixed, boolean isEnum) {
jdoc(accessors, doco, 2, true);
if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) {
if ((isEnum || isPrim) && extensionPolicy != ExtensionPolicy.Primitives && !isList) {
w(accessors, " public "+ptype+" get"+cname+"() {");
w(accessors, " return "+name+";");
w(accessors, " }");
w(accessors);
w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {");
w(accessors, " this."+name+" = value;");
w(accessors, " return this;");
w(accessors, " }");
w(accessors);
w(accessors, " public boolean has"+cname+"() {");
w(accessors, " return "+name+" != null;");
w(accessors, " }");
if (isFixed) {
w(accessors, " public boolean has"+cname+"() {");
w(accessors, " return true;");
w(accessors, " }");
} else {
w(accessors, " public "+this.name+" set"+cname+"("+ptype+" value) {");
w(accessors, " this."+name+" = value;");
w(accessors, " return this;");
w(accessors, " }");
w(accessors);
w(accessors, " public boolean has"+cname+"() {");
w(accessors, " return "+name+" != null;");
w(accessors, " }");
}
} else {
if (isPrim && !isList) {
w(accessors, " public "+ptype+" get"+cname+"() {");
@ -379,8 +579,27 @@ public class PECodeGenerator {
w(accessors);
}
private void genField(boolean isPrim, String name, String ptype, String ltype, String nn, boolean isList, String shortDoco) {
// jdoc(fields, field.documentation(), 2, true);
private void genField(boolean isPrim, String name, String ptype, String ltype, String nn, boolean isList, String shortDoco, int min, int max, ElementDefinition ed) {
// jdoc(fields, shortDoco, 2, true);
w(fields, " @Min(\""+min+"\") @Max(\""+(max == Integer.MAX_VALUE ? "*" : max) +"\")"+(" @Doco(\""+Utilities.escapeJava(shortDoco)+"\")"));
if (ed != null) {
if (ed.hasBinding() && ed.getBinding().hasValueSet()) {
w(fields, " @BindingStrength(\""+ed.getBinding().getStrength().toCode()+"\") @ValueSet(\""+ed.getBinding().getValueSet()+"\")");
}
if (ed.getMustSupport()) {
w(fields, " @MustSupport(true)");
}
if (ed.hasLabel() || ed.hasDefinition()) {
String s = "";
if (ed.hasLabel()) {
s = s + " @Label(\""+Utilities.escapeJava(ed.getLabel())+"\")";
}
if (ed.hasDefinition()) {
s = s + " @Definition(\""+Utilities.escapeJava(ed.getDefinition())+"\")";
}
w(fields, " "+s);
}
}
if (isPrim && extensionPolicy != ExtensionPolicy.Primitives && !isList) {
w(fields, " private "+ptype+" "+name+";"+nn+" // "+shortDoco);
} else if (isList) {
@ -388,6 +607,17 @@ public class PECodeGenerator {
} else {
w(fields, " private "+ltype+" "+name+";"+nn+" // "+shortDoco);
}
w(fields, "");
}
private void genFixed(String name, String pType, Type fixedValue) {
if ("String".equals(pType)) {
w(inits, " "+name+" = \""+Utilities.escapeJava(fixedValue.primitiveValue())+"\";");
} else {
unfixed.add(name);
System.out.println("Unable to handle the fixed value for "+name+" of type "+pType+" = "+fixedValue.toString());
}
}
}
@ -395,7 +625,8 @@ public class PECodeGenerator {
private IWorkerContext workerContext;
private String canonical;
private String pkgName;
private String version = "r4";
// options:
private ExtensionPolicy extensionPolicy;
private boolean narrative;
@ -421,6 +652,14 @@ public class PECodeGenerator {
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getCanonical() {
return canonical;
}
@ -500,19 +739,31 @@ public class PECodeGenerator {
* @throws IOException
*
*/
public void execute() throws IOException {
public String execute() throws IOException {
imports = new StringBuilder();
PEDefinition source = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true).buildPEDefinition(canonical);
w(imports, "import java.util.List;");
w(imports, "import java.util.ArrayList;");
w(imports, "import javax.annotation.Nullable;");
w(imports, "import java.util.Date;\r\n");
w(imports);
w(imports, "import org.hl7.fhir.r5.context.IWorkerContext;");
w(imports, "import org.hl7.fhir.r5.model.*;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEInstance;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;");
w(imports, "import org.hl7.fhir."+version+".context.IWorkerContext;");
w(imports, "import org.hl7.fhir."+version+".model.*;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEInstance;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder.PEElementPropertiesPolicy;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.PEGeneratedBase;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Min;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Max;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Label;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Doco;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.BindingStrength;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.ValueSet;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.MustSupport;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Definition;");
PEGenClass cls = genClass(source);
StringBuilder b = new StringBuilder();
w(b, "package "+pkgName+";");
@ -523,6 +774,7 @@ public class PECodeGenerator {
w(b, imports.toString());
cls.write(b, source.getProfile().getCopyright());
TextFile.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java"));
return cls.name+".java";
}
public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) {

View File

@ -0,0 +1,7 @@
package org.hl7.fhir.r4.profilemodel.gen;
public @interface ValueSet {
String value();
}

View File

@ -112,6 +112,10 @@ public interface ValueSetExpander {
return this;
}
public boolean isOk() {
return error == null;
}
}
/**

View File

@ -56,14 +56,16 @@ import org.hl7.fhir.r4.model.OperationOutcome;
*/
public class EFhirClientException extends RuntimeException {
private static final long serialVersionUID = 1L;
private int code;
private List<OperationOutcome> errors = new ArrayList<OperationOutcome>();
public EFhirClientException(String message) {
super(message);
}
public EFhirClientException(String message, List<OperationOutcome> serverErrors) {
public EFhirClientException(int code, String message, List<OperationOutcome> serverErrors) {
super(message);
this.code = code;
if (serverErrors != null && serverErrors.size() > 0) {
errors.addAll(serverErrors);
}
@ -73,8 +75,9 @@ public class EFhirClientException extends RuntimeException {
super(cause);
}
public EFhirClientException(String message, Exception cause) {
public EFhirClientException(int code, String message, Exception cause) {
super(message, cause);
this.code = code;
}
/**
@ -85,8 +88,9 @@ public class EFhirClientException extends RuntimeException {
* @param message
* @param serverError
*/
public EFhirClientException(String message, OperationOutcome serverError) {
public EFhirClientException(int code, String message, OperationOutcome serverError) {
super(message);
this.code = code;
if (serverError != null) {
errors.add(serverError);
}
@ -102,9 +106,10 @@ public class EFhirClientException extends RuntimeException {
*
* @param serverError
*/
public EFhirClientException(OperationOutcome serverError) {
public EFhirClientException(int code, OperationOutcome serverError) {
super("Error on the server: " + serverError.getText().getDiv().allText()
+ ". Refer to e.getServerErrors() for additional details.");
this.code = code;
if (serverError != null) {
errors.add(serverError);
}
@ -130,4 +135,8 @@ public class EFhirClientException extends RuntimeException {
return errors.size() > 0;
}
public int getCode() {
return code;
}
}

View File

@ -170,7 +170,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
@ -187,7 +187,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass.getName() + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
@ -205,7 +205,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version, timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
@ -223,11 +223,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Read " + resourceClass.getName() + "?url=" + canonicalURL,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
handleException("An error has occurred while trying to read this version of the resource", e);
handleException(0, "An error has occurred while trying to read this version of the resource", e);
}
Bundle bnd = (Bundle) result.getPayload();
if (bnd.getEntry().size() == 0)
@ -247,11 +247,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Update " + resource.fhirType() + "/" + resource.getId(),
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
}
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader
// is returned with an operationOutcome would be returned (and not the resource
@ -277,11 +277,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Update " + resource.fhirType() + "/" + id,
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
}
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader
// is returned with an operationOutcome would be returned (and not the resource
@ -319,7 +319,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"GET " + resourceClass.getName() + "/$" + name, timeoutLong);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
if (result.getPayload() instanceof Parameters) {
@ -339,7 +339,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
ByteUtils.resourceToByteArray(batch, false, isJson(getPreferredResourceFormat()), false),
withVer(getPreferredResourceFormat(), "4.0"), "transaction", timeoutOperation + (timeoutEntry * batch.getEntry().size()));
} catch (Exception e) {
handleException("An error occurred trying to process this transaction request", e);
handleException(0, "An error occurred trying to process this transaction request", e);
}
return transactionResult;
}
@ -354,11 +354,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(),
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
handleException("An error has occurred while trying to validate this resource", e);
handleException(0, "An error has occurred while trying to validate this resource", e);
}
return (OperationOutcome) result.getPayload();
}
@ -369,11 +369,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
* @param e
* @throws EFhirClientException
*/
protected void handleException(String message, Exception e) throws EFhirClientException {
protected void handleException(int code, String message, Exception e) throws EFhirClientException {
if (e instanceof EFhirClientException) {
throw (EFhirClientException) e;
} else {
throw new EFhirClientException(message, e);
throw new EFhirClientException(code, message, e);
}
}
@ -398,7 +398,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
feed = client.issueGetFeedRequest(new URI(url), withVer(getPreferredResourceFormat(), "4.0"), timeoutLong);
} catch (Exception e) {
handleException("An error has occurred while trying to retrieve history since last update", e);
handleException(0, "An error has occurred while trying to retrieve history since last update", e);
}
return feed;
}
@ -413,7 +413,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
@ -430,7 +430,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
@ -447,7 +447,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
@ -466,17 +466,17 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
generateHeaders(), source == null ? "ValueSet/$expand" : "ValueSet/$expand?url=" + source.getUrl(),
timeoutExpand);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (EFhirClientException e) {
if (e.getServerErrors().size() > 0) {
throw new EFhirClientException(e.getMessage(), e.getServerErrors().get(0));
throw new EFhirClientException(e.getCode(), e.getMessage(), e.getServerErrors().get(0));
} else {
throw new EFhirClientException(e.getMessage(), e);
throw new EFhirClientException(e.getCode(), e.getMessage(), e);
}
} catch (Exception e) {
throw new EFhirClientException(e.getMessage(), e);
throw new EFhirClientException(0, e.getMessage(), e);
}
return result == null ? null : (ValueSet) result.getPayload();
}
@ -496,7 +496,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "Closure?name=" + name, timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (IOException e) {
@ -517,7 +517,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
ByteUtils.resourceToByteArray(params, false, isJson(getPreferredResourceFormat()), true),
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(), "UpdateClosure?name=" + name, timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (IOException e) {
@ -632,7 +632,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (T) result.getPayload();

View File

@ -260,7 +260,7 @@ public class ResourceAddress {
public static URI buildAbsoluteURI(String absoluteURI) {
if (StringUtils.isBlank(absoluteURI)) {
throw new EFhirClientException("Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
throw new EFhirClientException(0, "Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
}
String endpoint = appendForwardSlashToPath(absoluteURI);
@ -289,7 +289,7 @@ public class ResourceAddress {
throw new EFhirClientException("host cannot be blank: " + uri);
}
} catch (URISyntaxException e) {
throw new EFhirClientException("Invalid URI", e);
throw new EFhirClientException(0, "Invalid URI", e);
}
return uri;
}
@ -301,7 +301,7 @@ public class ResourceAddress {
uriBuilder.setQuery(parameterName + "=" + parameterValue);
modifiedUri = uriBuilder.build();
} catch (Exception e) {
throw new EFhirClientException(
throw new EFhirClientException(0,
"Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e);
}
return modifiedUri;
@ -439,7 +439,7 @@ public class ResourceAddress {
return new URI(basePath.getScheme(), basePath.getUserInfo(), basePath.getHost(), basePath.getPort(),
basePath.getPath(), query, basePath.getFragment());
} catch (Exception e) {
throw new EFhirClientException("Error appending http parameter", e);
throw new EFhirClientException(0, "Error appending http parameter", e);
}
}

View File

@ -37,9 +37,9 @@ public class ByteUtils {
try {
baos.close();
} catch (Exception ex) {
throw new EFhirClientException("Error closing output stream", ex);
throw new EFhirClientException(0, "Error closing output stream", ex);
}
throw new EFhirClientException("Error converting output stream to byte array", e);
throw new EFhirClientException(0, "Error converting output stream to byte array", e);
}
return byteArray;
}

View File

@ -314,14 +314,14 @@ public class FhirRequestBuilder {
}
}
} catch (IOException ioe) {
throw new EFhirClientException("Error reading Http Response from "+source+":"+ioe.getMessage(), ioe);
throw new EFhirClientException(code, "Error reading Http Response from "+source+":"+ioe.getMessage(), ioe);
} catch (Exception e) {
throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e);
throw new EFhirClientException(code, "Error parsing response message from "+source+": "+e.getMessage(), e);
}
if (resource instanceof OperationOutcome && (!"OperationOutcome".equals(resourceType) || !ok)) {
OperationOutcome error = (OperationOutcome) resource;
if (hasError((OperationOutcome) resource)) {
throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error);
throw new EFhirClientException(code, "Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error);
} else {
// umm, weird...
System.out.println("Got OperationOutcome with no error from "+source+" with status "+code);

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -968,6 +968,7 @@ public class ProfileUtilities {
snapshotStack.remove(derived.getUrl());
}
derived.setUserData("profileutils.snapshot.generated", true); // used by the publisher
derived.setUserData("profileutils.snapshot.generated.messages", messages); // used by the publisher
}

View File

@ -499,7 +499,7 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
if (!oidCacheManual.containsKey(s)) {
oidCacheManual.put(s, new HashSet<>());
}
oidCacheManual.get(s).add(new OIDDefinition(r.fhirType(), s, url, ((CanonicalResource) r).getVersion(), null));
oidCacheManual.get(s).add(new OIDDefinition(r.fhirType(), s, url, ((CanonicalResource) r).getVersion(), null, null));
}
}
}
@ -3189,7 +3189,8 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
String rt = rs.getString(1);
String url = rs.getString(2);
String version = rs.getString(3);
summary.addOID(new OIDDefinition(rt, oid, url, version, os.pid));
String status = rs.getString(4);
summary.addOID(new OIDDefinition(rt, oid, url, version, os.pid, status));
}
}
} catch (Exception e) {
@ -3201,13 +3202,13 @@ public abstract class BaseWorkerContext extends I18nBase implements IWorkerConte
switch (oid) {
case "2.16.840.1.113883.6.1" :
summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.1", "http://loinc.org", null, null));
summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.1", "http://loinc.org", null, null, null));
break;
case "2.16.840.1.113883.6.8" :
summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.8", "http://unitsofmeasure.org", null, null));
summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.8", "http://unitsofmeasure.org", null, null, null));
break;
case "2.16.840.1.113883.6.96" :
summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.96", "http://snomed.info/sct", null, null));
summary.addOID(new OIDDefinition("CodeSystem", "2.16.840.1.113883.6.96", "http://snomed.info/sct", null, null, null));
break;
default:
}

View File

@ -139,13 +139,15 @@ public interface IWorkerContext {
private String url;
private String version;
private String packageSrc;
protected OIDDefinition(String type, String oid, String url, String version, String packageSrc) {
private String status;
protected OIDDefinition(String type, String oid, String url, String version, String status, String packageSrc) {
super();
this.type = type;
this.oid = oid;
this.url = url;
this.version = version == null ? "" : version;
this.packageSrc = packageSrc;
this.status = status;
}
public String getType() {
return type;
@ -159,6 +161,9 @@ public interface IWorkerContext {
public String getVersion() {
return version;
}
public String getStatus() {
return status;
}
public String getPackageSrc() {
return packageSrc;
}

View File

@ -481,6 +481,8 @@ public class JsonParser extends ParserBase {
} else if (propV.hasType(type)) {
pvl = new Property(propV.getContext(), propV.getDefinition(), propV.getStructure(), propV.getUtils(), propV.getContextUtils(), type);
ok = true;
} else if (propV.getDefinition().getType().size() == 1 && propV.typeIsConsistent(type)) {
pvl = new Property(propV.getContext(), propV.getDefinition(), propV.getStructure(), propV.getUtils(), propV.getContextUtils(), propV.getType());
} else {
logError(errors, ValidationMessage.NO_RULE_DATE, line(pv.getValue()), col(pv.getValue()), path, IssueType.STRUCTURE, this.context.formatMessage(I18nConstants.UNRECOGNISED_PROPERTY_TYPE_WRONG, describeType(pv.getValue()), propV.getName(), type, propV.typeSummary()), IssueSeverity.ERROR);
}

View File

@ -205,14 +205,35 @@ public class Property {
return ed.getType().get(0).getWorkingCode();
}
public boolean hasType(String elementName) {
public boolean typeIsConsistent(String typeName) {
for (TypeRefComponent tr : definition.getType()) {
if (typeName.equals(tr.getWorkingCode()) || typeSpecializes(tr.getWorkingCode(), typeName)) {
return true;
}
}
return false;
}
private boolean typeSpecializes(String workingCode, String typeName) {
if ("string".equals(typeName)) {
return Utilities.existsInList(workingCode, "uri", "oid", "canonical", "url", "uuid", "id", "markdown");
}
if ("integer".equals(typeName)) {
return Utilities.existsInList(workingCode, "positiveInt", "unsignedInt");
}
return false;
}
public boolean hasType(String typeName) {
if (type != null) {
return false; // ?
} else if (definition.getType().size() == 0) {
return false;
} else if (isJsonPrimitiveChoice()) {
for (TypeRefComponent tr : definition.getType()) {
if (elementName.equals(tr.getWorkingCode())) {
if (typeName.equals(tr.getWorkingCode())) {
return true;
}
}
@ -227,7 +248,7 @@ public class Property {
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))) {
if (tail.endsWith("[x]") && typeName.startsWith(tail.substring(0, tail.length()-3))) {
// String name = elementName.substring(tail.length()-3);
return true;
} else
@ -706,5 +727,5 @@ public class Property {
return false;
}
}

View File

@ -3430,7 +3430,7 @@ public class FHIRPathEngine {
case Item : {
checkOrdered(focus, "item", exp);
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_Integer));
return focus;
return focus.toSingleton();
}
case As : {
checkParamTypes(exp, exp.getFunction().toCode(), paramTypes, new TypeDetails(CollectionStatus.SINGLETON, TypeDetails.FP_String));

View File

@ -349,36 +349,41 @@ public class PEBuilder {
ElementDefinition defn = list.get(i);
if (!defn.getMax().equals("0") && (allFixed || include(defn))) {
if (passElementPropsCheck(defn) && !Utilities.existsInList(defn.getName(), omitList)) {
PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path());
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
pe.setMustHaveValue(definition.getMustHaveValue());
}
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
if (defn.hasSlicing()) {
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
res.add(pe);
pe.setSlicer(true);
}
if (defn.getType().size() > 1) {
// DebugUtilities.breakpoint();
i++;
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
StructureDefinition ext = getExtensionDefinition(list.get(i));
if (ext != null) {
res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path()));
} else if (isTypeSlicing(defn)) {
res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
} else {
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
} else {
res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
}
} else {
PEDefinitionElement pe = new PEDefinitionElement(this, profile, defn, parent.path());
pe.setRecursing(definition == defn || (profile.getDerivation() == TypeDerivationRule.SPECIALIZATION && profile.getType().equals("Extension")));
if (context.isPrimitiveType(definition.getTypeFirstRep().getWorkingCode()) && "value".equals(pe.name())) {
pe.setMustHaveValue(definition.getMustHaveValue());
}
pe.setInFixedValue(definition.hasFixed() || definition.hasPattern() || parent.isInFixedValue());
if (defn.hasSlicing()) {
if (defn.getSlicing().getRules() != SlicingRules.CLOSED) {
res.add(pe);
pe.setSlicer(true);
}
i++;
while (i < list.size() && list.get(i).getPath().equals(defn.getPath())) {
StructureDefinition ext = getExtensionDefinition(list.get(i));
if (ext != null) {
res.add(new PEDefinitionExtension(this, list.get(i).getSliceName(), profile, list.get(i), defn, ext, parent.path()));
} else if (isTypeSlicing(defn)) {
res.add(new PEDefinitionTypeSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
} else {
if (ProfileUtilities.isComplexExtension(profile) && defn.getPath().endsWith(".extension")) {
res.add(new PEDefinitionSubExtension(this, profile, list.get(i), parent.path()));
} else {
res.add(new PEDefinitionSlice(this, list.get(i).getSliceName(), profile, list.get(i), defn, parent.path()));
}
}
i++;
}
} else {
res.add(pe);
i++;
}
} else {
res.add(pe);
i++;
}
} else {
i++;

View File

@ -222,7 +222,7 @@ public abstract class PEDefinition {
if (types().size() == 1) {
return children(types.get(0).getUrl(), false);
} else {
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (element = "+path+", types = "+types()+")");
}
}
@ -230,7 +230,7 @@ public abstract class PEDefinition {
if (types().size() == 1) {
return children(types.get(0).getUrl(), allFixed);
} else {
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (types = "+types()+")");
throw new DefinitionException("Attempt to get children for an element that doesn't have a single type (element = "+path+", types = "+types()+")");
}
}

View File

@ -46,6 +46,7 @@ import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.HumanName;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.model.Address;
import org.hl7.fhir.r5.model.BackboneElement;
import org.hl7.fhir.r5.model.PrimitiveType;
import org.hl7.fhir.r5.model.Quantity;
import org.hl7.fhir.r5.model.Reference;
@ -225,6 +226,10 @@ public class PEInstance {
return (DataType) data;
}
public BackboneElement asElement() {
return (BackboneElement) data;
}
public CodeableConcept asCodeableConcept() {
return (CodeableConcept) asDataType();
}
@ -293,18 +298,27 @@ public class PEInstance {
return builder.getContext();
}
public void addChild(String name, DataType value) {
public Base addChild(String name, DataType value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), value);
}
public void addChild(String name, String value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), new StringType(value));
return b;
}
public void addChild(String name, Date value) {
public Base addChild(String name, BackboneElement value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), value);
return b;
}
public Base addChild(String name, String value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), new StringType(value));
return b;
}
public Base addChild(String name, Date value) {
PEDefinition child = byName(definition.children(), name);
Base b = data.setProperty(child.schemaName(), new DateType(value));
return b;
}
}

View File

@ -60,6 +60,29 @@ import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
/**
*
* The easiest way to generate code is to use the FHIR Validator, which can generate java classes for profiles
* using this code. Parameters:
*
* -codegen -version r4 -ig hl7.fhir.dk.core#3.2.0 -profiles http://hl7.dk/fhir/core/StructureDefinition/dk-core-gln-identifier,http://hl7.dk/fhir/core/StructureDefinition/dk-core-patient -output /Users/grahamegrieve/temp/codegen -package-name org.hl7.fhir.test
*
* Parameter Documentation:
* -codegen: tells the validator to generate code
* -version {r4|5}: which version to generate for
* -ig {name}: loads an IG (and it's dependencies) - see -ig documentation for the validator
* -profiles {list}: a comma separated list of profile URLs to generate code for
* -output {folder}: the folder where to generate the output java class source code
* -package-name {name}: the name of the java package to generate in
*
* options
* -option {name}: a code generation option, one of:
*
* narrative: generate code for the resource narrative (recommended: don't - leave that for the native resource level)
* meta: generate code the what's in meta
* contained: generate code for contained resources
* all-elements: generate code for all elements, not just the key elements (makes the code verbose)
*/
public class PECodeGenerator {
@ -229,7 +252,7 @@ public class PECodeGenerator {
w(enums, " public enum "+name+" {");
for (int i = 0; i < vse.getValueset().getExpansion().getContains().size(); i++) {
ValueSetExpansionContainsComponent cc = vse.getValueset().getExpansion().getContains().get(i);
String code = Utilities.nmtokenize(cc.getCode()).toUpperCase();
String code = Utilities.javaTokenize(cc.getCode(), true).toUpperCase();
if (cc.getAbstract()) {
code = "_"+code;
}
@ -321,7 +344,7 @@ public class PECodeGenerator {
String enumName = generateEnum(source, field);
boolean isPrim = sd.getKind() == StructureDefinitionKind.PRIMITIVETYPE;
boolean isAbstract = sd.getAbstract();
String name = field.name().replace("[x]", "");
String name = Utilities.javaTokenize(field.name().replace("[x]", ""), false);
String sname = name;
String type = null;
String init = "";
@ -375,7 +398,11 @@ public class PECodeGenerator {
private void genLoad(boolean isPrim, boolean isAbstract, String name, String sname, String type, String init, String ptype, String ltype, String cname, String csname, boolean isList, boolean isFixed, PEType typeInfo, boolean isEnum) {
if (isList) {
w(load, " for (PEInstance item : src.children(\""+sname+"\")) {");
w(load, " "+name+".add(("+type+") item.asDataType());");
if ("BackboneElement".equals(type)) {
w(load, " "+name+".add(("+type+") item.asElement());");
} else {
w(load, " "+name+".add(("+type+") item.asDataType());");
}
w(load, " }");
} else if (isEnum) {
w(load, " if (src.hasChild(\""+name+"\")) {");
@ -384,7 +411,7 @@ public class PECodeGenerator {
} else if ("Coding".equals(typeInfo.getName())) {
w(load, " "+name+" = "+type+".fromCoding((Coding) src.child(\""+name+"\").asDataType());");
} else {
w(load, " "+name+" = "+type+".fromCode(src.child(\""+name+"\").asDataType()).primitiveValue());");
w(load, " "+name+" = "+type+".fromCode(src.child(\""+name+"\").asDataType().primitiveValue());");
}
w(load, " }");
} else if (isPrim) {
@ -401,8 +428,12 @@ public class PECodeGenerator {
w(load, " "+name+" = "+type+".fromSource(src.child(\""+name+"\"));");
w(load, " }");
} else {
w(load, " if (src.hasChild(\""+name+"\")) {");
w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asDataType();");
w(load, " if (src.hasChild(\""+name+"\")) {");
if ("BackboneElement".equals(type)) {
w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asElement();");
} else {
w(load, " "+name+" = ("+type+") src.child(\""+name+"\").asDataType();");
}
w(load, " }");
}
}
@ -424,7 +455,7 @@ public class PECodeGenerator {
} else if ("Coding".equals(typeInfo.getName())) {
w(save, " tgt.addChild(\""+sname+"\", "+name+".toCoding());");
} else {
w(save, " tgt.addChild(\""+sname+"\", "+name+").toCode();");
w(save, " tgt.addChild(\""+sname+"\", "+name+".toCode());");
}
w(save, " }");
} else if (isPrim) {
@ -593,7 +624,8 @@ public class PECodeGenerator {
private IWorkerContext workerContext;
private String canonical;
private String pkgName;
private String version = "r5";
// options:
private ExtensionPolicy extensionPolicy;
private boolean narrative;
@ -619,6 +651,14 @@ public class PECodeGenerator {
}
public String getVersion() {
return version;
}
public void setVersion(String version) {
this.version = version;
}
public String getCanonical() {
return canonical;
}
@ -698,7 +738,7 @@ public class PECodeGenerator {
* @throws IOException
*
*/
public void execute() throws IOException {
public String execute() throws IOException {
imports = new StringBuilder();
PEDefinition source = new PEBuilder(workerContext, PEElementPropertiesPolicy.EXTENSION, true).buildPEDefinition(canonical);
@ -707,20 +747,20 @@ public class PECodeGenerator {
w(imports, "import javax.annotation.Nullable;");
w(imports, "import java.util.Date;\r\n");
w(imports);
w(imports, "import org.hl7.fhir.r5.context.IWorkerContext;");
w(imports, "import org.hl7.fhir.r5.model.*;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEInstance;");
w(imports, "import org.hl7.fhir.r5.profilemodel.PEBuilder.PEElementPropertiesPolicy;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.PEGeneratedBase;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Min;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Max;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Label;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Doco;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.BindingStrength;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.ValueSet;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.MustSupport;");
w(imports, "import org.hl7.fhir.r5.profilemodel.gen.Definition;");
w(imports, "import org.hl7.fhir."+version+".context.IWorkerContext;");
w(imports, "import org.hl7.fhir."+version+".model.*;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEInstance;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.PEBuilder.PEElementPropertiesPolicy;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.PEGeneratedBase;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Min;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Max;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Label;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Doco;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.BindingStrength;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.ValueSet;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.MustSupport;");
w(imports, "import org.hl7.fhir."+version+".profilemodel.gen.Definition;");
PEGenClass cls = genClass(source);
@ -733,6 +773,7 @@ public class PECodeGenerator {
w(b, imports.toString());
cls.write(b, source.getProfile().getCopyright());
TextFile.stringToFile(b.toString(), Utilities.path(folder, cls.name+".java"));
return cls.name+".java";
}
public void jdoc(StringBuilder b, String doco, int indent, boolean jdoc) {

View File

@ -249,6 +249,9 @@ public class DataRenderer extends Renderer implements CodeResolver {
return (context.formatPhrase(RenderingContext.DATA_REND_DICOM));
if (system.equals("http://unitsofmeasure.org"))
return (context.formatPhrase(RenderingContext.GENERAL_UCUM));
if (system.equals("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl"))
return (context.formatPhrase(RenderingContext.GENERAL_NCI_THES));
CodeSystem cs = context.getContext().fetchCodeSystem(system);
if (cs != null) {
@ -1213,7 +1216,9 @@ public class DataRenderer extends Renderer implements CodeResolver {
} else if ("http://loinc.org".equals(system)) {
return "https://loinc.org/";
} else if ("http://unitsofmeasure.org".equals(system)) {
return "http://ucum.org";
return "http://ucum.org";
} else if ("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl".equals(system)) {
return "https://ncit.nci.nih.gov/ncitbrowser/pages/home.jsf";
} else {
String url = system;
if (version != null) {
@ -1242,6 +1247,12 @@ public class DataRenderer extends Renderer implements CodeResolver {
} else {
return "https://www.nlm.nih.gov/research/umls/rxnorm/index.html";
}
} else if ("http://ncicb.nci.nih.gov/xml/owl/EVS/Thesaurus.owl".equals(system)) {
if (!Utilities.noString(code)) {
return "https://ncit.nci.nih.gov/ncitbrowser/ConceptReport.jsp?code="+code;
} else {
return "https://ncit.nci.nih.gov/ncitbrowser/pages/home.jsf";
}
} else if ("urn:iso:std:iso:3166".equals(system)) {
if (!Utilities.noString(code)) {
return "https://en.wikipedia.org/wiki/ISO_3166-2:"+code;
@ -1307,18 +1318,14 @@ public class DataRenderer extends Renderer implements CodeResolver {
if (Utilities.noString(s))
s = lookupCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"));
CodeSystem cs = context.getWorker().fetchCodeSystem(c.primitiveValue("system"));
String sn = displaySystem(c.primitiveValue("system"));
String link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"));
XhtmlNode xi = link != null ? x.ah(context.prefixLocalHref(link)) : x;
xi.tx(sn);
xi.tx(" ");
String sn = cs != null ? crPresent(cs) : displaySystem(c.primitiveValue("system"));
String link = getLinkForCode(c.primitiveValue("system"), c.primitiveValue("version"), c.primitiveValue("code"));
if (link != null) {
x.ah(context.prefixLocalHref(link)).tx(sn);
} else {
x.tx(sn);
}
x.tx(" ");
x.tx(c.primitiveValue("code"));
xi.tx(c.primitiveValue("code"));
if (!Utilities.noString(s)) {
x.tx(": ");
x.tx(s);

View File

@ -1,32 +0,0 @@
package org.hl7.fhir.r5.renderers;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import org.hl7.fhir.exceptions.DefinitionException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.FHIRFormatError;
import org.hl7.fhir.r5.renderers.utils.RenderingContext;
import org.hl7.fhir.r5.renderers.utils.ResourceWrapper;
import org.hl7.fhir.r5.utils.EOperationOutcome;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
public class EncounterRenderer extends ResourceRenderer {
public EncounterRenderer(RenderingContext context) {
super(context);
}
@Override
public String buildSummary(ResourceWrapper r) throws UnsupportedEncodingException, IOException {
return "todo";
}
@Override
public void buildNarrative(RenderingStatus status, XhtmlNode x, ResourceWrapper r) throws FHIRFormatError, DefinitionException, IOException, FHIRException, EOperationOutcome {
renderResourceTechDetails(r, x);
x.tx("Not done yet");
}
}

View File

@ -24,7 +24,6 @@ public class RendererFactory {
case "CompartmentDefinition": return new CompartmentDefinitionRenderer(context);
case "ConceptMap": return new ConceptMapRenderer(context);
case "DiagnosticReport": return new DiagnosticReportRenderer(context);
case "Encounter": return new EncounterRenderer(context);
case "ExampleScenario": return new ExampleScenarioRenderer(context);
case "ImplementationGuide": return new ImplementationGuideRenderer(context);
case "Library": return new LibraryRenderer(context);
@ -87,7 +86,6 @@ public class RendererFactory {
case "CodeSystem": return new CodeSystemRenderer(context);
case "CompartmentDefinition": return new CompartmentDefinitionRenderer(context);
case "ConceptMap": return new ConceptMapRenderer(context);
case "Encounter": return new EncounterRenderer(context);
case "ExampleScenario": return new ExampleScenarioRenderer(context);
case "ImplementationGuide": return new ImplementationGuideRenderer(context);
case "NamingSystem": return new NamingSystemRenderer(context);

View File

@ -304,13 +304,32 @@ public class CompareUtilities extends BaseTestingUtilities {
for (JsonProperty en : expectedJsonObject.getProperties()) {
String n = en.getName();
if (!n.equals("fhir_comments") && !n.equals("$optional$") && !optionals.contains(n)) {
if (!actualJsonObject.has(n))
if (!actualJsonObject.has(n) && !allOptional(en.getValue()))
return "properties differ at " + path + ": missing property " + n;
}
}
return null;
}
private boolean allOptional(JsonElement value) {
if (value.isJsonArray()) {
JsonArray a = value.asJsonArray();
for (JsonElement e : a) {
if (e.isJsonObject()) {
JsonObject o = e.asJsonObject();
if (!o.has("$optional$")) {
return false;
}
} else {
// nothing
}
}
return true;
} else {
return false;
}
}
private List<String> listOptionals(JsonObject expectedJsonObject) {
List<String> res = new ArrayList<>();
if (expectedJsonObject.has("$optional-properties$")) {

View File

@ -58,17 +58,20 @@ import org.hl7.fhir.r5.model.OperationOutcome;
public class EFhirClientException extends RuntimeException {
private static final long serialVersionUID = 1L;
private OperationOutcome error = null;
private int code;
public EFhirClientException(String message) {
public EFhirClientException(int code, String message) {
super(message);
this.code = code;
}
public EFhirClientException(Exception cause) {
super(cause);
}
public EFhirClientException(String message, Exception cause) {
public EFhirClientException(int code, String message, Exception cause) {
super(message, cause);
this.code = code;
}
/**
@ -78,8 +81,9 @@ public class EFhirClientException extends RuntimeException {
* @param message
* @param serverError
*/
public EFhirClientException(String message, OperationOutcome serverError) {
public EFhirClientException(int code, String message, OperationOutcome serverError) {
super(message);
this.code = code;
error = serverError;
}
@ -91,8 +95,9 @@ public class EFhirClientException extends RuntimeException {
* @param message
* @param serverError
*/
public EFhirClientException(String message, OperationOutcome serverError, Exception cause) {
public EFhirClientException(int code, String message, OperationOutcome serverError, Exception cause) {
super(message, cause);
this.code = code;
error = serverError;
}
@ -105,8 +110,9 @@ public class EFhirClientException extends RuntimeException {
*
* @param serverError
*/
public EFhirClientException(OperationOutcome serverError) {
public EFhirClientException(int code, OperationOutcome serverError) {
super("Error on the server: "+serverError.getText().getDiv().allText()+". Refer to e.getServerErrors() for additional details.");
this.code = code;
error = serverError;
}
@ -123,4 +129,8 @@ public class EFhirClientException extends RuntimeException {
return error != null;
}
public int getCode() {
return code;
}
}

View File

@ -217,7 +217,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "Read " + resourceClass + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
} catch (Exception e) {
@ -237,7 +237,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Read " + resourceClass.getName() + "/" + id,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new FHIRException(e);
@ -255,7 +255,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"VRead " + resourceClass.getName() + "/" + id + "/?_history/" + version,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new FHIRException("Error trying to read this version of the resource", e);
@ -273,16 +273,16 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Read " + resourceClass.getName() + "?url=" + canonicalURL,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
handleException("An error has occurred while trying to read this version of the resource", e);
handleException(0, "An error has occurred while trying to read this version of the resource", e);
}
Bundle bnd = (Bundle) result.getPayload();
if (bnd.getEntry().size() == 0)
throw new EFhirClientException("No matching resource found for canonical URL '" + canonicalURL + "'");
throw new EFhirClientException(0, "No matching resource found for canonical URL '" + canonicalURL + "'");
if (bnd.getEntry().size() > 1)
throw new EFhirClientException("Multiple matching resources found for canonical URL '" + canonicalURL + "'");
throw new EFhirClientException(0, "Multiple matching resources found for canonical URL '" + canonicalURL + "'");
return (T) bnd.getEntry().get(0).getResource();
}
@ -297,10 +297,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Update " + resource.fhirType() + "/" + resource.getId(),
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
}
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read
try {
@ -325,10 +325,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Update " + resource.fhirType() + "/" + id,
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
throw new EFhirClientException("An error has occurred while trying to update this resource", e);
throw new EFhirClientException(0, "An error has occurred while trying to update this resource", e);
}
// TODO oe 26.1.2015 could be made nicer if only OperationOutcome locationheader is returned with an operationOutcome would be returned (and not the resource also) we make another read
try {
@ -363,7 +363,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
result = client.issueGetResourceRequest(url, withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(false), "GET " + resourceClass.getName() + "/$" + name, timeoutLong);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
if (result.getPayload() instanceof Parameters) {
return (Parameters) result.getPayload();
@ -373,7 +373,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
return p_out;
}
} catch (Exception e) {
handleException("Error performing tx5 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
handleException(0, "Error performing tx5 operation '"+name+": "+e.getMessage()+"' (parameters = \"" + ps+"\")", e);
}
return null;
}
@ -386,7 +386,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
generateHeaders(true),
"transaction", timeoutOperation + (timeoutEntry * batch.getEntry().size()));
} catch (Exception e) {
handleException("An error occurred trying to process this transaction request", e);
handleException(0, "An error occurred trying to process this transaction request", e);
}
return transactionResult;
}
@ -401,10 +401,10 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
withVer(getPreferredResourceFormat(), "4.0"), generateHeaders(true),
"POST " + resourceClass.getName() + (id != null ? "/" + id : "") + "/$validate", timeoutLong);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (Exception e) {
handleException("An error has occurred while trying to validate this resource", e);
handleException(0, "An error has occurred while trying to validate this resource", e);
}
return (OperationOutcome) result.getPayload();
}
@ -415,11 +415,11 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
* @param e
* @throws EFhirClientException
*/
protected void handleException(String message, Exception e) throws EFhirClientException {
protected void handleException(int code, String message, Exception e) throws EFhirClientException {
if (e instanceof EFhirClientException) {
throw (EFhirClientException) e;
} else {
throw new EFhirClientException(message, e);
throw new EFhirClientException(code, message, e);
}
}
@ -444,7 +444,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
try {
feed = client.issueGetFeedRequest(new URI(url), getPreferredResourceFormat());
} catch (Exception e) {
handleException("An error has occurred while trying to retrieve history since last update", e);
handleException(0, "An error has occurred while trying to retrieve history since last update", e);
}
return feed;
}
@ -465,7 +465,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return result == null ? null : (ValueSet) result.getPayload();
}
@ -483,7 +483,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
e.printStackTrace();
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
}
@ -502,7 +502,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
e.printStackTrace();
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
}
@ -521,7 +521,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
e.printStackTrace();
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
return (Parameters) result.getPayload();
}
@ -543,7 +543,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"Closure?name=" + name,
timeoutNormal);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (IOException e) {
e.printStackTrace();
@ -565,7 +565,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
"UpdateClosure?name=" + name,
timeoutOperation);
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(), (OperationOutcome) result.getPayload());
}
} catch (IOException e) {
e.printStackTrace();
@ -694,7 +694,7 @@ public class FHIRToolingClient extends FHIRBaseToolingClient {
throw new FHIRException(e);
}
if (result.isUnsuccessfulRequest()) {
throw new EFhirClientException("Server returned error code " + result.getHttpStatus(),
throw new EFhirClientException(result.getHttpStatus(), "Server returned error code " + result.getHttpStatus(),
(OperationOutcome) result.getPayload());
}
return (T) result.getPayload();

View File

@ -255,7 +255,7 @@ public class ResourceAddress {
public static URI buildAbsoluteURI(String absoluteURI) {
if(StringUtils.isBlank(absoluteURI)) {
throw new EFhirClientException("Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
throw new EFhirClientException(0, "Invalid URI", new URISyntaxException(absoluteURI, "URI/URL cannot be blank"));
}
String endpoint = appendForwardSlashToPath(absoluteURI);
@ -278,13 +278,13 @@ public class ResourceAddress {
String scheme = uri.getScheme();
String host = uri.getHost();
if(!scheme.equalsIgnoreCase("http") && !scheme.equalsIgnoreCase("https")) {
throw new EFhirClientException("Scheme must be 'http' or 'https': " + uri);
throw new EFhirClientException(0, "Scheme must be 'http' or 'https': " + uri);
}
if(StringUtils.isBlank(host)) {
throw new EFhirClientException("host cannot be blank: " + uri);
throw new EFhirClientException(0, "host cannot be blank: " + uri);
}
} catch(URISyntaxException e) {
throw new EFhirClientException("Invalid URI", e);
throw new EFhirClientException(0, "Invalid URI", e);
}
return uri;
}
@ -296,7 +296,7 @@ public class ResourceAddress {
uriBuilder.setQuery(parameterName + "=" + parameterValue);
modifiedUri = uriBuilder.build();
} catch(Exception e) {
throw new EFhirClientException("Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e);
throw new EFhirClientException(0, "Unable to append query parameter '" + parameterName + "=" + parameterValue + " to URI " + uri, e);
}
return modifiedUri;
}
@ -431,7 +431,7 @@ public class ResourceAddress {
return new URI(basePath.getScheme(), basePath.getUserInfo(), basePath.getHost(),basePath.getPort(), basePath.getPath(), query, basePath.getFragment());
} catch(Exception e) {
throw new EFhirClientException("Error appending http parameter", e);
throw new EFhirClientException(0, "Error appending http parameter", e);
}
}

View File

@ -38,9 +38,9 @@ public class ByteUtils {
try {
baos.close();
} catch (Exception ex) {
throw new EFhirClientException("Error closing output stream", ex);
throw new EFhirClientException(0, "Error closing output stream", ex);
}
throw new EFhirClientException("Error converting output stream to byte array", e);
throw new EFhirClientException(0, "Error converting output stream to byte array", e);
}
return byteArray;
}

View File

@ -99,7 +99,7 @@ public class Client {
Headers headers,
String message,
long timeout) throws IOException {
if (payload == null) throw new EFhirClientException("PUT requests require a non-null payload");
if (payload == null) throw new EFhirClientException(0, "PUT requests require a non-null payload");
this.payload = payload;
RequestBody body = RequestBody.create(payload);
Request.Builder request = new Request.Builder()
@ -123,7 +123,7 @@ public class Client {
Headers headers,
String message,
long timeout) throws IOException {
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
if (payload == null) throw new EFhirClientException(0, "POST requests require a non-null payload");
this.payload = payload;
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
Request.Builder request = new Request.Builder()
@ -168,7 +168,7 @@ public class Client {
Headers headers,
String message,
int timeout) throws IOException {
if (payload == null) throw new EFhirClientException("POST requests require a non-null payload");
if (payload == null) throw new EFhirClientException(0, "POST requests require a non-null payload");
RequestBody body = RequestBody.create(MediaType.parse(resourceFormat + ";charset=" + DEFAULT_CHARSET), payload);
Request.Builder request = new Request.Builder()
.url(resourceUri.toURL())

View File

@ -255,7 +255,7 @@ public class FhirRequestBuilder {
boolean ok = code >= 200 && code < 300;
if (response.body() == null) {
if (!ok) {
throw new EFhirClientException(response.message());
throw new EFhirClientException(code, response.message());
} else {
return null;
}
@ -307,14 +307,14 @@ public class FhirRequestBuilder {
}
}
} catch (IOException ioe) {
throw new EFhirClientException("Error reading Http Response from "+source+":"+ioe.getMessage(), ioe);
throw new EFhirClientException(0, "Error reading Http Response from "+source+":"+ioe.getMessage(), ioe);
} catch (Exception e) {
throw new EFhirClientException("Error parsing response message from "+source+": "+e.getMessage(), e);
throw new EFhirClientException(0, "Error parsing response message from "+source+": "+e.getMessage(), e);
}
if (resource instanceof OperationOutcome && (!"OperationOutcome".equals(resourceType) || !ok)) {
OperationOutcome error = (OperationOutcome) resource;
if (hasError((OperationOutcome) resource)) {
throw new EFhirClientException("Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error);
throw new EFhirClientException(0, "Error from "+source+": " + ResourceUtilities.getErrorDescription(error), error);
} else {
// umm, weird...
System.out.println("Got OperationOutcome with no error from "+source+" with status "+code);
@ -328,7 +328,7 @@ public class FhirRequestBuilder {
return null; // shouldn't get here?
}
if (resourceType != null && !resource.fhirType().equals(resourceType)) {
throw new EFhirClientException("Error parsing response message from "+source+": Found an "+resource.fhirType()+" looking for a "+resourceType);
throw new EFhirClientException(0, "Error parsing response message from "+source+": Found an "+resource.fhirType()+" looking for a "+resourceType);
}
return (T) resource;
}
@ -359,7 +359,7 @@ public class FhirRequestBuilder {
} else if (mt.getBase().equalsIgnoreCase(ResourceFormat.RESOURCE_XML.getHeader())) {
return new XmlParser();
} else {
throw new EFhirClientException("Invalid format: " + format);
throw new EFhirClientException(0, "Invalid format: " + format);
}
}
}

View File

@ -338,16 +338,20 @@ public class Validator {
}
if (isColl) {
if (td.getCollectionStatus() == CollectionStatus.SINGLETON) {
hint(path, column, "collection is true, but the path statement(s) can only return single values for the column '"+columnName+"'");
hint(path, column, "collection is true, but the path statement(s) ('"+expr+"') can only return single values for the column '"+columnName+"'");
}
if (supportsArrays == TrueFalseOrUnknown.UNKNOWN) {
warning(path, expression, "The column '"+columnName+"' is defined as a collection, but collections are not supported in all execution contexts");
} else if (supportsArrays == TrueFalseOrUnknown.FALSE) {
if (td.getCollectionStatus() == CollectionStatus.SINGLETON) {
warning(path, expression, "The column '"+columnName+"' is defined as a collection, but this is not allowed in the current execution context. Note that the path '"+expr+"' can only return a single value");
} else {
warning(path, expression, "The column '"+columnName+"' is defined as a collection, but this is not allowed in the current execution context. Note that the path '"+expr+"' can return a collection of values");
}
}
} else {
if (supportsArrays == TrueFalseOrUnknown.UNKNOWN) {
warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path. Collections are not supported in all execution contexts");
} else if (supportsArrays == TrueFalseOrUnknown.FALSE) {
warning(path, expression, "The column '"+columnName+"' appears to be a collection based on it's path, but this is not allowed in the current execution context");
}
if (td.getCollectionStatus() != CollectionStatus.SINGLETON) {
warning(path, column, "collection is not true, but the path statement(s) might return multiple values for the column '"+columnName+"' for some inputs");
warning(path, column, "This column is not defined as a collection, but the path statement '"+expr+"' might return multiple values for the column '"+columnName+"' for some inputs");
}
}
Set<String> types = new HashSet<>();
@ -364,7 +368,7 @@ public class Validator {
if (typeJ instanceof JsonString) {
String type = typeJ.asString();
if (!td.hasType(type)) {
error(path+".type", typeJ, "The path expression does not return a value of the type '"+type+"' - found "+td.describe(), IssueType.VALUE);
error(path+".type", typeJ, "The path expression ('"+expr+"') does not return a value of the type '"+type+"' - found "+td.describe(), IssueType.VALUE);
} else {
types.clear();
types.add(simpleType(type));
@ -381,9 +385,9 @@ public class Validator {
boolean ok = false;
if (!isSimpleType(type) && !"null".equals(type)) {
if (supportsComplexTypes == TrueFalseOrUnknown.UNKNOWN) {
warning(path, expression, "Column is a complex type. This is not supported in some Runners");
warning(path, expression, "Column from path '"+expr+"' is a complex type ('"+type+"'). This is not supported in some Runners");
} else if (supportsComplexTypes == TrueFalseOrUnknown.FALSE) {
error(path, expression, "Column is a complex type but this is not allowed in this context", IssueType.BUSINESSRULE);
error(path, expression, "Column from path '"+expr+"' is a complex type ('"+type+"') but this is not allowed in this context", IssueType.BUSINESSRULE);
} else {
ok = true;
}
@ -411,6 +415,11 @@ public class Validator {
case "integer": return ColumnKind.Integer;
case "decimal": return ColumnKind.Decimal;
case "string": return ColumnKind.String;
case "canonical": return ColumnKind.String;
case "url": return ColumnKind.String;
case "uri": return ColumnKind.String;
case "oid": return ColumnKind.String;
case "uuid": return ColumnKind.String;
case "id": return ColumnKind.String;
case "code": return ColumnKind.String;
case "base64Binary": return ColumnKind.Binary;
@ -420,7 +429,7 @@ public class Validator {
}
private boolean isSimpleType(String type) {
return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary", "id", "code", "date", "time");
return Utilities.existsInList(type, "dateTime", "boolean", "integer", "decimal", "string", "base64Binary", "id", "code", "date", "time", "canonical");
}
private String simpleType(String type) {

View File

@ -101,7 +101,7 @@ public class PETests {
checkElement(children.get(2), "language", "language", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/code", 2, "language");
checkElement(children.get(3), "text", "text", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/Narrative", 3, "text");
checkElement(children.get(4), "contained", "contained", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Resource", 4, "contained");
checkElement(children.get(5), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension.where(((url = 'http://hl7.org/fhir/test/StructureDefinition/pe-extension-simple') or (url = 'http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex')).not())");
checkElement(children.get(5), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension.where(((url = 'http://hl7.org/fhir/test/StructureDefinition/pe-extension-simple') or (url = 'http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex')).not())");
checkElement(children.get(6), "extension", "simple", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/code", 2, "extension('http://hl7.org/fhir/test/StructureDefinition/pe-extension-simple').value");
checkElement(children.get(7), "extension", "complex", 0, 1, false, "http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex", 4, "extension('http://hl7.org/fhir/test/StructureDefinition/pe-extension-complex')");
checkElement(children.get(8), "identifier", "identifier", 0, 1, false, "http://hl7.org/fhir/StructureDefinition/Identifier", 7, "identifier");
@ -116,7 +116,7 @@ public class PETests {
checkElement(children.get(17), "value[x]", "valueCodeableConcept", 0, 1, false, "http://hl7.org/fhir/test/StructureDefinition/pe-profile2", 4, "value.ofType(CodeableConcept)");
List<PEDefinition> gchildren = children.get(11).children();
checkElement(gchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, true, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension");
checkElement(gchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, true, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension");
checkElement(gchildren.get(1), "coding", "coding", 0, Integer.MAX_VALUE, true, "http://hl7.org/fhir/StructureDefinition/Coding", 6, "coding");
checkElement(gchildren.get(2), "text", "text", 0, 1, true, "http://hl7.org/fhir/StructureDefinition/string", 2, "text");
@ -128,17 +128,17 @@ public class PETests {
checkElement(gchildren.get(3), "text", "text", 1, 1, false, "http://hl7.org/fhir/StructureDefinition/string", 2, "text");
List<PEDefinition> ggchildren = gchildren.get(3).children("http://hl7.org/fhir/StructureDefinition/string");
checkElement(ggchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension");
checkElement(ggchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension");
checkElement(ggchildren.get(1), "value", "value", 1, 1, false, null, 3, "value");
gchildren = children.get(7).children("http://hl7.org/fhir/StructureDefinition/Extension");
checkElement(gchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension.where(((url = 'slice1') or (url = 'slice2') or (url = 'slice3')).not())");
checkElement(gchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension.where(((url = 'slice1') or (url = 'slice2') or (url = 'slice3')).not())");
checkElement(gchildren.get(1), "extension", "slice1", 0, 2, false, "http://hl7.org/fhir/StructureDefinition/Coding", 6, "extension('slice1').value");
checkElement(gchildren.get(2), "extension", "slice2", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/string", 2, "extension('slice2').value");
checkElement(gchildren.get(3), "extension", "slice3", 1, 1, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension('slice3')");
ggchildren = gchildren.get(3).children("http://hl7.org/fhir/StructureDefinition/Extension");
checkElement(ggchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 3, "extension");
checkElement(ggchildren.get(0), "extension", "extension", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/Extension", 2, "extension");
checkElement(ggchildren.get(1), "extension", "slice3a", 0, 2, false, "http://hl7.org/fhir/StructureDefinition/Coding", 6, "extension('slice3a').value");
checkElement(ggchildren.get(2), "extension", "slice3b", 0, Integer.MAX_VALUE, false, "http://hl7.org/fhir/StructureDefinition/string", 2, "extension('slice3b').value");
}
@ -177,7 +177,7 @@ public class PETests {
Assertions.assertEquals("\\-", pe.documentation());
List<PEDefinition> children = pe.children("Patient");
Assertions.assertEquals(28, children.size());
Assertions.assertEquals(26, children.size());
pe = children.get(9);
Assertions.assertEquals("birthsex", pe.name());
@ -269,7 +269,7 @@ public class PETests {
Assertions.assertEquals("May be used to represent additional information that is not part of the basic definition of the resource. To make the use of extensions safe and managable, 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.", pe.documentation());
iChildren = pe.children("http://hl7.org/fhir/StructureDefinition/Extension");
Assertions.assertEquals(4, iChildren.size());
Assertions.assertEquals(3, iChildren.size());
pe = iChildren.get(1);
Assertions.assertEquals("extension", pe.name());
Assertions.assertEquals("extension", pe.schemaName());
@ -283,7 +283,7 @@ public class PETests {
iChildren = pe.children("http://hl7.org/fhir/StructureDefinition/Extension");
Assertions.assertEquals(4, iChildren.size());
Assertions.assertEquals(3, iChildren.size());
pe = iChildren.get(1);
Assertions.assertEquals("extension", pe.name());
Assertions.assertEquals("extension", pe.schemaName());

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -93,6 +93,9 @@ public class MarkDownProcessor {
*/
// todo: dialect dependency?
public boolean isProbablyMarkdown(String content, boolean mdIfParagrapghs) {
if (content == null) {
return false;
}
if (mdIfParagrapghs && content.contains("\n")) {
return true;
}

View File

@ -722,6 +722,27 @@ public class Utilities {
return s.toString();
}
public static String javaTokenize(String cs, boolean capFirst) {
if (cs == null)
return "";
StringBuilder s = new StringBuilder();
boolean upcase = capFirst;
for (int i = 0; i < cs.length(); i++) {
char c = cs.charAt(i);
if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z') || (c >= '0' && c <= '9') || c == '_') {
if (upcase) {
s.append(Character.toUpperCase(c));
upcase = false;
} else {
s.append(c);
}
} else {
upcase = true;
}
}
return s.toString();
}
public static boolean isToken(String tail) {
if (tail == null || tail.length() == 0)

View File

@ -3,5 +3,6 @@ package org.hl7.fhir.utilities.http;
public enum HTTPAuthenticationMode {
NONE,
BASIC,
TOKEN
TOKEN,
APIKEY
}

View File

@ -43,6 +43,7 @@ import java.util.List;
import java.util.Map;
import org.hl7.fhir.utilities.Utilities;
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
/**
* see security.md - manages access to the local file system by the FHIR HAPI Core library
@ -73,6 +74,7 @@ public class ManagedWebAccess {
private static List<String> allowedDomains = new ArrayList<>();
private static IWebAccessor accessor;
private static String userAgent;
private static List<ServerDetailsPOJO> serverAuthDetails;
public static WebAccessPolicy getAccessPolicy() {
@ -108,7 +110,7 @@ public class ManagedWebAccess {
}
public static ManagedWebAccessBuilder builder() {
return new ManagedWebAccessBuilder(userAgent);
return new ManagedWebAccessBuilder(userAgent, serverAuthDetails);
}
public static HTTPResult get(String url) throws IOException {

View File

@ -4,8 +4,11 @@ import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
public class ManagedWebAccessBuilder {
@ -15,10 +18,12 @@ public class ManagedWebAccessBuilder {
private String password;
private String token;
private String accept;
private List<ServerDetailsPOJO> serverAuthDetails;
private Map<String, String> headers = new HashMap<String, String>();
public ManagedWebAccessBuilder(String userAgent) {
public ManagedWebAccessBuilder(String userAgent, List<ServerDetailsPOJO> serverAuthDetails) {
this.userAgent = userAgent;
this.serverAuthDetails = serverAuthDetails;
}
public ManagedWebAccessBuilder withAccept(String accept) {
@ -70,16 +75,59 @@ public class ManagedWebAccessBuilder {
if (userAgent != null) {
client.addHeader("User-Agent", userAgent);
}
if (authenticationMode != null && authenticationMode != HTTPAuthenticationMode.NONE) {
client.setAuthenticationMode(authenticationMode);
switch (authenticationMode) {
case BASIC :
client.setUsername(username);
client.setPassword(password);
break;
case TOKEN :
client.setToken(token);
break;
case APIKEY :
client.setApiKey(token);
break;
}
} else {
ServerDetailsPOJO settings = getServer(url);
if (settings != null) {
switch (settings.getAuthenticationType()) {
case "basic" :
client.setUsername(settings.getUsername());
client.setPassword(settings.getPassword());
client.setAuthenticationMode(HTTPAuthenticationMode.BASIC);
break;
case "token" :
client.setToken(settings.getToken());
client.setAuthenticationMode(HTTPAuthenticationMode.TOKEN);
break;
case "apikey" :
client.setApiKey(settings.getApikey());
client.setAuthenticationMode(HTTPAuthenticationMode.APIKEY);
break;
}
}
}
if (username != null || token != null) {
client.setUsername(username);
client.setPassword(password);
client.setToken(token);
client.setAuthenticationMode(authenticationMode);
}
return client;
}
private ServerDetailsPOJO getServer(String url) {
if (serverAuthDetails != null) {
for (ServerDetailsPOJO t : serverAuthDetails) {
if (url.startsWith(t.getUrl())) {
return t;
}
}
}
return null;
}
public HTTPResult get(String url) throws IOException {
switch (ManagedWebAccess.getAccessPolicy()) {
case DIRECT:

View File

@ -54,6 +54,9 @@ public class SimpleHTTPClient {
@Getter @Setter
private String token;
@Getter @Setter
private String apiKey;
public void addHeader(String name, String value) {
headers.add(new Header(name, value));
}
@ -128,6 +131,8 @@ public class SimpleHTTPClient {
String auth = username+":"+password;
byte[] encodedAuth = Base64.getEncoder().encode(auth.getBytes(StandardCharsets.UTF_8));
authHeaderValue = "Basic " + new String(encodedAuth);
} else if (authenticationMode == HTTPAuthenticationMode.APIKEY) {
c.setRequestProperty("Api-Key", apiKey);
}
if (authHeaderValue != null) {

View File

@ -1118,4 +1118,5 @@ public class I18nConstants {
public static final String SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_ELEMENT = "SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_ELEMENT";
public static final String SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_TYPE = "SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_TYPE";
public static final String CODESYSTEM_CS_COMPLETE_AND_EMPTY = "CODESYSTEM_CS_COMPLETE_AND_EMPTY";
public static final String VALIDATION_VAL_VERSION_NOHASH = "VALIDATION_VAL_VERSION_NOHASH";
}

View File

@ -251,6 +251,7 @@ public class RenderingI18nContext extends I18nBase {
public static final String DATA_REND_TIMING = "DATA_REND_TIMING";
public static final String DATA_REND_TO_DO = "DATA_REND_TO_DO";
public static final String GENERAL_UCUM = "GENERAL_UCUM";
public static final String GENERAL_NCI_THES = "GENERAL_NCI_THES";
public static final String DATA_REND_UNKNWN = "DATA_REND_UNKNWN";
public static final String DATA_REND_UNRD_EX = "DATA_REND_UNRD_EX";
public static final String DATA_REND_UNTIL = "DATA_REND_UNTIL";

View File

@ -9,7 +9,7 @@ import javax.annotation.Nullable;
import org.hl7.fhir.utilities.http.HTTPAuthenticationMode;
import org.hl7.fhir.utilities.http.SimpleHTTPClient;
import org.hl7.fhir.utilities.settings.FhirSettings;
import org.hl7.fhir.utilities.settings.PackageServerPOJO;
import org.hl7.fhir.utilities.settings.ServerDetailsPOJO;
import lombok.Getter;
@ -65,7 +65,7 @@ public class PackageServer {
return servers;
}
public static PackageServer getPackageServerFromPOJO(PackageServerPOJO pojo) {
public static PackageServer getPackageServerFromPOJO(ServerDetailsPOJO pojo) {
return new PackageServer(pojo.getUrl())
.withAuthenticationMode(getModeFromPOJO(pojo))
.withServerType(
@ -77,7 +77,7 @@ public class PackageServer {
}
@Nullable
private static HTTPAuthenticationMode getModeFromPOJO(PackageServerPOJO pojo) {
private static HTTPAuthenticationMode getModeFromPOJO(ServerDetailsPOJO pojo) {
if (pojo.getAuthenticationType().equalsIgnoreCase("basic")) return HTTPAuthenticationMode.BASIC;
if (pojo.getAuthenticationType().equalsIgnoreCase("token")) return HTTPAuthenticationMode.TOKEN;
return null;

View File

@ -227,11 +227,11 @@ public class FhirSettings {
return instance.fhirSettings.getPackageManagement().getIgnoreDefaultServers();
}
public static List<PackageServerPOJO> getPackageServers() {
public static List<ServerDetailsPOJO> getPackageServers() {
getInstance();
if (instance.fhirSettings.getPackageManagement() == null) {
return Collections.emptyList();
}
return List.of(instance.fhirSettings.getPackageManagement().getServers().toArray(new PackageServerPOJO[]{}));
return List.of(instance.fhirSettings.getPackageManagement().getServers().toArray(new ServerDetailsPOJO[]{}));
}
}

View File

@ -40,6 +40,7 @@ public class FhirSettingsPOJO {
private String txFhirLocal;
private PackageManagementPOJO packageManagement;
private TerminologyServersPOJO terminologyServers;
protected FhirSettingsPOJO() {
apiKeys = null;
@ -54,5 +55,6 @@ public class FhirSettingsPOJO {
txFhirLocal = TX_SERVER_LOCAL;
packageManagement = null;
terminologyServers = null;
}
}

View File

@ -16,7 +16,7 @@ public class PackageManagementPOJO {
private Boolean ignoreDefaultServers;
private List<PackageServerPOJO> servers;
private List<ServerDetailsPOJO> servers;
protected PackageManagementPOJO() {
ignoreDefaultServers = false;

View File

@ -9,13 +9,15 @@ import lombok.extern.jackson.Jacksonized;
@Builder
@Jacksonized
@AllArgsConstructor
public class PackageServerPOJO {
public class ServerDetailsPOJO {
String url;
// possible values: none, basic, token, apikey
String authenticationType;
String serverType;
// npm or fhir, because the FHIR npm usage varies a little bit from general NPM usage (change over time)
String serverType;
String username;
@ -23,4 +25,6 @@ public class PackageServerPOJO {
String token;
String apikey;
}

View File

@ -0,0 +1,22 @@
package org.hl7.fhir.utilities.settings;
import java.util.ArrayList;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.extern.jackson.Jacksonized;
@Data
@Builder
@Jacksonized
@AllArgsConstructor
public class TerminologyServersPOJO {
private List<ServerDetailsPOJO> servers;
protected TerminologyServersPOJO() {
servers = new ArrayList<>();
}
}

View File

@ -1147,7 +1147,9 @@ NO_VALID_DISPLAY_AT_ALL = Cannot validate display Name ''{0}'' for {1}#{2}: No d
SD_BASE_EXPERIMENTAL = The definition builds on ''{0}'' which is experimental, but this definition is not labeled as experimental
SD_ED_EXPERIMENTAL_BINDING = The definition for the element ''{0}'' binds to the value set ''{1}'' which is experimental, but this structure is not labeled as experimental
VALIDATION_NO_EXPERIMENTAL_CONTENT = Experimental content is not allowed in this context
SD_ED_ADDITIONAL_BINDING_USAGE_UNKNOWN = The Usage Context {0}#{1} is not recognised and may not be correct
SD_ED_ADDITIONAL_BINDING_USAGE_UNKNOWN = The Usage Context {0}#{1} is not recognized so cannot be validated
SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_ELEMENT = The Usage Context {0}#{1} is a reference to an element that does not exist
SD_ED_ADDITIONAL_BINDING_USAGE_INVALID_TYPE = The Usage Context value must be of type {1} not {0}
CODESYSTEM_CS_COMPLETE_AND_EMPTY = When a CodeSystem has content = ''complete'', it doesn't make sense for there to be no concepts defined
VALIDATION_VAL_VERSION_NOHASH = Version ''{0}'' contains a ''#'', which as this character is used in some URLs to separate the version and the fragment id. When version does include '#', systems will not be able to parse the URL

View File

@ -245,6 +245,7 @@ DATA_REND_THESE_CODES = One of these codes:
DATA_REND_TIMING = Timing
DATA_REND_TO_DO = to do: {0}
GENERAL_UCUM = UCUM
GENERAL_NCI_THES = NCI Thesaurus
DATA_REND_UNKNWN = unknown
DATA_REND_UNRD_EX = WARNING: Unrenderable Modifier Extension!
DATA_REND_UNTIL = Until {0}

View File

@ -87,7 +87,7 @@ public class FhirSettingsTests implements ResourceLoaderTests {
assertTrue(fhirSettings.getPackageManagement().getIgnoreDefaultServers());
List<PackageServerPOJO> packageServers = fhirSettings.getPackageManagement().getServers();
List<ServerDetailsPOJO> packageServers = fhirSettings.getPackageManagement().getServers();
assertEquals(2, packageServers.size());

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -135,6 +135,7 @@ public class ValidatorCli {
new TxTestsTask(),
new TransformTask(),
new VersionTask(),
new CodeGenTask(),
defaultCliTask);
}

View File

@ -90,6 +90,8 @@ public class CliContext {
private String snomedCT = "900000000000207008";
@JsonProperty("targetVer")
private String targetVer = null;
@JsonProperty("packageName")
private String packageName = null;
@JsonProperty("noEcosystem")
private boolean noEcosystem = false;
@ -101,6 +103,8 @@ public class CliContext {
private QuestionnaireMode questionnaireMode = QuestionnaireMode.CHECK;
@JsonProperty("level")
private ValidationLevel level = ValidationLevel.HINTS;
@JsonProperty("options")
private List<String> options = new ArrayList<String>();
@JsonProperty("profiles")
private List<String> profiles = new ArrayList<String>();
@ -435,6 +439,25 @@ public class CliContext {
return this;
}
@JsonProperty("options")
public List<String> getOptions() {
return options;
}
@JsonProperty("options")
public CliContext setOptions(List<String> options) {
this.options = options;
return this;
}
public CliContext addOption(String option) {
if (this.options == null) {
this.options = new ArrayList<>();
}
this.options.add(option);
return this;
}
@JsonProperty("mode")
public EngineMode getMode() {
return mode;
@ -638,6 +661,17 @@ public class CliContext {
return this;
}
@JsonProperty("packageName")
public String getPackageName() {
return packageName;
}
@JsonProperty("packageName")
public CliContext setPackageName(String packageName) {
this.packageName = packageName;
return this;
}
@JsonProperty("doDebug")
public boolean isDoDebug() {
return doDebug;
@ -857,10 +891,12 @@ public class CliContext {
Objects.equals(fhirpath, that.fhirpath) &&
Objects.equals(snomedCT, that.snomedCT) &&
Objects.equals(targetVer, that.targetVer) &&
Objects.equals(packageName, that.packageName) &&
Objects.equals(igs, that.igs) &&
Objects.equals(questionnaireMode, that.questionnaireMode) &&
Objects.equals(level, that.level) &&
Objects.equals(profiles, that.profiles) &&
Objects.equals(options, that.options) &&
Objects.equals(sources, that.sources) &&
Objects.equals(crumbTrails, that.crumbTrails) &&
Objects.equals(showMessageIds, that.showMessageIds) &&
@ -885,7 +921,7 @@ public class CliContext {
public int hashCode() {
return Objects.hash(baseEngine, doNative, extensions, hintAboutNonMustSupport, recursive, doDebug, assumeValidRestReferences, canDoNative, noInternalCaching,
noExtensibleBindingMessages, noInvariants, displayWarnings, wantInvariantsInMessages, map, output, outputSuffix, htmlOutput, txServer, sv, txLog, txCache, mapLog, lang, srcLang, tgtLang, fhirpath, snomedCT,
targetVer, igs, questionnaireMode, level, profiles, sources, inputs, mode, locale, locations, crumbTrails, showMessageIds, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars,
targetVer, packageName, igs, questionnaireMode, level, profiles, options, sources, inputs, mode, locale, locations, crumbTrails, showMessageIds, forPublication, showTimes, allowExampleUrls, outputStyle, jurisdiction, noUnicodeBiDiControlChars,
watchMode, watchScanDelay, watchSettleTime, bestPracticeLevel, unknownCodeSystemsCauseErrors, noExperimentalContent, advisorFile, htmlInMarkdownCheck, allowDoubleQuotesInFHIRPath, checkIPSCodes);
}
@ -921,10 +957,12 @@ public class CliContext {
", fhirpath='" + fhirpath + '\'' +
", snomedCT='" + snomedCT + '\'' +
", targetVer='" + targetVer + '\'' +
", packageName='" + packageName + '\'' +
", igs=" + igs +
", questionnaireMode=" + questionnaireMode +
", level=" + level +
", profiles=" + profiles +
", options=" + options +
", sources=" + sources +
", inputs=" + inputs +
", mode=" + mode +

View File

@ -35,6 +35,8 @@ import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.StructureDefinition;
import org.hl7.fhir.r5.model.StructureMap;
import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.profilemodel.gen.PECodeGenerator;
import org.hl7.fhir.r5.profilemodel.gen.PECodeGenerator.ExtensionPolicy;
import org.hl7.fhir.r5.renderers.spreadsheets.CodeSystemSpreadsheetGenerator;
import org.hl7.fhir.r5.renderers.spreadsheets.ConceptMapSpreadsheetGenerator;
import org.hl7.fhir.r5.renderers.spreadsheets.StructureDefinitionSpreadsheetGenerator;
@ -73,6 +75,8 @@ import org.hl7.fhir.validation.cli.utils.VersionSourceInformation;
import org.hl7.fhir.validation.instance.advisor.JsonDrivenPolicyAdvisor;
import org.hl7.fhir.validation.instance.advisor.TextDrivenPolicyAdvisor;
import kotlin.NotImplementedError;
public class ValidationService {
private final SessionCache sessionCache;
@ -827,4 +831,63 @@ public class ValidationService {
}
}
public void codeGen(CliContext cliContext, ValidationEngine validationEngine) throws IOException {
boolean ok = true;
if (cliContext.getProfiles().isEmpty()) {
System.out.println("Must specify at least one profile to generate code for with -profile or -profiles ");
ok = false;
}
if (cliContext.getPackageName() == null) {
System.out.println("Must provide a Java package name (-package-name)");
ok = false;
}
if (cliContext.getSv() == null) {
System.out.println("Must specify a version (-version)");
ok = false;
} else if (!VersionUtilities.isR4Ver(cliContext.getSv()) && !VersionUtilities.isR5Ver(cliContext.getSv())) {
System.out.println("Only versions 4 and 5 are supported (-version)");
ok = false;
}
if (cliContext.getOutput() == null) {
System.out.println("Must provide an output directory (-output)");
ok = false;
}
if (ok) {
PECodeGenerator gen = new PECodeGenerator(validationEngine.getContext());
gen.setFolder(cliContext.getOutput());
gen.setExtensionPolicy(ExtensionPolicy.Complexes);
gen.setNarrative(cliContext.getOptions().contains("narrative"));
gen.setMeta(cliContext.getOptions().contains("meta"));
gen.setLanguage(Locale.getDefault().toLanguageTag());
gen.setContained(cliContext.getOptions().contains("contained"));
gen.setKeyElementsOnly(!cliContext.getOptions().contains("all-elements"));
gen.setGenDate(new SimpleDateFormat().format(new Date()));
gen.setPkgName(cliContext.getPackageName());
if (VersionUtilities.isR4Ver(cliContext.getSv())) {
gen.setVersion("r4");
} else {
gen.setVersion("r5");
}
for (String profile : cliContext.getProfiles()) {
if (profile.endsWith("*")) {
for (StructureDefinition sd : validationEngine.getContext().fetchResourcesByType(StructureDefinition.class)) {
if (sd.getUrl().startsWith(profile.replace("*", ""))) {
gen.setCanonical(sd.getUrl());
System.out.print("Generate for "+sd.getUrl());
String s = gen.execute();
System.out.println(": "+s);
}
}
} else {
gen.setCanonical(profile);
System.out.print("Generate for "+profile);
String s = gen.execute();
System.out.println(": "+s);
}
}
System.out.println("Done");
}
}
}

View File

@ -0,0 +1,43 @@
package org.hl7.fhir.validation.cli.tasks;
import java.io.PrintStream;
import org.hl7.fhir.utilities.TimeTracker;
import org.hl7.fhir.validation.ValidationEngine;
import org.hl7.fhir.validation.cli.model.CliContext;
import org.hl7.fhir.validation.cli.services.ValidationService;
import org.hl7.fhir.validation.cli.utils.EngineMode;
public class CodeGenTask extends ValidationEngineTask {
@Override
public String getName() {
return "codegen";
}
@Override
public String getDisplayName() {
return "Code Generation";
}
@Override
public boolean isHidden() {
return true;
}
@Override
public boolean shouldExecuteTask(CliContext cliContext, String[] args) {
return cliContext.getMode() == EngineMode.CODEGEN;
}
@Override
public void printHelp(PrintStream out) {
}
@Override
public void executeTask(ValidationService validationService, ValidationEngine validationEngine, CliContext cliContext, String[] args, TimeTracker tt, TimeTracker.Session tts) throws Exception {
validationService.codeGen(cliContext, validationEngine);
}
}

View File

@ -13,5 +13,6 @@ public enum EngineMode {
FHIRPATH,
VERSION,
RUN_TESTS,
INSTALL
INSTALL,
CODEGEN
}

View File

@ -30,6 +30,9 @@ public class Params {
public static final String HTTPS_PROXY = "-https-proxy";
public static final String PROXY_AUTH = "-auth";
public static final String PROFILE = "-profile";
public static final String PROFILES = "-profiles";
public static final String OPTION = "-option";
public static final String OPTIONS = "-options";
public static final String BUNDLE = "-bundle";
public static final String QUESTIONNAIRE = "-questionnaire";
public static final String NATIVE = "-native";
@ -42,9 +45,11 @@ public class Params {
public static final String EXTENSION = "-extension";
public static final String HINT_ABOUT_NON_MUST_SUPPORT = "-hintAboutNonMustSupport";
public static final String TO_VERSION = "-to-version";
public static final String PACKAGE_NAME = "-package-name";
public static final String DO_NATIVE = "-do-native";
public static final String NO_NATIVE = "-no-native";
public static final String COMPILE = "-compile";
public static final String CODEGEN = "-codegen";
public static final String TRANSFORM = "-transform";
public static final String LANG_TRANSFORM = "-lang-transform";
public static final String NARRATIVE = "-narrative";
@ -184,11 +189,39 @@ public class Params {
} else if (args[i].equals(PROFILE)) {
String p = null;
if (i + 1 == args.length) {
throw new Error("Specified -profile without indicating profile source");
throw new Error("Specified -profile without indicating profile url");
} else {
p = args[++i];
cliContext.addProfile(p);
}
} else if (args[i].equals(PROFILES)) {
String p = null;
if (i + 1 == args.length) {
throw new Error("Specified -profiles without indicating profile urls");
} else {
p = args[++i];
for (String s : p.split("\\,")) {
cliContext.addProfile(s);
}
}
} else if (args[i].equals(OPTION)) {
String p = null;
if (i + 1 == args.length) {
throw new Error("Specified -option without indicating option value");
} else {
p = args[++i];
cliContext.addOption(p);
}
} else if (args[i].equals(OPTIONS)) {
String p = null;
if (i + 1 == args.length) {
throw new Error("Specified -options without indicating option values");
} else {
p = args[++i];
for (String s : p.split("\\,")) {
cliContext.addOption(s);
}
}
} else if (args[i].equals(BUNDLE)) {
String profile = null;
String rule = null;
@ -294,6 +327,9 @@ public class Params {
} else if (args[i].equals(TO_VERSION)) {
cliContext.setTargetVer(args[++i]);
cliContext.setMode(EngineMode.VERSION);
} else if (args[i].equals(PACKAGE_NAME)) {
cliContext.setPackageName(args[++i]);
cliContext.setMode(EngineMode.CODEGEN);
} else if (args[i].equals(DO_NATIVE)) {
cliContext.setCanDoNative(true);
} else if (args[i].equals(NO_NATIVE)) {
@ -307,6 +343,8 @@ public class Params {
} else if (args[i].equals(COMPILE)) {
cliContext.setMap(args[++i]);
cliContext.setMode(EngineMode.COMPILE);
} else if (args[i].equals(CODEGEN)) {
cliContext.setMode(EngineMode.CODEGEN);
} else if (args[i].equals(NARRATIVE)) {
cliContext.setMode(EngineMode.NARRATIVE);
} else if (args[i].equals(SPREADSHEET)) {

View File

@ -5889,6 +5889,10 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
if (isHL7Core(element) && !isExample()) {
ok = checkPublisherConsistency(valContext, errors, element, stack, contained) && ok;
}
String version = element.getNamedChildValue("version");
if (!Utilities.noString(version)) {
warning(errors, "2024-10-18", IssueType.BUSINESSRULE, element.line(), element.col(), stack.getLiteralPath(), !version.contains("#"), I18nConstants.VALIDATION_VAL_VERSION_NOHASH, version);
}
}
if (element.getType().equals(BUNDLE)) {
return new BundleValidator(this, serverBase).validateBundle(errors, element, stack, checkSpecials, valContext, pct, mode) && ok;
@ -7061,7 +7065,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
errorSummaryForSlicingAsText(ei.sliceInfo), ei.sliceInfo, I18nConstants.THIS_ELEMENT_DOES_NOT_MATCH_ANY_KNOWN_SLICE_);
}
} else if (ei.definition.getSlicing().getRules().equals(ElementDefinition.SlicingRules.CLOSED)) {
bh.see(rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTSLICE, (profile == null ? "" : " defined in the profile " + profile.getVersionedUrl()), errorSummaryForSlicing(ei.sliceInfo)));
bh.see(rule(errors, NO_RULE_DATE, IssueType.INVALID, ei.line(), ei.col(), ei.getPath(), false, I18nConstants.VALIDATION_VAL_PROFILE_NOTSLICE, (profile == null ? "" : "defined in the profile " + profile.getVersionedUrl()), errorSummaryForSlicing(ei.sliceInfo)));
}
} else {
// Don't raise this if we're in an abstract profile, like Resource

View File

@ -26,7 +26,7 @@ import org.hl7.fhir.validation.instance.type.CodeSystemValidator.PropertyDef;
import org.hl7.fhir.validation.instance.utils.NodeStack;
import org.hl7.fhir.validation.instance.utils.ValidationContext;
public class CodeSystemValidator extends BaseValidator {
public class CodeSystemValidator extends BaseValidator {
public enum KnownProperty {
Status, Inactive, EffectiveDate, DeprecationDate, RetirementDate, NotSelectable, Parent, Child, PartOf, Synonym, Comment, ItemWeight;

View File

@ -1129,7 +1129,7 @@ public class StructureDefinitionValidator extends BaseValidator {
}
}
} else {
warning(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.SD_ED_ADDITIONAL_BINDING_USAGE_UNKNOWN, system, code);
hint(errors, "2024-09-17", IssueType.BUSINESSRULE, stack.getLiteralPath(), false, I18nConstants.SD_ED_ADDITIONAL_BINDING_USAGE_UNKNOWN, system, code);
}
}
return ok;

View File

@ -220,7 +220,7 @@ public class ValidationContext {
public ValidationContext forSlicing() {
ValidationContext res = new ValidationContext(appContext);
res.resource = resource;
res.rootResource = resource;
res.rootResource = rootResource;
res.groupingResource = groupingResource;
res.profile = profile;
res.checkSpecials = false;

View File

@ -34,6 +34,7 @@ import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.client.ITerminologyClient;
import org.hl7.fhir.r5.test.utils.CompareUtilities;
import org.hl7.fhir.r5.utils.client.EFhirClientException;
import org.hl7.fhir.utilities.CommaSeparatedStringBuilder;
import org.hl7.fhir.utilities.FhirPublication;
import org.hl7.fhir.utilities.TextFile;
import org.hl7.fhir.utilities.Utilities;
@ -43,6 +44,8 @@ import org.hl7.fhir.utilities.json.model.JsonArray;
import org.hl7.fhir.utilities.json.model.JsonObject;
import org.hl7.fhir.utilities.json.parser.JsonParser;
import okhttp3.internal.http2.Header;
public class TxTester {
@ -81,6 +84,12 @@ public class TxTester {
System.out.println("Run terminology service Tests");
System.out.println(" Source for tests: "+loader.describe());
System.out.println(" Output Directory: "+output);
if (!new File(output).exists()) {
Utilities.createDirectory(output);
}
if (!new File(output).exists()) {
throw new IOException("Unable to create output directory "+output);
}
System.out.println(" Term Service Url: "+server);
System.out.println(" External Strings: "+(externals != null));
System.out.println(" Test Exec Modes: "+modes.toString());
@ -95,7 +104,7 @@ public class TxTester {
json.add("date", new SimpleDateFormat("EEE, MMM d, yyyy HH:mmZ", new Locale("en", "US")).format(Calendar.getInstance().getTime()) + timezone());
try {
JsonObject tests = loadTests();
ITerminologyClient tx = connectToServer();
ITerminologyClient tx = connectToServer(modes);
boolean ok = checkClient(tx);
for (JsonObject suite : tests.getJsonObjects("suites")) {
if ((!suite.has("mode") || modes.contains(suite.asString("mode")))) {
@ -144,19 +153,21 @@ public class TxTester {
return JsonParser.parseObject(loader.loadContent("test-cases.json"));
}
private ITerminologyClient connectToServer() throws URISyntaxException {
private ITerminologyClient connectToServer(List<String> modes) throws URISyntaxException {
System.out.println("Connect to "+server);
return new TerminologyClientFactory(FhirPublication.R4).makeClient("Test-Server", server, "Tools/Java", null);
ITerminologyClient client = new TerminologyClientFactory(FhirPublication.R4).makeClient("Test-Server", server, "Tools/Java", null);
return client;
}
public String executeTest(JsonObject suite, JsonObject test, List<String> modes) throws URISyntaxException, FHIRFormatError, FileNotFoundException, IOException {
error = null;
if (tx == null) {
tx = connectToServer();
tx = connectToServer(modes);
checkClient(tx);
}
List<Resource> setup = loadSetupResources(suite);
if (runTest(test, tx, setup, modes, "*", null)) {
return null;
} else {
@ -174,10 +185,12 @@ public class TxTester {
List<Resource> setup = loadSetupResources(suite);
boolean ok = true;
for (JsonObject test : suite.getJsonObjects("tests")) {
if (test.asBoolean("disabled")) {
ok = true;
} else {
ok = runTest(test, tx, setup, modes, filter, outputS.forceArray("tests")) && ok;
if ((!test.has("mode") || modes.contains(test.asString("mode")))) {
if (test.asBoolean("disabled")) {
ok = true;
} else {
ok = runTest(test, tx, setup, modes, filter, outputS.forceArray("tests")) && ok;
}
}
}
return ok;
@ -193,12 +206,21 @@ public class TxTester {
outputT.add("name", test.asString("name"));
if (Utilities.noString(filter) || filter.equals("*") || test.asString("name").contains(filter)) {
System.out.print(" Test "+test.asString("name")+": ");
Header header = null;
try {
if (test.has("header")) {
JsonObject hdr = test.getJsonObject("header");
if (hdr.has("mode") && modes.contains(hdr.asString("mode"))) {
header = new Header(hdr.asString("name"), hdr.asString("value"));
tx.getClientHeaders().addHeader(header);
}
}
Parameters req = (Parameters) loader.loadResource(chooseParam(test, "request", modes));
String fn = chooseParam(test, "response", modes);
String resp = TextFile.bytesToString(loader.loadContent(fn));
String fp = Utilities.path("[tmp]", serverId(), fn);
String fp = this.output == null ? Utilities.path("[tmp]", serverId(), fn) : Utilities.path(this.output, fn);
File fo = ManagedFileAccess.file(fp);
if (fo.exists()) {
fo.delete();
@ -208,15 +230,15 @@ public class TxTester {
String lang = test.asString("Content-Language");
String msg = null;
if (test.asString("operation").equals("expand")) {
msg = expand(test.str("name"), tx, setup, req, resp, fp, lang, profile, ext);
msg = expand(test.str("name"), tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else if (test.asString("operation").equals("validate-code")) {
msg = validate(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext);
msg = validate(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else if (test.asString("operation").equals("cs-validate-code")) {
msg = validateCS(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext);
msg = validateCS(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else if (test.asString("operation").equals("lookup")) {
msg = lookup(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext);
msg = lookup(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else if (test.asString("operation").equals("translate")) {
msg = translate(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext);
msg = translate(test.str("name"),tx, setup, req, resp, fp, lang, profile, ext, getResponseCode(test));
} else {
throw new Exception("Unknown Operation "+test.asString("operation"));
}
@ -230,12 +252,18 @@ public class TxTester {
if (msg != null) {
outputT.add("message", msg);
}
if (header != null) {
tx.getClientHeaders().removeHeader(header);
}
return msg == null;
} catch (Exception e) {
System.out.println(" ... Exception: "+e.getMessage());
System.out.print(" ");
error = e.getMessage();
e.printStackTrace();
if (header != null) {
tx.getClientHeaders().removeHeader(header);
}
return false;
}
} else {
@ -244,6 +272,14 @@ public class TxTester {
}
}
private String getResponseCode(JsonObject test) {
if (test.has("http-code")) {
return test.asString("http-code");
} else {
return "2xx";
}
}
private String chooseParam(JsonObject test, String name, List<String> modes) {
for (String mode : modes) {
if (test.has(name+":"+mode)) {
@ -265,23 +301,29 @@ public class TxTester {
return new URI(server).getHost();
}
private String lookup(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private String lookup(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
tx.setContentLanguage(lang);
p.getParameter().addAll(profile.getParameter());
int code = 0;
String pj;
try {
Parameters po = tx.lookupCode(p);
TxTesterScrubbers.scrubParams(po);
TxTesterSorters.sortParameters(po);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(po);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
TxTesterScrubbers.scrubOO(oo, tight);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, pj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
@ -290,23 +332,29 @@ public class TxTester {
return diff;
}
private String translate(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private String translate(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
tx.setContentLanguage(lang);
p.getParameter().addAll(profile.getParameter());
int code = 0;
String pj;
try {
Parameters po = tx.translate(p);
TxTesterScrubbers.scrubParams(po);
TxTesterSorters.sortParameters(po);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(po);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
TxTesterScrubbers.scrubOO(oo, tight);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, pj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
@ -315,23 +363,29 @@ public class TxTester {
return diff;
}
private String expand(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private String expand(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
tx.setContentLanguage(lang);
p.getParameter().addAll(profile.getParameter());
int code = 0;
String vsj;
try {
ValueSet vs = tx.expandValueset(null, p);
TxTesterScrubbers.scrubVS(vs, tight);
TxTesterSorters.sortValueSet(vs);
vsj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(vs);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
TxTesterScrubbers.scrubOO(oo, tight);
vsj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, vsj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
@ -340,23 +394,41 @@ public class TxTester {
return diff;
}
private String validate(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private boolean httpCodeOk(String tcode, int code) {
switch (tcode) {
case "2xx" : return code >= 200 && code < 300;
case "3xx" : return code >= 300 && code < 400;
case "4xx" : return code >= 400 && code < 500;
case "5xx" : return code >= 500 && code < 600;
default:
throw new Error("unknown code string "+tcode);
}
}
private String validate(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
p.getParameter().addAll(profile.getParameter());
tx.setContentLanguage(lang);
int code = 0;
String pj;
try {
Parameters po = tx.validateVS(p);
TxTesterScrubbers.scrubParams(po);
TxTesterSorters.sortParameters(po);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(po);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
TxTesterScrubbers.scrubOO(oo, tight);
oo.setText(null);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, pj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
@ -365,23 +437,29 @@ public class TxTester {
return diff;
}
private String validateCS(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext) throws IOException {
private String validateCS(String id, ITerminologyClient tx, List<Resource> setup, Parameters p, String resp, String fp, String lang, Parameters profile, JsonObject ext, String tcode) throws IOException {
for (Resource r : setup) {
p.addParameter().setName("tx-resource").setResource(r);
}
p.getParameter().addAll(profile.getParameter());
tx.setContentLanguage(lang);
int code = 0;
String pj;
try {
Parameters po = tx.validateCS(p);
TxTesterScrubbers.scrubParams(po);
TxTesterSorters.sortParameters(po);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(po);
code = 200;
} catch (EFhirClientException e) {
code = e.getCode();
OperationOutcome oo = e.getServerError();
oo.setText(null);
pj = new org.hl7.fhir.r5.formats.JsonParser().setOutputStyle(OutputStyle.PRETTY).composeString(oo);
}
if (tcode != null && !httpCodeOk(tcode, code)) {
return "Response Code fail: should be '"+tcode+"' but is '"+code+"'";
}
String diff = CompareUtilities.checkJsonSrcIsSame(id, resp, pj, false, ext);
if (diff != null) {
Utilities.createDirectory(Utilities.getDirectoryForFile(fp));
@ -477,7 +555,7 @@ public class TxTester {
org.hl7.fhir.r4.model.Resource r4 = VersionConvertorFactory_40_50.convertResource(res);
String p = Utilities.path(folder, "r4", filename);
Utilities.createDirectory(Utilities.getDirectoryForFile(p));
new org.hl7.fhir.r4.formats.JsonParser().compose(ManagedFileAccess.outStream(p), r4);
new org.hl7.fhir.r4.formats.JsonParser().setOutputStyle(org.hl7.fhir.r4.formats.IParser.OutputStyle.PRETTY).compose(ManagedFileAccess.outStream(p), r4);
return res;
}

View File

@ -4,6 +4,7 @@ import org.hl7.fhir.r5.model.DomainResource;
import org.hl7.fhir.r5.model.Element;
import org.hl7.fhir.r5.model.Extension;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.r5.model.Parameters;
import org.hl7.fhir.r5.model.Resource;
import org.hl7.fhir.r5.model.ValueSet;
@ -90,6 +91,11 @@ public class TxTesterScrubbers {
public static void scrubOO(OperationOutcome po, boolean tight) {
scrubDR(po, tight);
po.getIssue().removeIf(i -> i.hasDiagnostics() & !i.hasDetails());
for (OperationOutcomeIssueComponent iss : po.getIssue()) {
if (iss.hasDiagnostics() && !iss.getDiagnostics().toLowerCase().contains("x-request-id")) {
iss.setDiagnostics(null);
}
}
}
}

View File

@ -212,8 +212,8 @@ public class TxTesterSorters {
if (code1 != null && code2 != null && !code1.equals(code2)) {
return code1.compareTo(code2);
}
String v1 = o1.getPart("value") != null && o1.getPart("value").hasPrimitiveValue() ? o1.getPart("value").getValue().primitiveValue().toLowerCase() : null;
String v2 = o2.getPart("value") != null && o2.getPart("value").hasPrimitiveValue() ? o2.getPart("value").getValue().primitiveValue().toLowerCase() : null;
String v1 = o1.getPart("value") != null && o1.getPart("value").getValue().hasPrimitiveValue() ? o1.getPart("value").getValue().primitiveValue().toLowerCase() : null;
String v2 = o2.getPart("value") != null && o2.getPart("value").getValue().hasPrimitiveValue() ? o2.getPart("value").getValue().primitiveValue().toLowerCase() : null;
if (v1 != null && v2 != null && !v1.equals(v2)) {
return v1.compareTo(v2);
}

View File

@ -1,207 +0,0 @@
# Introduction
This document describes the use of the advisor format that advises the validator which rules to enforce.
By default the validator enforces the entire FHIR specification as described. Implementers simply wishing
to know whether a resource is valid or not should not be using the advisor functionality.
The advisor functionality exists to assist implementers who are using the validator in pipelines where
known issues are tolerated and suppressed. Usually this arises because additional rules were added in
later validator versions after design decision had been made, and the specific issues cannot be rectified,
though there are other reasons.
Warning: Implementers use the advisor functionality to hide known issues with their resources.
To use this functionality, use the command line parameter ```-rules {filename}``` to make a set of rules apply.
There are two ways to turn off validation rules:
* Suppressing output messages
* Controlling what validation runs
## Suppressing Output Messages
This approach is conceptually simple: the entire validation is executed, and then
messages are filtered from the output based on their messageId.
The message ids are not usually shown in the validator output; use the command line parameter
```-show-message-ids``` to show the message ids in the output.
Messages can be suppressed by id, or by path and id (where path is the element the message is associated with).
Some notes on this approach:
* This turns off all uses of that message id(+path) - there's no way to be selective about what context they occur in beyond the element path
* the messageId is directly tied to the format of the message. For some messages, there are multiple variants of the message, each with their own id, and these all need to be suppressed
* message ids are stable (including across languages), but sometimes an existing message is improved in some contexts, and new message ids are introduced in later versions
* the underlying validation still happens, even when the messages are suppressed
* for invariants in profiles, the message id is the profile URL and the invariant id e.g. http://hl7.org/fhir/StructureDefinition/DomainResource#dom-6
## Controlling what validation runs
An alternative approach is to control what parts of the validation logic are executed.
As each step of the validation process occurs, the validator looks in the advisor file
to see whether any applicable rules apply at the point it is at, and proceeds as advised
by the rules in the advisor file.
# Validation Rules
The validator uses several different kinds of rules:
* resource: what to do when validating a resource
* element: what to do when validating an element
* invariant: what to when checking an invariant
* coded: what to do when validating a coded element (e.g. an element with a binding)
* reference: what to do when validating a reference to another resource
* contained: what to do when validating a contained resource
Each rule comes with a set of filters that decide whether the rule applies in a given context, and a set of options to choose from
## resource
What to do when validating a resource
Filters:
* path: the path in/from the validated resource where this resource is found. For the validated resource itself, this is the type of the resource, but for contained/referenced resource etc, this can be a complex path
* type: the type of the resource e.g. Patient
Options:
* base: validate against the base standard
* stated: validate against the profiles stated in the parameters to the validator
* meta: validate against the profiles stated in the meta element
* global: validate against the known global profiles
## element
What to do when validating an element
Filters:
* path: the path in/from the validated resource where this element is found
* Structure: the url|version for the applicable profile (may be a the base definition of a resource)
* id: the element id in the profile
Options:
* cardinality: check the element against it's stated cardinality
* invariants: check the element against it's applicable invariants
* bindings: check the element against it's applicable bindings (including additional bindings)
* fixed: check the element against it's applicable fixed/pattern values
Note that slicing determination happens irrespective of these options
## invariant
What do when evaluating an invariant
Filters:
* path: the path in/from the validated resource where this element is found
* Structure: the url|version for the applicable profile (may be a the base definition of a resource)
* id: the element id in the profile
* key: the key assigned to the invariant
Options:
* check: check the invariant
* warning: downgrade to a warning (if it's an error)
## coded
What to do when validating an element with a binding (including additional bindings)
Filters:
* path: the path in/from the validated resource where this element is found
* Structure: the url|version for the applicable profile (may be a the base definition of a resource)
* id: the element id in the profile
* type: the data type of the element
* kind: the binding strength (required, extensible, preferred)
* valueSet: the url|version of the valueSet in the binding
* system: a code system in the coded element (note: system isn't known at the decision point for `code` datatypes)
Options:
* concepts: check that the concepts are valid
* displays: check that the displays are valid
* status: check the status of the applicable value sets and code systems
## reference
what to do when validating a reference
Filters:
* path: the path in/from the validated resource where this element is found
* Structure: the url|version for the applicable profile (may be a the base definition of a resource)
* id: the element id in the profile
* url: the reference itself
Options:
* exists: check that the reference can be resolved
* type: check that the reference resolves to something of the correct type
* valid: validate the target of the reference against any applicable profiles
## contained
what to do when validating a contained resource
Filters:
* path: the path in/from the validated resource where this contained resource is found
* kind: the kind of containment (bundled, contained, parameter, outcome)
* type: the type of the resource e.g. Patient
* id: the id of the resource
Options:
* valid: validate the contained resource
# Format
There are two supported formats: text, and json.
Each format presents a list of rules. Rules are evaluated in order, and the first matching rule (all filters are true, or no filters) is used.
For each rule, zero or more filters are provided, and a set of options (which may be empty) is specified.
## Text Format
The file begins with zero or more suppression statements:
```
- VALIDATION_VAL_STATUS_INCONSISTENT_HINT@CodeSystem.valueSet
- MSG_DEPRECATED # comments are allowed
# comments are allowed here too
- This_element_does_not_match_any_known_slice_
```
Then there is zero or more rules. Each rule is a line of text. The line starts with the kind of rule, then a list of name=value pairs for the filters, followed by an ':', and then zero or more options separated by spaces:
```
element (path=Patient.identifier.*): cardinality fixed # comments
```
Comments can also be on empty lines e.g. lines with no rules:
```
# comment
```
## Json format
```json
{
"suppress" : ["VALIDATION_VAL_STATUS_INCONSISTENT_HINT@CodeSystem.valueSet", "MSG_DEPRECATED", "This_element_does_not_match_any_known_slice_"],
"rules" : [{
"type" : "element",
"filters" : [{
"name" : "path",
"value" : "path=Patient.identifier.*"
}],
"options" : ["cardinality", "fixed"]
}]
```
other properties are ignored, so comments etc can be introduced with any other property name
## Filter matching
Except for the path, filter matching is string based, using * as the wild card. A filter starting with ^ is treated as a regex that the filter value must match.
In the path, matching is based on segments, broken up by ```.```. If the path syntax in the filter rule doesn't specify an index, it applies to all occurrences. Indexes can be specified as
integer offsets. No selection criteria such as FHIRPath can be used
# Examples

View File

@ -1,15 +0,0 @@
rule (filter): details
types of rule:
references (path, url): (exists, type, valid)*
contained (path, kind, type, id): (type, valid)*
resource (path, type): (base, stated-profiles, meta-profiles, global-profiles)*
element (path, structure, id) : (cardinality, invariants, bindings, additional-bindings, fixed, status)*
coded (path, structure, id, strength, kind, url, system) : (codes, displays, status)*

View File

@ -60,15 +60,20 @@ public class OntoserverTests implements ITxTesterLoader {
public static Iterable<Object[]> data() throws IOException {
String contents = TestingUtilities.loadTestResource("tx", "test-cases.json");
externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(TestingUtilities.loadTestResource("tx", "messages-tx.fhir.org.json"));
externals = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(TestingUtilities.loadTestResource("tx", "messages-ontoserver.csiro.au.json"));
Map<String, JsonObjectPair> examples = new HashMap<String, JsonObjectPair>();
manifest = org.hl7.fhir.utilities.json.parser.JsonParser.parseObject(contents);
for (org.hl7.fhir.utilities.json.model.JsonObject suite : manifest.getJsonObjects("suites")) {
String sn = suite.asString("name");
for (org.hl7.fhir.utilities.json.model.JsonObject test : suite.getJsonObjects("tests")) {
String tn = test.asString("name");
examples.put(sn+"."+tn, new JsonObjectPair(suite, test));
if (!suite.has("mode") && !suite.asBoolean("disabled")) {
String sn = suite.asString("name");
for (org.hl7.fhir.utilities.json.model.JsonObject test : suite.getJsonObjects("tests")) {
if (!test.has("mode") && !test.asBoolean("disabled")) {
String tn = test.asString("name");
examples.put(sn+"."+tn, new JsonObjectPair(suite, test));
}
}
}
}
@ -107,10 +112,13 @@ public class OntoserverTests implements ITxTesterLoader {
return;
}
if (tester == null) {
tester = new TxTester(this, SERVER, true, externals);
tester = new TxTester(this, SERVER, false, externals);
}
String err = tester.executeTest(setup.suite, setup.test, modes);
Assertions.assertTrue(true); // we don't care what the result is, only that we didn't crash
if (err != null) {
System.out.println(err);
}
Assertions.assertTrue(err == null, err); // we don't care what the result is, only that we didn't crash
}

View File

@ -83,6 +83,9 @@ public class ValidatorCliTests {
@Spy
ValidateTask validateTask;
@Spy
CodeGenTask codeGenTask;
@Spy
ScanTask scanTask = new ScanTask() {
@Override
@ -118,6 +121,7 @@ public class ValidatorCliTests {
txTestsTask,
transformTask,
versionTask,
codeGenTask,
//validate is the default
validateTask
);

View File

@ -14,7 +14,7 @@
HAPI FHIR
-->
<artifactId>org.hl7.fhir.core</artifactId>
<version>6.3.33-SNAPSHOT</version>
<version>6.4.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
@ -23,7 +23,7 @@
<commons_io_version>2.17.0</commons_io_version>
<guava_version>32.0.1-jre</guava_version>
<hapi_fhir_version>6.4.1</hapi_fhir_version>
<validator_test_case_version>1.5.28-SNAPSHOT</validator_test_case_version>
<validator_test_case_version>1.6.0-SNAPSHOT</validator_test_case_version>
<jackson_version>2.17.0</jackson_version>
<junit_jupiter_version>5.9.2</junit_jupiter_version>
<junit_platform_launcher_version>1.8.2</junit_platform_launcher_version>