Fix second part of #216 - Correctly encode choice elements on profiled datatypes

This commit is contained in:
jamesagnew 2015-09-08 08:01:51 -04:00
parent 9af3fcf02d
commit bb5816d8e9
39 changed files with 373 additions and 78 deletions

View File

@ -24,4 +24,6 @@ public interface IRuntimeDatatypeDefinition {
boolean isSpecialization();
public BaseRuntimeElementDefinition<?> getProfileOf();
}

View File

@ -98,7 +98,20 @@ public class RuntimeChildChoiceDefinition extends BaseRuntimeDeclaredChildDefini
} else {
nextDef = theClassToElementDefinitions.get(next);
elementName = getElementName() + StringUtils.capitalize(nextDef.getName());
BaseRuntimeElementDefinition<?> nextDefForChoice = nextDef;
if (nextDef instanceof IRuntimeDatatypeDefinition) {
IRuntimeDatatypeDefinition nextDefDatatype = (IRuntimeDatatypeDefinition) nextDef;
if (nextDefDatatype.getProfileOf() != null) {
/*
* Elements which are called foo[x] and have a choice which is a profiled datatype
* must use the unprofiled datatype as the element name. E.g. if foo[x] allows
* markdown as a datatype, it calls the element fooString when encoded, because
* markdown is a profile of string. This is according to the FHIR spec
*/
nextDefForChoice = nextDefDatatype.getProfileOf();
}
}
elementName = getElementName() + StringUtils.capitalize(nextDefForChoice.getName());
}
myNameToChildDefinition.put(elementName, nextDef);

View File

@ -19,9 +19,12 @@ package ca.uhn.fhir.context;
* limitations under the License.
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.*;
import java.util.Map;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.ICompositeType;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
@ -30,6 +33,8 @@ import ca.uhn.fhir.model.api.annotation.ResourceDef;
public class RuntimeCompositeDatatypeDefinition extends BaseRuntimeElementCompositeDefinition<ICompositeType> implements IRuntimeDatatypeDefinition {
private boolean mySpecialization;
private Class<? extends IBaseDatatype> myProfileOfType;
private BaseRuntimeElementDefinition<?> myProfileOf;
public RuntimeCompositeDatatypeDefinition(DatatypeDef theDef, Class<? extends ICompositeType> theImplementingClass, boolean theStandardType) {
super(theDef.name(), theImplementingClass, theStandardType);
@ -40,9 +45,30 @@ public class RuntimeCompositeDatatypeDefinition extends BaseRuntimeElementCompos
}
mySpecialization = theDef.isSpecialization();
myProfileOfType = theDef.profileOf();
if (myProfileOfType.equals(IBaseDatatype.class)) {
myProfileOfType = null;
}
}
@Override
public void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
super.sealAndInitialize(theContext, theClassToElementDefinitions);
if (myProfileOfType != null) {
myProfileOf = theClassToElementDefinitions.get(myProfileOfType);
if (myProfileOf == null) {
throw new ConfigurationException("Unknown profileOf value: " + myProfileOfType);
}
}
}
@Override
public BaseRuntimeElementDefinition<?> getProfileOf() {
return myProfileOf;
}
@Override
public boolean isSpecialization() {
return mySpecialization;

View File

@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.*;
import java.util.Map;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
@ -32,6 +33,8 @@ import ca.uhn.fhir.model.api.annotation.ResourceDef;
public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefinition<IPrimitiveType<?>> implements IRuntimeDatatypeDefinition {
private BaseRuntimeElementDefinition<?> myProfileOf;
private Class<? extends IBaseDatatype> myProfileOfType;
private boolean mySpecialization;
public RuntimePrimitiveDatatypeDefinition(DatatypeDef theDef, Class<? extends IPrimitiveType<?>> theImplementingClass, boolean theStandardType) {
@ -43,6 +46,20 @@ public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefini
}
mySpecialization = theDef.isSpecialization();
myProfileOfType = theDef.profileOf();
if (myProfileOfType.equals(IBaseDatatype.class)) {
myProfileOfType = null;
}
}
@Override
public ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum getChildType() {
return ChildTypeEnum.PRIMITIVE_DATATYPE;
}
@Override
public BaseRuntimeElementDefinition<?> getProfileOf() {
return myProfileOf;
}
@Override
@ -52,12 +69,14 @@ public class RuntimePrimitiveDatatypeDefinition extends BaseRuntimeElementDefini
@Override
void sealAndInitialize(FhirContext theContext, Map<Class<? extends IBase>, BaseRuntimeElementDefinition<?>> theClassToElementDefinitions) {
// nothing
}
@Override
public ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum getChildType() {
return ChildTypeEnum.PRIMITIVE_DATATYPE;
super.sealAndInitialize(theContext, theClassToElementDefinitions);
if (myProfileOfType != null) {
myProfileOf = theClassToElementDefinitions.get(myProfileOfType);
if (myProfileOf == null) {
throw new ConfigurationException("Unknown profileOf value: " + myProfileOfType);
}
}
}

View File

@ -25,6 +25,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
import ca.uhn.fhir.model.primitive.CodeDt;
@ -49,4 +51,12 @@ public @interface DatatypeDef {
*/
boolean isSpecialization() default false;
/**
* Indicates that this datatype is a profile of the given datatype, which
* implies certain parsing/encoding rules (e.g. a choice element named
* foo[x] which allows a Markdown value will still be encoded as
* fooString because Markdown is a profile of string.
*/
Class<? extends IBaseDatatype> profileOf() default IBaseDatatype.class;
}

View File

@ -25,7 +25,7 @@ import ca.uhn.fhir.model.api.BasePrimitive;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
@DatatypeDef(name = "code")
@DatatypeDef(name = "code", profileOf=StringDt.class)
public class CodeDt extends BasePrimitive<String> implements ICodedDatatype, Comparable<CodeDt> {
/**

View File

@ -52,7 +52,7 @@ import ca.uhn.fhir.util.UrlUtil;
* regex: [a-z0-9\-\.]{1,36}
* </p>
*/
@DatatypeDef(name = "id")
@DatatypeDef(name = "id", profileOf=StringDt.class)
public class IdDt extends UriDt implements IPrimitiveDatatype<String>, IIdType {
private String myBaseUrl;

View File

@ -23,7 +23,7 @@ package ca.uhn.fhir.model.primitive;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import net.sourceforge.cobertura.CoverageIgnore;
@DatatypeDef(name = "markdown")
@DatatypeDef(name = "markdown", profileOf=StringDt.class)
@CoverageIgnore
public class MarkdownDt extends StringDt {

View File

@ -22,7 +22,7 @@ package ca.uhn.fhir.model.primitive;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
@DatatypeDef(name = "oid")
@DatatypeDef(name = "oid", profileOf=UriDt.class)
public class OidDt extends UriDt {
// TODO: implement restrictions

View File

@ -25,7 +25,7 @@ import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name = "positiveInt")
@DatatypeDef(name = "positiveInt", profileOf=IntegerDt.class)
@CoverageIgnore
public class PositiveIntDt extends IntegerDt {

View File

@ -25,7 +25,7 @@ import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name = "unsignedInt")
@DatatypeDef(name = "unsignedInt", profileOf=IntegerDt.class)
@CoverageIgnore
public class UnsignedIntDt extends IntegerDt {

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.model.dstu2.composite;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.dstu2.composite.QuantityDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
/*
* #%L
@ -23,7 +24,7 @@ import ca.uhn.fhir.model.dstu2.composite.QuantityDt;
* #L%
*/
@DatatypeDef(name="AgeDt")
@DatatypeDef(name="AgeDt", profileOf=QuantityDt.class)
public class AgeDt extends QuantityDt {
}

View File

@ -23,7 +23,7 @@ import ca.uhn.fhir.model.dstu2.composite.QuantityDt;
* #L%
*/
@DatatypeDef(name="CountDt")
@DatatypeDef(name="CountDt", profileOf=QuantityDt.class)
public class CountDt extends QuantityDt {
}

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.model.dstu2.composite;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.dstu2.composite.QuantityDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
/*
* #%L
@ -23,7 +24,7 @@ import ca.uhn.fhir.model.dstu2.composite.QuantityDt;
* #L%
*/
@DatatypeDef(name="DistanceDt")
@DatatypeDef(name="DistanceDt", profileOf=QuantityDt.class)
public class DistanceDt extends QuantityDt {
}

View File

@ -23,7 +23,7 @@ import ca.uhn.fhir.model.dstu2.composite.QuantityDt;
* #L%
*/
@DatatypeDef(name="DurationDt")
@DatatypeDef(name="DurationDt", profileOf=QuantityDt.class)
public class DurationDt extends QuantityDt {
}

View File

@ -23,7 +23,7 @@ import ca.uhn.fhir.model.dstu2.composite.QuantityDt;
* #L%
*/
@DatatypeDef(name="Money")
@DatatypeDef(name="Money", profileOf=QuantityDt.class)
public class MoneyDt extends QuantityDt {
}

View File

@ -25,7 +25,7 @@ import ca.uhn.fhir.model.dstu2.valueset.QuantityComparatorEnum;
* #L%
*/
@DatatypeDef(name="SimpleQuantity")
@DatatypeDef(name="SimpleQuantity", profileOf=QuantityDt.class)
public class SimpleQuantityDt extends QuantityDt {
private static final long serialVersionUID = 1L;

View File

@ -53,6 +53,7 @@ import ca.uhn.fhir.model.dstu2.composite.ElementDefinitionDt.Binding;
import ca.uhn.fhir.model.dstu2.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu2.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.composite.SimpleQuantityDt;
import ca.uhn.fhir.model.dstu2.resource.AllergyIntolerance;
import ca.uhn.fhir.model.dstu2.resource.Binary;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
@ -63,6 +64,7 @@ import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu2.resource.Encounter;
import ca.uhn.fhir.model.dstu2.resource.Medication;
import ca.uhn.fhir.model.dstu2.resource.MedicationOrder;
import ca.uhn.fhir.model.dstu2.resource.MedicationStatement;
import ca.uhn.fhir.model.dstu2.resource.Observation;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
@ -214,6 +216,22 @@ public class XmlParserDstu2Test {
assertTrue(parsed.getEntries().get(0).getResource().getId().isEmpty());
}
/**
* See #216 - Profiled datatypes should use their unprofiled parent type as the choice[x] name
*/
@Test
public void testEncodeAndParseProfiledDatatypeChoice() throws Exception {
IParser xmlParser = ourCtx.newXmlParser();
String input = IOUtils.toString(XmlParser.class.getResourceAsStream("/medicationstatement_invalidelement.xml"));
MedicationStatement ms = xmlParser.parseResource(MedicationStatement.class, input);
SimpleQuantityDt q = (SimpleQuantityDt) ms.getDosage().get(0).getQuantity();
assertEquals("1", q.getValueElement().getValueAsString());
String output = xmlParser.encodeResourceToString(ms);
assertThat(output, containsString("<quantityQuantity><value value=\"1\"/></quantityQuantity>"));
}
/**
* See #216
*/

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?><MedicationStatement xmlns="http://hl7.org/fhir">
<id value="example001"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Tylenol No 1 tablet - Taking 1 tablet four times daily as needed for pain</p>
</div>
</text>
<patient>
<reference value="Patient/pat1"/>
</patient>
<informationSource>
<reference value="Patient/pat2"/>
</informationSource>
<dateAsserted value="2015-02-22"/>
<status value="completed"/>
<wasNotTaken value="false"/>
<reasonForUseCodeableConcept>
<coding>
<system value="http://snomed.info/sct"/>
<code value="22253000"/>
<display value="Pain"/>
</coding>
</reasonForUseCodeableConcept>
<effectiveDateTime value="2015-01-23"/>
<note value="Patient indicates they miss the occasional dose"/>
<medicationReference> <!-- Linked to a RESOURCE Medication -->
<reference value="Medication/MedicationExample7"/>
</medicationReference>
<dosage>
<text value="one tablet four times daily as needed for pain"/>
<timing>
<repeat>
<frequency value="4"/>
<period value="1"/>
<periodUnits value="d"/>
</repeat>
</timing>
<asNeededBoolean value="true"/>
<route>
<coding>
<system value="http://snomed.info/sct"/>
<code value="260548002"/>
<display value="Oral"/>
</coding>
</route>
<quantityQuantity>
<value value="1"/>
</quantityQuantity>
<maxDosePerPeriod>
<numerator>
<value value="4"/>
<unit value="tablets"/>
<system value="http://snomed.info/sct"/>
<code value="385055001"/>
</numerator>
<denominator>
<value value="1"/>
<system value="http://unitsofmeasure.org"/>
<code value="d"/>
</denominator>
</maxDosePerPeriod>
</dosage>
</MedicationStatement>

View File

@ -37,7 +37,7 @@ import org.hl7.fhir.instance.model.api.*;
/**
* A measured amount (or an amount that can potentially be measured). Note that measured amounts include amounts that are not precisely quantified, including amounts involving arbitrary units and floating currencies.
*/
@DatatypeDef(name="Age")
@DatatypeDef(name="Age", profileOf=Quantity.class)
public class Age extends Quantity {
private static final long serialVersionUID = 1069574054L;

View File

@ -35,8 +35,8 @@ import org.hl7.fhir.instance.model.annotations.DatatypeDef;
/**
* Primitive type "code" in FHIR, when not bound to an enumerated list of codes
*/
@DatatypeDef(name="code")
public class CodeType extends PrimitiveType<String> implements Comparable<CodeType> {
@DatatypeDef(name="code", profileOf=StringType.class)
public class CodeType extends StringType implements Comparable<CodeType> {
private static final long serialVersionUID = 3L;

View File

@ -37,7 +37,7 @@ import org.hl7.fhir.instance.model.api.*;
/**
* A measured amount (or an amount that can potentially be measured). Note that measured amounts include amounts that are not precisely quantified, including amounts involving arbitrary units and floating currencies.
*/
@DatatypeDef(name="Count")
@DatatypeDef(name="Count", profileOf=Quantity.class)
public class Count extends Quantity {
private static final long serialVersionUID = 1069574054L;

View File

@ -37,7 +37,7 @@ import org.hl7.fhir.instance.model.api.*;
/**
* A measured amount (or an amount that can potentially be measured). Note that measured amounts include amounts that are not precisely quantified, including amounts involving arbitrary units and floating currencies.
*/
@DatatypeDef(name="Distance")
@DatatypeDef(name="Distance", profileOf=Quantity.class)
public class Distance extends Quantity {
private static final long serialVersionUID = 1069574054L;

View File

@ -37,7 +37,7 @@ import org.hl7.fhir.instance.model.api.*;
/**
* A measured amount (or an amount that can potentially be measured). Note that measured amounts include amounts that are not precisely quantified, including amounts involving arbitrary units and floating currencies.
*/
@DatatypeDef(name="Duration")
@DatatypeDef(name="Duration", profileOf=Quantity.class)
public class Duration extends Quantity {
private static final long serialVersionUID = 1069574054L;

View File

@ -81,7 +81,7 @@ import org.hl7.fhir.instance.model.api.IPrimitiveType;
* Regex for ID: [a-z0-9\-\.]{1,36}
* </p>
*/
@DatatypeDef(name = "id")
@DatatypeDef(name = "id", profileOf=Type.class)
public final class IdType extends UriType implements IPrimitiveType<String>, IIdType {
/**
* This is the maximum length for the ID

View File

@ -35,8 +35,8 @@ import org.hl7.fhir.instance.model.annotations.DatatypeDef;
/**
* Primitive type "code" in FHIR, when not bound to an enumerated list of codes
*/
@DatatypeDef(name="markdown")
public class MarkdownType extends PrimitiveType<String> implements Comparable<MarkdownType> {
@DatatypeDef(name="markdown", profileOf=StringType.class)
public class MarkdownType extends StringType implements Comparable<MarkdownType> {
private static final long serialVersionUID = 3L;

View File

@ -37,7 +37,7 @@ import org.hl7.fhir.instance.model.api.*;
/**
* A measured amount (or an amount that can potentially be measured). Note that measured amounts include amounts that are not precisely quantified, including amounts involving arbitrary units and floating currencies.
*/
@DatatypeDef(name="Money")
@DatatypeDef(name="Money", profileOf=Quantity.class)
public class Money extends Quantity {
private static final long serialVersionUID = 1069574054L;

View File

@ -35,7 +35,7 @@ import org.hl7.fhir.instance.model.annotations.DatatypeDef;
/**
* Primitive type "oid" in FHIR: an OID represented as urn:oid:0.1.2.3.4...
*/
@DatatypeDef(name="oid")
@DatatypeDef(name="oid", profileOf=UriType.class)
public class OidType extends UriType {
private static final long serialVersionUID = 3L;

View File

@ -36,7 +36,7 @@ import org.hl7.fhir.instance.model.annotations.DatatypeDef;
/**
* Primitive type "integer" in FHIR: A signed 32-bit integer
*/
@DatatypeDef(name = "positiveInt")
@DatatypeDef(name = "positiveInt", profileOf=IntegerType.class)
public class PositiveIntType extends IntegerType {
/**

View File

@ -37,7 +37,7 @@ import org.hl7.fhir.instance.model.api.*;
/**
* A measured amount (or an amount that can potentially be measured). Note that measured amounts include amounts that are not precisely quantified, including amounts involving arbitrary units and floating currencies.
*/
@DatatypeDef(name="SimpleQuantity")
@DatatypeDef(name="SimpleQuantity", profileOf=Quantity.class)
public class SimpleQuantity extends Quantity {
private static final long serialVersionUID = 1069574054L;

View File

@ -36,7 +36,7 @@ import org.hl7.fhir.instance.model.annotations.DatatypeDef;
/**
* Primitive type "integer" in FHIR: A signed 32-bit integer
*/
@DatatypeDef(name = "unsignedInt")
@DatatypeDef(name = "unsignedInt", profileOf=IntegerType.class)
public class UnsignedIntType extends IntegerType {

View File

@ -54,6 +54,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
/**
* Class annotation to note a class which defines a datatype
*/
@ -75,4 +77,12 @@ public @interface DatatypeDef {
*/
boolean isSpecialization() default false;
/**
* Indicates that this datatype is a profile of the given datatype, which
* implies certain parsing/encoding rules (e.g. a choice element named
* foo[x] which allows a Markdown value will still be encoded as
* fooString because Markdown is a profile of string.
*/
Class<? extends IBaseDatatype> profileOf() default IBaseDatatype.class;
}

View File

@ -129,15 +129,16 @@ public class BaseValidator {
}
/**
* Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
* Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean hint(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
protected boolean hint(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.INFORMATION));
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, line, col, path, message, IssueSeverity.INFORMATION));
}
return thePass;
}
@ -158,6 +159,20 @@ public class BaseValidator {
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#INFORMATION} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean hint(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.INFORMATION));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
*
@ -165,9 +180,10 @@ public class BaseValidator {
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean rule(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) {
protected boolean rule(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.ERROR));
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, line, col, path, message, IssueSeverity.ERROR));
}
return thePass;
}
@ -203,13 +219,6 @@ public class BaseValidator {
return thePass;
}
private String toPath(List<String> pathParts) {
if (pathParts == null || pathParts.isEmpty()) {
return "";
}
return "//" + StringUtils.join(pathParts, '/');
}
/**
* Test a rule and add a {@link IssueSeverity#ERROR} validation message if the validation fails
*
@ -259,6 +268,13 @@ public class BaseValidator {
return b.toString();
}
private String toPath(List<String> pathParts) {
if (pathParts == null || pathParts.isEmpty()) {
return "";
}
return "//" + StringUtils.join(pathParts, '/');
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
@ -275,6 +291,22 @@ public class BaseValidator {
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean warning(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String path = toPath(pathParts);
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.WARNING));
}
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
@ -318,20 +350,4 @@ public class BaseValidator {
return thePass;
}
/**
* Test a rule and add a {@link IssueSeverity#WARNING} validation message if the validation fails
*
* @param thePass
* Set this parameter to <code>false</code> if the validation does not pass
* @return Returns <code>thePass</code> (in other words, returns <code>true</code> if the rule did not fail validation)
*/
protected boolean warning(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
if (!thePass) {
String path = toPath(pathParts);
String message = formatMessage(theMessage, theMessageArguments);
errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.WARNING));
}
return thePass;
}
}

View File

@ -1699,17 +1699,18 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
ElementDefinitionBindingComponent binding = context.getBinding();
if (binding.hasValueSet() && binding.getValueSet() instanceof Reference) {
ValueSet vs = resolveBindingReference(binding.getValueSet());
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet "+describeReference(binding.getValueSet())+" not found")) {
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "ValueSet {0} not found", describeReference(binding.getValueSet()))) {
try {
vs = cache.getExpander().expand(vs).getValueset();
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "Unable to expand value set for "+describeReference(binding.getValueSet()))) {
ValueSetExpansionOutcome expansionOutcome = cache.getExpander().expand(vs);
vs = expansionOutcome != null ? expansionOutcome.getValueset() : null;
if (warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, vs != null, "Unable to expand value set for {0}", describeReference(binding.getValueSet()))) {
boolean ok = codeInExpansion(vs, null, value);
if (binding.getStrength() == BindingStrength.REQUIRED)
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, ok, "Coded value "+value+" is not in value set "+describeReference(binding.getValueSet())+" ("+vs.getUrl()+")");
rule(errors, IssueType.CODEINVALID, element.line(), element.col(), path, ok, "Coded value {0} is not in value set {1} ({2})", value, describeReference(binding.getValueSet()), vs.getUrl());
else if (binding.getStrength() == BindingStrength.EXTENSIBLE)
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, ok, "Coded value "+value+" is not in value set "+describeReference(binding.getValueSet())+" ("+vs.getUrl()+")");
warning(errors, IssueType.CODEINVALID, element.line(), element.col(), path, ok, "Coded value {0} is not in value set {1} ({2})", value, describeReference(binding.getValueSet()), vs.getUrl());
else
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, ok, "Coded value "+value+" is not in value set "+describeReference(binding.getValueSet())+" ("+vs.getUrl()+")");
hint(errors, IssueType.CODEINVALID, element.line(), element.col(), path, ok, "Coded value {0} is not in value set {1} ({2})", value, describeReference(binding.getValueSet()), vs.getUrl());
}
} catch (ETooCostly e) {
if (e.getMessage() == null)

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.instance.model.Base;
import org.hl7.fhir.instance.model.Binary;
import org.hl7.fhir.instance.model.BooleanType;
import org.hl7.fhir.instance.model.Bundle;
import org.hl7.fhir.instance.model.CodeType;
import org.hl7.fhir.instance.model.Coding;
import org.hl7.fhir.instance.model.DecimalType;
import org.hl7.fhir.instance.model.DomainResource;
@ -24,40 +25,43 @@ import org.hl7.fhir.instance.model.Identifier.IdentifierUseEnumFactory;
import org.hl7.fhir.instance.model.IntegerType;
import org.hl7.fhir.instance.model.List_;
import org.hl7.fhir.instance.model.Meta;
import org.hl7.fhir.instance.model.Money;
import org.hl7.fhir.instance.model.Narrative;
import org.hl7.fhir.instance.model.Parameters;
import org.hl7.fhir.instance.model.PrimitiveType;
import org.hl7.fhir.instance.model.Quantity;
import org.hl7.fhir.instance.model.Reference;
import org.hl7.fhir.instance.model.Resource;
import org.hl7.fhir.instance.model.StringType;
import org.hl7.fhir.instance.model.Timing;
import org.hl7.fhir.instance.model.Type;
import org.hl7.fhir.instance.model.annotations.Block;
import org.hl7.fhir.instance.model.annotations.Child;
import org.hl7.fhir.instance.model.annotations.DatatypeDef;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseBackboneElement;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseBooleanDatatype;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.instance.model.api.IBaseDecimalDatatype;
import org.hl7.fhir.instance.model.api.IBaseEnumeration;
import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
import org.hl7.fhir.instance.model.api.IBaseHasModifierExtensions;
import org.hl7.fhir.instance.model.api.IBaseIntegerDatatype;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseXhtml;
import org.hl7.fhir.instance.model.api.ICompositeType;
import org.hl7.fhir.instance.model.api.IBaseDatatypeElement;
import org.hl7.fhir.instance.model.api.IDomainResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IBaseMetaType;
import org.hl7.fhir.instance.model.api.INarrative;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.Test;
@ -153,7 +157,15 @@ public class ModelInheritanceTest {
assertTrue(IBase.class.isAssignableFrom(Base.class));
}
@Test
public void testProfiledDatatype() {
assertEquals(StringType.class, CodeType.class.getSuperclass());
assertEquals(StringType.class, CodeType.class.getAnnotation(DatatypeDef.class).profileOf());
assertEquals(Quantity.class, Money.class.getSuperclass());
assertEquals(Quantity.class, Money.class.getAnnotation(DatatypeDef.class).profileOf());
}
@Test
public void testBinary() {
assertTrue(IBaseBinary.class.isAssignableFrom(Binary.class));

View File

@ -1,7 +1,14 @@
package ca.uhn.fhir.parser;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.io.IOException;
import java.io.OutputStreamWriter;
@ -39,6 +46,7 @@ import org.hl7.fhir.instance.model.HumanName;
import org.hl7.fhir.instance.model.Identifier;
import org.hl7.fhir.instance.model.Identifier.IdentifierUse;
import org.hl7.fhir.instance.model.InstantType;
import org.hl7.fhir.instance.model.MedicationStatement;
import org.hl7.fhir.instance.model.Narrative.NarrativeStatus;
import org.hl7.fhir.instance.model.Observation;
import org.hl7.fhir.instance.model.Organization;
@ -46,6 +54,7 @@ import org.hl7.fhir.instance.model.Patient;
import org.hl7.fhir.instance.model.PrimitiveType;
import org.hl7.fhir.instance.model.Reference;
import org.hl7.fhir.instance.model.Resource;
import org.hl7.fhir.instance.model.SimpleQuantity;
import org.hl7.fhir.instance.model.Specimen;
import org.hl7.fhir.instance.model.StringType;
import org.hl7.fhir.instance.model.api.IBaseResource;
@ -78,6 +87,23 @@ public class XmlParserHl7OrgDstu2Test {
return htmlNoNs.replace("<div>", "<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\">");
}
/**
* See #216 - Profiled datatypes should use their unprofiled parent type as the choice[x] name
*/
@Test
public void testEncodeAndParseProfiledDatatypeChoice() throws Exception {
IParser xmlParser = ourCtx.newXmlParser();
String input = IOUtils.toString(XmlParser.class.getResourceAsStream("/medicationstatement_invalidelement.xml"));
MedicationStatement ms = xmlParser.parseResource(MedicationStatement.class, input);
SimpleQuantity q = (SimpleQuantity) ms.getDosage().get(0).getQuantity();
assertEquals("1", q.getValueElement().getValueAsString());
String output = xmlParser.encodeResourceToString(ms);
assertThat(output, containsString("<quantityQuantity><value value=\"1\"/></quantityQuantity>"));
}
@Test
public void testComposition() {

View File

@ -9,9 +9,9 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.CodeType;
import org.hl7.fhir.instance.model.Observation;
import org.hl7.fhir.instance.model.Observation.ObservationStatus;
@ -25,8 +25,6 @@ import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.primitive.IdDt;
public class FhirInstanceValidatorTest {
@ -199,6 +197,19 @@ public class FhirInstanceValidatorTest {
}
/**
* See #216
*/
@Test
public void testValidateRawXmlInvalidChoiceName() throws Exception {
String input = IOUtils.toString(FhirInstanceValidator.class.getResourceAsStream("/medicationstatement_invalidelement.xml"));
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> res = logResultsAndReturnNonInformationalOnes(output);
assertEquals(output.toString(), 0, res.size());
}
@Test
public void testValidateResourceContainingProfileDeclarationDoesntResolve() {
addValidConcept("http://loinc.org", "12345");

View File

@ -0,0 +1,64 @@
<?xml version="1.0" encoding="UTF-8"?><MedicationStatement xmlns="http://hl7.org/fhir">
<id value="example001"/>
<text>
<status value="generated"/>
<div xmlns="http://www.w3.org/1999/xhtml">
<p>Tylenol No 1 tablet - Taking 1 tablet four times daily as needed for pain</p>
</div>
</text>
<patient>
<reference value="Patient/pat1"/>
</patient>
<informationSource>
<reference value="Patient/pat2"/>
</informationSource>
<dateAsserted value="2015-02-22"/>
<status value="completed"/>
<wasNotTaken value="false"/>
<reasonForUseCodeableConcept>
<coding>
<system value="http://snomed.info/sct"/>
<code value="22253000"/>
<display value="Pain"/>
</coding>
</reasonForUseCodeableConcept>
<effectiveDateTime value="2015-01-23"/>
<note value="Patient indicates they miss the occasional dose"/>
<medicationReference> <!-- Linked to a RESOURCE Medication -->
<reference value="Medication/MedicationExample7"/>
</medicationReference>
<dosage>
<text value="one tablet four times daily as needed for pain"/>
<timing>
<repeat>
<frequency value="4"/>
<period value="1"/>
<periodUnits value="d"/>
</repeat>
</timing>
<asNeededBoolean value="true"/>
<route>
<coding>
<system value="http://snomed.info/sct"/>
<code value="260548002"/>
<display value="Oral"/>
</coding>
</route>
<quantityQuantity>
<value value="1"/>
</quantityQuantity>
<maxDosePerPeriod>
<numerator>
<value value="4"/>
<unit value="tablets"/>
<system value="http://snomed.info/sct"/>
<code value="385055001"/>
</numerator>
<denominator>
<value value="1"/>
<system value="http://unitsofmeasure.org"/>
<code value="d"/>
</denominator>
</maxDosePerPeriod>
</dosage>
</MedicationStatement>

View File

@ -189,6 +189,7 @@
</p>
<ul>
<li>
<!-- TODO: add search.maven.org links to these -->
<b>hapi-fhir-structures-hl7org-dstu2</b>
: This file contains the "reference implementation"
structures and tooling. You need to include it even if you are not using the RI model