Merge pull request #686 from SRiviere/Stu3_validation

Fix STU3 validation
This commit is contained in:
James Agnew 2017-07-31 17:08:50 -04:00 committed by GitHub
commit 9901b802c4
6 changed files with 632 additions and 6 deletions

View File

@ -2,6 +2,7 @@ package org.hl7.fhir.dstu3.elementmodel;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu3.conformance.ProfileUtilities;
import org.hl7.fhir.dstu3.context.IWorkerContext;
@ -203,7 +204,7 @@ public class Property {
ElementDefinition ed = definition;
StructureDefinition sd = structure;
List<ElementDefinition> children = ProfileUtilities.getChildMap(sd, ed);
if (children.isEmpty()) {
if (children.isEmpty() || isElementWithOnlyExtension(ed, children)) {
// ok, find the right definitions
String t = null;
if (ed.getType().size() == 1)
@ -240,7 +241,13 @@ public class Property {
}
}
if (!"xhtml".equals(t)) {
sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+t);
final String url;
if (StringUtils.isNotBlank(ed.getType().get(0).getProfile())) {
url = ed.getType().get(0).getProfile();
} else {
url = "http://hl7.org/fhir/StructureDefinition/" + t;
}
sd = context.fetchResource(StructureDefinition.class, url);
if (sd == null)
throw new DefinitionException("Unable to find type '"+t+"' for name '"+elementName+"' on property "+definition.getPath());
children = ProfileUtilities.getChildMap(sd, sd.getSnapshot().getElement().get(0));
@ -340,5 +347,18 @@ public class Property {
return context;
}
private boolean isElementWithOnlyExtension(final ElementDefinition ed, final List<ElementDefinition> children) {
boolean result = false;
if (!ed.getType().isEmpty()) {
result = true;
for (final ElementDefinition ele : children) {
if (!ele.getPath().contains("extension")) {
result = false;
break;
}
}
}
return result;
}
}

View File

@ -23,6 +23,7 @@ import org.hl7.fhir.dstu3.elementmodel.Element.SpecialElement;
import org.hl7.fhir.dstu3.formats.FormatUtilities;
import org.hl7.fhir.dstu3.formats.IParser.OutputStyle;
import org.hl7.fhir.dstu3.model.DateTimeType;
import org.hl7.fhir.dstu3.model.ElementDefinition;
import org.hl7.fhir.dstu3.model.ElementDefinition.PropertyRepresentation;
import org.hl7.fhir.dstu3.model.Enumeration;
import org.hl7.fhir.dstu3.model.StructureDefinition;
@ -45,6 +46,7 @@ import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.InputSource;
import org.xml.sax.XMLReader;
import org.apache.commons.lang3.StringUtils;
public class XmlParser extends ParserBase {
private boolean allowXsiLocation;
@ -250,7 +252,13 @@ public class XmlParser extends ParserBase {
Node child = node.getFirstChild();
while (child != null) {
if (child.getNodeType() == Node.ELEMENT_NODE) {
Property property = getElementProp(properties, child.getLocalName());
final Property property;
if (StringUtils.contains(child.getLocalName(), "extension")) {
property = getExtensionProp(properties, child);
} else {
property = getElementProp(properties, child.getLocalName());
}
if (property != null) {
if (!property.isChoice() && "xhtml".equals(property.getType())) {
XhtmlNode xhtml = new XhtmlParser().setValidatorMode(true).parseHtmlNode((org.w3c.dom.Element) child);
@ -293,6 +301,48 @@ public class XmlParser extends ParserBase {
}
}
private Property getExtensionProp(final List<Property> properties, final Node child) {
// get the correct property corresponding to the extension
Property property = null;
final Node extensionNode = child.getAttributes().getNamedItem("url");
if (extensionNode != null) {
for (final Property prop : properties) {
if (prop.getName().contains("extension")) {
if (findExtension(prop.getDefinition(), extensionNode.getNodeValue())) {
property = prop;
break;
}
}
}
}
if (property == null) {
property = getElementProp(properties, child.getLocalName());
}
return isDefaultExtensionElement(property) ? null : property;
}
private boolean isDefaultExtensionElement(final Property property) {
if (property == null) {
return false;
}
final ElementDefinition ed = property.getDefinition();
return StringUtils.contains(ed.getPath(), "extension") && ed.getType().isEmpty() && !ed.getSlicing().isEmpty();
}
private boolean findExtension(final ElementDefinition definition, final String localName) {
boolean result = false;
if(StringUtils.isNotBlank(localName)) {
for (ElementDefinition.TypeRefComponent type:definition.getType()) {
if(localName.equals(type.getProfile())) {
result = true;
break;
}
}
}
return result;
}
private Property getElementProp(List<Property> properties, String nodeName) {
List<Property> propsSortedByLongestFirst = new ArrayList<Property>(properties);
// sort properties according to their name longest first, so .requestOrganizationReference comes first before .request[x]
@ -337,15 +387,34 @@ public class XmlParser extends ParserBase {
private void parseResource(String string, org.w3c.dom.Element container, Element parent, Property elementProperty) throws FHIRFormatError, DefinitionException, FHIRException, IOException {
org.w3c.dom.Element res = XMLUtil.getFirstChild(container);
String name = res.getLocalName();
StructureDefinition sd = context.fetchResource(StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/"+name);
final String profile = findProfile(res);
final StructureDefinition sd = context.fetchResource(StructureDefinition.class, profile);
if (sd == null)
throw new FHIRFormatError("Contained resource does not appear to be a FHIR resource (unknown name '"+res.getLocalName()+"')");
parent.updateProperty(new Property(context, sd.getSnapshot().getElement().get(0), sd), SpecialElement.fromProperty(parent.getProperty()), elementProperty);
parent.setType(name);
parent.setType(res.getLocalName());
parseChildren(res.getLocalName(), res, parent);
}
private String findProfile(final org.w3c.dom.Element res) {
String url = null;
for (int i = 0; i < res.getChildNodes().getLength(); i++) {
final Node child = res.getChildNodes().item(i);
if ("meta".equals(child.getLocalName())) {
for (int j = 0; j < child.getChildNodes().getLength(); j++) {
final Node subChild = child.getChildNodes().item(j);
if ("profile".equals(subChild.getLocalName())) {
final Node profileNode = subChild.getAttributes().getNamedItem("value");
url = profileNode.getNodeValue();
break;
}
}
break;
}
}
return StringUtils.isNotBlank(url) ? url : "http://hl7.org/fhir/StructureDefinition/" + res.getLocalName();
}
private void reapComments(org.w3c.dom.Element element, Element context) {
Node node = element.getPreviousSibling();
while (node != null && node.getNodeType() != Node.ELEMENT_NODE) {

View File

@ -44,6 +44,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
/**
* Constructor
@ -127,6 +128,21 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myAnyExtensionsAllowed = theAnyExtensionsAllowed;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public boolean isNoTerminologyChecks() {
return noTerminologyChecks;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public void setNoTerminologyChecks(final boolean theNoTerminologyChecks) {
noTerminologyChecks = theNoTerminologyChecks;
}
/**
* Sets the "best practice warning level". When validating, any deviations from best practices will be reported at
* this level.
@ -171,6 +187,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
v.setBestPracticeWarningLevel(getBestPracticeWarningLevel());
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks());
List<ValidationMessage> messages = new ArrayList<ValidationMessage>();

View File

@ -7,6 +7,7 @@ import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertFalse;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
@ -838,6 +839,13 @@ public class FhirInstanceValidatorDstu3Test {
assertEquals(0, all.size());
}
@Test
public void testIsNoTerminologyChecks() {
assertFalse(myInstanceVal.isNoTerminologyChecks());
myInstanceVal.setNoTerminologyChecks(true);
assertTrue(myInstanceVal.isNoTerminologyChecks());
}
@Test
@Ignore
public void testValidateStructureDefinition() throws IOException {

View File

@ -0,0 +1,65 @@
package org.hl7.fhir.dstu3.elementmodel;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.hapi.validation.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.HapiWorkerContext;
import org.hl7.fhir.dstu3.model.ElementDefinition;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.exceptions.DefinitionException;
import org.junit.Before;
import org.junit.Test;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.List;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
/**
* Created by axemj on 14/07/2017.
*/
public class PropertyTest {
private static final FhirContext ourCtx = FhirContext.forDstu3();
private Property property;
private StructureDefinition sd;
private HapiWorkerContext workerContext;
@Before
public void setUp() throws IOException {
final String sdString = IOUtils.toString(getClass().getResourceAsStream("/customPatientSd.xml"), StandardCharsets.UTF_8);
final IParser parser = ourCtx.newXmlParser();
sd = parser.parseResource(StructureDefinition.class, sdString);
workerContext = new HapiWorkerContext(ourCtx, new DefaultProfileValidationSupport());
}
@Test
public void getChildPropertiesPrimitiveTest() throws DefinitionException {
final ElementDefinition ed = sd.getSnapshot().getElement().get(1);
property = new Property(workerContext, ed, sd);
final List<Property> result = property.getChildProperties("id", null);
assertFalse(result.isEmpty());
assertEquals(3, result.size());
assertEquals("id.id", result.get(0).getDefinition().getPath());
}
@Test
public void getChildPropertiesOnlyExtensionElementTest() throws DefinitionException {
final ElementDefinition ed = sd.getSnapshot().getElement().get(23);
property = new Property(workerContext, ed, sd);
final List<Property> result = property.getChildProperties("birthdate", null);
assertFalse(result.isEmpty());
assertEquals(3, result.size());
assertEquals("date.id", result.get(0).getDefinition().getPath());
}
@Test(expected = Error.class)
public void getChildPropertiesErrorTest() throws DefinitionException {
final ElementDefinition ed = sd.getSnapshot().getElement().get(7);
property = new Property(workerContext, ed, sd);
property.getChildProperties("birthdate", null);
}
}

View File

@ -0,0 +1,447 @@
<StructureDefinition xmlns="http://hl7.org/fhir">
<id value="Patient"/>
<meta>
<lastUpdated value="2017-07-14T08:37:31.190+02:00"/>
</meta>
<contained>
<ValueSet xmlns="http://hl7.org/fhir">
<id value="1"/>
</ValueSet>
</contained>
<contained>
<ValueSet xmlns="http://hl7.org/fhir">
<id value="2"/>
</ValueSet>
</contained>
<contained>
<ValueSet xmlns="http://hl7.org/fhir">
<id value="3"/>
</ValueSet>
</contained>
<contained>
<ValueSet xmlns="http://hl7.org/fhir">
<id value="4"/>
</ValueSet>
</contained>
<contained>
<ValueSet xmlns="http://hl7.org/fhir">
<id value="5"/>
</ValueSet>
</contained>
<url value="http://www.myServer.com/fhir/StructureDefinition/Patient"/>
<version value="1.0"/>
<name value="Custom Patient"/>
<title value="Patient"/>
<status value="draft"/>
<experimental value="false"/>
<date value="2017-07-14T08:37:31+02:00"/>
<publisher value="Me"/>
<contact>
<name value="test"/>
<telecom>
<system value="email"/>
<value value="test@test.com"/>
<use value="work"/>
</telecom>
</contact>
<description value="Information about an individual receiving health care services"/>
<useContext>
<code>
<system value="urn:iso:std:iso:3166"/>
<code value="FR"/>
<display value="France"/>
</code>
</useContext>
<fhirVersion value="3.0.1"/>
<kind value="resource"/>
<abstract value="false"/>
<type value="Patient"/>
<baseDefinition value="http://hl7.org/fhir/StructureDefinition/patient"/>
<derivation value="constraint"/>
<snapshot>
<element>
<path value="Patient"/>
</element>
<element>
<path value="Patient.id"/>
<short value="Logical id of this artifact"/>
<definition value="The logical id of the resource, as used in the URL for the resource. Once assigned, this value never changes."/>
<min value="0"/>
<max value="1"/>
<type>
<code value="id"/>
</type>
<isSummary value="true"/>
</element>
<element>
<path value="Patient.meta"/>
<short value="Metadata about the resource"/>
<definition value="The metadata about the resource. This is content that is maintained by the infrastructure. Changes to the content may not always be associated with version changes to the resource."/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Meta"/>
</type>
<isSummary value="true"/>
</element>
<element>
<path value="Patient.text"/>
<max value="0"/>
</element>
<element>
<path value="Patient.contained"/>
<max value="0"/>
</element>
<element>
<path value="Patient.implicitRules"/>
<max value="0"/>
</element>
<element>
<path value="Patient.language"/>
<short value="Language of the resource content"/>
<definition value="The base language in which the resource is written."/>
<min value="0"/>
<max value="1"/>
<type>
<code value="code"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
</element>
<element>
<path value="Patient.extension"/>
<slicing>
<discriminator>
<type value="value"/>
<path value="url"/>
</discriminator>
<rules value="open"/>
</slicing>
<definition value="May be used to represent additional information that is not part of the basic definition of the element."/>
</element>
<element>
<path value="Patient.extension"/>
<short value="identityReliabilityCode"/>
<definition value="The patient identity reliability code"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/identityReliabilityCode"/>
</type>
<isModifier value="false"/>
<isSummary value="true"/>
<binding>
<strength value="required"/>
<valueSetReference>
<reference value="ValueSet/IDENTITYSTATUS"/>
</valueSetReference>
</binding>
</element>
<element>
<path value="Patient.extension"/>
<short value="lunarBirthDate"/>
<definition value="The patient lunar's birth date"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/lunarBirthDate"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
</element>
<element>
<path value="Patient.extension"/>
<short value="legalStatus"/>
<definition value="The patient legal status"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/legalStatus"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
<binding>
<strength value="required"/>
<valueSetReference>
<reference value="ValueSet/ADT_LEGALSTATUS"/>
</valueSetReference>
</binding>
</element>
<element>
<path value="Patient.extension"/>
<short value="familyStatus"/>
<definition value="The patient family status"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/familyStatus"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
<binding>
<strength value="required"/>
<valueSetReference>
<reference value="ValueSet/FAMILYSTATUS"/>
</valueSetReference>
</binding>
</element>
<element>
<path value="Patient.extension"/>
<short value="birthPlace"/>
<definition value="The patient birth place"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://hl7.org/fhir/StructureDefinition/birthPlace"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
</element>
<element>
<path value="Patient.extension"/>
<short value="homeless"/>
<definition value="The patient being homeless, true if homeless"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/homeless"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
</element>
<element>
<path value="Patient.extension"/>
<short value="phoneConsent"/>
<definition value="The patient consent on phone level"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/phoneConsent"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
<binding>
<strength value="required"/>
<valueSetReference>
<reference value="ValueSet/ADT_CONTACT_CONSENT_MOBILE"/>
</valueSetReference>
</binding>
</element>
<element>
<path value="Patient.extension"/>
<short value="emailConsent"/>
<definition value="The patient consent on email level"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/emailConsent"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
<binding>
<strength value="required"/>
<valueSetReference>
<reference value="ValueSet/ADT_CONTACT_CONSENT_EMAIL"/>
</valueSetReference>
</binding>
</element>
<element>
<path value="Patient.extension"/>
<short value="comments"/>
<definition value="The patient comments"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/comments"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
</element>
<element>
<path value="Patient.extension"/>
<short value="nationality"/>
<definition value="The patient nationality"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://hl7.org/fhir/StructureDefinition/patient-nationality"/>
</type>
<isModifier value="false"/>
</element>
<element>
<path value="Patient.identifier"/>
<short value="An identifier for this patient"/>
<definition value="An identifier for this patient."/>
<min value="0"/>
<max value="*"/>
<type>
<code value="Identifier"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/Identifier"/>
</type>
<isModifier value="false"/>
<isSummary value="true"/>
</element>
<element>
<path value="Patient.active"/>
<max value="0"/>
</element>
<element>
<path value="Patient.name"/>
<short value="A name associated with the patient"/>
<definition value="A name associated with the individual."/>
<min value="0"/>
<max value="*"/>
<type>
<code value="HumanName"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/HumanName"/>
</type>
<isModifier value="false"/>
<isSummary value="true"/>
</element>
<element>
<path value="Patient.telecom"/>
<short value="A contact detail for the individual"/>
<definition value="A contact detail (e.g. a telephone number or an email address) by which the individual may be contacted."/>
<min value="0"/>
<max value="*"/>
<type>
<code value="ContactPoint"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/ContactPoint"/>
</type>
<isModifier value="false"/>
<isSummary value="true"/>
</element>
<element>
<path value="Patient.gender"/>
<short value="male | female | other | unknown"/>
<definition value="Administrative Gender - the gender that the patient is considered to have for administration and record keeping purposes."/>
<min value="0"/>
<max value="1"/>
<type>
<code value="code"/>
</type>
<isModifier value="false"/>
<isSummary value="true"/>
</element>
<element>
<path value="Patient.birthDate"/>
<short value="The date of birth for the individual"/>
<definition value="The date of birth for the individual."/>
<min value="0"/>
<max value="1"/>
<type>
<code value="date"/>
</type>
<isModifier value="false"/>
<isSummary value="true"/>
</element>
<element>
<path value="Patient.birthDate.extension"/>
<slicing>
<discriminator>
<type value="value"/>
<path value="url"/>
</discriminator>
<rules value="open"/>
</slicing>
</element>
<element>
<path value="Patient.birthDate.extension"/>
<short value="approximateBirthDate"/>
<definition value="Flag to indicate if the birthdate is approximative or not"/>
<min value="0"/>
<max value="1"/>
<type>
<code value="Extension"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/birthdate-approximative"/>
</type>
<isModifier value="false"/>
</element>
<element>
<path value="Patient.deceased[x]"/>
<short value="Indicates if the individual is deceased or not"/>
<definition value="Indicates if the individual is deceased or not."/>
<min value="0"/>
<max value="1"/>
<type>
<code value="boolean"/>
</type>
<type>
<code value="dateTime"/>
</type>
<isModifier value="true"/>
<isSummary value="true"/>
</element>
<element>
<path value="Patient.address"/>
<short value="Addresses for the individual"/>
<definition value="Addresses for the individual."/>
<min value="0"/>
<max value="*"/>
<type>
<code value="Address"/>
<profile value="http://www.myServer.com/fhir/StructureDefinition/Address"/>
</type>
<isModifier value="false"/>
<isSummary value="true"/>
</element>
<element>
<path value="Patient.maritalStatus"/>
<max value="0"/>
</element>
<element>
<path value="Patient.multipleBirth[x]"/>
<short value="Whether patient is part of a multiple birth"/>
<definition value="Indicates whether the patient is part of a multiple (bool) or indicates the actual birth order (integer)."/>
<min value="0"/>
<max value="1"/>
<type>
<code value="boolean"/>
</type>
<type>
<code value="integer"/>
</type>
<isModifier value="false"/>
<isSummary value="false"/>
</element>
<element>
<path value="Patient.photo"/>
<max value="0"/>
</element>
<element>
<path value="Patient.contact"/>
<max value="0"/>
</element>
<element>
<path value="Patient.animal"/>
<max value="0"/>
</element>
<element>
<path value="Patient.communication"/>
<max value="0"/>
</element>
<element>
<path value="Patient.generalPractitioner"/>
<max value="0"/>
</element>
<element>
<path value="Patient.managingOrganization"/>
<max value="0"/>
</element>
<element>
<path value="Patient.link"/>
<max value="0"/>
</element>
</snapshot>
</StructureDefinition>