Clean up the validation framework and integrate the QuestionnaireAnswers
validator
This commit is contained in:
parent
f2a725ae53
commit
c22aa14d29
|
@ -2,22 +2,26 @@ package example;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileReader;
|
import java.io.FileReader;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.commons.io.filefilter.WildcardFileFilter;
|
import org.apache.commons.io.filefilter.WildcardFileFilter;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum;
|
import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum;
|
||||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||||
import ca.uhn.fhir.rest.client.IGenericClient;
|
import ca.uhn.fhir.rest.client.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.validation.FhirValidator;
|
import ca.uhn.fhir.validation.FhirValidator;
|
||||||
|
import ca.uhn.fhir.validation.SingleValidationMessage;
|
||||||
import ca.uhn.fhir.validation.ValidationResult;
|
import ca.uhn.fhir.validation.ValidationResult;
|
||||||
|
|
||||||
public class ValidatorExamples {
|
public class ValidatorExamples {
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public void enableValidation() {
|
public void enableValidation() {
|
||||||
// START SNIPPET: enableValidation
|
// START SNIPPET: enableValidation
|
||||||
FhirContext ctx = FhirContext.forDstu2();
|
FhirContext ctx = FhirContext.forDstu2();
|
||||||
|
@ -55,13 +59,22 @@ public class ValidatorExamples {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// We failed validation!
|
// We failed validation!
|
||||||
|
|
||||||
System.out.println("Validation failed");
|
System.out.println("Validation failed");
|
||||||
|
|
||||||
// The result contains an OperationOutcome outlining the failures
|
|
||||||
String results = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.getOperationOutcome());
|
|
||||||
System.out.println(results);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The result contains a list of "messages"
|
||||||
|
List<SingleValidationMessage> messages = result.getMessages();
|
||||||
|
for (SingleValidationMessage next : messages) {
|
||||||
|
System.out.println("Message:");
|
||||||
|
System.out.println(" * Location: " + next.getLocationString());
|
||||||
|
System.out.println(" * Severity: " + next.getSeverity());
|
||||||
|
System.out.println(" * Message : " + next.getMessage());
|
||||||
|
}
|
||||||
|
|
||||||
|
// You can also convert the results into an OperationOutcome resource
|
||||||
|
OperationOutcome oo = (OperationOutcome) result.toOperationOutcome();
|
||||||
|
String results = ctx.newXmlParser().setPrettyPrint(true).encodeResourceToString(oo);
|
||||||
|
System.out.println(results);
|
||||||
// END SNIPPET: basicValidation
|
// END SNIPPET: basicValidation
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -149,7 +149,7 @@ public class FhirValidator {
|
||||||
next.validateBundle(ctx);
|
next.validateBundle(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
IBaseOperationOutcome oo = ctx.getOperationOutcome();
|
IBaseOperationOutcome oo = ctx.toResult().toOperationOutcome();
|
||||||
if (oo != null && OperationOutcomeUtil.hasIssues(myContext, oo)) {
|
if (oo != null && OperationOutcomeUtil.hasIssues(myContext, oo)) {
|
||||||
throw new ValidationFailureException(myContext, oo);
|
throw new ValidationFailureException(myContext, oo);
|
||||||
}
|
}
|
||||||
|
@ -169,15 +169,15 @@ public class FhirValidator {
|
||||||
public void validate(IResource theResource) throws ValidationFailureException {
|
public void validate(IResource theResource) throws ValidationFailureException {
|
||||||
ValidationResult validationResult = validateWithResult(theResource);
|
ValidationResult validationResult = validateWithResult(theResource);
|
||||||
if (!validationResult.isSuccessful()) {
|
if (!validationResult.isSuccessful()) {
|
||||||
throw new ValidationFailureException(myContext, validationResult.getOperationOutcome());
|
throw new ValidationFailureException(myContext, validationResult.toOperationOutcome());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a bundle instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results.
|
* Validates a bundle instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results. This validation includes validation of all resources in the bundle.
|
||||||
* This validation includes validation of all resources in the bundle.
|
|
||||||
*
|
*
|
||||||
* @param theBundle the bundle to validate
|
* @param theBundle
|
||||||
|
* the bundle to validate
|
||||||
* @return the results of validation
|
* @return the results of validation
|
||||||
* @since 0.7
|
* @since 0.7
|
||||||
*/
|
*/
|
||||||
|
@ -190,14 +190,14 @@ public class FhirValidator {
|
||||||
next.validateBundle(ctx);
|
next.validateBundle(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
IBaseOperationOutcome oo = ctx.getOperationOutcome();
|
return ctx.toResult();
|
||||||
return ValidationResult.valueOf(myContext, oo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results.
|
* Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results.
|
||||||
*
|
*
|
||||||
* @param theResource the resource to validate
|
* @param theResource
|
||||||
|
* the resource to validate
|
||||||
* @return the results of validation
|
* @return the results of validation
|
||||||
* @since 0.7
|
* @since 0.7
|
||||||
*/
|
*/
|
||||||
|
@ -210,7 +210,6 @@ public class FhirValidator {
|
||||||
next.validateResource(ctx);
|
next.validateResource(ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
IBaseOperationOutcome oo = ctx.getOperationOutcome();
|
return ctx.toResult();
|
||||||
return ValidationResult.valueOf(myContext, oo);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,17 +1,22 @@
|
||||||
package ca.uhn.fhir.validation;
|
package ca.uhn.fhir.validation;
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
|
|
||||||
interface IValidationContext<T> {
|
interface IValidationContext<T> {
|
||||||
|
|
||||||
public abstract FhirContext getFhirContext();
|
FhirContext getFhirContext();
|
||||||
|
|
||||||
public abstract IBaseOperationOutcome getOperationOutcome();
|
T getResource();
|
||||||
|
|
||||||
public abstract T getResource();
|
String getResourceAsString();
|
||||||
|
|
||||||
public abstract String getXmlEncodedResource();
|
EncodingEnum getResourceAsStringEncoding();
|
||||||
|
|
||||||
|
String getResourceName();
|
||||||
|
|
||||||
|
void addValidationMessage(SingleValidationMessage theMessage);
|
||||||
|
|
||||||
|
ValidationResult toResult();
|
||||||
|
|
||||||
}
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
package ca.uhn.fhir.validation;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
public enum ResultSeverityEnum {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The issue has no relation to the degree of success of the action
|
||||||
|
*/
|
||||||
|
INFORMATION("information"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The issue is not important enough to cause the action to fail, but may cause it to be performed suboptimally or in a way that is not as desired
|
||||||
|
*/
|
||||||
|
WARNING("warning"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The issue is sufficiently important to cause the action to fail
|
||||||
|
*/
|
||||||
|
ERROR("error"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The issue caused the action to fail, and no further checking could be performed
|
||||||
|
*/
|
||||||
|
FATAL("fatal");
|
||||||
|
|
||||||
|
private static Map<String, ResultSeverityEnum> ourValues;
|
||||||
|
private String myCode;
|
||||||
|
|
||||||
|
private ResultSeverityEnum(String theCode) {
|
||||||
|
myCode = theCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getCode() {
|
||||||
|
return myCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ResultSeverityEnum fromCode(String theCode) {
|
||||||
|
if (ourValues == null) {
|
||||||
|
HashMap<String, ResultSeverityEnum> values = new HashMap<String, ResultSeverityEnum>();
|
||||||
|
for (ResultSeverityEnum next : values()) {
|
||||||
|
values.put(next.getCode(), next);
|
||||||
|
}
|
||||||
|
ourValues = Collections.unmodifiableMap(values);
|
||||||
|
}
|
||||||
|
return ourValues.get(theCode);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -49,8 +49,8 @@ import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
import ca.uhn.fhir.model.api.Bundle;
|
||||||
|
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
|
||||||
|
|
||||||
class SchemaBaseValidator implements IValidator {
|
class SchemaBaseValidator implements IValidator {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SchemaBaseValidator.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SchemaBaseValidator.class);
|
||||||
|
@ -83,10 +83,12 @@ class SchemaBaseValidator implements IValidator {
|
||||||
Validator validator = schema.newValidator();
|
Validator validator = schema.newValidator();
|
||||||
MyErrorHandler handler = new MyErrorHandler(theContext);
|
MyErrorHandler handler = new MyErrorHandler(theContext);
|
||||||
validator.setErrorHandler(handler);
|
validator.setErrorHandler(handler);
|
||||||
String encodedResource = theContext.getXmlEncodedResource();
|
String encodedResource;
|
||||||
|
if (theContext.getResourceAsStringEncoding() == EncodingEnum.XML) {
|
||||||
// ourLog.info(new FhirContext().newXmlParser().setPrettyPrint(true).encodeBundleToString((Bundle)
|
encodedResource = theContext.getResourceAsString();
|
||||||
// theContext.getResource()));
|
} else {
|
||||||
|
encodedResource = theContext.getFhirContext().newXmlParser().encodeResourceToString((IBaseResource) theContext.getResource());
|
||||||
|
}
|
||||||
|
|
||||||
validator.validate(new StreamSource(new StringReader(encodedResource)));
|
validator.validate(new StreamSource(new StringReader(encodedResource)));
|
||||||
} catch (SAXException e) {
|
} catch (SAXException e) {
|
||||||
|
@ -106,10 +108,10 @@ class SchemaBaseValidator implements IValidator {
|
||||||
return schema;
|
return schema;
|
||||||
}
|
}
|
||||||
|
|
||||||
Source baseSource = loadXml("dstu", null, theSchemaName);
|
Source baseSource = loadXml(null, theSchemaName);
|
||||||
|
|
||||||
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
SchemaFactory schemaFactory = SchemaFactory.newInstance(XMLConstants.W3C_XML_SCHEMA_NS_URI);
|
||||||
schemaFactory.setResourceResolver(new MyResourceResolver("dstu"));
|
schemaFactory.setResourceResolver(new MyResourceResolver());
|
||||||
|
|
||||||
try {
|
try {
|
||||||
schema = schemaFactory.newSchema(new Source[] { baseSource });
|
schema = schemaFactory.newSchema(new Source[] { baseSource });
|
||||||
|
@ -121,7 +123,7 @@ class SchemaBaseValidator implements IValidator {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Source loadXml(String theVersion, String theSystemId, String theSchemaName) {
|
private Source loadXml(String theSystemId, String theSchemaName) {
|
||||||
String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theSchemaName;
|
String pathToBase = myCtx.getVersion().getPathToSchemaDefinitions() + '/' + theSchemaName;
|
||||||
ourLog.debug("Going to load resource: {}", pathToBase);
|
ourLog.debug("Going to load resource: {}", pathToBase);
|
||||||
InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase);
|
InputStream baseIs = FhirValidator.class.getResourceAsStream(pathToBase);
|
||||||
|
@ -132,17 +134,6 @@ class SchemaBaseValidator implements IValidator {
|
||||||
InputStreamReader baseReader = new InputStreamReader(baseIs, Charset.forName("UTF-8"));
|
InputStreamReader baseReader = new InputStreamReader(baseIs, Charset.forName("UTF-8"));
|
||||||
Source baseSource = new StreamSource(baseReader, theSystemId);
|
Source baseSource = new StreamSource(baseReader, theSystemId);
|
||||||
|
|
||||||
// String schema;
|
|
||||||
// try {
|
|
||||||
// schema = IOUtils.toString(baseIs, Charset.forName("UTF-8"));
|
|
||||||
// } catch (IOException e) {
|
|
||||||
// throw new InternalErrorException(e);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// ourLog.info("Schema is:\n{}", schema);
|
|
||||||
//
|
|
||||||
// Source baseSource = new StreamSource(new StringReader(schema), theSystemId);
|
|
||||||
// Source baseSource = new StreamSource(baseIs, theSystemId);
|
|
||||||
return baseSource;
|
return baseSource;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,34 +159,34 @@ class SchemaBaseValidator implements IValidator {
|
||||||
myContext = theContext;
|
myContext = theContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addIssue(SAXParseException theException, String theSeverity) {
|
private void addIssue(SAXParseException theException, ResultSeverityEnum theSeverity) {
|
||||||
String details = theException.getLocalizedMessage();
|
SingleValidationMessage message = new SingleValidationMessage();
|
||||||
String location = "Line[" + theException.getLineNumber() + "] Col[" + theException.getColumnNumber() + "]";
|
message.setLocationRow(theException.getLineNumber());
|
||||||
OperationOutcomeUtil.addIssue(myContext.getFhirContext(), myContext.getOperationOutcome(), theSeverity, details, location);
|
message.setLocationCol(theException.getColumnNumber());
|
||||||
|
message.setMessage(theException.getLocalizedMessage());
|
||||||
|
message.setSeverity(theSeverity);
|
||||||
|
myContext.addValidationMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void error(SAXParseException theException) throws SAXException {
|
public void error(SAXParseException theException) {
|
||||||
addIssue(theException, "error");
|
addIssue(theException, ResultSeverityEnum.ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void fatalError(SAXParseException theException) throws SAXException {
|
public void fatalError(SAXParseException theException) {
|
||||||
addIssue(theException, "fatal");
|
addIssue(theException, ResultSeverityEnum.FATAL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void warning(SAXParseException theException) throws SAXException {
|
public void warning(SAXParseException theException) {
|
||||||
addIssue(theException, "warning");
|
addIssue(theException, ResultSeverityEnum.WARNING);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class MyResourceResolver implements LSResourceResolver {
|
private final class MyResourceResolver implements LSResourceResolver {
|
||||||
private String myVersion;
|
private MyResourceResolver() {
|
||||||
|
|
||||||
private MyResourceResolver(String theVersion) {
|
|
||||||
myVersion = theVersion;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -35,7 +35,6 @@ import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
import ca.uhn.fhir.model.api.Bundle;
|
||||||
import ca.uhn.fhir.model.api.BundleEntry;
|
import ca.uhn.fhir.model.api.BundleEntry;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
|
||||||
|
|
||||||
import com.phloc.commons.error.IResourceError;
|
import com.phloc.commons.error.IResourceError;
|
||||||
import com.phloc.commons.error.IResourceErrorGroup;
|
import com.phloc.commons.error.IResourceErrorGroup;
|
||||||
|
@ -56,7 +55,7 @@ public class SchematronBaseValidator implements IValidator {
|
||||||
public void validateResource(IValidationContext<IBaseResource> theCtx) {
|
public void validateResource(IValidationContext<IBaseResource> theCtx) {
|
||||||
|
|
||||||
ISchematronResource sch = getSchematron(theCtx);
|
ISchematronResource sch = getSchematron(theCtx);
|
||||||
StreamSource source = new StreamSource(new StringReader(theCtx.getXmlEncodedResource()));
|
StreamSource source = new StreamSource(new StringReader(theCtx.getResourceAsString()));
|
||||||
|
|
||||||
SchematronOutputType results = SchematronHelper.applySchematron(sch, source);
|
SchematronOutputType results = SchematronHelper.applySchematron(sch, source);
|
||||||
if (results == null) {
|
if (results == null) {
|
||||||
|
@ -70,16 +69,16 @@ public class SchematronBaseValidator implements IValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (IResourceError next : errors.getAllErrors().getAllResourceErrors()) {
|
for (IResourceError next : errors.getAllErrors().getAllResourceErrors()) {
|
||||||
String severity;
|
ResultSeverityEnum severity;
|
||||||
switch (next.getErrorLevel()) {
|
switch (next.getErrorLevel()) {
|
||||||
case ERROR:
|
case ERROR:
|
||||||
severity = ("error");
|
severity = ResultSeverityEnum.ERROR;
|
||||||
break;
|
break;
|
||||||
case FATAL_ERROR:
|
case FATAL_ERROR:
|
||||||
severity = ("fatal");
|
severity = ResultSeverityEnum.FATAL;
|
||||||
break;
|
break;
|
||||||
case WARN:
|
case WARN:
|
||||||
severity = ("warning");
|
severity = ResultSeverityEnum.WARNING;
|
||||||
break;
|
break;
|
||||||
case INFO:
|
case INFO:
|
||||||
case SUCCESS:
|
case SUCCESS:
|
||||||
|
@ -88,7 +87,14 @@ public class SchematronBaseValidator implements IValidator {
|
||||||
}
|
}
|
||||||
|
|
||||||
String details = next.getAsString(Locale.getDefault());
|
String details = next.getAsString(Locale.getDefault());
|
||||||
OperationOutcomeUtil.addIssue(myCtx, theCtx.getOperationOutcome(), severity, details);
|
|
||||||
|
SingleValidationMessage message = new SingleValidationMessage();
|
||||||
|
message.setMessage(details);
|
||||||
|
message.setLocationRow(next.getLocation().getLineNumber());
|
||||||
|
message.setLocationCol(next.getLocation().getColumnNumber());
|
||||||
|
message.setLocationString(next.getLocation().getAsString());
|
||||||
|
message.setSeverity(severity);
|
||||||
|
theCtx.addValidationMessage(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -97,10 +103,10 @@ public class SchematronBaseValidator implements IValidator {
|
||||||
Class<? extends IBaseResource> resource = theCtx.getResource().getClass();
|
Class<? extends IBaseResource> resource = theCtx.getResource().getClass();
|
||||||
Class<? extends IBaseResource> baseResourceClass = theCtx.getFhirContext().getResourceDefinition(resource).getBaseDefinition().getImplementingClass();
|
Class<? extends IBaseResource> baseResourceClass = theCtx.getFhirContext().getResourceDefinition(resource).getBaseDefinition().getImplementingClass();
|
||||||
|
|
||||||
return getSchematronAndCache(theCtx, "dstu", baseResourceClass);
|
return getSchematronAndCache(theCtx, baseResourceClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ISchematronResource getSchematronAndCache(IValidationContext<IBaseResource> theCtx, String theVersion, Class<? extends IBaseResource> theClass) {
|
private ISchematronResource getSchematronAndCache(IValidationContext<IBaseResource> theCtx, Class<? extends IBaseResource> theClass) {
|
||||||
synchronized (myClassToSchematron) {
|
synchronized (myClassToSchematron) {
|
||||||
ISchematronResource retVal = myClassToSchematron.get(theClass);
|
ISchematronResource retVal = myClassToSchematron.get(theClass);
|
||||||
if (retVal != null) {
|
if (retVal != null) {
|
||||||
|
|
|
@ -0,0 +1,105 @@
|
||||||
|
package ca.uhn.fhir.validation;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.builder.EqualsBuilder;
|
||||||
|
import org.apache.commons.lang3.builder.HashCodeBuilder;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
|
|
||||||
|
public class SingleValidationMessage {
|
||||||
|
|
||||||
|
private Integer myLocationCol;
|
||||||
|
private Integer myLocationRow;
|
||||||
|
private String myLocationString;
|
||||||
|
private String myMessage;
|
||||||
|
private ResultSeverityEnum mySeverity;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(obj instanceof SingleValidationMessage)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
SingleValidationMessage other = (SingleValidationMessage) obj;
|
||||||
|
EqualsBuilder b = new EqualsBuilder();
|
||||||
|
b.append(myLocationCol, other.myLocationCol);
|
||||||
|
b.append(myLocationRow, other.myLocationRow);
|
||||||
|
b.append(myLocationString, other.myLocationString);
|
||||||
|
b.append(myMessage, other.myMessage);
|
||||||
|
b.append(mySeverity, other.mySeverity);
|
||||||
|
return b.isEquals();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLocationCol() {
|
||||||
|
return myLocationCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public Integer getLocationRow() {
|
||||||
|
return myLocationRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getLocationString() {
|
||||||
|
return myLocationString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getMessage() {
|
||||||
|
return myMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public ResultSeverityEnum getSeverity() {
|
||||||
|
return mySeverity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
HashCodeBuilder b = new HashCodeBuilder();
|
||||||
|
b.append(myLocationCol);
|
||||||
|
b.append(myLocationCol);
|
||||||
|
b.append(myLocationString);
|
||||||
|
b.append(myMessage);
|
||||||
|
b.append(mySeverity);
|
||||||
|
return b.toHashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocationCol(Integer theLocationCol) {
|
||||||
|
myLocationCol = theLocationCol;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocationRow(Integer theLocationRow) {
|
||||||
|
myLocationRow = theLocationRow;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setLocationString(String theLocationString) {
|
||||||
|
myLocationString = theLocationString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setMessage(String theMessage) {
|
||||||
|
myMessage = theMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setSeverity(ResultSeverityEnum theSeverity) {
|
||||||
|
mySeverity = theSeverity;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||||
|
if (myLocationCol != null || myLocationRow != null) {
|
||||||
|
b.append("myLocationCol", myLocationCol);
|
||||||
|
b.append("myLocationRow", myLocationRow);
|
||||||
|
}
|
||||||
|
if (myLocationString != null) {
|
||||||
|
b.append("myLocationString", myLocationString);
|
||||||
|
}
|
||||||
|
b.append("myMessage", myMessage);
|
||||||
|
if (mySeverity != null) {
|
||||||
|
b.append("mySeverity", mySeverity.getCode());
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -20,97 +20,152 @@ package ca.uhn.fhir.validation;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.thymeleaf.util.Validate;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
import ca.uhn.fhir.model.api.Bundle;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
|
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
|
|
||||||
class ValidationContext<T> implements IValidationContext<T> {
|
class ValidationContext<T> implements IValidationContext<T> {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ValidationContext.class);
|
|
||||||
private final IEncoder myEncoder;
|
private final IEncoder myEncoder;
|
||||||
private final FhirContext myFhirContext;
|
private final FhirContext myFhirContext;
|
||||||
private IBaseOperationOutcome myOperationOutcome;
|
private List<SingleValidationMessage> myMessages = new ArrayList<SingleValidationMessage>();
|
||||||
private final T myResource;
|
private final T myResource;
|
||||||
private String myXmlEncodedResource;
|
private String myResourceAsString;
|
||||||
|
private final EncodingEnum myResourceAsStringEncoding;
|
||||||
|
private final String myResourceName;
|
||||||
|
|
||||||
private ValidationContext(FhirContext theContext, T theResource, IEncoder theEncoder) {
|
private ValidationContext(FhirContext theContext, T theResource, String theResourceName, IEncoder theEncoder) {
|
||||||
myFhirContext = theContext;
|
myFhirContext = theContext;
|
||||||
myResource = theResource;
|
myResource = theResource;
|
||||||
myEncoder = theEncoder;
|
myEncoder = theEncoder;
|
||||||
|
myResourceName = theResourceName;
|
||||||
|
if (theEncoder != null) {
|
||||||
|
myResourceAsStringEncoding = theEncoder.getEncoding();
|
||||||
|
} else {
|
||||||
|
myResourceAsStringEncoding = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addValidationMessage(SingleValidationMessage theMessage) {
|
||||||
|
Validate.notNull(theMessage, "theMessage must not be null");
|
||||||
|
myMessages.add(theMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see ca.uhn.fhir.validation.IValidationContext#getFhirContext()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public FhirContext getFhirContext() {
|
public FhirContext getFhirContext() {
|
||||||
return myFhirContext;
|
return myFhirContext;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see ca.uhn.fhir.validation.IValidationContext#getOperationOutcome()
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
public IBaseOperationOutcome getOperationOutcome() {
|
|
||||||
if (myOperationOutcome == null) {
|
|
||||||
try {
|
|
||||||
myOperationOutcome = (IBaseOperationOutcome) myFhirContext.getResourceDefinition("OperationOutcome").getImplementingClass().newInstance();
|
|
||||||
} catch (Exception e1) {
|
|
||||||
ourLog.error("Failed to instantiate OperationOutcome resource instance", e1);
|
|
||||||
throw new InternalErrorException("Failed to instantiate OperationOutcome resource instance", e1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return myOperationOutcome;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see ca.uhn.fhir.validation.IValidationContext#getResource()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public T getResource() {
|
public T getResource() {
|
||||||
return myResource;
|
return myResource;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* (non-Javadoc)
|
|
||||||
* @see ca.uhn.fhir.validation.IValidationContext#getXmlEncodedResource()
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public String getXmlEncodedResource() {
|
public String getResourceAsString() {
|
||||||
if (myXmlEncodedResource == null) {
|
if (myResourceAsString == null) {
|
||||||
myXmlEncodedResource = myEncoder.encode();
|
myResourceAsString = myEncoder.encode();
|
||||||
}
|
}
|
||||||
return myXmlEncodedResource;
|
return myResourceAsString;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncodingEnum getResourceAsStringEncoding() {
|
||||||
|
return myResourceAsStringEncoding;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getResourceName() {
|
||||||
|
return myResourceName;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValidationResult toResult() {
|
||||||
|
return new ValidationResult(myFhirContext, myMessages);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IValidationContext<Bundle> forBundle(final FhirContext theContext, final Bundle theBundle) {
|
public static IValidationContext<Bundle> forBundle(final FhirContext theContext, final Bundle theBundle) {
|
||||||
return new ValidationContext<Bundle>(theContext, theBundle, new IEncoder() {
|
String resourceName = "Bundle";
|
||||||
|
return new ValidationContext<Bundle>(theContext, theBundle, resourceName, new IEncoder() {
|
||||||
@Override
|
@Override
|
||||||
public String encode() {
|
public String encode() {
|
||||||
return theContext.newXmlParser().encodeBundleToString(theBundle);
|
return theContext.newXmlParser().encodeBundleToString(theBundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncodingEnum getEncoding() {
|
||||||
|
return EncodingEnum.XML;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static <T extends IBaseResource> IValidationContext<T> forResource(final FhirContext theContext, final T theResource) {
|
public static <T extends IBaseResource> IValidationContext<T> forResource(final FhirContext theContext, final T theResource) {
|
||||||
return new ValidationContext<T>(theContext, theResource, new IEncoder() {
|
String resourceName = theContext.getResourceDefinition(theResource).getName();
|
||||||
|
return new ValidationContext<T>(theContext, theResource, resourceName, new IEncoder() {
|
||||||
@Override
|
@Override
|
||||||
public String encode() {
|
public String encode() {
|
||||||
return theContext.newXmlParser().encodeResourceToString(theResource);
|
return theContext.newXmlParser().encodeResourceToString(theResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncodingEnum getEncoding() {
|
||||||
|
return EncodingEnum.XML;
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public static IValidationContext<IBaseResource> newChild(IValidationContext<Bundle> theContext, IBaseResource theResource) {
|
public static IValidationContext<IBaseResource> newChild(final IValidationContext<Bundle> theContext, final IResource theResource) {
|
||||||
ValidationContext<IBaseResource> retVal = (ValidationContext<IBaseResource>) forResource(theContext.getFhirContext(), theResource);
|
return new IValidationContext<IBaseResource>() {
|
||||||
retVal.myOperationOutcome = theContext.getOperationOutcome();
|
|
||||||
return retVal;
|
@Override
|
||||||
|
public void addValidationMessage(SingleValidationMessage theMessage) {
|
||||||
|
theContext.addValidationMessage(theMessage);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public FhirContext getFhirContext() {
|
||||||
|
return theContext.getFhirContext();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public IBaseResource getResource() {
|
||||||
|
return theResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getResourceAsString() {
|
||||||
|
return theContext.getFhirContext().newXmlParser().encodeResourceToString(theResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public EncodingEnum getResourceAsStringEncoding() {
|
||||||
|
return EncodingEnum.XML;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getResourceName() {
|
||||||
|
return theContext.getFhirContext().getResourceDefinition(theResource).getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ValidationResult toResult() {
|
||||||
|
return theContext.toResult();
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface IEncoder {
|
private interface IEncoder {
|
||||||
String encode();
|
String encode();
|
||||||
|
|
||||||
|
EncodingEnum getEncoding();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,11 @@ package ca.uhn.fhir.validation;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
@ -32,40 +37,25 @@ import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||||
* @since 0.7
|
* @since 0.7
|
||||||
*/
|
*/
|
||||||
public class ValidationResult {
|
public class ValidationResult {
|
||||||
private IBaseOperationOutcome myOperationOutcome;
|
private final FhirContext myCtx;
|
||||||
private boolean myIsSuccessful;
|
private final boolean myIsSuccessful;
|
||||||
private FhirContext myCtx;
|
private final List<SingleValidationMessage> myMessages;
|
||||||
|
|
||||||
private ValidationResult(FhirContext theCtx, IBaseOperationOutcome theOperationOutcome, boolean isSuccessful) {
|
public ValidationResult(FhirContext theCtx, List<SingleValidationMessage> theMessages) {
|
||||||
this.myCtx = theCtx;
|
boolean successful = true;
|
||||||
this.myOperationOutcome = theOperationOutcome;
|
myCtx = theCtx;
|
||||||
this.myIsSuccessful = isSuccessful;
|
myMessages = theMessages;
|
||||||
|
for (SingleValidationMessage next : myMessages) {
|
||||||
|
next.getSeverity();
|
||||||
|
if (next.getSeverity() == null || next.getSeverity().ordinal() > ResultSeverityEnum.WARNING.ordinal()) {
|
||||||
|
successful = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
myIsSuccessful = successful;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static ValidationResult valueOf(FhirContext theCtx, IBaseOperationOutcome myOperationOutcome) {
|
public List<SingleValidationMessage> getMessages() {
|
||||||
boolean noIssues = !OperationOutcomeUtil.hasIssues(theCtx, myOperationOutcome);
|
return Collections.unmodifiableList(myMessages);
|
||||||
return new ValidationResult(theCtx, myOperationOutcome, noIssues);
|
|
||||||
}
|
|
||||||
|
|
||||||
public IBaseOperationOutcome getOperationOutcome() {
|
|
||||||
return myOperationOutcome;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String toString() {
|
|
||||||
return "ValidationResult{" + "myOperationOutcome=" + myOperationOutcome + ", isSuccessful=" + myIsSuccessful + ", description='" + toDescription() + '\'' + '}';
|
|
||||||
}
|
|
||||||
|
|
||||||
private String toDescription() {
|
|
||||||
StringBuilder b = new StringBuilder(100);
|
|
||||||
if (myOperationOutcome != null && OperationOutcomeUtil.hasIssues(myCtx, myOperationOutcome)) {
|
|
||||||
b.append(OperationOutcomeUtil.getFirstIssueDetails(myCtx, myOperationOutcome));
|
|
||||||
b.append(" - ");
|
|
||||||
b.append(OperationOutcomeUtil.getFirstIssueLocation(myCtx, myOperationOutcome));
|
|
||||||
} else {
|
|
||||||
b.append("No issues");
|
|
||||||
}
|
|
||||||
return b.toString();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -76,4 +66,50 @@ public class ValidationResult {
|
||||||
public boolean isSuccessful() {
|
public boolean isSuccessful() {
|
||||||
return myIsSuccessful;
|
return myIsSuccessful;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String toDescription() {
|
||||||
|
StringBuilder b = new StringBuilder(100);
|
||||||
|
if (myMessages.size() > 0) {
|
||||||
|
b.append(myMessages.get(0).getMessage());
|
||||||
|
b.append(" - ");
|
||||||
|
b.append(myMessages.get(0).getLocationString());
|
||||||
|
} else {
|
||||||
|
b.append("No issues");
|
||||||
|
}
|
||||||
|
return b.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #toOperationOutcome()} instead since this method returns a view
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public IBaseOperationOutcome getOperationOutcome() {
|
||||||
|
return toOperationOutcome();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an OperationOutcome resource which contains all of the messages found as a result of this validation
|
||||||
|
*/
|
||||||
|
public IBaseOperationOutcome toOperationOutcome() {
|
||||||
|
IBaseOperationOutcome oo = (IBaseOperationOutcome) myCtx.getResourceDefinition("OperationOutcome").newInstance();
|
||||||
|
for (SingleValidationMessage next : myMessages) {
|
||||||
|
String location;
|
||||||
|
if (isNotBlank(next.getLocationString())) {
|
||||||
|
location = next.getLocationString();
|
||||||
|
} else if (next.getLocationRow() != null || next.getLocationCol() != null) {
|
||||||
|
location = "Line[" + next.getLocationRow() + "] Col[" + next.getLocationCol() + "]";
|
||||||
|
} else {
|
||||||
|
location = null;
|
||||||
|
}
|
||||||
|
String severity = next.getSeverity() != null ? next.getSeverity().getCode() : null;
|
||||||
|
OperationOutcomeUtil.addIssue(myCtx, oo, severity, next.getMessage(), location);
|
||||||
|
}
|
||||||
|
|
||||||
|
return oo;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return "ValidationResult{" + "messageCount=" + myMessages.size() + ", isSuccessful=" + myIsSuccessful + ", description='" + toDescription() + '\'' + '}';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,18 @@
|
||||||
|
package ca.uhn.fhir.validation;
|
||||||
|
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
public class ResultSeverityEnumTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testOrdinals() {
|
||||||
|
assertEquals(0, ResultSeverityEnum.INFORMATION.ordinal());
|
||||||
|
assertEquals(1, ResultSeverityEnum.WARNING.ordinal());
|
||||||
|
assertEquals(2, ResultSeverityEnum.ERROR.ordinal());
|
||||||
|
assertEquals(3, ResultSeverityEnum.FATAL.ordinal());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -51,6 +51,12 @@
|
||||||
<version>1.1-SNAPSHOT</version>
|
<version>1.1-SNAPSHOT</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
|
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
|
||||||
|
<version>1.1-SNAPSHOT</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.slf4j</groupId>
|
<groupId>org.slf4j</groupId>
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
package ca.uhn.fhir.jpa.provider;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR JPA Server
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2015 University Health Network
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.dstu2.resource.Encounter;
|
||||||
|
|
||||||
|
public class BaseJpaResourceProviderQuestionnaireAnswersDstu2 extends JpaResourceProviderDstu2<Encounter> {
|
||||||
|
|
||||||
|
// nothing yet
|
||||||
|
|
||||||
|
}
|
|
@ -87,6 +87,9 @@ public class BaseJpaResourceProviderValueSetDstu2 extends JpaResourceProviderDst
|
||||||
} else {
|
} else {
|
||||||
String filter = theFilter.getValue().toLowerCase();
|
String filter = theFilter.getValue().toLowerCase();
|
||||||
if (next.getDisplay().toLowerCase().contains(filter) || next.getCode().toLowerCase().contains(filter)) {
|
if (next.getDisplay().toLowerCase().contains(filter) || next.getCode().toLowerCase().contains(filter)) {
|
||||||
|
if (include == null) {
|
||||||
|
include = getOrAddComposeInclude(retVal, systemToCompose, source.getDefine().getSystem());
|
||||||
|
}
|
||||||
include.addConcept(new ComposeIncludeConcept().setCode(next.getCode()).setDisplay(next.getDisplay()));
|
include.addConcept(new ComposeIncludeConcept().setCode(next.getCode()).setDisplay(next.getDisplay()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -340,8 +340,7 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Try it with a raw socket call. The Apache client won't let us use the unescaped "|" in the URL but we want to
|
* Try it with a raw socket call. The Apache client won't let us use the unescaped "|" in the URL but we want to make sure that works too..
|
||||||
* make sure that works too..
|
|
||||||
*/
|
*/
|
||||||
Socket sock = new Socket();
|
Socket sock = new Socket();
|
||||||
sock.setSoTimeout(3000);
|
sock.setSoTimeout(3000);
|
||||||
|
@ -715,7 +714,8 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
|
||||||
p1.addIdentifier().setValue("testSearchByIdentifierWithoutSystem01");
|
p1.addIdentifier().setValue("testSearchByIdentifierWithoutSystem01");
|
||||||
IdDt p1Id = (IdDt) ourClient.create().resource(p1).execute().getId();
|
IdDt p1Id = (IdDt) ourClient.create().resource(p1).execute().getId();
|
||||||
|
|
||||||
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint().execute();
|
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint()
|
||||||
|
.execute();
|
||||||
assertEquals(1, actual.size());
|
assertEquals(1, actual.size());
|
||||||
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getResource().getId().getIdPart());
|
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getResource().getId().getIdPart());
|
||||||
|
|
||||||
|
@ -1141,7 +1141,8 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
|
||||||
|
|
||||||
assertThat(p1Id.getValue(), containsString("Patient/testUpdateWithClientSuppliedIdWhichDoesntExistRpDstu2/_history"));
|
assertThat(p1Id.getValue(), containsString("Patient/testUpdateWithClientSuppliedIdWhichDoesntExistRpDstu2/_history"));
|
||||||
|
|
||||||
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExistRpDstu2")).encodedJson().prettyPrint().execute();
|
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testUpdateWithClientSuppliedIdWhichDoesntExistRpDstu2"))
|
||||||
|
.encodedJson().prettyPrint().execute();
|
||||||
assertEquals(1, actual.size());
|
assertEquals(1, actual.size());
|
||||||
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getResource().getId().getIdPart());
|
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getResource().getId().getIdPart());
|
||||||
|
|
||||||
|
@ -1174,54 +1175,6 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// @Test
|
|
||||||
public void testValueSetExpandOperartion() throws IOException {
|
|
||||||
|
|
||||||
ValueSet upload = ourCtx.newXmlParser().parseResource(ValueSet.class, new InputStreamReader(ResourceProviderDstu2Test.class.getResourceAsStream("/extensional-case-2.xml")));
|
|
||||||
IIdType vsid = ourClient.create().resource(upload).execute().getId().toUnqualifiedVersionless();
|
|
||||||
|
|
||||||
HttpGet get = new HttpGet(ourServerBase + "/ValueSet/" + vsid.getIdPart() + "/$expand");
|
|
||||||
CloseableHttpResponse response = ourHttpClient.execute(get);
|
|
||||||
try {
|
|
||||||
String resp = IOUtils.toString(response.getEntity().getContent());
|
|
||||||
ourLog.info(resp);
|
|
||||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
|
||||||
//@formatter:on
|
|
||||||
assertThat(resp, stringContainsInOrder(
|
|
||||||
"<ValueSet xmlns=\"http://hl7.org/fhir\">",
|
|
||||||
"<compose>" ,
|
|
||||||
"<include>" ,
|
|
||||||
"<system value=\"http://loinc.org\"/>" ,
|
|
||||||
"<concept>" ,
|
|
||||||
"<code value=\"11378-7\"/>" ,
|
|
||||||
"<display value=\"Systolic blood pressure at First encounter\"/>" ,
|
|
||||||
"</concept>"));
|
|
||||||
//@formatter:off
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(response.getEntity().getContent());
|
|
||||||
response.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Filter
|
|
||||||
*/
|
|
||||||
|
|
||||||
get = new HttpGet(ourServerBase + "/ValueSet/" + vsid.getIdPart() + "/$expand?filter=systolic");
|
|
||||||
response = ourHttpClient.execute(get);
|
|
||||||
try {
|
|
||||||
String resp = IOUtils.toString(response.getEntity().getContent());
|
|
||||||
ourLog.info(resp);
|
|
||||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
|
||||||
assertThat(resp, stringContainsInOrder(
|
|
||||||
"<code value=\"11378-7\"/>" ,
|
|
||||||
"<display value=\"Systolic blood pressure at First encounter\"/>"));
|
|
||||||
} finally {
|
|
||||||
IOUtils.closeQuietly(response.getEntity().getContent());
|
|
||||||
response.close();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testValidateResourceHuge() throws IOException {
|
public void testValidateResourceHuge() throws IOException {
|
||||||
|
|
||||||
|
@ -1277,6 +1230,83 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testValueSetExpandOperation() throws IOException {
|
||||||
|
|
||||||
|
ValueSet upload = ourCtx.newXmlParser().parseResource(ValueSet.class, new InputStreamReader(ResourceProviderDstu2Test.class.getResourceAsStream("/extensional-case-2.xml")));
|
||||||
|
IIdType vsid = ourClient.create().resource(upload).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
HttpGet get = new HttpGet(ourServerBase + "/ValueSet/" + vsid.getIdPart() + "/$expand");
|
||||||
|
CloseableHttpResponse response = ourHttpClient.execute(get);
|
||||||
|
try {
|
||||||
|
String resp = IOUtils.toString(response.getEntity().getContent());
|
||||||
|
ourLog.info(resp);
|
||||||
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
|
// @formatter:off
|
||||||
|
assertThat(
|
||||||
|
resp,
|
||||||
|
stringContainsInOrder("<ValueSet xmlns=\"http://hl7.org/fhir\">",
|
||||||
|
"<compose>",
|
||||||
|
"<include>",
|
||||||
|
"<system value=\"http://loinc.org\"/>",
|
||||||
|
"<concept>",
|
||||||
|
"<code value=\"11378-7\"/>",
|
||||||
|
"<display value=\"Systolic blood pressure at First encounter\"/>",
|
||||||
|
"</concept>",
|
||||||
|
"<concept>",
|
||||||
|
"<code value=\"8450-9\"/>",
|
||||||
|
"<display value=\"Systolic blood pressure--expiration\"/>",
|
||||||
|
"</concept>"
|
||||||
|
));
|
||||||
|
//@formatter:on
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(response.getEntity().getContent());
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filter with display name
|
||||||
|
*/
|
||||||
|
|
||||||
|
get = new HttpGet(ourServerBase + "/ValueSet/" + vsid.getIdPart() + "/$expand?filter=systolic");
|
||||||
|
response = ourHttpClient.execute(get);
|
||||||
|
try {
|
||||||
|
String resp = IOUtils.toString(response.getEntity().getContent());
|
||||||
|
ourLog.info(resp);
|
||||||
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
|
//@formatter:off
|
||||||
|
assertThat(resp, stringContainsInOrder(
|
||||||
|
"<code value=\"11378-7\"/>",
|
||||||
|
"<display value=\"Systolic blood pressure at First encounter\"/>"));
|
||||||
|
//@formatter:on
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(response.getEntity().getContent());
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Filter with code
|
||||||
|
*/
|
||||||
|
|
||||||
|
get = new HttpGet(ourServerBase + "/ValueSet/" + vsid.getIdPart() + "/$expand?filter=11378");
|
||||||
|
response = ourHttpClient.execute(get);
|
||||||
|
try {
|
||||||
|
String resp = IOUtils.toString(response.getEntity().getContent());
|
||||||
|
ourLog.info(resp);
|
||||||
|
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||||
|
//@formatter:off
|
||||||
|
assertThat(resp, stringContainsInOrder(
|
||||||
|
"<code value=\"11378-7\"/>",
|
||||||
|
"<display value=\"Systolic blood pressure at First encounter\"/>"
|
||||||
|
));
|
||||||
|
//@formatter:on
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(response.getEntity().getContent());
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
private List<IdDt> toIdListUnqualifiedVersionless(Bundle found) {
|
private List<IdDt> toIdListUnqualifiedVersionless(Bundle found) {
|
||||||
List<IdDt> list = new ArrayList<IdDt>();
|
List<IdDt> list = new ArrayList<IdDt>();
|
||||||
for (BundleEntry next : found.getEntries()) {
|
for (BundleEntry next : found.getEntries()) {
|
||||||
|
|
|
@ -19,6 +19,13 @@
|
||||||
<description value="an enumeration of codes defined by LOINC" />
|
<description value="an enumeration of codes defined by LOINC" />
|
||||||
<status value="draft" />
|
<status value="draft" />
|
||||||
<experimental value="true" />
|
<experimental value="true" />
|
||||||
|
<define>
|
||||||
|
<system value="http://loinc.org" />
|
||||||
|
<concept>
|
||||||
|
<code value="8450-9" />
|
||||||
|
<display value="Systolic blood pressure--expiration" />
|
||||||
|
</concept>
|
||||||
|
</define>
|
||||||
<compose>
|
<compose>
|
||||||
<include>
|
<include>
|
||||||
<system value="http://loinc.org" />
|
<system value="http://loinc.org" />
|
||||||
|
@ -38,10 +45,6 @@
|
||||||
<code value="8495-4" />
|
<code value="8495-4" />
|
||||||
<display value="Systolic blood pressure 24 hour minimum" />
|
<display value="Systolic blood pressure 24 hour minimum" />
|
||||||
</concept>
|
</concept>
|
||||||
<concept>
|
|
||||||
<code value="8450-9" />
|
|
||||||
<display value="Systolic blood pressure--expiration" />
|
|
||||||
</concept>
|
|
||||||
<concept>
|
<concept>
|
||||||
<code value="8451-7" />
|
<code value="8451-7" />
|
||||||
<display value="Systolic blood pressure--inspiration" />
|
<display value="Systolic blood pressure--inspiration" />
|
||||||
|
|
|
@ -84,10 +84,10 @@ public class ResourceValidatorTest {
|
||||||
assertEquals("2001-03-31", new SimpleDateFormat("yyyy-MM-dd").format(p.getBirthDate().getValue()));
|
assertEquals("2001-03-31", new SimpleDateFormat("yyyy-MM-dd").format(p.getBirthDate().getValue()));
|
||||||
|
|
||||||
ValidationResult result = ourCtx.newValidator().validateWithResult(p);
|
ValidationResult result = ourCtx.newValidator().validateWithResult(p);
|
||||||
String resultString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.getOperationOutcome());
|
String resultString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
|
||||||
ourLog.info(resultString);
|
ourLog.info(resultString);
|
||||||
|
|
||||||
assertEquals(2, ((OperationOutcome)result.getOperationOutcome()).getIssue().size());
|
assertEquals(2, ((OperationOutcome)result.toOperationOutcome()).getIssue().size());
|
||||||
assertThat(resultString, StringContains.containsString("cvc-datatype-valid.1.2.3"));
|
assertThat(resultString, StringContains.containsString("cvc-datatype-valid.1.2.3"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -129,7 +129,7 @@ public class ResourceValidatorTest {
|
||||||
p.getTelecomFirstRep().setValue("123-4567");
|
p.getTelecomFirstRep().setValue("123-4567");
|
||||||
validationResult = val.validateWithResult(p);
|
validationResult = val.validateWithResult(p);
|
||||||
assertFalse(validationResult.isSuccessful());
|
assertFalse(validationResult.isSuccessful());
|
||||||
OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome();
|
OperationOutcome operationOutcome = (OperationOutcome) validationResult.toOperationOutcome();
|
||||||
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
|
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
|
||||||
assertEquals(1, operationOutcome.getIssue().size());
|
assertEquals(1, operationOutcome.getIssue().size());
|
||||||
assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2:"));
|
assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2:"));
|
||||||
|
@ -147,7 +147,7 @@ public class ResourceValidatorTest {
|
||||||
FhirValidator val = createFhirValidator();
|
FhirValidator val = createFhirValidator();
|
||||||
|
|
||||||
ValidationResult result = val.validateWithResult(b);
|
ValidationResult result = val.validateWithResult(b);
|
||||||
OperationOutcome operationOutcome = (OperationOutcome) result.getOperationOutcome();
|
OperationOutcome operationOutcome = (OperationOutcome) result.toOperationOutcome();
|
||||||
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
|
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
|
||||||
|
|
||||||
|
|
||||||
|
@ -170,7 +170,7 @@ public class ResourceValidatorTest {
|
||||||
p.getTelecomFirstRep().setValue("123-4567");
|
p.getTelecomFirstRep().setValue("123-4567");
|
||||||
validationResult = val.validateWithResult(b);
|
validationResult = val.validateWithResult(b);
|
||||||
assertFalse(validationResult.isSuccessful());
|
assertFalse(validationResult.isSuccessful());
|
||||||
OperationOutcome operationOutcome = (OperationOutcome) validationResult.getOperationOutcome();
|
OperationOutcome operationOutcome = (OperationOutcome) validationResult.toOperationOutcome();
|
||||||
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
|
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
|
||||||
assertEquals(1, operationOutcome.getIssue().size());
|
assertEquals(1, operationOutcome.getIssue().size());
|
||||||
assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2:"));
|
assertThat(operationOutcome.getIssueFirstRep().getDetails().getValue(), containsString("Inv-2:"));
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
package ca.uhn.fhir.validation;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
|
|
||||||
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
public class ValidationResultDstu1Test {
|
|
||||||
|
|
||||||
private static final FhirContext ourCtx = FhirContext.forDstu1();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isSuccessful_FalseForIssues() {
|
|
||||||
OperationOutcome operationOutcome = new OperationOutcome();
|
|
||||||
OperationOutcome.Issue issue = operationOutcome.addIssue();
|
|
||||||
String errorMessage = "There was a validation problem";
|
|
||||||
issue.setDetails(errorMessage);
|
|
||||||
ValidationResult result = ValidationResult.valueOf(ourCtx, operationOutcome);
|
|
||||||
assertFalse(result.isSuccessful());
|
|
||||||
List<? extends BaseIssue> issues = ((OperationOutcome) result.getOperationOutcome()).getIssue();
|
|
||||||
assertEquals(1, issues.size());
|
|
||||||
assertEquals(errorMessage, issues.get(0).getDetailsElement().getValue());
|
|
||||||
|
|
||||||
assertThat("ValidationResult#toString should contain the issue description", result.toString(), containsString(errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isSuccessful_IsTrueForNoIssues() {
|
|
||||||
OperationOutcome operationOutcome = new OperationOutcome();
|
|
||||||
// make sure a non-null ID doesn't cause the validation result to be a fail
|
|
||||||
operationOutcome.setId(UUID.randomUUID().toString());
|
|
||||||
ValidationResult result = ValidationResult.valueOf(ourCtx, operationOutcome);
|
|
||||||
assertTrue(result.isSuccessful());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isSuccessful_IsTrueForNullOperationOutcome() {
|
|
||||||
ValidationResult result = ValidationResult.valueOf(ourCtx, null);
|
|
||||||
assertTrue(result.isSuccessful());
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Test for https://github.com/jamesagnew/hapi-fhir/issues/51
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void toString_ShouldNotCauseResultToBecomeFailure() {
|
|
||||||
OperationOutcome operationOutcome = new OperationOutcome();
|
|
||||||
ValidationResult result = ValidationResult.valueOf(ourCtx, operationOutcome);
|
|
||||||
assertEquals(true, result.isSuccessful());
|
|
||||||
// need to call toString to make sure any unwanted side effects are generated
|
|
||||||
result.toString();
|
|
||||||
assertEquals(true, result.isSuccessful());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -34,6 +34,7 @@ import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
import ca.uhn.fhir.model.dstu2.resource.Parameters;
|
||||||
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
import ca.uhn.fhir.model.dstu2.resource.Patient;
|
||||||
|
import ca.uhn.fhir.model.primitive.DateTimeDt;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
@ -68,6 +69,14 @@ public class OperationServerTest {
|
||||||
ourLastMethod = "";
|
ourLastMethod = "";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void main(String[] theValue) {
|
||||||
|
Parameters p = new Parameters();
|
||||||
|
p.addParameter().setName("start").setValue(new DateTimeDt("2001-01-02"));
|
||||||
|
p.addParameter().setName("end").setValue(new DateTimeDt("2015-07-10"));
|
||||||
|
String inParamsStr = FhirContext.forDstu2().newXmlParser().encodeResourceToString(p);
|
||||||
|
ourLog.info(inParamsStr.replace("\"", "\\\""));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOperationOnType() throws Exception {
|
public void testOperationOnType() throws Exception {
|
||||||
Parameters p = new Parameters();
|
Parameters p = new Parameters();
|
||||||
|
|
|
@ -1,64 +0,0 @@
|
||||||
package ca.uhn.fhir.validation;
|
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.containsString;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
|
||||||
import static org.junit.Assert.assertFalse;
|
|
||||||
import static org.junit.Assert.assertThat;
|
|
||||||
import static org.junit.Assert.assertTrue;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.UUID;
|
|
||||||
|
|
||||||
import org.junit.Test;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome.BaseIssue;
|
|
||||||
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
|
|
||||||
|
|
||||||
public class ValidationResultDstu2Test {
|
|
||||||
|
|
||||||
private static final FhirContext ourCtx = FhirContext.forDstu2();
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isSuccessful_IsTrueForNullOperationOutcome() {
|
|
||||||
ValidationResult result = ValidationResult.valueOf(ourCtx, null);
|
|
||||||
assertTrue(result.isSuccessful());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isSuccessful_IsTrueForNoIssues() {
|
|
||||||
OperationOutcome operationOutcome = new OperationOutcome();
|
|
||||||
// make sure a non-null ID doesn't cause the validation result to be a fail
|
|
||||||
operationOutcome.setId(UUID.randomUUID().toString());
|
|
||||||
ValidationResult result = ValidationResult.valueOf(ourCtx, operationOutcome);
|
|
||||||
assertTrue(result.isSuccessful());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void isSuccessful_FalseForIssues() {
|
|
||||||
OperationOutcome operationOutcome = new OperationOutcome();
|
|
||||||
OperationOutcome.Issue issue = operationOutcome.addIssue();
|
|
||||||
String errorMessage = "There was a validation problem";
|
|
||||||
issue.setDetails(errorMessage);
|
|
||||||
ValidationResult result = ValidationResult.valueOf(ourCtx, operationOutcome);
|
|
||||||
assertFalse(result.isSuccessful());
|
|
||||||
List<? extends BaseIssue> issues = ((OperationOutcome)result.getOperationOutcome()).getIssue();
|
|
||||||
assertEquals(1, issues.size());
|
|
||||||
assertEquals(errorMessage, issues.get(0).getDetailsElement().getValue());
|
|
||||||
|
|
||||||
assertThat("ValidationResult#toString should contain the issue description", result.toString(), containsString(errorMessage));
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Test for https://github.com/jamesagnew/hapi-fhir/issues/51
|
|
||||||
*/
|
|
||||||
@Test
|
|
||||||
public void toString_ShouldNotCauseResultToBecomeFailure() {
|
|
||||||
OperationOutcome operationOutcome = new OperationOutcome();
|
|
||||||
ValidationResult result = ValidationResult.valueOf(ourCtx, operationOutcome);
|
|
||||||
assertEquals(true, result.isSuccessful());
|
|
||||||
// need to call toString to make sure any unwanted side effects are generated
|
|
||||||
@SuppressWarnings("UnusedDeclaration") String unused = result.toString();
|
|
||||||
assertEquals(true, result.isSuccessful());
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -23,24 +23,22 @@ import com.google.gson.JsonObject;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.api.Bundle;
|
||||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
|
||||||
public class StructureDefinitionValidator {
|
public class FhirInstanceValidator implements IValidator {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StructureDefinitionValidator.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
|
||||||
|
|
||||||
private FhirContext myCtx;
|
|
||||||
private DocumentBuilderFactory myDocBuilderFactory;
|
private DocumentBuilderFactory myDocBuilderFactory;
|
||||||
|
|
||||||
StructureDefinitionValidator(FhirContext theContext) {
|
FhirInstanceValidator() {
|
||||||
myCtx = theContext;
|
|
||||||
|
|
||||||
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
|
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
|
||||||
myDocBuilderFactory.setNamespaceAware(true);
|
myDocBuilderFactory.setNamespaceAware(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<ValidationMessage> validate(String theInput, EncodingEnum theEncoding, Class<? extends IBaseResource> theResourceType) {
|
List<ValidationMessage> validate(FhirContext theCtx, String theInput, EncodingEnum theEncoding, String theResourceName) {
|
||||||
WorkerContext workerContext = new WorkerContext();
|
WorkerContext workerContext = new WorkerContext();
|
||||||
org.hl7.fhir.instance.validation.InstanceValidator v;
|
org.hl7.fhir.instance.validation.InstanceValidator v;
|
||||||
try {
|
try {
|
||||||
|
@ -49,14 +47,14 @@ public class StructureDefinitionValidator {
|
||||||
throw new ConfigurationException(e);
|
throw new ConfigurationException(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
String profileCpName = "/org/hl7/fhir/instance/model/profile/" + myCtx.getResourceDefinition(theResourceType).getName().toLowerCase() + ".profile.xml";
|
String profileCpName = "/org/hl7/fhir/instance/model/profile/" + theResourceName.toLowerCase() + ".profile.xml";
|
||||||
String profileText;
|
String profileText;
|
||||||
try {
|
try {
|
||||||
profileText = IOUtils.toString(StructureDefinitionValidator.class.getResourceAsStream(profileCpName), "UTF-8");
|
profileText = IOUtils.toString(FhirInstanceValidator.class.getResourceAsStream(profileCpName), "UTF-8");
|
||||||
} catch (IOException e1) {
|
} catch (IOException e1) {
|
||||||
throw new ConfigurationException("Failed to load profile from classpath: " + profileCpName, e1);
|
throw new ConfigurationException("Failed to load profile from classpath: " + profileCpName, e1);
|
||||||
}
|
}
|
||||||
StructureDefinition profile = myCtx.newXmlParser().parseResource(StructureDefinition.class, profileText);
|
StructureDefinition profile = theCtx.newXmlParser().parseResource(StructureDefinition.class, profileText);
|
||||||
|
|
||||||
if (theEncoding == EncodingEnum.XML) {
|
if (theEncoding == EncodingEnum.XML) {
|
||||||
Document document;
|
Document document;
|
||||||
|
@ -91,4 +89,16 @@ public class StructureDefinitionValidator {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateResource(IValidationContext<IBaseResource> theCtx) {
|
||||||
|
String resourceName = theCtx.getResourceName();
|
||||||
|
String resourceBody = theCtx.getResourceAsString();
|
||||||
|
validate(theCtx.getFhirContext(), resourceBody, theCtx.getResourceAsStringEncoding(), resourceName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateBundle(IValidationContext<Bundle> theContext) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
|
@ -624,7 +624,7 @@ public class ValueSet extends DomainResource {
|
||||||
/**
|
/**
|
||||||
* If this code is not for use as a real concept.
|
* If this code is not for use as a real concept.
|
||||||
*/
|
*/
|
||||||
@Child(name = "abstract_", type = {BooleanType.class}, order=2, min=0, max=1)
|
@Child(name = "abstract", type = {BooleanType.class}, order=2, min=0, max=1)
|
||||||
@Description(shortDefinition="If this code is not for use as a real concept", formalDefinition="If this code is not for use as a real concept." )
|
@Description(shortDefinition="If this code is not for use as a real concept", formalDefinition="If this code is not for use as a real concept." )
|
||||||
protected BooleanType abstract_;
|
protected BooleanType abstract_;
|
||||||
|
|
||||||
|
@ -1210,7 +1210,7 @@ public class ValueSet extends DomainResource {
|
||||||
/**
|
/**
|
||||||
* Includes the contents of the referenced value set as a part of the contents of this value set. This is an absolute URI that is a reference to ValueSet.uri.
|
* Includes the contents of the referenced value set as a part of the contents of this value set. This is an absolute URI that is a reference to ValueSet.uri.
|
||||||
*/
|
*/
|
||||||
@Child(name = "import_", type = {UriType.class}, order=1, min=0, max=Child.MAX_UNLIMITED)
|
@Child(name = "import", type = {UriType.class}, order=1, min=0, max=Child.MAX_UNLIMITED)
|
||||||
@Description(shortDefinition="Import the contents of another value set", formalDefinition="Includes the contents of the referenced value set as a part of the contents of this value set. This is an absolute URI that is a reference to ValueSet.uri." )
|
@Description(shortDefinition="Import the contents of another value set", formalDefinition="Includes the contents of the referenced value set as a part of the contents of this value set. This is an absolute URI that is a reference to ValueSet.uri." )
|
||||||
protected List<UriType> import_;
|
protected List<UriType> import_;
|
||||||
|
|
||||||
|
@ -2757,7 +2757,7 @@ public class ValueSet extends DomainResource {
|
||||||
/**
|
/**
|
||||||
* If true, this entry is included in the expansion for navigational purposes, and the user cannot select the code directly as a proper value.
|
* If true, this entry is included in the expansion for navigational purposes, and the user cannot select the code directly as a proper value.
|
||||||
*/
|
*/
|
||||||
@Child(name = "abstract_", type = {BooleanType.class}, order=2, min=0, max=1)
|
@Child(name = "abstract", type = {BooleanType.class}, order=2, min=0, max=1)
|
||||||
@Description(shortDefinition="If user cannot select this entry", formalDefinition="If true, this entry is included in the expansion for navigational purposes, and the user cannot select the code directly as a proper value." )
|
@Description(shortDefinition="If user cannot select this entry", formalDefinition="If true, this entry is included in the expansion for navigational purposes, and the user cannot select the code directly as a proper value." )
|
||||||
protected BooleanType abstract_;
|
protected BooleanType abstract_;
|
||||||
|
|
||||||
|
|
|
@ -17,6 +17,7 @@ import org.hl7.fhir.instance.model.Conformance;
|
||||||
import org.hl7.fhir.instance.model.ElementDefinition.TypeRefComponent;
|
import org.hl7.fhir.instance.model.ElementDefinition.TypeRefComponent;
|
||||||
import org.hl7.fhir.instance.model.OperationOutcome;
|
import org.hl7.fhir.instance.model.OperationOutcome;
|
||||||
import org.hl7.fhir.instance.model.Parameters;
|
import org.hl7.fhir.instance.model.Parameters;
|
||||||
|
import org.hl7.fhir.instance.model.Questionnaire;
|
||||||
import org.hl7.fhir.instance.model.Resource;
|
import org.hl7.fhir.instance.model.Resource;
|
||||||
import org.hl7.fhir.instance.model.StructureDefinition;
|
import org.hl7.fhir.instance.model.StructureDefinition;
|
||||||
import org.hl7.fhir.instance.model.ValueSet;
|
import org.hl7.fhir.instance.model.ValueSet;
|
||||||
|
@ -57,14 +58,13 @@ public class WorkerContext implements NameResolver {
|
||||||
private Map<String, StructureDefinition> extensionDefinitions = new HashMap<String, StructureDefinition>();
|
private Map<String, StructureDefinition> extensionDefinitions = new HashMap<String, StructureDefinition>();
|
||||||
private String version;
|
private String version;
|
||||||
private List<String> resourceNames = new ArrayList<String>();
|
private List<String> resourceNames = new ArrayList<String>();
|
||||||
|
private Map<String, Questionnaire> questionnaires = new HashMap<String, Questionnaire>();
|
||||||
|
|
||||||
public WorkerContext() {
|
public WorkerContext() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public WorkerContext(ITerminologyServices conceptLocator, IFHIRClient client, Map<String, ValueSet> codeSystems,
|
public WorkerContext(ITerminologyServices conceptLocator, IFHIRClient client, Map<String, ValueSet> codeSystems, Map<String, ValueSet> valueSets, Map<String, ConceptMap> maps, Map<String, StructureDefinition> profiles) {
|
||||||
Map<String, ValueSet> valueSets, Map<String, ConceptMap> maps, Map<String, StructureDefinition> profiles) {
|
|
||||||
super();
|
super();
|
||||||
if (conceptLocator != null)
|
if (conceptLocator != null)
|
||||||
this.terminologyServices = conceptLocator;
|
this.terminologyServices = conceptLocator;
|
||||||
|
@ -87,6 +87,7 @@ public class WorkerContext implements NameResolver {
|
||||||
public boolean hasClient() {
|
public boolean hasClient() {
|
||||||
return !(client == null || client instanceof NullClient);
|
return !(client == null || client instanceof NullClient);
|
||||||
}
|
}
|
||||||
|
|
||||||
public IFHIRClient getClient() {
|
public IFHIRClient getClient() {
|
||||||
return client;
|
return client;
|
||||||
}
|
}
|
||||||
|
@ -111,6 +112,10 @@ public class WorkerContext implements NameResolver {
|
||||||
return extensionDefinitions;
|
return extensionDefinitions;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, Questionnaire> getQuestionnaires() {
|
||||||
|
return questionnaires;
|
||||||
|
}
|
||||||
|
|
||||||
public WorkerContext setTerminologyServices(ITerminologyServices terminologyServices) {
|
public WorkerContext setTerminologyServices(ITerminologyServices terminologyServices) {
|
||||||
this.terminologyServices = terminologyServices;
|
this.terminologyServices = terminologyServices;
|
||||||
return this;
|
return this;
|
||||||
|
@ -124,9 +129,6 @@ public class WorkerContext implements NameResolver {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public void seeExtensionDefinition(String base, StructureDefinition ed) throws Exception {
|
public void seeExtensionDefinition(String base, StructureDefinition ed) throws Exception {
|
||||||
if (extensionDefinitions.get(ed.getUrl()) != null)
|
if (extensionDefinitions.get(ed.getUrl()) != null)
|
||||||
throw new Exception("duplicate extension definition: " + ed.getUrl());
|
throw new Exception("duplicate extension definition: " + ed.getUrl());
|
||||||
|
@ -135,6 +137,11 @@ public class WorkerContext implements NameResolver {
|
||||||
extensionDefinitions.put(ed.getUrl(), ed);
|
extensionDefinitions.put(ed.getUrl(), ed);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void seeQuestionnaire(String base, Questionnaire theQuestionnaire) throws Exception {
|
||||||
|
questionnaires.put(theQuestionnaire.getId(), theQuestionnaire);
|
||||||
|
questionnaires.put(base + "/Questionnaire/" + theQuestionnaire.getId(), theQuestionnaire);
|
||||||
|
}
|
||||||
|
|
||||||
public void seeValueSet(String base, ValueSet vs) {
|
public void seeValueSet(String base, ValueSet vs) {
|
||||||
valueSets.put(vs.getId(), vs);
|
valueSets.put(vs.getId(), vs);
|
||||||
valueSets.put(base + "/ValueSet/" + vs.getId(), vs);
|
valueSets.put(base + "/ValueSet/" + vs.getId(), vs);
|
||||||
|
@ -408,5 +415,3 @@ public class WorkerContext implements NameResolver {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -29,8 +29,10 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.text.MessageFormat;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
|
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
|
||||||
import org.hl7.fhir.instance.model.valuesets.IssueType;
|
import org.hl7.fhir.instance.model.valuesets.IssueType;
|
||||||
import org.hl7.fhir.instance.validation.ValidationMessage.Source;
|
import org.hl7.fhir.instance.validation.ValidationMessage.Source;
|
||||||
|
@ -39,73 +41,180 @@ public class BaseValidator {
|
||||||
|
|
||||||
protected Source source;
|
protected Source source;
|
||||||
|
|
||||||
protected boolean fail(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean b, String msg) {
|
/**
|
||||||
if (!b)
|
* Test a rule and add a {@link IssueSeverity#FATAL} 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 fail(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) {
|
||||||
|
if (!thePass) {
|
||||||
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.FATAL));
|
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.FATAL));
|
||||||
return b;
|
}
|
||||||
|
return thePass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
protected boolean rule(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean b, String msg) {
|
* Test a rule and add a {@link IssueSeverity#FATAL} validation message if the validation fails
|
||||||
if (!b)
|
*
|
||||||
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.ERROR));
|
* @param thePass
|
||||||
return b;
|
* 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, int line, int col, String path, boolean b, String msg) {
|
protected boolean fail(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String msg) {
|
||||||
if (!b)
|
if (!thePass) {
|
||||||
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.INFORMATION));
|
String path = StringUtils.join(pathParts, '.');
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean warning(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean b, String msg) {
|
|
||||||
if (!b)
|
|
||||||
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.WARNING));
|
|
||||||
return b;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean fail(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg) {
|
|
||||||
if (!b)
|
|
||||||
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.FATAL));
|
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.FATAL));
|
||||||
return b;
|
}
|
||||||
|
return thePass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test a rule and add a {@link IssueSeverity#FATAL} 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 fail(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
|
||||||
|
if (!thePass) {
|
||||||
|
String path = StringUtils.join(pathParts, '.');
|
||||||
|
errors.add(new ValidationMessage(source, type, -1, -1, path, formatMessage(theMessage, theMessageArguments), IssueSeverity.FATAL));
|
||||||
|
}
|
||||||
|
return thePass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test a rule and add a {@link IssueSeverity#FATAL} 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 fail(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
|
||||||
|
if (!thePass) {
|
||||||
|
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.FATAL));
|
||||||
|
}
|
||||||
|
return thePass;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg) {
|
private String formatMessage(String theMessage, Object... theMessageArguments) {
|
||||||
if (!b)
|
String message;
|
||||||
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.ERROR));
|
if (theMessageArguments != null && theMessageArguments.length > 0) {
|
||||||
return b;
|
message = MessageFormat.format(theMessage, theMessageArguments);
|
||||||
|
} else {
|
||||||
|
message = theMessage;
|
||||||
}
|
}
|
||||||
|
return message;
|
||||||
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg, String html) {
|
|
||||||
if (!b)
|
|
||||||
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.ERROR));
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean hint(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg) {
|
|
||||||
if (!b)
|
|
||||||
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.INFORMATION));
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean warning(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg) {
|
|
||||||
if (!b)
|
|
||||||
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.WARNING));
|
|
||||||
return b;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected boolean warning(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg, String html) {
|
|
||||||
if (!b)
|
|
||||||
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.WARNING));
|
|
||||||
return b;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean grammarWord(String w) {
|
protected boolean grammarWord(String w) {
|
||||||
return w.equals("and") || w.equals("or") || w.equals("a") || w.equals("the") || w.equals("for") || w.equals("this") || w.equals("that") || w.equals("of");
|
return w.equals("and") || w.equals("or") || w.equals("a") || w.equals("the") || w.equals("for") || w.equals("this") || w.equals("that") || w.equals("of");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, int line, int col, String path, boolean thePass, String msg) {
|
||||||
|
if (!thePass) {
|
||||||
|
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.INFORMATION));
|
||||||
|
}
|
||||||
|
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
|
||||||
|
*
|
||||||
|
* @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 rule(List<ValidationMessage> errors, IssueType type, int line, int col, String path, boolean thePass, String msg) {
|
||||||
|
if (!thePass) {
|
||||||
|
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.ERROR));
|
||||||
|
}
|
||||||
|
return thePass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 rule(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String msg) {
|
||||||
|
if (!thePass) {
|
||||||
|
String path = StringUtils.join(pathParts, '.');
|
||||||
|
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.ERROR));
|
||||||
|
}
|
||||||
|
return thePass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 rule(List<ValidationMessage> errors, IssueType type, List<String> pathParts, boolean thePass, String theMessage, Object... theMessageArguments) {
|
||||||
|
if (!thePass) {
|
||||||
|
String path = StringUtils.join(pathParts, '.');
|
||||||
|
String message = formatMessage(theMessage, theMessageArguments);
|
||||||
|
errors.add(new ValidationMessage(source, type, -1, -1, path, message, IssueSeverity.ERROR));
|
||||||
|
}
|
||||||
|
return thePass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 rule(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg) {
|
||||||
|
if (!thePass) {
|
||||||
|
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.ERROR));
|
||||||
|
}
|
||||||
|
return thePass;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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 rule(List<ValidationMessage> errors, IssueType type, String path, boolean thePass, String msg, String html) {
|
||||||
|
if (!thePass) {
|
||||||
|
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.ERROR));
|
||||||
|
}
|
||||||
|
return thePass;
|
||||||
|
}
|
||||||
|
|
||||||
protected String splitByCamelCase(String s) {
|
protected String splitByCamelCase(String s) {
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
for (int i = 0; i < s.length(); i++) {
|
for (int i = 0; i < s.length(); i++) {
|
||||||
|
@ -127,5 +236,47 @@ public class BaseValidator {
|
||||||
return b.toString();
|
return b.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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, int line, int col, String path, boolean thePass, String msg) {
|
||||||
|
if (!thePass) {
|
||||||
|
errors.add(new ValidationMessage(source, type, line, col, path, msg, IssueSeverity.WARNING));
|
||||||
|
}
|
||||||
|
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, String path, boolean thePass, String msg) {
|
||||||
|
if (!thePass) {
|
||||||
|
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, IssueSeverity.WARNING));
|
||||||
|
}
|
||||||
|
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, String path, boolean thePass, String msg, String html) {
|
||||||
|
if (!thePass) {
|
||||||
|
errors.add(new ValidationMessage(source, type, -1, -1, path, msg, html, IssueSeverity.WARNING));
|
||||||
|
}
|
||||||
|
return thePass;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -842,7 +842,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
// the instance validator had no issues against the base resource profile
|
// the instance validator had no issues against the base resource profile
|
||||||
private void start(List<ValidationMessage> errors, WrapperElement element, StructureDefinition profile, NodeStack stack) throws Exception {
|
private void start(List<ValidationMessage> errors, WrapperElement element, StructureDefinition profile, NodeStack stack) throws Exception {
|
||||||
// profile is valid, and matches the resource name
|
// profile is valid, and matches the resource name
|
||||||
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), profile.hasSnapshot(), "StructureDefinition has no snapshort - validation is against the snapshot, so it must be provided")) {
|
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), stack.getLiteralPath(), profile.hasSnapshot(), "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) {
|
||||||
validateElement(errors, profile, profile.getSnapshot().getElement().get(0), null, null, element, element.getName(), stack);
|
validateElement(errors, profile, profile.getSnapshot().getElement().get(0), null, null, element, element.getName(), stack);
|
||||||
|
|
||||||
checkDeclaredProfiles(errors, element, stack);
|
checkDeclaredProfiles(errors, element, stack);
|
||||||
|
@ -878,7 +878,7 @@ public class InstanceValidator extends BaseValidator implements IResourceValidat
|
||||||
if (rule(errors, IssueType.INVALID, element.line(), element.col(), p, !Utilities.noString(ref), "StructureDefinition reference invalid")) {
|
if (rule(errors, IssueType.INVALID, element.line(), element.col(), p, !Utilities.noString(ref), "StructureDefinition reference invalid")) {
|
||||||
StructureDefinition pr = context.getProfiles().get(ref);
|
StructureDefinition pr = context.getProfiles().get(ref);
|
||||||
if (warning(errors, IssueType.INVALID, element.line(), element.col(), p, pr != null, "StructureDefinition reference could not be resolved")) {
|
if (warning(errors, IssueType.INVALID, element.line(), element.col(), p, pr != null, "StructureDefinition reference could not be resolved")) {
|
||||||
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), p, pr.hasSnapshot(), "StructureDefinition has no snapshort - validation is against the snapshot, so it must be provided")) {
|
if (rule(errors, IssueType.STRUCTURE, element.line(), element.col(), p, pr.hasSnapshot(), "StructureDefinition has no snapshot - validation is against the snapshot, so it must be provided")) {
|
||||||
validateElement(errors, pr, pr.getSnapshot().getElement().get(0), null, null, element, element.getName(), stack);
|
validateElement(errors, pr, pr.getSnapshot().getElement().get(0), null, null, element, element.getName(), stack);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,389 @@
|
||||||
|
package org.hl7.fhir.instance.validation;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.LinkedList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
import org.hl7.fhir.instance.model.Attachment;
|
||||||
|
import org.hl7.fhir.instance.model.BooleanType;
|
||||||
|
import org.hl7.fhir.instance.model.Coding;
|
||||||
|
import org.hl7.fhir.instance.model.DateTimeType;
|
||||||
|
import org.hl7.fhir.instance.model.DateType;
|
||||||
|
import org.hl7.fhir.instance.model.DecimalType;
|
||||||
|
import org.hl7.fhir.instance.model.InstantType;
|
||||||
|
import org.hl7.fhir.instance.model.IntegerType;
|
||||||
|
import org.hl7.fhir.instance.model.Quantity;
|
||||||
|
import org.hl7.fhir.instance.model.Questionnaire;
|
||||||
|
import org.hl7.fhir.instance.model.Questionnaire.AnswerFormat;
|
||||||
|
import org.hl7.fhir.instance.model.Questionnaire.GroupComponent;
|
||||||
|
import org.hl7.fhir.instance.model.Questionnaire.QuestionComponent;
|
||||||
|
import org.hl7.fhir.instance.model.QuestionnaireAnswers;
|
||||||
|
import org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionAnswerComponent;
|
||||||
|
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.TimeType;
|
||||||
|
import org.hl7.fhir.instance.model.Type;
|
||||||
|
import org.hl7.fhir.instance.model.UriType;
|
||||||
|
import org.hl7.fhir.instance.model.ValueSet;
|
||||||
|
import org.hl7.fhir.instance.model.ValueSet.ConceptDefinitionComponent;
|
||||||
|
import org.hl7.fhir.instance.model.valuesets.IssueType;
|
||||||
|
import org.hl7.fhir.instance.utils.WorkerContext;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Validates that an instance of {@link QuestionnaireAnswers} is valid against the {@link Questionnaire} that it claims to conform to.
|
||||||
|
*
|
||||||
|
* @author James Agnew
|
||||||
|
*/
|
||||||
|
public class QuestionnaireAnswersValidator extends BaseValidator {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Note to anyone working on this class -
|
||||||
|
*
|
||||||
|
* This class has unit tests which run within the HAPI project build. Please sync any changes here to HAPI and ensure that unit tests are run.
|
||||||
|
*/
|
||||||
|
|
||||||
|
private WorkerContext myWorkerCtx;
|
||||||
|
|
||||||
|
public QuestionnaireAnswersValidator(WorkerContext theWorkerCtx) {
|
||||||
|
this.myWorkerCtx = theWorkerCtx;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Class<? extends Type>> allowedTypes(Class<? extends Type> theClass0) {
|
||||||
|
HashSet<Class<? extends Type>> retVal = new HashSet<Class<? extends Type>>();
|
||||||
|
retVal.add(theClass0);
|
||||||
|
return Collections.unmodifiableSet(retVal);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent> findAnswersByLinkId(List<org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent> theQuestion,
|
||||||
|
String theLinkId) {
|
||||||
|
Validate.notBlank(theLinkId, "theLinkId must not be blank");
|
||||||
|
|
||||||
|
ArrayList<org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent> retVal = new ArrayList<QuestionnaireAnswers.QuestionComponent>();
|
||||||
|
for (org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent next : theQuestion) {
|
||||||
|
if (theLinkId.equals(next.getLinkId())) {
|
||||||
|
retVal.add(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent> findGroupByLinkId(List<org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent> theGroups, String theLinkId) {
|
||||||
|
Validate.notBlank(theLinkId, "theLinkId must not be blank");
|
||||||
|
|
||||||
|
ArrayList<org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent> retVal = new ArrayList<QuestionnaireAnswers.GroupComponent>();
|
||||||
|
for (org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent next : theGroups) {
|
||||||
|
if (theLinkId.equals(next.getLinkId())) {
|
||||||
|
retVal.add(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void validate(List<ValidationMessage> theErrors, QuestionnaireAnswers theAnswers) {
|
||||||
|
LinkedList<String> pathStack = new LinkedList<String>();
|
||||||
|
pathStack.add("QuestionnaireAnswers");
|
||||||
|
pathStack.add(QuestionnaireAnswers.SP_QUESTIONNAIRE);
|
||||||
|
|
||||||
|
if (!fail(theErrors, IssueType.INVALID, pathStack, theAnswers.hasQuestionnaire(), "QuestionnaireAnswers does not specity which questionnaire it is providing answers to")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Reference questionnaireRef = theAnswers.getQuestionnaire();
|
||||||
|
Questionnaire questionnaire = getQuestionnaire(theAnswers, questionnaireRef);
|
||||||
|
if (!fail(theErrors, IssueType.INVALID, pathStack, questionnaire != null, "Questionnaire {0} is not found in the WorkerContext", theAnswers.getQuestionnaire().getReference())) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
pathStack.removeLast();
|
||||||
|
pathStack.add("group(0)");
|
||||||
|
validateGroup(theErrors, questionnaire.getGroup(), theAnswers.getGroup(), pathStack, theAnswers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Questionnaire getQuestionnaire(QuestionnaireAnswers theAnswers, Reference theQuestionnaireRef) {
|
||||||
|
Questionnaire retVal;
|
||||||
|
if (theQuestionnaireRef.getReferenceElement().isLocal()) {
|
||||||
|
retVal = (Questionnaire) theQuestionnaireRef.getResource();
|
||||||
|
if (retVal == null) {
|
||||||
|
for (Resource next : theAnswers.getContained()) {
|
||||||
|
if (theQuestionnaireRef.getReferenceElement().getValue().equals(next.getId())) {
|
||||||
|
retVal = (Questionnaire) next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
retVal = myWorkerCtx.getQuestionnaires().get(theQuestionnaireRef.getReferenceElement().getValue());
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private ValueSet getValueSet(QuestionnaireAnswers theAnswers, Reference theQuestionnaireRef) {
|
||||||
|
ValueSet retVal;
|
||||||
|
if (theQuestionnaireRef.getReferenceElement().isLocal()) {
|
||||||
|
retVal = (ValueSet) theQuestionnaireRef.getResource();
|
||||||
|
if (retVal == null) {
|
||||||
|
for (Resource next : theAnswers.getContained()) {
|
||||||
|
if (theQuestionnaireRef.getReferenceElement().getValue().equals(next.getId())) {
|
||||||
|
retVal = (ValueSet) next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
retVal = myWorkerCtx.getValueSets().get(theQuestionnaireRef.getReferenceElement().getValue());
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateGroup(List<ValidationMessage> theErrors, GroupComponent theQuestGroup, org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent theAnsGroup,
|
||||||
|
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers) {
|
||||||
|
|
||||||
|
for (org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent next : theAnsGroup.getQuestion()) {
|
||||||
|
rule(theErrors, IssueType.INVALID, thePathStack, isNotBlank(next.getLinkId()), "Question found with no linkId");
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> allowedQuestions = new HashSet<String>();
|
||||||
|
for (QuestionComponent nextQuestion : theQuestGroup.getQuestion()) {
|
||||||
|
allowedQuestions.add(nextQuestion.getLinkId());
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < theQuestGroup.getQuestion().size(); i++) {
|
||||||
|
QuestionComponent nextQuestion = theQuestGroup.getQuestion().get(i);
|
||||||
|
validateQuestion(theErrors, nextQuestion, theAnsGroup, thePathStack, theAnswers);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that there are no extra answers
|
||||||
|
for (int i = 0; i < theAnsGroup.getQuestion().size(); i++) {
|
||||||
|
org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent nextQuestion = theAnsGroup.getQuestion().get(i);
|
||||||
|
thePathStack.add("question(" + i + ")");
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, allowedQuestions.contains(nextQuestion.getLinkId()), "Found answer with linkId[{0}] but this ID is not allowed at this position",
|
||||||
|
nextQuestion.getLinkId());
|
||||||
|
thePathStack.remove();
|
||||||
|
}
|
||||||
|
|
||||||
|
validateGroupGroups(theErrors, theQuestGroup, theAnsGroup, thePathStack, theAnswers);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateQuestion(List<ValidationMessage> theErrors, QuestionComponent theQuestion, org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent theAnsGroup,
|
||||||
|
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers) {
|
||||||
|
String linkId = theQuestion.getLinkId();
|
||||||
|
if (!fail(theErrors, IssueType.INVALID, thePathStack, isNotBlank(linkId), "Questionnaire is invalid, question found with no link ID")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
AnswerFormat type = theQuestion.getType();
|
||||||
|
if (type == null) {
|
||||||
|
if (theQuestion.getGroup().isEmpty()) {
|
||||||
|
rule(theErrors, IssueType.INVALID, thePathStack, false, "Questionnaire in invalid, no type and no groups specified for question with link ID[{0}]", linkId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
type = AnswerFormat.NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent> answers = findAnswersByLinkId(theAnsGroup.getQuestion(), linkId);
|
||||||
|
if (answers.size() > 1) {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRequired(), "Multiple answers repetitions found with linkId[{0}]", linkId);
|
||||||
|
}
|
||||||
|
if (answers.size() == 0) {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !theQuestion.getRequired(), "Missing answer to required question with linkId[{0}]", linkId);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent answerQuestion = answers.get(0);
|
||||||
|
try {
|
||||||
|
thePathStack.add("question(" + answers.indexOf(answerQuestion) + ")");
|
||||||
|
validateQuestionAnswers(theErrors, theQuestion, thePathStack, type, answerQuestion, theAnswers);
|
||||||
|
validateQuestionGroups(theErrors, theQuestion, answerQuestion, thePathStack, theAnswers);
|
||||||
|
} finally {
|
||||||
|
thePathStack.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateQuestionGroups(List<ValidationMessage> theErrors, QuestionComponent theQuestion, org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent theAnswerQuestion,
|
||||||
|
LinkedList<String> thePathSpec, QuestionnaireAnswers theAnswers) {
|
||||||
|
validateGroups(theErrors, theQuestion.getGroup(), theAnswerQuestion.getGroup(), thePathSpec, theAnswers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateGroupGroups(List<ValidationMessage> theErrors, GroupComponent theQuestGroup, org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent theAnsGroup,
|
||||||
|
LinkedList<String> thePathSpec, QuestionnaireAnswers theAnswers) {
|
||||||
|
validateGroups(theErrors, theQuestGroup.getGroup(), theAnsGroup.getGroup(), thePathSpec, theAnswers);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateGroups(List<ValidationMessage> theErrors, List<GroupComponent> theQuestionGroups, List<org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent> theAnswerGroups,
|
||||||
|
LinkedList<String> thePathStack, QuestionnaireAnswers theAnswers) {
|
||||||
|
Set<String> allowedGroups = new HashSet<String>();
|
||||||
|
for (GroupComponent nextQuestionGroup : theQuestionGroups) {
|
||||||
|
String linkId = nextQuestionGroup.getLinkId();
|
||||||
|
allowedGroups.add(linkId);
|
||||||
|
|
||||||
|
List<org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent> answerGroups = findGroupByLinkId(theAnswerGroups, linkId);
|
||||||
|
if (answerGroups.isEmpty()) {
|
||||||
|
if (nextQuestionGroup.getRequired()) {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Missing required group with linkId[{0}]", linkId);
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (answerGroups.size() > 1) {
|
||||||
|
if (nextQuestionGroup.getRepeats() == false) {
|
||||||
|
int index = theAnswerGroups.indexOf(answerGroups.get(1));
|
||||||
|
thePathStack.add("group(" + index + ")");
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Multiple repetitions of group with linkId[{0}] found at this position, but this group can not repeat", linkId);
|
||||||
|
thePathStack.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent nextAnswerGroup : answerGroups) {
|
||||||
|
int index = theAnswerGroups.indexOf(answerGroups.get(1));
|
||||||
|
thePathStack.add("group(" + index + ")");
|
||||||
|
validateGroup(theErrors, nextQuestionGroup, nextAnswerGroup, thePathStack, theAnswers);
|
||||||
|
thePathStack.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Make sure there are no groups in answers that aren't in the questionnaire
|
||||||
|
int idx = -1;
|
||||||
|
for (org.hl7.fhir.instance.model.QuestionnaireAnswers.GroupComponent next : theAnswerGroups) {
|
||||||
|
idx++;
|
||||||
|
if (!allowedGroups.contains(next.getLinkId())) {
|
||||||
|
thePathStack.add("group(" + idx + ")");
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Group with linkId[{0}] found at this position, but this group does not exist at this position in Questionnaire", next.getLinkId());
|
||||||
|
thePathStack.removeLast();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void validateQuestionAnswers(List<ValidationMessage> theErrors, QuestionComponent theQuestion, LinkedList<String> thePathStack, AnswerFormat type,
|
||||||
|
org.hl7.fhir.instance.model.QuestionnaireAnswers.QuestionComponent answerQuestion, QuestionnaireAnswers theAnswers) {
|
||||||
|
|
||||||
|
String linkId = theQuestion.getLinkId();
|
||||||
|
Set<Class<? extends Type>> allowedAnswerTypes = determineAllowedAnswerTypes(type);
|
||||||
|
if (allowedAnswerTypes.isEmpty()) {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, answerQuestion.isEmpty(), "Question with linkId[{0}] has no answer type but an answer was provided", linkId);
|
||||||
|
} else {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !(answerQuestion.getAnswer().size() > 1 && !theQuestion.getRepeats()), "Multiple answers to non repeating question with linkId[{0}]",
|
||||||
|
linkId);
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, !(theQuestion.getRequired() && answerQuestion.getAnswer().isEmpty()), "Missing answer to required question with linkId[{0}]", linkId);
|
||||||
|
}
|
||||||
|
|
||||||
|
int answerIdx = -1;
|
||||||
|
for (QuestionAnswerComponent nextAnswer : answerQuestion.getAnswer()) {
|
||||||
|
answerIdx++;
|
||||||
|
try {
|
||||||
|
thePathStack.add("answer(" + answerIdx + ")");
|
||||||
|
Type nextValue = nextAnswer.getValue();
|
||||||
|
if (!allowedAnswerTypes.contains(nextValue.getClass())) {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] found of type [{1}] but this is invalid for question of type [{2}]", linkId, nextValue
|
||||||
|
.getClass().getSimpleName(), type.toCode());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Validate choice answers
|
||||||
|
if (type == AnswerFormat.CHOICE || type == AnswerFormat.OPENCHOICE) {
|
||||||
|
Coding coding = (Coding) nextAnswer.getValue();
|
||||||
|
if (isBlank(coding.getCode()) && isBlank(coding.getSystem()) && isBlank(coding.getSystem())) {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Answer to question with linkId[{0}] is of type coding, but none of code, system, and display are populated", linkId);
|
||||||
|
continue;
|
||||||
|
} else if (isBlank(coding.getCode()) && isBlank(coding.getSystem())) {
|
||||||
|
if (type != AnswerFormat.OPENCHOICE) {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false,
|
||||||
|
"Answer to question with linkId[{0}] is of type only has a display populated (no code or system) but question does not allow {1}", linkId, AnswerFormat.OPENCHOICE.name());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
} else if (isBlank(coding.getCode()) || isBlank(coding.getSystem())) {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false,
|
||||||
|
"Answer to question with linkId[{0}] has a coding, but this coding does not contain a code and system (both must be present, or neither is the question allows {1})", linkId,
|
||||||
|
AnswerFormat.OPENCHOICE.name());
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
String optionsRef = theQuestion.getOptions().getReference();
|
||||||
|
if (isNotBlank(optionsRef)) {
|
||||||
|
ValueSet valueSet = getValueSet(theAnswers, theQuestion.getOptions());
|
||||||
|
if (valueSet == null) {
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, false, "Question with linkId[{0}] has options ValueSet[{1}] but this ValueSet can not be found", linkId, optionsRef);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean found = false;
|
||||||
|
if (coding.getSystem().equals(valueSet.getDefine().getSystem())) {
|
||||||
|
for (ConceptDefinitionComponent next : valueSet.getDefine().getConcept()) {
|
||||||
|
if (coding.getCode().equals(next.getCode())) {
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rule(theErrors, IssueType.BUSINESSRULE, thePathStack, found, "Question with linkId[{0}] has answer with system[{1}] and code[{2}] but this is not a valid answer for ValueSet[{3}]",
|
||||||
|
linkId, coding.getSystem(), coding.getCode(), optionsRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
thePathStack.removeLast();
|
||||||
|
}
|
||||||
|
|
||||||
|
} // for answers
|
||||||
|
}
|
||||||
|
|
||||||
|
private Set<Class<? extends Type>> determineAllowedAnswerTypes(AnswerFormat type) {
|
||||||
|
Set<Class<? extends Type>> allowedAnswerTypes;
|
||||||
|
switch (type) {
|
||||||
|
case ATTACHMENT:
|
||||||
|
allowedAnswerTypes = allowedTypes(Attachment.class);
|
||||||
|
break;
|
||||||
|
case BOOLEAN:
|
||||||
|
allowedAnswerTypes = allowedTypes(BooleanType.class);
|
||||||
|
break;
|
||||||
|
case CHOICE:
|
||||||
|
allowedAnswerTypes = allowedTypes(Coding.class);
|
||||||
|
break;
|
||||||
|
case DATE:
|
||||||
|
allowedAnswerTypes = allowedTypes(DateType.class);
|
||||||
|
break;
|
||||||
|
case DATETIME:
|
||||||
|
allowedAnswerTypes = allowedTypes(DateTimeType.class);
|
||||||
|
break;
|
||||||
|
case DECIMAL:
|
||||||
|
allowedAnswerTypes = allowedTypes(DecimalType.class);
|
||||||
|
break;
|
||||||
|
case INSTANT:
|
||||||
|
allowedAnswerTypes = allowedTypes(InstantType.class);
|
||||||
|
break;
|
||||||
|
case INTEGER:
|
||||||
|
allowedAnswerTypes = allowedTypes(IntegerType.class);
|
||||||
|
break;
|
||||||
|
case OPENCHOICE:
|
||||||
|
allowedAnswerTypes = allowedTypes(Coding.class);
|
||||||
|
break;
|
||||||
|
case QUANTITY:
|
||||||
|
allowedAnswerTypes = allowedTypes(Quantity.class);
|
||||||
|
break;
|
||||||
|
case REFERENCE:
|
||||||
|
allowedAnswerTypes = allowedTypes(Reference.class);
|
||||||
|
break;
|
||||||
|
case STRING:
|
||||||
|
allowedAnswerTypes = allowedTypes(StringType.class);
|
||||||
|
break;
|
||||||
|
case TEXT:
|
||||||
|
allowedAnswerTypes = allowedTypes(StringType.class);
|
||||||
|
break;
|
||||||
|
case TIME:
|
||||||
|
allowedAnswerTypes = allowedTypes(TimeType.class);
|
||||||
|
break;
|
||||||
|
case URL:
|
||||||
|
allowedAnswerTypes = allowedTypes(UriType.class);
|
||||||
|
break;
|
||||||
|
case NULL:
|
||||||
|
default:
|
||||||
|
allowedAnswerTypes = Collections.emptySet();
|
||||||
|
}
|
||||||
|
return allowedAnswerTypes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -29,6 +29,8 @@ POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.builder.ToStringBuilder;
|
||||||
|
import org.apache.commons.lang3.builder.ToStringStyle;
|
||||||
import org.hl7.fhir.instance.model.CodeableConcept;
|
import org.hl7.fhir.instance.model.CodeableConcept;
|
||||||
import org.hl7.fhir.instance.model.OperationOutcome;
|
import org.hl7.fhir.instance.model.OperationOutcome;
|
||||||
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
|
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
|
||||||
|
@ -184,5 +186,21 @@ public class ValidationMessage
|
||||||
return html == null ? Utilities.escapeXml(message) : html;
|
return html == null ? Utilities.escapeXml(message) : html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a representation of this ValidationMessage suitable for logging. The values of
|
||||||
|
* most of the internal fields are included, so this may not be suitable for display to
|
||||||
|
* an end user.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
ToStringBuilder b = new ToStringBuilder(this, ToStringStyle.SHORT_PREFIX_STYLE);
|
||||||
|
b.append("level", level);
|
||||||
|
b.append("type", type);
|
||||||
|
b.append("location", location);
|
||||||
|
b.append("message", message);
|
||||||
|
return b.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
|
@ -3,15 +3,18 @@ package ca.uhn.fhir.model;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.Map.Entry;
|
import java.util.Map.Entry;
|
||||||
import java.util.Properties;
|
import java.util.Properties;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||||
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.context.RuntimeChildResourceBlockDefinition;
|
||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
|
|
||||||
public class InstantiationTest {
|
public class InstantiationTest {
|
||||||
|
@ -28,16 +31,27 @@ public class InstantiationTest {
|
||||||
Class<? extends IBaseResource> clazz = (Class<? extends IBaseResource>) Class.forName(next.getValue().toString());
|
Class<? extends IBaseResource> clazz = (Class<? extends IBaseResource>) Class.forName(next.getValue().toString());
|
||||||
RuntimeResourceDefinition res = ctx.getResourceDefinition(clazz);
|
RuntimeResourceDefinition res = ctx.getResourceDefinition(clazz);
|
||||||
|
|
||||||
scanChildren(clazz, res);
|
scanChildren(new HashSet<Class<?>>(), clazz, res);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void scanChildren(Class<? extends IBaseResource> theClazz, BaseRuntimeElementCompositeDefinition<?> theRes) {
|
private void scanChildren(HashSet<Class<?>> theHashSet, Class<? extends IBase> theClazz, BaseRuntimeElementCompositeDefinition<?> theRes) {
|
||||||
for (BaseRuntimeChildDefinition next : theRes.getChildren()) {
|
for (BaseRuntimeChildDefinition next : theRes.getChildren()) {
|
||||||
if (next.getElementName().contains("_")) {
|
if (next.getElementName().contains("_")) {
|
||||||
fail("Element name " + next.getElementName() + " in type " + theClazz + " contains illegal '_'");
|
fail("Element name " + next.getElementName() + " in type " + theClazz + " contains illegal '_'");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (next instanceof RuntimeChildResourceBlockDefinition) {
|
||||||
|
RuntimeChildResourceBlockDefinition nextBlock = (RuntimeChildResourceBlockDefinition) next;
|
||||||
|
for (String nextName : nextBlock.getValidChildNames()) {
|
||||||
|
BaseRuntimeElementCompositeDefinition<?> elementDef = nextBlock.getChildByName(nextName);
|
||||||
|
if (theHashSet.add(elementDef.getImplementingClass())) {
|
||||||
|
scanChildren(theHashSet, elementDef.getImplementingClass(), elementDef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,18 +1,18 @@
|
||||||
package ca.uhn.fhir.validation;
|
package ca.uhn.fhir.validation;
|
||||||
|
|
||||||
import static org.hamcrest.Matchers.*;
|
import static org.hamcrest.Matchers.stringContainsInOrder;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.Patient;
|
|
||||||
import org.hl7.fhir.instance.validation.ValidationMessage;
|
import org.hl7.fhir.instance.validation.ValidationMessage;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.server.EncodingEnum;
|
import ca.uhn.fhir.rest.server.EncodingEnum;
|
||||||
|
|
||||||
public class StructureDefinitionValidatorTest {
|
public class FhirInstanceValidatorTest {
|
||||||
|
|
||||||
private static FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
|
private static FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
|
||||||
|
|
||||||
|
@ -23,8 +23,8 @@ public class StructureDefinitionValidatorTest {
|
||||||
+ "\"id\":\"123\""
|
+ "\"id\":\"123\""
|
||||||
+ "}";
|
+ "}";
|
||||||
|
|
||||||
StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx);
|
FhirInstanceValidator val = new FhirInstanceValidator();
|
||||||
List<ValidationMessage> output = val.validate(input, EncodingEnum.JSON, Patient.class);
|
List<ValidationMessage> output = val.validate(ourCtx, input, EncodingEnum.JSON, "Patient");
|
||||||
assertEquals(output.toString(), 0, output.size());
|
assertEquals(output.toString(), 0, output.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -37,8 +37,8 @@ public class StructureDefinitionValidatorTest {
|
||||||
+ "}";
|
+ "}";
|
||||||
|
|
||||||
|
|
||||||
StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx);
|
FhirInstanceValidator val = new FhirInstanceValidator();
|
||||||
List<ValidationMessage> output = val.validate(input, EncodingEnum.JSON, Patient.class);
|
List<ValidationMessage> output = val.validate(ourCtx, input, EncodingEnum.JSON, "Patient");
|
||||||
assertEquals(output.toString(), 1, output.size());
|
assertEquals(output.toString(), 1, output.size());
|
||||||
assertThat(output.get(0).toXML(), stringContainsInOrder("/foo", "Element is unknown"));
|
assertThat(output.get(0).toXML(), stringContainsInOrder("/foo", "Element is unknown"));
|
||||||
}
|
}
|
||||||
|
@ -49,8 +49,8 @@ public class StructureDefinitionValidatorTest {
|
||||||
+ "<id value=\"123\"/>"
|
+ "<id value=\"123\"/>"
|
||||||
+ "</Patient>";
|
+ "</Patient>";
|
||||||
|
|
||||||
StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx);
|
FhirInstanceValidator val = new FhirInstanceValidator();
|
||||||
List<ValidationMessage> output = val.validate(input, EncodingEnum.XML, Patient.class);
|
List<ValidationMessage> output = val.validate(ourCtx, input, EncodingEnum.XML, "Patient");
|
||||||
assertEquals(output.toString(), 0, output.size());
|
assertEquals(output.toString(), 0, output.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -62,8 +62,8 @@ public class StructureDefinitionValidatorTest {
|
||||||
+ "<foo value=\"222\"/>"
|
+ "<foo value=\"222\"/>"
|
||||||
+ "</Patient>";
|
+ "</Patient>";
|
||||||
|
|
||||||
StructureDefinitionValidator val = new StructureDefinitionValidator(ourCtx);
|
FhirInstanceValidator val = new FhirInstanceValidator();
|
||||||
List<ValidationMessage> output = val.validate(input, EncodingEnum.XML, Patient.class);
|
List<ValidationMessage> output = val.validate(ourCtx, input, EncodingEnum.XML, "Patient");
|
||||||
assertEquals(output.toString(), 1, output.size());
|
assertEquals(output.toString(), 1, output.size());
|
||||||
assertThat(output.get(0).toXML(), stringContainsInOrder("/f:Patient/f:foo", "Element is unknown"));
|
assertThat(output.get(0).toXML(), stringContainsInOrder("/f:Patient/f:foo", "Element is unknown"));
|
||||||
}
|
}
|
|
@ -0,0 +1,185 @@
|
||||||
|
package ca.uhn.fhir.validation;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.hl7.fhir.instance.model.Coding;
|
||||||
|
import org.hl7.fhir.instance.model.Questionnaire;
|
||||||
|
import org.hl7.fhir.instance.model.QuestionnaireAnswers;
|
||||||
|
import org.hl7.fhir.instance.model.Reference;
|
||||||
|
import org.hl7.fhir.instance.model.StringType;
|
||||||
|
import org.hl7.fhir.instance.model.ValueSet;
|
||||||
|
import org.hl7.fhir.instance.model.Questionnaire.AnswerFormat;
|
||||||
|
import org.hl7.fhir.instance.utils.WorkerContext;
|
||||||
|
import org.hl7.fhir.instance.validation.QuestionnaireAnswersValidator;
|
||||||
|
import org.hl7.fhir.instance.validation.ValidationMessage;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
|
||||||
|
public class QuestionnaireAnswersValidatorTest {
|
||||||
|
private static final FhirContext ourCtx = FhirContext.forDstu2Hl7Org();
|
||||||
|
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(QuestionnaireAnswersValidatorTest.class);
|
||||||
|
private QuestionnaireAnswersValidator myVal;
|
||||||
|
|
||||||
|
private WorkerContext myWorkerCtx;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
myWorkerCtx = new WorkerContext();
|
||||||
|
myVal = new QuestionnaireAnswersValidator(myWorkerCtx);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAnswerWithWrongType() {
|
||||||
|
Questionnaire q = new Questionnaire();
|
||||||
|
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.BOOLEAN);
|
||||||
|
|
||||||
|
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||||
|
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||||
|
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
|
||||||
|
|
||||||
|
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
|
||||||
|
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
|
||||||
|
myVal.validate(errors, qa);
|
||||||
|
|
||||||
|
ourLog.info(errors.toString());
|
||||||
|
assertThat(errors.toString(), containsString("Answer to question with linkId[link0] found of type [StringType] but this is invalid for question of type [boolean]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testCodedAnswer() {
|
||||||
|
String questionnaireRef = "http://example.com/Questionnaire/q1";
|
||||||
|
|
||||||
|
Questionnaire q = new Questionnaire();
|
||||||
|
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.CHOICE).setOptions(new Reference("http://somevalueset"));
|
||||||
|
myWorkerCtx.getQuestionnaires().put(questionnaireRef, q);
|
||||||
|
|
||||||
|
ValueSet options = new ValueSet();
|
||||||
|
options.getDefine().setSystem("urn:system").addConcept().setCode("code0");
|
||||||
|
myWorkerCtx.getValueSets().put("http://somevalueset", options);
|
||||||
|
|
||||||
|
QuestionnaireAnswers qa;
|
||||||
|
List<ValidationMessage> errors;
|
||||||
|
|
||||||
|
// Good code
|
||||||
|
|
||||||
|
qa = new QuestionnaireAnswers();
|
||||||
|
qa.getQuestionnaire().setReference(questionnaireRef);
|
||||||
|
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code0"));
|
||||||
|
errors = new ArrayList<ValidationMessage>();
|
||||||
|
myVal.validate(errors, qa);
|
||||||
|
assertEquals(errors.toString(), 0, errors.size());
|
||||||
|
|
||||||
|
// Bad code
|
||||||
|
|
||||||
|
qa = new QuestionnaireAnswers();
|
||||||
|
qa.getQuestionnaire().setReference(questionnaireRef);
|
||||||
|
qa.getGroup().addQuestion().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("urn:system").setCode("code1"));
|
||||||
|
errors = new ArrayList<ValidationMessage>();
|
||||||
|
myVal.validate(errors, qa);
|
||||||
|
ourLog.info(errors.toString());
|
||||||
|
assertThat(errors.toString(), containsString("location=QuestionnaireAnswers.group(0).question(0).answer(0)"));
|
||||||
|
assertThat(errors.toString(), containsString("message=Question with linkId[link0] has answer with system[urn:system] and code[code1] but this is not a valid answer for ValueSet[http://somevalueset]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testMissingRequiredQuestion() {
|
||||||
|
|
||||||
|
Questionnaire q = new Questionnaire();
|
||||||
|
q.getGroup().addQuestion().setLinkId("link0").setRequired(true).setType(AnswerFormat.STRING);
|
||||||
|
q.getGroup().addQuestion().setLinkId("link1").setRequired(true).setType(AnswerFormat.STRING);
|
||||||
|
|
||||||
|
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||||
|
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||||
|
qa.getGroup().addQuestion().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
|
||||||
|
|
||||||
|
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
|
||||||
|
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
|
||||||
|
myVal.validate(errors, qa);
|
||||||
|
|
||||||
|
ourLog.info(errors.toString());
|
||||||
|
assertThat(errors.toString(), containsString("Missing answer to required question with linkId[link0]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnexpectedAnswer() {
|
||||||
|
Questionnaire q = new Questionnaire();
|
||||||
|
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.BOOLEAN);
|
||||||
|
|
||||||
|
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||||
|
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||||
|
qa.getGroup().addQuestion().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
|
||||||
|
|
||||||
|
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
|
||||||
|
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
|
||||||
|
myVal.validate(errors, qa);
|
||||||
|
|
||||||
|
ourLog.info(errors.toString());
|
||||||
|
assertThat(errors.toString(), containsString("location=QuestionnaireAnswers.group(0).question"));
|
||||||
|
assertThat(errors.toString(), containsString("message=Found answer with linkId[link1] but this ID is not allowed at this position"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testUnexpectedGroup() {
|
||||||
|
Questionnaire q = new Questionnaire();
|
||||||
|
q.getGroup().addQuestion().setLinkId("link0").setRequired(false).setType(AnswerFormat.BOOLEAN);
|
||||||
|
|
||||||
|
QuestionnaireAnswers qa = new QuestionnaireAnswers();
|
||||||
|
qa.getQuestionnaire().setReference("http://example.com/Questionnaire/q1");
|
||||||
|
qa.getGroup().addGroup().setLinkId("link1");
|
||||||
|
|
||||||
|
myWorkerCtx.getQuestionnaires().put(qa.getQuestionnaire().getReference(), q);
|
||||||
|
List<ValidationMessage> errors = new ArrayList<ValidationMessage>();
|
||||||
|
myVal.validate(errors, qa);
|
||||||
|
|
||||||
|
ourLog.info(errors.toString());
|
||||||
|
assertThat(errors.toString(), containsString("location=QuestionnaireAnswers.group(0).group(0)"));
|
||||||
|
assertThat(errors.toString(), containsString("Group with linkId[link1] found at this position, but this group does not exist at this position in Questionnaire"));
|
||||||
|
}
|
||||||
|
|
||||||
|
// @Test
|
||||||
|
public void validateHealthConnexExample() throws Exception {
|
||||||
|
String input = IOUtils.toString(QuestionnaireAnswersValidatorTest.class.getResourceAsStream("/questionnaireanswers-0f431c50ddbe4fff8e0dd6b7323625fc.xml"));
|
||||||
|
|
||||||
|
QuestionnaireAnswers qa = ourCtx.newXmlParser().parseResource(QuestionnaireAnswers.class, input);
|
||||||
|
ArrayList<ValidationMessage> errors = new ArrayList<ValidationMessage>();
|
||||||
|
myVal.validate(errors, qa);
|
||||||
|
assertEquals(errors.toString(), 0, errors.size());
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Now change a coded value
|
||||||
|
*/
|
||||||
|
//@formatter:off
|
||||||
|
input = input.replaceAll("<answer>\n" +
|
||||||
|
" <valueCoding>\n" +
|
||||||
|
" <system value=\"f69573b8-cb63-4d31-85a4-23ac784735ab\"/>\n" +
|
||||||
|
" <code value=\"2\"/>\n" +
|
||||||
|
" <display value=\"Once/twice\"/>\n" +
|
||||||
|
" </valueCoding>\n" +
|
||||||
|
" </answer>", "<answer>\n" +
|
||||||
|
" <valueCoding>\n" +
|
||||||
|
" <system value=\"f69573b8-cb63-4d31-85a4-23ac784735ab\"/>\n" +
|
||||||
|
" <code value=\"GGG\"/>\n" +
|
||||||
|
" <display value=\"Once/twice\"/>\n" +
|
||||||
|
" </valueCoding>\n" +
|
||||||
|
" </answer>");
|
||||||
|
assertThat(input, containsString("GGG"));
|
||||||
|
//@formatter:on
|
||||||
|
|
||||||
|
qa = ourCtx.newXmlParser().parseResource(QuestionnaireAnswers.class, input);
|
||||||
|
errors = new ArrayList<ValidationMessage>();
|
||||||
|
myVal.validate(errors, qa);
|
||||||
|
assertEquals(errors.toString(), 10, errors.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -5,7 +5,11 @@ import java.util.*;
|
||||||
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
|
||||||
|
#if ( $version != 'dstu' && (${className} == 'Encounter' || ${className} == 'Patient' || ${className} == 'ValueSet' || ${className} == 'QuestionnaireAnswers'))
|
||||||
|
import ca.uhn.fhir.jpa.provider.BaseJpaResourceProvider${className}Dstu2;
|
||||||
|
#else
|
||||||
import ca.uhn.fhir.jpa.provider.JpaResourceProvider${versionCapitalized};
|
import ca.uhn.fhir.jpa.provider.JpaResourceProvider${versionCapitalized};
|
||||||
|
#end
|
||||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
import ca.uhn.fhir.model.api.Include;
|
||||||
|
@ -20,8 +24,10 @@ import ca.uhn.fhir.model.dstu.resource.Binary;
|
||||||
// import ca.uhn.fhir.model.api.Bundle;
|
// import ca.uhn.fhir.model.api.Bundle;
|
||||||
|
|
||||||
public class ${className}ResourceProvider extends
|
public class ${className}ResourceProvider extends
|
||||||
#if ( $version != 'dstu' && (${className} == 'Patient' || ${className} == 'Encounter') )
|
## We have specialized base classes for RPs that handle certain resource types. These
|
||||||
BaseJpaResourceProvider${className}${versionCapitalized}<${className}>
|
## RPs implement type specific operations
|
||||||
|
#if ( $version != 'dstu' && (${className} == 'Encounter' || ${className} == 'Patient' || ${className} == 'ValueSet' || ${className} == 'QuestionnaireAnswers'))
|
||||||
|
BaseJpaResourceProvider${className}${versionCapitalized}
|
||||||
#else
|
#else
|
||||||
JpaResourceProvider${versionCapitalized}<${className}>
|
JpaResourceProvider${versionCapitalized}<${className}>
|
||||||
#end
|
#end
|
||||||
|
|
|
@ -12,7 +12,6 @@ cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/f
|
||||||
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/fhir/instance/utils/NameResolver.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/utils/
|
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/fhir/instance/utils/NameResolver.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/utils/
|
||||||
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/fhir/instance/validation/BaseValidator.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/validation/
|
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/fhir/instance/validation/BaseValidator.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/validation/
|
||||||
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/fhir/instance/validation/InstanceValidator.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/validation/
|
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/fhir/instance/validation/InstanceValidator.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/validation/
|
||||||
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/fhir/instance/validation/ProfileValidator.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/validation/
|
|
||||||
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/fhir/instance/validation/ValidationMessage.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/validation/ValidationMessage.java
|
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.instance/src/org/hl7/fhir/instance/validation/ValidationMessage.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/validation/ValidationMessage.java
|
||||||
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.utilities/src/org/hl7/fhir/utilities/xhtml/HeirarchicalTableGenerator.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/utilities/xhtml/
|
cp -vp $FHIRTRUNK/build/implementations/java/org.hl7.fhir.utilities/src/org/hl7/fhir/utilities/xhtml/HeirarchicalTableGenerator.java hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/utilities/xhtml/
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue