Clean up the validation framework and integrate the QuestionnaireAnswers

validator
This commit is contained in:
James Agnew 2015-07-10 16:05:40 -04:00
parent f2a725ae53
commit c22aa14d29
32 changed files with 5638 additions and 614 deletions

View File

@ -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
} }

View File

@ -44,8 +44,8 @@ public class FhirValidator {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirValidator.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirValidator.class);
private static final String I18N_KEY_NO_PHLOC_WARNING = FhirValidator.class.getName()+".noPhlocWarningOnStartup"; private static final String I18N_KEY_NO_PHLOC_WARNING = FhirValidator.class.getName() + ".noPhlocWarningOnStartup";
private static final String I18N_KEY_NO_PHLOC_ERROR = FhirValidator.class.getName()+".noPhlocError"; private static final String I18N_KEY_NO_PHLOC_ERROR = FhirValidator.class.getName() + ".noPhlocError";
private FhirContext myContext; private FhirContext myContext;
private List<IValidator> myValidators = new ArrayList<IValidator>(); private List<IValidator> myValidators = new ArrayList<IValidator>();
@ -134,12 +134,12 @@ public class FhirValidator {
* Validates a bundle instance, throwing a {@link ValidationFailureException} if the validation fails. This validation includes validation of all resources in the bundle. * Validates a bundle instance, throwing a {@link ValidationFailureException} if the validation fails. This validation includes validation of all resources in the bundle.
* *
* @param theBundle * @param theBundle
* The resource to validate * The resource to validate
* @throws ValidationFailureException * @throws ValidationFailureException
* If the validation fails * If the validation fails
* @deprecated use {@link #validateWithResult(ca.uhn.fhir.model.api.Bundle)} instead * @deprecated use {@link #validateWithResult(ca.uhn.fhir.model.api.Bundle)} instead
*/ */
@Deprecated @Deprecated
public void validate(Bundle theBundle) { public void validate(Bundle theBundle) {
Validate.notNull(theBundle, "theBundle must not be null"); Validate.notNull(theBundle, "theBundle must not be null");
@ -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);
} }
@ -160,57 +160,56 @@ public class FhirValidator {
* Validates a resource instance, throwing a {@link ValidationFailureException} if the validation fails * Validates a resource instance, throwing a {@link ValidationFailureException} if the validation fails
* *
* @param theResource * @param theResource
* The resource to validate * The resource to validate
* @throws ValidationFailureException * @throws ValidationFailureException
* If the validation fails * If the validation fails
* @deprecated use {@link #validateWithResult(IBaseResource)} instead * @deprecated use {@link #validateWithResult(IBaseResource)} instead
*/ */
@Deprecated @Deprecated
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
* @param theBundle the bundle to validate * the bundle to validate
* @return the results of validation * @return the results of validation
* @since 0.7 * @since 0.7
*/ */
public ValidationResult validateWithResult(Bundle theBundle) { public ValidationResult validateWithResult(Bundle theBundle) {
Validate.notNull(theBundle, "theBundle must not be null"); Validate.notNull(theBundle, "theBundle must not be null");
IValidationContext<Bundle> ctx = ValidationContext.forBundle(myContext, theBundle); IValidationContext<Bundle> ctx = ValidationContext.forBundle(myContext, theBundle);
for (IValidator next : myValidators) { for (IValidator next : myValidators) {
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
* @return the results of validation * the resource to validate
* @since 0.7 * @return the results of validation
*/ * @since 0.7
public ValidationResult validateWithResult(IBaseResource theResource) { */
Validate.notNull(theResource, "theResource must not be null"); public ValidationResult validateWithResult(IBaseResource theResource) {
Validate.notNull(theResource, "theResource must not be null");
IValidationContext<IBaseResource> ctx = ValidationContext.forResource(myContext, theResource); IValidationContext<IBaseResource> ctx = ValidationContext.forResource(myContext, theResource);
for (IValidator next : myValidators) { for (IValidator next : myValidators) {
next.validateResource(ctx); next.validateResource(ctx);
} }
IBaseOperationOutcome oo = ctx.getOperationOutcome(); return ctx.toResult();
return ValidationResult.valueOf(myContext, oo); }
}
} }

View File

@ -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();
} }

View File

@ -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);
}
}

View File

@ -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

View File

@ -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) {

View File

@ -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();
}
}

View File

@ -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();
} }
} }

View File

