package org.hl7.fhir.instance.hapi.validation;
import static org.apache.commons.lang3.StringUtils.isBlank;
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.QuestionnaireResponse;
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.QuestionnaireResponseValidator;
import org.hl7.fhir.instance.validation.ValidationMessage;
import org.hl7.fhir.instance.validation.ValidationMessage.Source;
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;
import ca.uhn.fhir.validation.IResourceLoader;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
public class FhirQuestionnaireResponseValidator extends BaseValidatorBridge implements IValidatorModule {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory
.getLogger(FhirQuestionnaireResponseValidator.class);
private IResourceLoader myResourceLoader;
/**
* Set the class which will be used to load linked resources from the
* <code>QuestionnaireResponse</code>. Specifically, if the
* <code>QuestionnaireResponse</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>QuestionaireResponse</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 QuestionnaireResponse) {
return doValidate(theCtx, (QuestionnaireResponse) resource);
}
RuntimeResourceDefinition def = theCtx.getFhirContext().getResourceDefinition((IBaseResource) resource);
if ("QuestionnaireResponse".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);
QuestionnaireResponse qa = p.parseResource(QuestionnaireResponse.class, string);
return doValidate(theCtx, qa);
}
private List<ValidationMessage> doValidate(IValidationContext<?> theValCtx, QuestionnaireResponse theResource) {
WorkerContext workerCtx = new WorkerContext();
ArrayList<ValidationMessage> retVal = new ArrayList<ValidationMessage>();
if (!loadReferences(theResource, workerCtx, theValCtx, retVal)) {
return retVal;
}
QuestionnaireResponseValidator val = new QuestionnaireResponseValidator(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 (nextRef.isLocal()) {
IBaseResource resource = nextRefInfo.getResourceReference().getResource();
if (resource instanceof ValueSet) {
theWorkerCtx.getValueSets().put(nextRef.getValue(), (ValueSet) resource);
newResources.add(resource);
} else if (resource instanceof Questionnaire) {
theWorkerCtx.getQuestionnaires().put(nextRef.getValue(), (Questionnaire) resource);
newResources.add(resource);
} else if (resource == null) {
theMessages.add(new ValidationMessage(Source.QuestionnaireResponseValidator,
org.hl7.fhir.instance.model.OperationOutcome.IssueType.INVALID,
"Invalid reference '" + nextRef.getValue() + "' - No contained resource with this ID found", IssueSeverity.FATAL));
}
} else if (isBlank(resourceType)) {
theMessages.add(new ValidationMessage(Source.QuestionnaireResponseValidator,
org.hl7.fhir.instance.model.OperationOutcome.IssueType.INVALID,
"Invalid reference '" + nextRef.getValue() + "' - Does not identify resource type", IssueSeverity.FATAL));
} else 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;
}
}
}