Skip to content

Commit

Permalink
Add validation module for QuestionnaireAnswers
Browse files Browse the repository at this point in the history
  • Loading branch information
jamesagnew committed Jul 15, 2015
1 parent 818c404 commit 3bba0c0
Show file tree
Hide file tree
Showing 7 changed files with 374 additions and 61 deletions.
@@ -0,0 +1,22 @@
package ca.uhn.fhir.validation;

import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;

import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;

public interface IResourceLoader {

/**
* Load the latest version of a given resource
*
* @param theType
* The type of the resource to load
* @param theId
* The ID of the resource to load
* @throws ResourceNotFoundException
* If the resource is not known
*/
public <T extends IBaseResource> T load(Class<T> theType, IIdType theId) throws ResourceNotFoundException;

}
Expand Up @@ -23,7 +23,7 @@
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.server.EncodingEnum;

interface IValidationContext<T> {
public interface IValidationContext<T> {

FhirContext getFhirContext();

Expand Down
1 change: 0 additions & 1 deletion hapi-fhir-structures-hl7org-dstu2/pom.xml
Expand Up @@ -218,7 +218,6 @@
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<fork>true</fork>
<verbose>true</verbose>
</configuration>
</plugin>
</plugins>
Expand Down
@@ -0,0 +1,51 @@
package ca.uhn.fhir.validation;

import java.util.List;

import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.validation.ValidationMessage;

import ca.uhn.fhir.model.api.Bundle;

/**
* Base class for a bridge between the RI validation tools and HAPI
*/
abstract class BaseValidatorBridge implements IValidator {

public BaseValidatorBridge() {
super();
}

private void doValidate(IValidationContext<?> theCtx) {
List<ValidationMessage> messages = validate(theCtx);

for (ValidationMessage riMessage : messages) {
SingleValidationMessage hapiMessage = new SingleValidationMessage();
if (riMessage.getCol() != -1) {
hapiMessage.setLocationCol(riMessage.getCol());
}
if (riMessage.getLine() != -1) {
hapiMessage.setLocationLine(riMessage.getLine());
}
hapiMessage.setLocationString(riMessage.getLocation());
hapiMessage.setMessage(riMessage.getMessage());
if (riMessage.getLevel() != null) {
hapiMessage.setSeverity(ResultSeverityEnum.fromCode(riMessage.getLevel().toCode()));
}
theCtx.addValidationMessage(hapiMessage);
}
}

protected abstract List<ValidationMessage> validate(IValidationContext<?> theCtx);

@Override
public void validateBundle(IValidationContext<Bundle> theCtx) {
doValidate(theCtx);
}

@Override
public void validateResource(IValidationContext<IBaseResource> theCtx) {
doValidate(theCtx);
}

}
Expand Up @@ -14,7 +14,6 @@
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.instance.model.StructureDefinition;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.utils.WorkerContext;
import org.hl7.fhir.instance.validation.ValidationMessage;
import org.w3c.dom.Document;
Expand All @@ -24,17 +23,16 @@

import ca.uhn.fhir.context.ConfigurationException;
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.exceptions.InternalErrorException;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.google.gson.JsonObject;

public class FhirInstanceValidator implements IValidator {
public class FhirInstanceValidator extends BaseValidatorBridge implements IValidator {

private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);

private DocumentBuilderFactory myDocBuilderFactory;

Expand All @@ -43,7 +41,39 @@ public FhirInstanceValidator() {
myDocBuilderFactory.setNamespaceAware(true);
}

List<ValidationMessage> validate(FhirContext theCtx, String theInput, EncodingEnum theEncoding) {
private String determineResourceName(Document theDocument) {
Element root = null;

NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
root = (Element) list.item(i);
break;
}
}
root = theDocument.getDocumentElement();
return root.getLocalName();
}

private StructureDefinition loadProfileOrReturnNull(List<ValidationMessage> theMessages, FhirContext theCtx, String theResourceName) {
if (isBlank(theResourceName)) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("Could not determine resource type from request. Content appears invalid."));
return null;
}

String profileCpName = "/org/hl7/fhir/instance/model/profile/" + theResourceName.toLowerCase() + ".profile.xml";
String profileText;
try {
profileText = IOUtils.toString(FhirInstanceValidator.class.getResourceAsStream(profileCpName), "UTF-8");
} catch (IOException e1) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("No profile found for resource type " + theResourceName));
return null;
}
StructureDefinition profile = theCtx.newXmlParser().parseResource(StructureDefinition.class, profileText);
return profile;
}

protected List<ValidationMessage> validate(FhirContext theCtx, String theInput, EncodingEnum theEncoding) {
WorkerContext workerContext = new WorkerContext();
org.hl7.fhir.instance.validation.InstanceValidator v;
try {
Expand Down Expand Up @@ -97,61 +127,9 @@ List<ValidationMessage> validate(FhirContext theCtx, String theInput, EncodingEn
return messages;
}

private StructureDefinition loadProfileOrReturnNull(List<ValidationMessage> theMessages, FhirContext theCtx, String theResourceName) {
if (isBlank(theResourceName)) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("Could not determine resource type from request. Content appears invalid."));
return null;
}

String profileCpName = "/org/hl7/fhir/instance/model/profile/" + theResourceName.toLowerCase() + ".profile.xml";
String profileText;
try {
profileText = IOUtils.toString(FhirInstanceValidator.class.getResourceAsStream(profileCpName), "UTF-8");
} catch (IOException e1) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("No profile found for resource type " + theResourceName));
return null;
}
StructureDefinition profile = theCtx.newXmlParser().parseResource(StructureDefinition.class, profileText);
return profile;
}

private String determineResourceName(Document theDocument) {
Element root = null;

NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
root = (Element) list.item(i);
break;
}
}
root = theDocument.getDocumentElement();
return root.getLocalName();
}

@Override
public void validateResource(IValidationContext<IBaseResource> theCtx) {
List<ValidationMessage> messages = validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
for (ValidationMessage riMessage : messages) {
SingleValidationMessage hapiMessage = new SingleValidationMessage();
if (riMessage.getCol() != -1) {
hapiMessage.setLocationCol(riMessage.getCol());
}
if (riMessage.getLine() != -1) {
hapiMessage.setLocationLine(riMessage.getLine());
}
hapiMessage.setLocationString(riMessage.getLocation());
hapiMessage.setMessage(riMessage.getMessage());
if (riMessage.getLevel() != null) {
hapiMessage.setSeverity(ResultSeverityEnum.fromCode(riMessage.getLevel().toCode()));
}
theCtx.addValidationMessage(hapiMessage);
}
}

@Override
public void validateBundle(IValidationContext<Bundle> theContext) {
// nothing for now
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
}

}
@@ -0,0 +1,139 @@
package ca.uhn.fhir.validation;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import org.hl7.fhir.instance.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.instance.model.Questionnaire;
import org.hl7.fhir.instance.model.QuestionnaireAnswers;
import org.hl7.fhir.instance.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.utils.WorkerContext;
import org.hl7.fhir.instance.validation.QuestionnaireAnswersValidator;
import org.hl7.fhir.instance.validation.ValidationMessage;

import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.util.ResourceReferenceInfo;

public class FhirQuestionnaireAnswersValidator extends BaseValidatorBridge {

private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirQuestionnaireAnswersValidator.class);
private IResourceLoader myResourceLoader;

/**
* Set the class which will be used to load linked resources from the <code>QuestionnaireAnswers</code>. Specifically, if the <code>QuestionnaireAnswers</code> refers to an external (non-contained)
* <code>Questionnaire</code>, or to any external (non-contained) <code>ValueSet</code>, the resource loader will be used to fetch those resources during the validation.
*
* @param theResourceLoader
* The resourceloader to use. May be <code>null</code> if no resource loader should be used (in which case any <code>QuestionaireAnswers</code> with external references will fail to
* validate.)
*/
public void setResourceLoader(IResourceLoader theResourceLoader) {
myResourceLoader = theResourceLoader;
}

@Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
Object resource = theCtx.getResource();
if (!(theCtx.getResource() instanceof IBaseResource)) {
ourLog.debug("Not validating object of type {}", theCtx.getResource().getClass());
return Collections.emptyList();
}

if (resource instanceof QuestionnaireAnswers) {
return doValidate(theCtx, (QuestionnaireAnswers) resource);
}

RuntimeResourceDefinition def = theCtx.getFhirContext().getResourceDefinition((IBaseResource) resource);
if ("QuestionnaireAnswers".equals(def.getName()) == false) {
return Collections.emptyList();
}

/*
* If we have a non-RI structure, convert it
*/

IParser p = theCtx.getFhirContext().newJsonParser();
String string = p.encodeResourceToString((IBaseResource) resource);
QuestionnaireAnswers qa = p.parseResource(QuestionnaireAnswers.class, string);

return doValidate(theCtx, qa);
}

private List<ValidationMessage> doValidate(IValidationContext<?> theValCtx, QuestionnaireAnswers theResource) {

WorkerContext workerCtx = new WorkerContext();
ArrayList<ValidationMessage> retVal = new ArrayList<ValidationMessage>();

if (!loadReferences(theResource, workerCtx, theValCtx, retVal)) {
return retVal;
}

QuestionnaireAnswersValidator val = new QuestionnaireAnswersValidator(workerCtx);

val.validate(retVal, theResource);
return retVal;
}

private boolean loadReferences(IBaseResource theResource, WorkerContext theWorkerCtx, IValidationContext<?> theValCtx, ArrayList<ValidationMessage> theMessages) {
List<ResourceReferenceInfo> refs = theValCtx.getFhirContext().newTerser().getAllResourceReferences(theResource);

List<IBaseResource> newResources = new ArrayList<IBaseResource>();

for (ResourceReferenceInfo nextRefInfo : refs) {
IIdType nextRef = nextRefInfo.getResourceReference().getReferenceElement();
String resourceType = nextRef.getResourceType();
if ("ValueSet".equals(resourceType)) {
if (!theWorkerCtx.getValueSets().containsKey(nextRef.getValue())) {
ValueSet resource = tryToLoad(ValueSet.class, nextRef, theMessages);
if (resource == null) {
return false;
}
theWorkerCtx.getValueSets().put(nextRef.getValue(), resource);
newResources.add(resource);
}
} else if ("Questionnaire".equals(resourceType)) {
if (!theWorkerCtx.getQuestionnaires().containsKey(nextRef.getValue())) {
Questionnaire resource = tryToLoad(Questionnaire.class, nextRef, theMessages);
if (resource == null) {
return false;
}
theWorkerCtx.getQuestionnaires().put(nextRef.getValue(), resource);
newResources.add(resource);
}
}
}

for (IBaseResource nextAddedResource : newResources) {
boolean outcome = loadReferences(nextAddedResource, theWorkerCtx, theValCtx, theMessages);
if (!outcome) {
return false;
}
}

return true;
}

private <T extends IBaseResource> T tryToLoad(Class<T> theType, IIdType theReference, List<ValidationMessage> theMessages) {
if (myResourceLoader == null) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("No resource loader present, could not load " + theReference));
return null;
}

try {
T retVal = myResourceLoader.load(theType, theReference);
if (retVal == null) {
throw new IllegalStateException("ResourceLoader returned null. This is a bug with the resourceloader. Reference was: " + theReference);
}
return retVal;
} catch (ResourceNotFoundException e) {
theMessages.add(new ValidationMessage().setLevel(IssueSeverity.FATAL).setMessage("Reference could not be found: " + theReference));
return null;
}
}

}

0 comments on commit 3bba0c0

Please sign in to comment.