@ -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();
public static ValidationResult valueOf(FhirContext theCtx, IBaseOperationOutcome myOperationOutcome) { if (next.getSeverity() == null || next.getSeverity().ordinal() > ResultSeverityEnum.WARNING.ordinal()) {
boolean noIssues = !OperationOutcomeUtil.hasIssues(theCtx, myOperationOutcome); successful = false;
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(); myIsSuccessful = successful;
}
public List<SingleValidationMessage> getMessages() {
return Collections.unmodifiableList(myMessages);
} }
/** /**
@ -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() + '\'' + '}';
}
} }

View File

@ -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());
}
}

View File

@ -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>

View File

@ -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
}

View File

@ -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()));
} }
} }

View File

@ -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()) {

View File

@ -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" />

View File

@ -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:"));

View File

@ -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());
}
}

View File

@ -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();

View File

@ -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());
}
}

View File

@ -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) {
}
} }

View File

@ -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_;

View File

@ -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;
@ -28,27 +29,27 @@ import org.hl7.fhir.instance.terminologies.ValueSetExpander.ValueSetExpansionOut
/* /*
* private static Map<String, StructureDefinition> loadProfiles() throws Exception { * private static Map<String, StructureDefinition> loadProfiles() throws Exception {
HashMap<String, StructureDefinition> result = new HashMap<String, StructureDefinition>(); HashMap<String, StructureDefinition> result = new HashMap<String, StructureDefinition>();
Bundle feed = new XmlParser().parseGeneral(new FileInputStream(PROFILES)).getFeed(); Bundle feed = new XmlParser().parseGeneral(new FileInputStream(PROFILES)).getFeed();
for (AtomEntry<? extends Resource> e : feed.getEntryList()) { for (AtomEntry<? extends Resource> e : feed.getEntryList()) {
if (e.getReference() instanceof StructureDefinition) { if (e.getReference() instanceof StructureDefinition) {
result.put(e.getId(), (StructureDefinition) e.getReference()); result.put(e.getId(), (StructureDefinition) e.getReference());
} }
} }
return result; return result;
} }
private static final String TEST_PROFILE = "C:\\work\\org.hl7.fhir\\build\\publish\\namespace.profile.xml"; private static final String TEST_PROFILE = "C:\\work\\org.hl7.fhir\\build\\publish\\namespace.profile.xml";
private static final String PROFILES = "C:\\work\\org.hl7.fhir\\build\\publish\\profiles-resources.xml"; private static final String PROFILES = "C:\\work\\org.hl7.fhir\\build\\publish\\profiles-resources.xml";
igtodo - things to add: igtodo - things to add:
- version - version
- list of resource names - list of resource names
*/ */
public class WorkerContext implements NameResolver { public class WorkerContext implements NameResolver {
private ITerminologyServices terminologyServices = new NullTerminologyServices(); private ITerminologyServices terminologyServices = new NullTerminologyServices();
private IFHIRClient client = new NullClient(); private IFHIRClient client = new NullClient();
private Map<String, ValueSet> codeSystems = new HashMap<String, ValueSet>(); private Map<String, ValueSet> codeSystems = new HashMap<String, ValueSet>();
private Map<String, ValueSet> valueSets = new HashMap<String, ValueSet>(); private Map<String, ValueSet> valueSets = new HashMap<String, 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;
@ -85,8 +85,9 @@ 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,181 +129,183 @@ 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());
extensionDefinitions.put(ed.getId(), ed); extensionDefinitions.put(ed.getId(), ed);
extensionDefinitions.put(base+"/StructureDefinition/"+ed.getId(), ed); extensionDefinitions.put(base + "/StructureDefinition/" + ed.getId(), ed);
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);
valueSets.put(vs.getUrl(), vs); valueSets.put(vs.getUrl(), vs);
if (vs.hasDefine()) { if (vs.hasDefine()) {
codeSystems.put(vs.getDefine().getSystem().toString(), vs); codeSystems.put(vs.getDefine().getSystem().toString(), vs);
} }
} }
public void seeProfile(String base, StructureDefinition p) { public void seeProfile(String base, StructureDefinition p) {
profiles.put(p.getId(), p); profiles.put(p.getId(), p);
profiles.put(base+"/StructureDefinition/"+p.getId(), p); profiles.put(base + "/StructureDefinition/" + p.getId(), p);
profiles.put(p.getUrl(), p); profiles.put(p.getUrl(), p);
} }
public class NullClient implements IFHIRClient { public class NullClient implements IFHIRClient {
@Override @Override
public VersionInfo getVersions() { public VersionInfo getVersions() {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public IFHIRClient initialize(String baseServiceUrl) throws URISyntaxException { public IFHIRClient initialize(String baseServiceUrl) throws URISyntaxException {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public void initialize(String baseServiceUrl, int recordCount) throws URISyntaxException { public void initialize(String baseServiceUrl, int recordCount) throws URISyntaxException {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public void setPreferredResourceFormat(ResourceFormat resourceFormat) { public void setPreferredResourceFormat(ResourceFormat resourceFormat) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public String getPreferredResourceFormat() { public String getPreferredResourceFormat() {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public void setPreferredFeedFormat(FeedFormat feedFormat) { public void setPreferredFeedFormat(FeedFormat feedFormat) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public String getPreferredFeedFormat() { public String getPreferredFeedFormat() {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public int getMaximumRecordCount() { public int getMaximumRecordCount() {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public void setMaximumRecordCount(int recordCount) { public void setMaximumRecordCount(int recordCount) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public Conformance getConformanceStatement() { public Conformance getConformanceStatement() {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public Conformance getConformanceStatement(boolean useOptionsVerb) { public Conformance getConformanceStatement(boolean useOptionsVerb) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> T read(Class<T> resource, String id) { public <T extends Resource> T read(Class<T> resource, String id) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> T vread(Class<T> resource, String id, String versionid) { public <T extends Resource> T vread(Class<T> resource, String id, String versionid) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) { public <T extends Resource> T update(Class<T> resourceClass, T resource, String id) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> boolean delete(Class<T> resourceClass, String id) { public <T extends Resource> boolean delete(Class<T> resourceClass, String id) {
throw new Error("call to NullClient");
}
@Override
public <T extends Resource> OperationOutcome create(Class<T> resourceClass, T resource) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass, String id) { public <T extends Resource> OperationOutcome create(Class<T> resourceClass, T resource) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass, String id) { public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass, String id) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle history(Class<T> resource, String id) { public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass, String id) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass) { public <T extends Resource> Bundle history(Class<T> resource, String id) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass) { public <T extends Resource> Bundle history(Calendar lastUpdate, Class<T> resourceClass) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle history(Class<T> resourceClass) { public <T extends Resource> Bundle history(Date lastUpdate, Class<T> resourceClass) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle history(Calendar lastUpdate) { public <T extends Resource> Bundle history(Class<T> resourceClass) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle history(Date lastUpdate) { public <T extends Resource> Bundle history(Calendar lastUpdate) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle history() { public <T extends Resource> Bundle history(Date lastUpdate) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) { public <T extends Resource> Bundle history() {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle search(Class<T> resourceClass, Map<String, String> params) { public <T extends Resource> OperationOutcome validate(Class<T> resourceClass, T resource, String id) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Bundle searchPost(Class<T> resourceClass, T resource, Map<String, String> params) { public <T extends Resource> Bundle search(Class<T> resourceClass, Map<String, String> params) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public Bundle transaction(Bundle batch) { public <T extends Resource> Bundle searchPost(Class<T> resourceClass, T resource, Map<String, String> params) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public Bundle fetchFeed(String url) { public Bundle transaction(Bundle batch) {
throw new Error("call to NullClient");
}
@Override
public Bundle fetchFeed(String url) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@ -307,7 +314,7 @@ public class WorkerContext implements NameResolver {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@Override @Override
public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) { public <T extends Resource> Parameters operateType(Class<T> resourceClass, String name, Parameters params) {
throw new Error("call to NullClient"); throw new Error("call to NullClient");
} }
@ -325,20 +332,20 @@ public class WorkerContext implements NameResolver {
} }
public StructureDefinition getExtensionStructure(StructureDefinition context, String url) throws Exception { public StructureDefinition getExtensionStructure(StructureDefinition context, String url) throws Exception {
if (url.startsWith("#")) { if (url.startsWith("#")) {
throw new Error("Contained extensions not done yet"); throw new Error("Contained extensions not done yet");
} else { } else {
if (url.contains("#")) if (url.contains("#"))
url = url.substring(0, url.indexOf("#")); url = url.substring(0, url.indexOf("#"));
StructureDefinition res = extensionDefinitions.get(url); StructureDefinition res = extensionDefinitions.get(url);
if (res == null) if (res == null)
res = profiles.get(url); res = profiles.get(url);
if (res == null) if (res == null)
return null; return null;
if (res.getSnapshot() == null || res.getSnapshot().getElement().isEmpty()) if (res.getSnapshot() == null || res.getSnapshot().getElement().isEmpty())
throw new Exception("no snapshot on extension for url "+url); throw new Exception("no snapshot on extension for url " + url);
return res; return res;
} }
} }
public class NullTerminologyServices implements ITerminologyServices { public class NullTerminologyServices implements ITerminologyServices {
@ -392,7 +399,7 @@ public class WorkerContext implements NameResolver {
public boolean isResource(String name) { public boolean isResource(String name) {
if (resourceNames.contains(name)) if (resourceNames.contains(name))
return true; return true;
StructureDefinition sd = profiles.get("http://hl7.org/fhir/StructureDefinition/"+name); StructureDefinition sd = profiles.get("http://hl7.org/fhir/StructureDefinition/" + name);
return sd != null && (sd.getBase().endsWith("Resource") || sd.getBase().endsWith("DomainResource")); return sd != null && (sd.getBase().endsWith("Resource") || sd.getBase().endsWith("DomainResource"));
} }
@ -408,5 +415,3 @@ public class WorkerContext implements NameResolver {
} }
} }

View File

@ -1,36 +1,38 @@
package org.hl7.fhir.instance.validation; package org.hl7.fhir.instance.validation;
/* /*
Copyright (c) 2011+, HL7, Inc Copyright (c) 2011+, HL7, Inc
All rights reserved. All rights reserved.
Redistribution and use in source and binary forms, with or without modification, Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met: are permitted provided that the following conditions are met:
* Redistributions of source code must retain the above copyright notice, this * Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer. list of conditions and the following disclaimer.
* Redistributions in binary form must reproduce the above copyright notice, * Redistributions in binary form must reproduce the above copyright notice,
this list of conditions and the following disclaimer in the documentation this list of conditions and the following disclaimer in the documentation
and/or other materials provided with the distribution. and/or other materials provided with the distribution.
* Neither the name of HL7 nor the names of its contributors may be used to * Neither the name of HL7 nor the names of its contributors may be used to
endorse or promote products derived from this software without specific endorse or promote products derived from this software without specific
prior written permission. prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE. 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,78 +41,185 @@ 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;
protected boolean rule(List<ValidationMessage> errors, IssueType type, String path, boolean b, String msg, String html) { }
if (!b) return message;
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++) {
char c = s.charAt(i); char c = s.charAt(i);
if (Character.isUpperCase(c) && !(i == 0 || Character.isUpperCase(s.charAt(i-1)))) if (Character.isUpperCase(c) && !(i == 0 || Character.isUpperCase(s.charAt(i - 1))))
b.append(' '); b.append(' ');
b.append(c); b.append(c);
} }
@ -123,9 +232,51 @@ public class BaseValidator {
int t = Character.getType(c); int t = Character.getType(c);
if (t == Character.UPPERCASE_LETTER || t == Character.LOWERCASE_LETTER || t == Character.TITLECASE_LETTER || t == Character.MODIFIER_LETTER || t == Character.OTHER_LETTER || (t == Character.DECIMAL_DIGIT_NUMBER && numbers) || (t == Character.LETTER_NUMBER && numbers) || c == ' ') if (t == Character.UPPERCASE_LETTER || t == Character.LOWERCASE_LETTER || t == Character.TITLECASE_LETTER || t == Character.MODIFIER_LETTER || t == Character.OTHER_LETTER || (t == Character.DECIMAL_DIGIT_NUMBER && numbers) || (t == Character.LETTER_NUMBER && numbers) || c == ' ')
b.append(c); b.append(c);
} }
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;
}
} }

View File

@ -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);
} }
} }

View File

@ -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;
}
}

View File

@ -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();
}
} }

View File

@ -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 {
@ -23,21 +26,32 @@ public class InstantiationTest {
Properties prop = new Properties(); Properties prop = new Properties();
prop.load(ctx.getVersion().getFhirVersionPropertiesFile()); prop.load(ctx.getVersion().getFhirVersionPropertiesFile());
for(Entry<Object, Object> next : prop.entrySet()) { for (Entry<Object, Object> next : prop.entrySet()) {
if (next.getKey().toString().startsWith("resource.")) { if (next.getKey().toString().startsWith("resource.")) {
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);
}
}
}
} }
} }

View File

@ -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"));
} }

View File

@ -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());
}
}

View File

@ -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

View File

@ -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/