validate operation ignores parameter profile (#1417)

* This should be working now - need to check tests

* Add a changelog and some docs

* One more test fix

* Tests should be passing

* Fix compile

* Test fixes

* Ignore outdated DSTU2 validation test
This commit is contained in:
James Agnew 2019-08-07 15:31:59 -04:00 committed by GitHub
parent 1b17097f62
commit dd0cb10dbb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
48 changed files with 1780 additions and 1377 deletions

View File

@ -181,7 +181,13 @@ public class ValidatorExamples {
// Validate
ValidationResult result = validator.validateWithResult(obs);
/*
* Note: You can also explicitly declare a profile to validate against
* using the block below.
*/
// ValidationResult result = validator.validateWithResult(obs, new ValidationOptions().addProfile("http://myprofile.com"));
// Do we have any errors or fatal errors?
System.out.println(result.isSuccessful()); // false

View File

@ -31,10 +31,17 @@ abstract class BaseValidationContext<T> implements IValidationContext<T> {
protected final FhirContext myFhirContext;
private List<SingleValidationMessage> myMessages;
/**
* Constructor
*/
BaseValidationContext(FhirContext theFhirContext) {
this(theFhirContext, new ArrayList<SingleValidationMessage>());
this(theFhirContext, new ArrayList<>());
}
/**
* Constructor
*/
BaseValidationContext(FhirContext theFhirContext, List<SingleValidationMessage> theMessages) {
myFhirContext = theFhirContext;
myMessages = theMessages;

View File

@ -25,7 +25,6 @@ import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.validation.schematron.SchematronProvider;
/**
@ -46,7 +45,7 @@ public class FhirValidator {
private static volatile Boolean ourPhPresentOnClasspath;
private final FhirContext myContext;
private List<IValidatorModule> myValidators = new ArrayList<IValidatorModule>();
private List<IValidatorModule> myValidators = new ArrayList<>();
/**
* Constructor (this should not be called directly, but rather {@link FhirContext#newValidator()} should be called to obtain an instance of {@link FhirValidator})
@ -173,25 +172,6 @@ public class FhirValidator {
}
}
/**
* Validates a resource instance, throwing a {@link ValidationFailureException} if the validation fails
*
* @param theResource
* The resource to validate
* @throws ValidationFailureException
* If the validation fails
* @deprecated use {@link #validateWithResult(IBaseResource)} instead
*/
@Deprecated
public void validate(IResource theResource) throws ValidationFailureException {
applyDefaultValidators();
ValidationResult validationResult = validateWithResult(theResource);
if (!validationResult.isSuccessful()) {
throw new ValidationFailureException(myContext, validationResult.toOperationOutcome());
}
}
/**
@ -203,11 +183,37 @@ public class FhirValidator {
* @since 0.7
*/
public ValidationResult validateWithResult(IBaseResource theResource) {
return validateWithResult(theResource, null);
}
/**
* Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results.
*
* @param theResource
* the resource to validate
* @return the results of validation
* @since 1.1
*/
public ValidationResult validateWithResult(String theResource) {
return validateWithResult(theResource, null);
}
/**
* Validates a resource instance returning a {@link ca.uhn.fhir.validation.ValidationResult} which contains the results.
*
* @param theResource
* the resource to validate
* @param theOptions
* Optionally provides options to the validator
* @return the results of validation
* @since 4.0.0
*/
public ValidationResult validateWithResult(IBaseResource theResource, ValidationOptions theOptions) {
Validate.notNull(theResource, "theResource must not be null");
applyDefaultValidators();
IValidationContext<IBaseResource> ctx = ValidationContext.forResource(myContext, theResource);
IValidationContext<IBaseResource> ctx = ValidationContext.forResource(myContext, theResource, theOptions);
for (IValidatorModule next : myValidators) {
next.validateResource(ctx);
@ -221,15 +227,17 @@ public class FhirValidator {
*
* @param theResource
* the resource to validate
* @param theOptions
* Optionally provides options to the validator
* @return the results of validation
* @since 1.1
* @since 4.0.0
*/
public ValidationResult validateWithResult(String theResource) {
public ValidationResult validateWithResult(String theResource, ValidationOptions theOptions) {
Validate.notNull(theResource, "theResource must not be null");
applyDefaultValidators();
IValidationContext<IBaseResource> ctx = ValidationContext.forText(myContext, theResource);
IValidationContext<IBaseResource> ctx = ValidationContext.forText(myContext, theResource, theOptions);
for (IValidatorModule next : myValidators) {
next.validateResource(ctx);
@ -237,5 +245,4 @@ public class FhirValidator {
return ctx.toResult();
}
}

View File

@ -25,6 +25,8 @@ import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.EncodingEnum;
import javax.annotation.Nonnull;
public interface IValidationContext<T> {
FhirContext getFhirContext();
@ -41,4 +43,7 @@ public interface IValidationContext<T> {
ValidationResult toResult();
@Nonnull
ValidationOptions getOptions();
}

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.validation;
import net.sf.saxon.lib.Validation;
import org.hl7.fhir.instance.model.api.IBaseResource;

View File

@ -1,8 +1,19 @@
package ca.uhn.fhir.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ObjectUtil;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.annotation.Nonnull;
import java.util.ArrayList;
import java.util.List;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
/*
* #%L
* HAPI FHIR - Core Library
@ -23,30 +34,23 @@ import java.util.List;
* #L%
*/
import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.LenientErrorHandler;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.ObjectUtil;
public class ValidationContext<T> extends BaseValidationContext<T> implements IValidationContext<T> {
private final IEncoder myEncoder;
private final T myResource;
private String myResourceAsString;
private final EncodingEnum myResourceAsStringEncoding;
private final ValidationOptions myOptions;
private String myResourceAsString;
private ValidationContext(FhirContext theContext, T theResource, IEncoder theEncoder) {
this(theContext, theResource, theEncoder, new ArrayList<SingleValidationMessage>());
private ValidationContext(FhirContext theContext, T theResource, IEncoder theEncoder, ValidationOptions theOptions) {
this(theContext, theResource, theEncoder, new ArrayList<>(), theOptions);
}
private ValidationContext(FhirContext theContext, T theResource, IEncoder theEncoder, List<SingleValidationMessage> theMessages) {
private ValidationContext(FhirContext theContext, T theResource, IEncoder theEncoder, List<SingleValidationMessage> theMessages, ValidationOptions theOptions) {
super(theContext, theMessages);
myResource = theResource;
myEncoder = theEncoder;
myOptions = theOptions;
if (theEncoder != null) {
myResourceAsStringEncoding = theEncoder.getEncoding();
} else {
@ -72,8 +76,24 @@ public class ValidationContext<T> extends BaseValidationContext<T> implements IV
return myResourceAsStringEncoding;
}
public static <T extends IBaseResource> IValidationContext<T> forResource(final FhirContext theContext, final T theResource) {
return new ValidationContext<T>(theContext, theResource, new IEncoder() {
@Nonnull
@Override
public ValidationOptions getOptions() {
return myOptions;
}
private interface IEncoder {
String encode();
EncodingEnum getEncoding();
}
public static <T extends IBaseResource> IValidationContext<T> forResource(final FhirContext theContext, final T theResource, ValidationOptions theOptions) {
ObjectUtil.requireNonNull(theContext, "theContext can not be null");
ObjectUtil.requireNonNull(theResource, "theResource can not be null");
ValidationOptions options = defaultIfNull(theOptions, ValidationOptions.empty());
IEncoder encoder = new IEncoder() {
@Override
public String encode() {
return theContext.newXmlParser().encodeResourceToString(theResource);
@ -83,18 +103,15 @@ public class ValidationContext<T> extends BaseValidationContext<T> implements IV
public EncodingEnum getEncoding() {
return EncodingEnum.XML;
}
});
};
return new ValidationContext<>(theContext, theResource, encoder, options);
}
private interface IEncoder {
String encode();
EncodingEnum getEncoding();
}
public static IValidationContext<IBaseResource> forText(final FhirContext theContext, final String theResourceBody) {
public static IValidationContext<IBaseResource> forText(final FhirContext theContext, final String theResourceBody, final ValidationOptions theOptions) {
ObjectUtil.requireNonNull(theContext, "theContext can not be null");
ObjectUtil.requireNotEmpty(theResourceBody, "theResourceBody can not be null or empty");
ValidationOptions options = defaultIfNull(theOptions, ValidationOptions.empty());
return new BaseValidationContext<IBaseResource>(theContext) {
private EncodingEnum myEncoding;
@ -128,11 +145,17 @@ public class ValidationContext<T> extends BaseValidationContext<T> implements IV
return myEncoding;
}
@Nonnull
@Override
public ValidationOptions getOptions() {
return options;
}
};
}
public static IValidationContext<IBaseResource> subContext(final IValidationContext<IBaseResource> theCtx, final IBaseResource theResource) {
return new ValidationContext<IBaseResource>(theCtx.getFhirContext(), theResource, new IEncoder() {
public static IValidationContext<IBaseResource> subContext(final IValidationContext<IBaseResource> theCtx, final IBaseResource theResource, ValidationOptions theOptions) {
return new ValidationContext<>(theCtx.getFhirContext(), theResource, new IEncoder() {
@Override
public String encode() {
return theCtx.getFhirContext().newXmlParser().encodeResourceToString(theResource);
@ -142,6 +165,6 @@ public class ValidationContext<T> extends BaseValidationContext<T> implements IV
public EncodingEnum getEncoding() {
return EncodingEnum.XML;
}
}, theCtx.getMessages());
}, theCtx.getMessages(), theOptions);
}
}

View File

@ -0,0 +1,47 @@
package ca.uhn.fhir.validation;
import org.apache.commons.lang3.Validate;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class ValidationOptions {
private static ValidationOptions ourEmpty;
private Set<String> myProfiles;
public Set<String> getProfiles() {
return myProfiles != null ? Collections.unmodifiableSet(myProfiles) : Collections.emptySet();
}
public ValidationOptions addProfile(String theProfileUri) {
Validate.notBlank(theProfileUri);
if (myProfiles == null) {
myProfiles = new HashSet<>();
}
myProfiles.add(theProfileUri);
return this;
}
public ValidationOptions addProfileIfNotBlank(String theProfileUri) {
if (isNotBlank(theProfileUri)) {
return addProfile(theProfileUri);
}
return this;
}
public static ValidationOptions empty() {
ValidationOptions retVal = ourEmpty;
if (retVal == null) {
retVal = new ValidationOptions();
retVal.myProfiles = Collections.emptySet();
ourEmpty = retVal;
}
return retVal;
}
}

View File

@ -69,7 +69,7 @@ public class SchematronBaseValidator implements IValidatorModule {
IBaseBundle bundle = (IBaseBundle) theCtx.getResource();
List<IBaseResource> subResources = BundleUtil.toListOfResources(myCtx, bundle);
for (IBaseResource nextSubResource : subResources) {
validateResource(ValidationContext.subContext(theCtx, nextSubResource));
validateResource(ValidationContext.subContext(theCtx, nextSubResource, theCtx.getOptions()));
}
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.cli;
import org.junit.Before;
import org.junit.Ignore;
import org.junit.Test;
import static org.junit.Assert.fail;
@ -26,6 +27,7 @@ public class ValidateTest {
}
@Test
@Ignore
public void testValidateUsingIgPackSucceedingDstu2() {
String resourcePath = ValidateTest.class.getResource("/argo-dstu2-observation-good.json").getFile();
ourLog.info(resourcePath);

View File

@ -50,6 +50,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetai
import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.*;
import ca.uhn.fhir.validation.*;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.*;
import org.hl7.fhir.r4.model.InstantType;
@ -178,6 +179,8 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return createOperationOutcome(OO_SEVERITY_INFO, theMessage, "informational");
}
protected abstract IValidatorModule getInstanceValidator();
protected abstract IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode);
@Override
@ -1404,6 +1407,74 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
return update(theResource, theMatchUrl, thePerformIndexing, false, theRequestDetails);
}
@Override
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId);
notifyInterceptors(RestOperationTypeEnum.VALIDATE, requestDetails);
}
if (theMode == ValidationModeEnum.DELETE) {
if (theId == null || theId.hasIdPart() == false) {
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
}
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
// Validate that there are no resources pointing to the candidate that
// would prevent deletion
DeleteConflictList deleteConflicts = new DeleteConflictList();
if (myDaoConfig.isEnforceReferentialIntegrityOnDelete()) {
myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, true, theRequest);
}
myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
IBaseOperationOutcome oo = createInfoOperationOutcome("Ok to delete");
return new MethodOutcome(new IdDt(theId.getValue()), oo);
}
FhirValidator validator = getContext().newValidator();
validator.registerValidatorModule(getInstanceValidator());
validator.registerValidatorModule(new IdChecker(theMode));
IBaseResource resourceToValidateById = null;
if (theId != null && theId.hasResourceType() && theId.hasIdPart()) {
Class<? extends IBaseResource> type = getContext().getResourceDefinition(theId.getResourceType()).getImplementingClass();
IFhirResourceDao<? extends IBaseResource> dao = getDao(type);
resourceToValidateById = dao.read(theId, theRequest);
}
ValidationResult result;
ValidationOptions options = new ValidationOptions()
.addProfileIfNotBlank(theProfile);
if (theResource == null) {
if (resourceToValidateById != null) {
result = validator.validateWithResult(resourceToValidateById, options);
} else {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "cantValidateWithNoResource");
throw new InvalidRequestException(msg);
}
} else if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource, options);
} else if (theResource != null) {
result = validator.validateWithResult(theResource, options);
} else {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "cantValidateWithNoResource");
throw new InvalidRequestException(msg);
}
if (result.isSuccessful()) {
MethodOutcome retVal = new MethodOutcome();
retVal.setOperationOutcome(result.toOperationOutcome());
return retVal;
} else {
throw new PreconditionFailedException("Validation failed", result.toOperationOutcome());
}
}
/**
* Get the resource definition from the criteria which specifies the resource type
*
@ -1439,7 +1510,6 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
private void validateResourceType(BaseHasResource entity) {
validateResourceType(entity, myResourceName);
}
@ -1451,4 +1521,29 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
}
}
private static class IdChecker implements IValidatorModule {
private ValidationModeEnum myMode;
IdChecker(ValidationModeEnum theMode) {
myMode = theMode;
}
@Override
public void validateResource(IValidationContext<IBaseResource> theCtx) {
boolean hasId = theCtx.getResource().getIdElement().hasIdPart();
if (myMode == ValidationModeEnum.CREATE) {
if (hasId) {
throw new UnprocessableEntityException("Resource has an ID - ID must not be populated for a FHIR create");
}
} else if (myMode == ValidationModeEnum.UPDATE) {
if (hasId == false) {
throw new UnprocessableEntityException("Resource has no ID - ID must be populated for a FHIR update");
}
}
}
}
}

View File

@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
import ca.uhn.fhir.model.dstu2.valueset.BundleTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import java.util.Set;
@ -42,10 +41,9 @@ public class FhirResourceDaoBundleDstu2 extends FhirResourceDaoDstu2<Bundle> {
}
for (Entry next : theResource.getEntry()) {
next.setFullUrl((String)null);
next.setFullUrl((String) null);
}
}
}

View File

@ -1,33 +1,12 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.delete.DeleteConflictList;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu2.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.instance.hapi.validation.IValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR JPA Server
@ -50,14 +29,14 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResourceDao<T> {
@Autowired()
@Qualifier("myJpaValidationSupportDstu2")
private IValidationSupport myJpaValidationSupport;
@Autowired()
@Autowired
@Qualifier("myInstanceValidatorDstu2")
private IValidatorModule myInstanceValidator;
@Override
protected IValidatorModule getInstanceValidator() {
return myInstanceValidator;
}
@Override
protected IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode) {
@ -68,79 +47,5 @@ public class FhirResourceDaoDstu2<T extends IResource> extends BaseHapiFhirResou
return oo;
}
@Override
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId);
notifyInterceptors(RestOperationTypeEnum.VALIDATE, requestDetails);
if (theMode == ValidationModeEnum.DELETE) {
if (theId == null || theId.hasIdPart() == false) {
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
}
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
// Validate that there are no resources pointing to the candidate that
// would prevent deletion
DeleteConflictList deleteConflicts = new DeleteConflictList();
if (myDaoConfig.isEnforceReferentialIntegrityOnDelete()) {
myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, true, theRequest);
}
myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setSeverity(IssueSeverityEnum.INFORMATION).setDiagnostics("Ok to delete");
return new MethodOutcome(new IdDt(theId.getValue()), oo);
}
FhirValidator validator = getContext().newValidator();
validator.registerValidatorModule(myInstanceValidator);
validator.registerValidatorModule(new IdChecker(theMode));
ValidationResult result;
if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource);
} else if (theResource != null) {
result = validator.validateWithResult(theResource);
} else {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "cantValidateWithNoResource");
throw new InvalidRequestException(msg);
}
if (result.isSuccessful()) {
MethodOutcome retVal = new MethodOutcome();
retVal.setOperationOutcome(result.toOperationOutcome());
return retVal;
} else {
throw new PreconditionFailedException("Validation failed", result.toOperationOutcome());
}
}
private class IdChecker implements IValidatorModule {
private ValidationModeEnum myMode;
public IdChecker(ValidationModeEnum theMode) {
myMode = theMode;
}
@Override
public void validateResource(IValidationContext<IBaseResource> theCtx) {
boolean hasId = theCtx.getResource().getIdElement().hasIdPart();
if (myMode == ValidationModeEnum.CREATE) {
if (hasId) {
throw new UnprocessableEntityException("Resource has an ID - ID must not be populated for a FHIR create");
}
} else if (myMode == ValidationModeEnum.UPDATE) {
if (hasId == false) {
throw new UnprocessableEntityException("Resource has no ID - ID must be populated for a FHIR update");
}
}
}
}
}

View File

@ -21,36 +21,19 @@ package ca.uhn.fhir.jpa.dao.dstu3;
*/
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.delete.DeleteConflictList;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.OperationOutcome;
import org.hl7.fhir.dstu3.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.dstu3.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirResourceDao<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3.class);
@ -59,6 +42,11 @@ public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirRe
@Qualifier("myInstanceValidatorDstu3")
private IValidatorModule myInstanceValidator;
@Override
protected IValidatorModule getInstanceValidator() {
return myInstanceValidator;
}
@Override
protected IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode) {
OperationOutcome oo = new OperationOutcome();
@ -74,92 +62,4 @@ public class FhirResourceDaoDstu3<T extends IAnyResource> extends BaseHapiFhirRe
}
@Override
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
if (theRequest != null) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId);
notifyInterceptors(RestOperationTypeEnum.VALIDATE, requestDetails);
}
if (theMode == ValidationModeEnum.DELETE) {
if (theId == null || theId.hasIdPart() == false) {
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
}
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
// Validate that there are no resources pointing to the candidate that
// would prevent deletion
DeleteConflictList deleteConflicts = new DeleteConflictList();
if (myDaoConfig.isEnforceReferentialIntegrityOnDelete()) {
myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, true, theRequest);
}
myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setSeverity(IssueSeverity.INFORMATION).setDiagnostics("Ok to delete");
return new MethodOutcome(new IdType(theId.getValue()), oo);
}
FhirValidator validator = getContext().newValidator();
validator.registerValidatorModule(myInstanceValidator);
validator.registerValidatorModule(new IdChecker(theMode));
IBaseResource resourceToValidateById = null;
if (theId != null && theId.hasResourceType() && theId.hasIdPart()) {
Class<? extends IBaseResource> type = getContext().getResourceDefinition(theId.getResourceType()).getImplementingClass();
IFhirResourceDao<? extends IBaseResource> dao = getDao(type);
resourceToValidateById = dao.read(theId, theRequest);
}
ValidationResult result;
if (theResource == null) {
if (resourceToValidateById != null) {
result = validator.validateWithResult(resourceToValidateById);
} else {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "cantValidateWithNoResource");
throw new InvalidRequestException(msg);
}
} else if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource);
} else {
result = validator.validateWithResult(theResource);
}
if (result.isSuccessful()) {
MethodOutcome retVal = new MethodOutcome();
retVal.setOperationOutcome(result.toOperationOutcome());
return retVal;
} else {
throw new PreconditionFailedException("Validation failed", result.toOperationOutcome());
}
}
private class IdChecker implements IValidatorModule {
private ValidationModeEnum myMode;
public IdChecker(ValidationModeEnum theMode) {
myMode = theMode;
}
@Override
public void validateResource(IValidationContext<IBaseResource> theCtx) {
boolean hasId = theCtx.getResource().getIdElement().hasIdPart();
if (myMode == ValidationModeEnum.CREATE) {
if (hasId) {
throw new UnprocessableEntityException("Resource has an ID - ID must not be populated for a FHIR create");
}
} else if (myMode == ValidationModeEnum.UPDATE) {
if (hasId == false) {
throw new UnprocessableEntityException("Resource has no ID - ID must be populated for a FHIR update");
}
}
}
}
}

View File

@ -53,6 +53,7 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3, Ap
private IFhirResourceDao<ValueSet> myValueSetDao;
private IFhirResourceDao<Questionnaire> myQuestionnaireDao;
private IFhirResourceDao<CodeSystem> myCodeSystemDao;
private IFhirResourceDao<ImplementationGuide> myImplementationGuideDao;
@Autowired
private FhirContext myDstu3Ctx;
private ApplicationContext myApplicationContext;
@ -148,6 +149,11 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3, Ap
params.setLoadSynchronousUpTo(1);
params.add(CodeSystem.SP_URL, new UriParam(theUri));
search = myCodeSystemDao.search(params);
} else if ("ImplementationGuide".equals(resourceName)) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ImplementationGuide.SP_URL, new UriParam(theUri));
search = myImplementationGuideDao.search(params);
} else {
throw new IllegalArgumentException("Can't fetch resource type: " + resourceName);
}
@ -185,6 +191,7 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3, Ap
myValueSetDao = myApplicationContext.getBean("myValueSetDaoDstu3", IFhirResourceDao.class);
myQuestionnaireDao = myApplicationContext.getBean("myQuestionnaireDaoDstu3", IFhirResourceDao.class);
myCodeSystemDao = myApplicationContext.getBean("myCodeSystemDaoDstu3", IFhirResourceDao.class);
myImplementationGuideDao = myApplicationContext.getBean("myImplementationGuideDaoDstu3", IFhirResourceDao.class);
}
@Override

View File

@ -59,6 +59,11 @@ public class FhirResourceDaoR4<T extends IAnyResource> extends BaseHapiFhirResou
@Qualifier("myInstanceValidatorR4")
private IValidatorModule myInstanceValidator;
@Override
protected IValidatorModule getInstanceValidator() {
return myInstanceValidator;
}
@Override
protected IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode) {
OperationOutcome oo = new OperationOutcome();
@ -74,90 +79,5 @@ public class FhirResourceDaoR4<T extends IAnyResource> extends BaseHapiFhirResou
}
@Override
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId);
notifyInterceptors(RestOperationTypeEnum.VALIDATE, requestDetails);
if (theMode == ValidationModeEnum.DELETE) {
if (theId == null || theId.hasIdPart() == false) {
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
}
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
// Validate that there are no resources pointing to the candidate that
// would prevent deletion
DeleteConflictList deleteConflicts = new DeleteConflictList();
if (myDaoConfig.isEnforceReferentialIntegrityOnDelete()) {
myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, true, theRequest);
}
myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setSeverity(IssueSeverity.INFORMATION).setDiagnostics("Ok to delete");
return new MethodOutcome(new IdType(theId.getValue()), oo);
}
FhirValidator validator = getContext().newValidator();
validator.registerValidatorModule(myInstanceValidator);
validator.registerValidatorModule(new IdChecker(theMode));
IBaseResource resourceToValidateById = null;
if (theId != null && theId.hasResourceType() && theId.hasIdPart()) {
Class<? extends IBaseResource> type = getContext().getResourceDefinition(theId.getResourceType()).getImplementingClass();
IFhirResourceDao<? extends IBaseResource> dao = getDao(type);
resourceToValidateById = dao.read(theId, theRequest);
}
ValidationResult result;
if (theResource == null) {
if (resourceToValidateById != null) {
result = validator.validateWithResult(resourceToValidateById);
} else {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "cantValidateWithNoResource");
throw new InvalidRequestException(msg);
}
} else if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource);
} else {
result = validator.validateWithResult(theResource);
}
if (result.isSuccessful()) {
MethodOutcome retVal = new MethodOutcome();
retVal.setOperationOutcome(result.toOperationOutcome());
return retVal;
} else {
throw new PreconditionFailedException("Validation failed", result.toOperationOutcome());
}
}
private class IdChecker implements IValidatorModule {
private ValidationModeEnum myMode;
public IdChecker(ValidationModeEnum theMode) {
myMode = theMode;
}
@Override
public void validateResource(IValidationContext<IBaseResource> theCtx) {
boolean hasId = theCtx.getResource().getIdElement().hasIdPart();
if (myMode == ValidationModeEnum.CREATE) {
if (hasId) {
throw new UnprocessableEntityException("Resource has an ID - ID must not be populated for a FHIR create");
}
} else if (myMode == ValidationModeEnum.UPDATE) {
if (hasId == false) {
throw new UnprocessableEntityException("Resource has no ID - ID must be populated for a FHIR update");
}
}
}
}
}

View File

@ -51,6 +51,7 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4, Applicat
private IFhirResourceDao<ValueSet> myValueSetDao;
private IFhirResourceDao<Questionnaire> myQuestionnaireDao;
private IFhirResourceDao<CodeSystem> myCodeSystemDao;
private IFhirResourceDao<ImplementationGuide> myImplementationGuideDao;
@Autowired
private FhirContext myR4Ctx;
@ -142,6 +143,11 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4, Applicat
params.setLoadSynchronousUpTo(1);
params.add(CodeSystem.SP_URL, new UriParam(theUri));
search = myCodeSystemDao.search(params);
} else if ("ImplementationGuide".equals(resourceName)) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ImplementationGuide.SP_URL, new UriParam(theUri));
search = myImplementationGuideDao.search(params);
} else {
throw new IllegalArgumentException("Can't fetch resource type: " + resourceName);
}
@ -179,6 +185,7 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4, Applicat
myValueSetDao = myApplicationContext.getBean("myValueSetDaoR4", IFhirResourceDao.class);
myQuestionnaireDao = myApplicationContext.getBean("myQuestionnaireDaoR4", IFhirResourceDao.class);
myCodeSystemDao = myApplicationContext.getBean("myCodeSystemDaoR4", IFhirResourceDao.class);
myImplementationGuideDao = myApplicationContext.getBean("myImplementationGuideDaoR4", IFhirResourceDao.class);
}
@Override

View File

@ -21,36 +21,15 @@ package ca.uhn.fhir.jpa.dao.r5;
*/
import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.delete.DeleteConflictList;
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.api.ValidationModeEnum;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.OperationOutcome;
import org.hl7.fhir.r5.model.OperationOutcome.IssueSeverity;
import org.hl7.fhir.r5.model.OperationOutcome.OperationOutcomeIssueComponent;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class FhirResourceDaoR5<T extends IAnyResource> extends BaseHapiFhirResourceDao<T> {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR5.class);
@ -59,6 +38,11 @@ public class FhirResourceDaoR5<T extends IAnyResource> extends BaseHapiFhirResou
@Qualifier("myInstanceValidatorR5")
private IValidatorModule myInstanceValidator;
@Override
protected IValidatorModule getInstanceValidator() {
return myInstanceValidator;
}
@Override
protected IBaseOperationOutcome createOperationOutcome(String theSeverity, String theMessage, String theCode) {
OperationOutcome oo = new OperationOutcome();
@ -74,90 +58,4 @@ public class FhirResourceDaoR5<T extends IAnyResource> extends BaseHapiFhirResou
}
@Override
public MethodOutcome validate(T theResource, IIdType theId, String theRawResource, EncodingEnum theEncoding, ValidationModeEnum theMode, String theProfile, RequestDetails theRequest) {
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, theResource, null, theId);
notifyInterceptors(RestOperationTypeEnum.VALIDATE, requestDetails);
if (theMode == ValidationModeEnum.DELETE) {
if (theId == null || theId.hasIdPart() == false) {
throw new InvalidRequestException("No ID supplied. ID is required when validating with mode=DELETE");
}
final ResourceTable entity = readEntityLatestVersion(theId, theRequest);
// Validate that there are no resources pointing to the candidate that
// would prevent deletion
DeleteConflictList deleteConflicts = new DeleteConflictList();
if (myDaoConfig.isEnforceReferentialIntegrityOnDelete()) {
myDeleteConflictService.validateOkToDelete(deleteConflicts, entity, true, theRequest);
}
myDeleteConflictService.validateDeleteConflictsEmptyOrThrowException(deleteConflicts);
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setSeverity(IssueSeverity.INFORMATION).setDiagnostics("Ok to delete");
return new MethodOutcome(new IdType(theId.getValue()), oo);
}
FhirValidator validator = getContext().newValidator();
validator.registerValidatorModule(myInstanceValidator);
validator.registerValidatorModule(new IdChecker(theMode));
IBaseResource resourceToValidateById = null;
if (theId != null && theId.hasResourceType() && theId.hasIdPart()) {
Class<? extends IBaseResource> type = getContext().getResourceDefinition(theId.getResourceType()).getImplementingClass();
IFhirResourceDao<? extends IBaseResource> dao = getDao(type);
resourceToValidateById = dao.read(theId, theRequest);
}
ValidationResult result;
if (theResource == null) {
if (resourceToValidateById != null) {
result = validator.validateWithResult(resourceToValidateById);
} else {
String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "cantValidateWithNoResource");
throw new InvalidRequestException(msg);
}
} else if (isNotBlank(theRawResource)) {
result = validator.validateWithResult(theRawResource);
} else {
result = validator.validateWithResult(theResource);
}
if (result.isSuccessful()) {
MethodOutcome retVal = new MethodOutcome();
retVal.setOperationOutcome(result.toOperationOutcome());
return retVal;
} else {
throw new PreconditionFailedException("Validation failed", result.toOperationOutcome());
}
}
private class IdChecker implements IValidatorModule {
private ValidationModeEnum myMode;
public IdChecker(ValidationModeEnum theMode) {
myMode = theMode;
}
@Override
public void validateResource(IValidationContext<IBaseResource> theCtx) {
boolean hasId = theCtx.getResource().getIdElement().hasIdPart();
if (myMode == ValidationModeEnum.CREATE) {
if (hasId) {
throw new UnprocessableEntityException("Resource has an ID - ID must not be populated for a FHIR create");
}
} else if (myMode == ValidationModeEnum.UPDATE) {
if (hasId == false) {
throw new UnprocessableEntityException("Resource has no ID - ID must be populated for a FHIR update");
}
}
}
}
}

View File

@ -51,6 +51,7 @@ public class JpaValidationSupportR5 implements IJpaValidationSupportR5, Applicat
private IFhirResourceDao<ValueSet> myValueSetDao;
private IFhirResourceDao<Questionnaire> myQuestionnaireDao;
private IFhirResourceDao<CodeSystem> myCodeSystemDao;
private IFhirResourceDao<ImplementationGuide> myImplementationGuideDao;
@Autowired
private FhirContext myR5Ctx;
@ -142,6 +143,11 @@ public class JpaValidationSupportR5 implements IJpaValidationSupportR5, Applicat
params.setLoadSynchronousUpTo(1);
params.add(CodeSystem.SP_URL, new UriParam(theUri));
search = myCodeSystemDao.search(params);
} else if ("ImplementationGuide".equals(resourceName)) {
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(1);
params.add(ImplementationGuide.SP_URL, new UriParam(theUri));
search = myImplementationGuideDao.search(params);
} else {
throw new IllegalArgumentException("Can't fetch resource type: " + resourceName);
}
@ -179,6 +185,7 @@ public class JpaValidationSupportR5 implements IJpaValidationSupportR5, Applicat
myValueSetDao = myApplicationContext.getBean("myValueSetDaoR5", IFhirResourceDao.class);
myQuestionnaireDao = myApplicationContext.getBean("myQuestionnaireDaoR5", IFhirResourceDao.class);
myCodeSystemDao = myApplicationContext.getBean("myCodeSystemDaoR5", IFhirResourceDao.class);
myImplementationGuideDao = myApplicationContext.getBean("myImplementationGuideDaoR5", IFhirResourceDao.class);
}
@Override

View File

@ -51,13 +51,13 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
public class HapiTerminologySvcR5 extends BaseHapiTerminologySvcImpl implements IValidationSupport, IHapiTerminologySvcR5 {
@Autowired
@Qualifier("myConceptMapDaoR4")
@Qualifier("myConceptMapDaoR5")
private IFhirResourceDao<ConceptMap> myConceptMapResourceDao;
@Autowired
@Qualifier("myCodeSystemDaoR4")
@Qualifier("myCodeSystemDaoR5")
private IFhirResourceDao<CodeSystem> myCodeSystemResourceDao;
@Autowired
@Qualifier("myValueSetDaoR4")
@Qualifier("myValueSetDaoR5")
private IFhirResourceDao<ValueSet> myValueSetResourceDao;
@Autowired
private IValidationSupport myValidationSupport;

View File

@ -2889,8 +2889,9 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
myStructureDefinitionDao.create(sd, mySrd);
String rawResource = IOUtils.toString(getClass().getResourceAsStream("/binu_testpatient_resource.json"), StandardCharsets.UTF_8);
IBaseResource parsedResource = myFhirCtx.newJsonParser().parseResource(rawResource);
try {
myValueSetDao.validate(null, null, rawResource, EncodingEnum.JSON, ValidationModeEnum.UPDATE, null, mySrd);
myPatientDao.validate((Patient) parsedResource, null, rawResource, EncodingEnum.JSON, ValidationModeEnum.UPDATE, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));

View File

@ -17,6 +17,7 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.dstu3.hapi.validation.CachingValidationSupport;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceCategory;
import org.hl7.fhir.dstu3.model.AllergyIntolerance.AllergyIntoleranceClinicalStatus;
@ -45,6 +46,8 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
@Autowired
private IHapiTerminologySvc myHapiTerminologySvc;
@Autowired
private CachingValidationSupport myCachingValidationSupport;
@After
public void after() {
@ -56,7 +59,7 @@ public class FhirResourceDaoDstu3TerminologyTest extends BaseJpaDstu3Test {
@Before
public void before() {
myDaoConfig.setMaximumExpansionSize(5000);
// my
myCachingValidationSupport.flushCaches();
}
private CodeSystem createExternalCs() {

View File

@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.validation.IValidatorModule;
import org.apache.commons.io.IOUtils;
import org.hl7.fhir.dstu3.hapi.validation.CachingValidationSupport;
import org.hl7.fhir.dstu3.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Bundle.BundleEntryComponent;
@ -35,6 +36,10 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3ValidateTest.class);
@Autowired
private IValidatorModule myValidatorModule;
@Autowired
private CachingValidationSupport myValidationSupport;
@Autowired
private FhirInstanceValidator myFhirInstanceValidator;
@Test
public void testValidateChangedQuestionnaire() {
@ -70,7 +75,10 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
MethodOutcome results = myQuestionnaireResponseDao.validate(qr, null, null, null, null, null, null);
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(results.getOperationOutcome()));
TestUtil.sleepAtLeast(2500);
ourLog.info("Clearing cache");
myValidationSupport.flushCaches();
myFhirInstanceValidator.flushCaches();
try {
myQuestionnaireResponseDao.validate(qr, null, null, null, null, null, null);
fail();
@ -273,7 +281,7 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
}
@Test
public void testValidateResourceContainingProfileDeclarationInvalid() throws Exception {
public void testValidateResourceContainingProfileDeclarationInvalid() {
String methodName = "testValidateResourceContainingProfileDeclarationInvalid";
Observation input = new Observation();
@ -287,11 +295,14 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
ValidationModeEnum mode = ValidationModeEnum.CREATE;
String encoded = myFhirCtx.newJsonParser().encodeResourceToString(input);
MethodOutcome outcome = myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome());
ourLog.info(ooString);
assertThat(ooString, containsString("StructureDefinition reference \\\"" + profileUri + "\\\" could not be resolved"));
try {
myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome());
ourLog.info(ooString);
assertThat(ooString, containsString("StructureDefinition reference \\\"" + profileUri + "\\\" could not be resolved"));
}
}

View File

@ -18,6 +18,7 @@ import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.hapi.validation.CachingValidationSupport;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.model.AllergyIntolerance.AllergyIntoleranceCategory;
import org.hl7.fhir.r4.model.CodeSystem.CodeSystemContentMode;
@ -43,6 +44,9 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoR4TerminologyTest.class);
@Autowired
private IHapiTerminologySvc myHapiTerminologySvc;
@Autowired
private CachingValidationSupport myCachingValidationSupport;
@After
public void after() {
@ -54,7 +58,7 @@ public class FhirResourceDaoR4TerminologyTest extends BaseJpaR4Test {
@Before
public void before() {
myDaoConfig.setMaximumExpansionSize(5000);
// my
myCachingValidationSupport.flushCaches();
}
private CodeSystem createExternalCs() {

View File

@ -77,7 +77,6 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
}
ourLog.info("Done validation");
// ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome()));
}
@Test
@ -173,11 +172,16 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
ValidationModeEnum mode = ValidationModeEnum.CREATE;
String encoded = myFhirCtx.newJsonParser().encodeResourceToString(input);
MethodOutcome outcome = myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome.getOperationOutcome());
ourLog.info(ooString);
assertThat(ooString, containsString("StructureDefinition reference \\\"" + profileUri + "\\\" could not be resolved"));
try {
myObservationDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
fail();
} catch (PreconditionFailedException e) {
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome());
ourLog.info(ooString);
assertThat(ooString, containsString("StructureDefinition reference \\\"" + profileUri + "\\\" could not be resolved"));
}
}

View File

@ -2839,7 +2839,7 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
String inputStr = myFhirCtx.newXmlParser().encodeResourceToString(input);
ourLog.info(inputStr);
HttpPost post = new HttpPost(ourServerBase + "/Patient/123/$validate");
HttpPost post = new HttpPost(ourServerBase + "/Patient/$validate");
post.setEntity(new StringEntity(inputStr, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
CloseableHttpResponse response = ourHttpClient.execute(post);

View File

@ -4087,15 +4087,11 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
IIdType id = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart() + "/$validate");
CloseableHttpResponse response = ourHttpClient.execute(get);
try {
try (CloseableHttpResponse response = ourHttpClient.execute(get)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(412, response.getStatusLine().getStatusCode());
assertThat(resp, containsString("SHALL at least contain a contact's details or a reference to an organization"));
} finally {
IOUtils.closeQuietly(response.getEntity().getContent());
response.close();
}
}

View File

@ -75,7 +75,7 @@ public class ResourceProviderQuestionnaireResponseR4Test extends BaseResourcePro
ourClient.create().resource(qr1).execute();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.toString(), containsString("Answer value must be of type string"));
assertThat(myFhirCtx.newJsonParser().encodeResourceToString(e.getOperationOutcome()), containsString("Answer value must be of type string"));
}
}
@ -98,7 +98,7 @@ public class ResourceProviderQuestionnaireResponseR4Test extends BaseResourcePro
ourClient.create().resource(qr1).execute();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.toString(), containsString("Answer value must be of type string"));
assertThat(myFhirCtx.newJsonParser().encodeResourceToString(e.getOperationOutcome()), containsString("Answer value must be of type string"));
}
}

View File

@ -181,19 +181,22 @@ public class RestfulServerUtils {
break;
}
}
switch (theRequestDetails.getRestOperationType()) {
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case HISTORY_INSTANCE:
case GET_PAGE:
if (!haveExplicitBundleElement) {
parser.setEncodeElementsAppliesToChildResourcesOnly(true);
}
break;
default:
break;
if (theRequestDetails.getRestOperationType() != null) {
switch (theRequestDetails.getRestOperationType()) {
case SEARCH_SYSTEM:
case SEARCH_TYPE:
case HISTORY_SYSTEM:
case HISTORY_TYPE:
case HISTORY_INSTANCE:
case GET_PAGE:
if (!haveExplicitBundleElement) {
parser.setEncodeElementsAppliesToChildResourcesOnly(true);
}
break;
default:
break;
}
}
parser.setEncodeElements(newElements);

View File

@ -1,39 +1,44 @@
package ca.uhn.fhir.validation;
import static org.hamcrest.Matchers.containsString;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.stringContainsInOrder;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.*;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.ExtensionDt;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu2.composite.TimingDt;
import ca.uhn.fhir.model.dstu2.resource.*;
import ca.uhn.fhir.model.dstu2.valueset.ConditionVerificationStatusEnum;
import ca.uhn.fhir.model.dstu2.valueset.ContactPointSystemEnum;
import ca.uhn.fhir.model.dstu2.valueset.NarrativeStatusEnum;
import ca.uhn.fhir.model.dstu2.valueset.UnitsOfTimeEnum;
import ca.uhn.fhir.model.primitive.DateDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.StrictErrorHandler;
import ca.uhn.fhir.parser.XmlParserDstu2Test.TestPatientFor327;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.schematron.SchematronBaseValidator;
import org.apache.commons.io.IOUtils;
import org.hamcrest.core.StringContains;
import org.junit.AfterClass;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.dstu2.composite.*;
import ca.uhn.fhir.model.dstu2.resource.*;
import ca.uhn.fhir.model.dstu2.valueset.*;
import ca.uhn.fhir.model.primitive.*;
import ca.uhn.fhir.parser.*;
import ca.uhn.fhir.parser.XmlParserDstu2Test.TestPatientFor327;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.schematron.SchematronBaseValidator;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
public class ResourceValidatorDstu2Test {
private static FhirContext ourCtx = FhirContext.forDstu2();
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ResourceValidatorDstu2Test.class);
private static FhirContext ourCtx = FhirContext.forDstu2();
private FhirValidator createFhirValidator() {
FhirValidator val = ourCtx.newValidator();
@ -60,7 +65,7 @@ public class ResourceValidatorDstu2Test {
// Put in an invalid date
IParser parser = ourCtx.newXmlParser();
parser.setParserErrorHandler(new StrictErrorHandler());
String encoded = parser.setPrettyPrint(true).encodeResourceToString(p).replace("2000-12-31", "2000-15-31");
ourLog.info(encoded);
@ -70,9 +75,9 @@ public class ResourceValidatorDstu2Test {
String resultString = parser.setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
ourLog.info(resultString);
assertEquals(2, ((OperationOutcome)result.toOperationOutcome()).getIssue().size());
assertEquals(2, ((OperationOutcome) result.toOperationOutcome()).getIssue().size());
assertThat(resultString, StringContains.containsString("cvc-pattern-valid"));
try {
parser.parseResource(encoded);
fail();
@ -80,16 +85,17 @@ public class ResourceValidatorDstu2Test {
assertEquals("DataFormatException at [[row,col {unknown-source}]: [2,4]]: Invalid attribute value \"2000-15-31\": Invalid date/time format: \"2000-15-31\"", e.getMessage());
}
}
@SuppressWarnings("deprecation")
@Test
public void testSchemaBundleValidator() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("bundle-example.json"));
String res = IOUtils.toString(ResourceValidatorDstu2Test.class.getResourceAsStream("/bundle-example.json"));
Bundle b = ourCtx.newJsonParser().parseResource(Bundle.class, res);
FhirValidator val = createFhirValidator();
val.validate(b);
ValidationResult result = val.validateWithResult(b);
assertTrue(result.isSuccessful());
MedicationOrder p = (MedicationOrder) b.getEntry().get(0).getResource();
TimingDt timing = new TimingDt();
@ -97,19 +103,17 @@ public class ResourceValidatorDstu2Test {
timing.getRepeat().setDurationUnits((UnitsOfTimeEnum) null);
p.getDosageInstructionFirstRep().setTiming(timing);
try {
val.validate(b);
fail();
} catch (ValidationFailureException e) {
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome());
ourLog.info(encoded);
assertThat(encoded, containsString("tim-1:"));
}
result = val.validateWithResult(b);
assertFalse(result.isSuccessful());
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.getOperationOutcome());
ourLog.info(encoded);
assertThat(encoded, containsString("tim-1:"));
}
@Test
public void testSchemaBundleValidatorFails() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("bundle-example.json"), StandardCharsets.UTF_8);
String res = IOUtils.toString(ResourceValidatorDstu2Test.class.getResourceAsStream("/bundle-example.json"), StandardCharsets.UTF_8);
Bundle b = ourCtx.newJsonParser().parseResource(Bundle.class, res);
@ -127,18 +131,18 @@ public class ResourceValidatorDstu2Test {
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(b));
validationResult = val.validateWithResult(b);
ourLog.info(ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(validationResult.toOperationOutcome()));
assertFalse(validationResult.isSuccessful());
String encoded = logOperationOutcome(validationResult);
assertThat(encoded, containsString("tim-1:"));
}
@Test
public void testSchemaBundleValidatorIsSuccessful() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("bundle-example.json"), StandardCharsets.UTF_8);
String res = IOUtils.toString(ResourceValidatorDstu2Test.class.getResourceAsStream("/bundle-example.json"), StandardCharsets.UTF_8);
Bundle b = ourCtx.newJsonParser().parseResource(Bundle.class, res);
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(b));
@ -181,7 +185,7 @@ public class ResourceValidatorDstu2Test {
@SuppressWarnings("deprecation")
@Test
public void testSchemaResourceValidator() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("patient-example-dicom.json"));
String res = IOUtils.toString(ResourceValidatorDstu2Test.class.getResourceAsStream("/patient-example-dicom.json"));
Patient p = ourCtx.newJsonParser().parseResource(Patient.class, res);
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(p));
@ -190,23 +194,21 @@ public class ResourceValidatorDstu2Test {
val.setValidateAgainstStandardSchema(true);
val.setValidateAgainstStandardSchematron(false);
val.validate(p);
ValidationResult result = val.validateWithResult(p);
assertTrue(result.isSuccessful());
p.getAnimal().getBreed().setText("The Breed");
try {
val.validate(p);
fail();
} catch (ValidationFailureException e) {
OperationOutcome operationOutcome = (OperationOutcome) e.getOperationOutcome();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
assertEquals(1, operationOutcome.getIssue().size());
assertThat(operationOutcome.getIssueFirstRep().getDetailsElement().getValue(), containsString("cvc-complex-type"));
}
result = val.validateWithResult(p);
assertFalse(result.isSuccessful());
OperationOutcome operationOutcome = (OperationOutcome) result.getOperationOutcome();
ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(operationOutcome));
assertEquals(1, operationOutcome.getIssue().size());
assertThat(operationOutcome.getIssueFirstRep().getDetailsElement().getValue(), containsString("cvc-complex-type"));
}
@Test
public void testSchematronResourceValidator() throws IOException {
String res = IOUtils.toString(getClass().getClassLoader().getResourceAsStream("patient-example-dicom.json"), StandardCharsets.UTF_8);
String res = IOUtils.toString(ResourceValidatorDstu2Test.class.getResourceAsStream("/patient-example-dicom.json"), StandardCharsets.UTF_8);
Patient p = ourCtx.newJsonParser().parseResource(Patient.class, res);
FhirValidator val = ourCtx.newValidator();
@ -236,35 +238,35 @@ public class ResourceValidatorDstu2Test {
@Test
public void testValidateResourceWithResourceElements() {
TestPatientFor327 patient = new TestPatientFor327();
patient.setBirthDate(new Date(), TemporalPrecisionEnum.DAY);
patient.setId("123");
patient.getText().setDiv("<div>FOO</div>");
patient.getText().setStatus(NarrativeStatusEnum.GENERATED);
patient.getLanguage().setValue("en");
patient.addUndeclaredExtension(true, "http://foo").setValue(new StringDt("MOD"));
ResourceMetadataKeyEnum.UPDATED.put(patient, new InstantDt(new Date()));
TestPatientFor327 patient = new TestPatientFor327();
patient.setBirthDate(new Date(), TemporalPrecisionEnum.DAY);
patient.setId("123");
patient.getText().setDiv("<div>FOO</div>");
patient.getText().setStatus(NarrativeStatusEnum.GENERATED);
patient.getLanguage().setValue("en");
patient.addUndeclaredExtension(true, "http://foo").setValue(new StringDt("MOD"));
ResourceMetadataKeyEnum.UPDATED.put(patient, new InstantDt(new Date()));
List<ResourceReferenceDt> conditions = new ArrayList<ResourceReferenceDt>();
Condition condition = new Condition();
condition.getPatient().setReference("Patient/123");
condition.addBodySite().setText("BODY SITE");
condition.getCode().setText("CODE");
condition.setVerificationStatus(ConditionVerificationStatusEnum.CONFIRMED);
List<ResourceReferenceDt> conditions = new ArrayList<>();
Condition condition = new Condition();
condition.getPatient().setReference("Patient/123");
condition.addBodySite().setText("BODY SITE");
condition.getCode().setText("CODE");
condition.setVerificationStatus(ConditionVerificationStatusEnum.CONFIRMED);
conditions.add(new ResourceReferenceDt(condition));
patient.setCondition(conditions);
patient.addIdentifier().setSystem("http://foo").setValue("123");
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
patient.setCondition(conditions);
patient.addIdentifier().setSystem("http://foo").setValue("123");
String encoded = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(patient);
ourLog.info(encoded);
FhirValidator val = createFhirValidator();
ValidationResult result = val.validateWithResult(encoded);
String messageString = logOperationOutcome(result);
assertTrue(result.isSuccessful());
assertThat(messageString, containsString("No issues"));
}
@ -305,13 +307,13 @@ public class ResourceValidatorDstu2Test {
FhirValidator val = ourCtx.newValidator();
val.registerValidatorModule(new SchemaBaseValidator(ourCtx));
val.registerValidatorModule(new SchematronBaseValidator(ourCtx));
ValidationResult result = val.validateWithResult(messageString);
logOperationOutcome(result);
assertTrue(result.isSuccessful());
assertThat(messageString, containsString("valueReference"));
assertThat(messageString, not(containsString("valueResource")));
}
@ -333,7 +335,7 @@ public class ResourceValidatorDstu2Test {
IParser p = FhirContext.forDstu2().newXmlParser().setPrettyPrint(true);
String messageString = p.encodeResourceToString(myPatient);
ourLog.info(messageString);
//@formatter:off
assertThat(messageString, stringContainsInOrder(
"meta",
@ -354,13 +356,13 @@ public class ResourceValidatorDstu2Test {
FhirValidator val = ourCtx.newValidator();
val.registerValidatorModule(new SchemaBaseValidator(ourCtx));
val.registerValidatorModule(new SchematronBaseValidator(ourCtx));
ValidationResult result = val.validateWithResult(messageString);
logOperationOutcome(result);
assertTrue(result.isSuccessful());
assertThat(messageString, containsString("valueReference"));
assertThat(messageString, not(containsString("valueResource")));
}

View File

@ -83,6 +83,19 @@ public class ResponseHighlightingInterceptorTest {
assertEquals("Attachment;", status.getFirstHeader("Content-Disposition").getValue());
}
@Test
public void testInvalidRequest() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/html?_elements=Patient:foo");
httpGet.addHeader("Accept", "text/html");
CloseableHttpResponse status = ourClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent(), Charsets.UTF_8);
status.close();
assertEquals(400, status.getStatusLine().getStatusCode());
assertThat(status.getFirstHeader("content-type").getValue(), containsString("text/html"));
assertThat(responseContent, containsString("Invalid _elements value"));
}
@Test
public void testBinaryReadAcceptBrowser() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Binary/foo");

View File

@ -36,6 +36,10 @@
<groupId>com.github.ben-manes.caffeine</groupId>
<artifactId>caffeine</artifactId>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
</dependency>
<!--
Optional dependencies from RI codebase

View File

@ -0,0 +1,181 @@
package org.hl7.fhir.common.hapi.validation;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.validation.IValidationContext;
import com.google.gson.*;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.r5.utils.ValidationProfileSet;
import org.hl7.fhir.r5.validation.InstanceValidator;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
public class ValidatorWrapper {
private static final Logger ourLog = LoggerFactory.getLogger(ValidatorWrapper.class);
private final DocumentBuilderFactory myDocBuilderFactory;
private IResourceValidator.BestPracticeWarningLevel myBestPracticeWarningLevel;
private boolean myAnyExtensionsAllowed;
private boolean myErrorForUnknownProfiles;
private boolean myNoTerminologyChecks;
private Collection<? extends String> myExtensionDomains;
/**
* Constructor
*/
public ValidatorWrapper() {
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
myDocBuilderFactory.setNamespaceAware(true);
}
public ValidatorWrapper setBestPracticeWarningLevel(IResourceValidator.BestPracticeWarningLevel theBestPracticeWarningLevel) {
myBestPracticeWarningLevel = theBestPracticeWarningLevel;
return this;
}
public ValidatorWrapper setAnyExtensionsAllowed(boolean theAnyExtensionsAllowed) {
myAnyExtensionsAllowed = theAnyExtensionsAllowed;
return this;
}
public ValidatorWrapper setErrorForUnknownProfiles(boolean theErrorForUnknownProfiles) {
myErrorForUnknownProfiles = theErrorForUnknownProfiles;
return this;
}
public ValidatorWrapper setNoTerminologyChecks(boolean theNoTerminologyChecks) {
myNoTerminologyChecks = theNoTerminologyChecks;
return this;
}
public ValidatorWrapper setExtensionDomains(Collection<? extends String> theExtensionDomains) {
myExtensionDomains = theExtensionDomains;
return this;
}
public List<ValidationMessage> validate(IWorkerContext theWorkerContext, IValidationContext<?> theValidationContext) {
InstanceValidator v;
FHIRPathEngine.IEvaluationContext evaluationCtx = new org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator.NullEvaluationContext();
try {
v = new InstanceValidator(theWorkerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
v.setBestPracticeWarningLevel(myBestPracticeWarningLevel);
v.setAnyExtensionsAllowed(myAnyExtensionsAllowed);
v.setResourceIdRule(IResourceValidator.IdStatus.OPTIONAL);
v.setNoTerminologyChecks(myNoTerminologyChecks);
v.setErrorForUnknownProfiles(myErrorForUnknownProfiles);
v.getExtensionDomains().addAll(myExtensionDomains);
List<ValidationMessage> messages = new ArrayList<>();
ValidationProfileSet profileSet = new ValidationProfileSet();
for (String next : theValidationContext.getOptions().getProfiles()) {
profileSet.getCanonical().add(new ValidationProfileSet.ProfileRegistration(next, true));
}
String input = theValidationContext.getResourceAsString();
EncodingEnum encoding = theValidationContext.getResourceAsStringEncoding();
if (encoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(input));
document = builder.parse(src);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();
m.setLevel(ValidationMessage.IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
messages.add(m);
return messages;
}
// Determine if meta/profiles are present...
ArrayList<String> profiles = determineIfProfilesSpecified(document);
for (String nextProfile : profiles) {
profileSet.getCanonical().add(new ValidationProfileSet.ProfileRegistration(nextProfile, true));
}
v.validate(null, messages, document, profileSet);
} else if (encoding == EncodingEnum.JSON) {
Gson gson = new GsonBuilder().create();
JsonObject json = gson.fromJson(input, JsonObject.class);
JsonObject meta = json.getAsJsonObject("meta");
if (meta != null) {
JsonElement profileElement = meta.get("profile");
if (profileElement != null && profileElement.isJsonArray()) {
JsonArray profiles = profileElement.getAsJsonArray();
for (JsonElement element : profiles) {
profileSet.getCanonical().add(new ValidationProfileSet.ProfileRegistration(element.getAsString(), true));
}
}
}
v.validate(null, messages, json, profileSet);
} else {
throw new IllegalArgumentException("Unknown encoding: " + encoding);
}
for (int i = 0; i < messages.size(); i++) {
ValidationMessage next = messages.get(i);
String message = next.getMessage();
if ("Binding has no source, so can't be checked".equals(message) ||
"ValueSet http://hl7.org/fhir/ValueSet/mimetypes not found".equals(message)) {
messages.remove(i);
i--;
}
}
return messages;
}
private String determineResourceName(Document theDocument) {
NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
return list.item(i).getLocalName();
}
}
return theDocument.getDocumentElement().getLocalName();
}
private ArrayList<String> determineIfProfilesSpecified(Document theDocument) {
ArrayList<String> profileNames = new ArrayList<>();
NodeList list = theDocument.getChildNodes().item(0).getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i).getNodeName().compareToIgnoreCase("meta") == 0) {
NodeList metaList = list.item(i).getChildNodes();
for (int j = 0; j < metaList.getLength(); j++) {
if (metaList.item(j).getNodeName().compareToIgnoreCase("profile") == 0) {
profileNames.add(metaList.item(j).getAttributes().item(0).getNodeValue());
}
}
break;
}
}
return profileNames;
}
}

View File

@ -3,18 +3,24 @@ package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.FhirContext;
import com.github.benmanes.caffeine.cache.Cache;
import com.github.benmanes.caffeine.cache.Caffeine;
import org.checkerframework.checker.nullness.qual.Nullable;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.CodeSystem;
import org.hl7.fhir.dstu3.model.StructureDefinition;
import org.hl7.fhir.dstu3.model.ValueSet;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@SuppressWarnings("unchecked")
public class CachingValidationSupport implements IValidationSupport {
private static final Logger ourLog = LoggerFactory.getLogger(CachingValidationSupport.class);
private final IValidationSupport myWrap;
private final Cache<String, Object> myCache;
@ -30,13 +36,13 @@ public class CachingValidationSupport implements IValidationSupport {
@Override
public List<IBaseResource> fetchAllConformanceResources(FhirContext theContext) {
return (List<IBaseResource>) myCache.get("fetchAllConformanceResources",
return loadFromCache("fetchAllConformanceResources",
t -> myWrap.fetchAllConformanceResources(theContext));
}
@Override
public List<StructureDefinition> fetchAllStructureDefinitions(FhirContext theContext) {
return (List<StructureDefinition>) myCache.get("fetchAllStructureDefinitions",
return loadFromCache("fetchAllStructureDefinitions",
t -> myWrap.fetchAllStructureDefinitions(theContext));
}
@ -52,7 +58,8 @@ public class CachingValidationSupport implements IValidationSupport {
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
return myWrap.fetchResource(theContext, theClass, theUri);
return loadFromCache("fetchResource " + theClass.getName() + " " + theUri,
t -> myWrap.fetchResource(theContext, theClass, theUri));
}
@Override
@ -79,4 +86,19 @@ public class CachingValidationSupport implements IValidationSupport {
public StructureDefinition generateSnapshot(StructureDefinition theInput, String theUrl, String theName) {
return myWrap.generateSnapshot(theInput, theUrl, theName);
}
@Nullable
private <T> T loadFromCache(String theKey, Function<String, T> theLoader) {
ourLog.trace("Loading: {}", theKey);
Function<String, Optional<T>> loaderWrapper = key -> {
ourLog.trace("Loading {} from cache", theKey);
return Optional.ofNullable(theLoader.apply(theKey));
};
Optional<T> result = (Optional<T>) myCache.get(theKey, loaderWrapper);
return result.orElse(null);
}
public void flushCaches() {
myCache.invalidateAll();
}
}

View File

@ -1,21 +1,19 @@
package org.hl7.fhir.dstu3.hapi.validation;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.gson.*;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService;
import org.hl7.fhir.common.hapi.validation.ValidatorWrapper;
import org.hl7.fhir.convertors.VersionConvertor_30_50;
import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
@ -27,12 +25,9 @@ import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.formats.ParserType;
import org.hl7.fhir.r5.terminologies.ValueSetExpander;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.INarrativeGenerator;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.IResourceValidator.IdStatus;
import org.hl7.fhir.r5.validation.InstanceValidator;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.validation.ValidationMessage;
@ -40,11 +35,10 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.*;
@ -64,7 +58,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
private volatile WorkerContextWrapper myWrappedWorkerContext;
private boolean errorForUnknownProfiles;
private List<String> extensionDomains = Collections.emptyList();
private List<String> myExtensionDomains = Collections.emptyList();
/**
* Constructor
@ -90,8 +84,8 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
@ -101,7 +95,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(List<String> extensionDomains) {
this.extensionDomains = extensionDomains;
this.myExtensionDomains = extensionDomains;
return this;
}
@ -109,8 +103,8 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
@ -120,7 +114,7 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) {
this.extensionDomains = Arrays.asList(extensionDomains);
this.myExtensionDomains = Arrays.asList(extensionDomains);
return this;
}
@ -225,14 +219,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
public boolean isAnyExtensionsAllowed() {
return myAnyExtensionsAllowed;
}
public boolean isErrorForUnknownProfiles() {
return errorForUnknownProfiles;
}
public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
this.errorForUnknownProfiles = errorForUnknownProfiles;
}
/**
* If set to {@literal true} (default is true) extensions which are not known to the
@ -243,13 +229,21 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myAnyExtensionsAllowed = theAnyExtensionsAllowed;
}
public boolean isErrorForUnknownProfiles() {
return errorForUnknownProfiles;
}
public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
this.errorForUnknownProfiles = errorForUnknownProfiles;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public boolean isNoTerminologyChecks() {
return noTerminologyChecks;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
@ -261,131 +255,33 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myStructureDefintion = theStructureDefintion;
}
protected List<ValidationMessage> validate(final FhirContext theCtx, String theInput, EncodingEnum theEncoding) {
private List<String> getExtensionDomains() {
return myExtensionDomains;
}
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) {
final FhirContext ctx = theValidationCtx.getFhirContext();
WorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
if (wrappedWorkerContext == null) {
HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport);
HapiWorkerContext workerContext = new HapiWorkerContext(ctx, myValidationSupport);
wrappedWorkerContext = new WorkerContextWrapper(workerContext);
}
myWrappedWorkerContext = wrappedWorkerContext;
InstanceValidator v;
FHIRPathEngine.IEvaluationContext evaluationCtx = new org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator.NullEvaluationContext();
try {
v = new InstanceValidator(wrappedWorkerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
return new ValidatorWrapper()
.setAnyExtensionsAllowed(isAnyExtensionsAllowed())
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
.setExtensionDomains(getExtensionDomains())
.setNoTerminologyChecks(isNoTerminologyChecks())
.validate(wrappedWorkerContext, theValidationCtx);
v.setBestPracticeWarningLevel(getBestPracticeWarningLevel());
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks());
v.setErrorForUnknownProfiles(isErrorForUnknownProfiles());
v.getExtensionDomains().addAll(extensionDomains);
List<ValidationMessage> messages = new ArrayList<>();
if (theEncoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();
m.setLevel(IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
return Collections.singletonList(m);
}
// Determine if meta/profiles are present...
ArrayList<String> resourceNames = determineIfProfilesSpecified(document);
if (resourceNames.isEmpty()) {
resourceNames.add(determineResourceName(document));
}
for (String resourceName : resourceNames) {
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, document, profile.getUrl());
} catch (Exception e) {
ourLog.error("Failure during validation", e);
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
} else {
profile = findStructureDefinitionForResourceName(theCtx, determineResourceName(document));
if (profile != null) {
try {
v.validate(null, messages, document, profile.getUrl());
} catch (Exception e) {
ourLog.error("Failure during validation", e);
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
}
}
} else if (theEncoding == EncodingEnum.JSON) {
Gson gson = new GsonBuilder().create();
JsonObject json = gson.fromJson(theInput, JsonObject.class);
ArrayList<String> resourceNames = new ArrayList<String>();
JsonArray profiles = null;
try {
profiles = json.getAsJsonObject("meta").getAsJsonArray("profile");
for (JsonElement element : profiles) {
resourceNames.add(element.getAsString());
}
} catch (Exception e) {
resourceNames.add(json.get("resourceType").getAsString());
}
for (String resourceName : resourceNames) {
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, json, profile.getUrl());
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
} else {
profile = findStructureDefinitionForResourceName(theCtx, json.get("resourceType").getAsString());
if (profile != null) {
try {
v.validate(null, messages, json, profile.getUrl());
} catch (Exception e) {
ourLog.error("Failure during validation", e);
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
}
}
} else {
throw new IllegalArgumentException("Unknown encoding: " + theEncoding);
}
for (int i = 0; i < messages.size(); i++) {
ValidationMessage next = messages.get(i);
String message = next.getMessage();
if ("Binding has no source, so can't be checked".equals(message) ||
"ValueSet http://hl7.org/fhir/ValueSet/mimetypes not found".equals(message)) {
messages.remove(i);
i--;
}
}
return messages;
}
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
}
private class WorkerContextWrapper implements IWorkerContext {
private static class WorkerContextWrapper implements IWorkerContext {
private final HapiWorkerContext myWrap;
private final VersionConvertor_30_50 myConverter;
private volatile List<org.hl7.fhir.r5.model.StructureDefinition> myAllStructures;
@ -404,36 +300,36 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
myFetchResourceCache = Caffeine.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.maximumSize(10000)
.build(new CacheLoader<ResourceKey, org.hl7.fhir.r5.model.Resource>() {
@Override
public org.hl7.fhir.r5.model.Resource load(ResourceKey key) throws Exception {
Resource fetched;
switch (key.getResourceName()) {
case "StructureDefinition":
fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri());
break;
case "ValueSet":
fetched = myWrap.fetchResource(ValueSet.class, key.getUri());
break;
case "CodeSystem":
fetched = myWrap.fetchResource(CodeSystem.class, key.getUri());
break;
case "Questionnaire":
fetched = myWrap.fetchResource(Questionnaire.class, key.getUri());
break;
default:
throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName());
}
.build(key -> {
Resource fetched;
switch (key.getResourceName()) {
case "StructureDefinition":
fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri());
break;
case "ValueSet":
fetched = myWrap.fetchResource(ValueSet.class, key.getUri());
break;
case "CodeSystem":
fetched = myWrap.fetchResource(CodeSystem.class, key.getUri());
break;
case "Questionnaire":
fetched = myWrap.fetchResource(Questionnaire.class, key.getUri());
break;
case "ImplementationGuide":
fetched = myWrap.fetchResource(ImplementationGuide.class, key.getUri());
break;
default:
throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName());
}
if (fetched == null) {
return null;
}
if (fetched == null) {
return null;
}
try {
return VersionConvertor_30_50.convertResource(fetched, true);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
try {
return VersionConvertor_30_50.convertResource(fetched, true);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
});
}
@ -487,19 +383,28 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new UnsupportedOperationException();
}
private ValidationResult convertValidationResult(org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult theResult) {
IssueSeverity issueSeverity = theResult.getSeverity();
String message = theResult.getMessage();
org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null;
if (theResult.asConceptDefinition() != null) {
try {
conceptDefinition = VersionConvertor_30_50.convertConceptDefinitionComponent(theResult.asConceptDefinition());
} catch (FHIRException e) {
throw new InternalErrorException(e);
@Nonnull
private ValidationResult convertValidationResult(@Nullable org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult theResult) {
ValidationResult retVal = null;
if (theResult != null) {
IssueSeverity issueSeverity = theResult.getSeverity();
String message = theResult.getMessage();
org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null;
if (theResult.asConceptDefinition() != null) {
try {
conceptDefinition = VersionConvertor_30_50.convertConceptDefinitionComponent(theResult.asConceptDefinition());
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
retVal = new ValidationResult(issueSeverity, message, conceptDefinition);
}
if (retVal == null) {
retVal = new ValidationResult(IssueSeverity.ERROR, "Validation failed");
}
ValidationResult retVal = new ValidationResult(issueSeverity, message, conceptDefinition);
return retVal;
}
@ -655,11 +560,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
}
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override
public List<String> getTypeNames() {
return myWrap.getTypeNames();
@ -670,6 +570,11 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new UnsupportedOperationException();
}
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override
public String getVersion() {
return myWrap.getVersion();
@ -716,13 +621,13 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
}
@Override
public void setLogger(ILoggingService logger) {
throw new UnsupportedOperationException();
public ILoggingService getLogger() {
return null;
}
@Override
public ILoggingService getLogger() {
return null;
public void setLogger(ILoggingService logger) {
throw new UnsupportedOperationException();
}
@Override
@ -743,7 +648,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
@Override
public ValidationResult validateCode(TerminologyServiceOptions theOptions, String system, String code, String display) {
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(system, code, display);
// TODO: converted code might be null -> NPE
return convertValidationResult(result);
}
@ -794,7 +698,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new InternalErrorException(e);
}
// TODO: converted code might be null -> NPE
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result);
}
@ -815,7 +718,6 @@ public class FhirInstanceValidator extends BaseValidatorBridge implements IValid
throw new InternalErrorException(e);
}
// TODO: converted code might be null -> NPE
org.hl7.fhir.dstu3.context.IWorkerContext.ValidationResult result = myWrap.validateCode(convertedCode, convertedVs);
return convertValidationResult(result);
}

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.r4.model.ValueSet;
import org.hl7.fhir.r4.terminologies.ValueSetExpander;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@ -59,7 +60,8 @@ public class CachingValidationSupport implements IValidationSupport {
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
return myWrap.fetchResource(theContext, theClass, theUri);
return loadFromCache("fetchResource " + theClass.getName() + " " + theUri,
t -> myWrap.fetchResource(theContext, theClass, theUri));
}
@Override
@ -92,6 +94,12 @@ public class CachingValidationSupport implements IValidationSupport {
@Nullable
private <T> T loadFromCache(String theKey, Function<String, T> theLoader) {
return (T) myCache.get(theKey, theLoader);
Function<String, Optional<T>> loaderWrapper = key -> Optional.ofNullable(theLoader.apply(theKey));
Optional<T> result = (Optional<T>) myCache.get(theKey, loaderWrapper);
return result.orElse(null);
}
public void flushCaches() {
myCache.invalidateAll();
}
}

View File

@ -1,38 +1,31 @@
package org.hl7.fhir.r4.hapi.validation;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.gson.*;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService;
import org.hl7.fhir.common.hapi.validation.ValidatorWrapper;
import org.hl7.fhir.convertors.VersionConvertor_40_50;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
import org.hl7.fhir.r5.context.IWorkerContext;
import org.hl7.fhir.r5.formats.IParser;
import org.hl7.fhir.r5.formats.ParserType;
import org.hl7.fhir.r5.terminologies.ValueSetExpander;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.INarrativeGenerator;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.IResourceValidator.IdStatus;
import org.hl7.fhir.r5.validation.InstanceValidator;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.validation.ValidationMessage;
@ -40,25 +33,17 @@ import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings({"PackageAccessibility", "Duplicates"})
public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseValidatorBridge implements IValidatorModule {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
private boolean myAnyExtensionsAllowed = true;
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
private volatile WorkerContextWrapper myWrappedWorkerContext;
@ -81,8 +66,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
* @param theValidationSupport The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
myDocBuilderFactory.setNamespaceAware(true);
myValidationSupport = theValidationSupport;
}
@ -90,8 +73,8 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
@ -109,8 +92,8 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
@ -120,53 +103,10 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) {
this.extensionDomains = Arrays.asList(extensionDomains);
setCustomExtensionDomains(Arrays.asList(extensionDomains));
return this;
}
private String determineResourceName(Document theDocument) {
NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
return list.item(i).getLocalName();
}
}
return theDocument.getDocumentElement().getLocalName();
}
private ArrayList<String> determineIfProfilesSpecified(Document theDocument) {
ArrayList<String> profileNames = new ArrayList<>();
NodeList list = theDocument.getChildNodes().item(0).getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i).getNodeName().compareToIgnoreCase("meta") == 0) {
NodeList metaList = list.item(i).getChildNodes();
for (int j = 0; j < metaList.getLength(); j++) {
if (metaList.item(j).getNodeName().compareToIgnoreCase("profile") == 0) {
profileNames.add(metaList.item(j).getAttributes().item(0).getNodeValue());
}
}
break;
}
}
return profileNames;
}
private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) {
String sdName = null;
try {
// Test if a URL was passed in specifying the structure definition and test if "StructureDefinition" is part of the URL
URL testIfUrl = new URL(resourceName);
sdName = resourceName;
} catch (MalformedURLException e) {
sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName;
}
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName);
return profile;
}
public void flushCaches() {
myWrappedWorkerContext = null;
}
/**
* Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}).
@ -225,14 +165,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
public boolean isAnyExtensionsAllowed() {
return myAnyExtensionsAllowed;
}
public boolean isErrorForUnknownProfiles() {
return errorForUnknownProfiles;
}
public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
this.errorForUnknownProfiles = errorForUnknownProfiles;
}
/**
* If set to {@literal true} (default is true) extensions which are not known to the
@ -243,13 +175,21 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
myAnyExtensionsAllowed = theAnyExtensionsAllowed;
}
public boolean isErrorForUnknownProfiles() {
return errorForUnknownProfiles;
}
public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
this.errorForUnknownProfiles = errorForUnknownProfiles;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public boolean isNoTerminologyChecks() {
return noTerminologyChecks;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
@ -257,144 +197,38 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
noTerminologyChecks = theNoTerminologyChecks;
}
public void setStructureDefintion(StructureDefinition theStructureDefintion) {
myStructureDefintion = theStructureDefintion;
}
protected List<ValidationMessage> validate(final FhirContext theCtx, String theInput, EncodingEnum theEncoding) {
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) {
WorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
if (wrappedWorkerContext == null) {
HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport);
HapiWorkerContext workerContext = new HapiWorkerContext(theValidationCtx.getFhirContext(), myValidationSupport);
wrappedWorkerContext = new WorkerContextWrapper(workerContext);
}
myWrappedWorkerContext = wrappedWorkerContext;
InstanceValidator v;
FHIRPathEngine.IEvaluationContext evaluationCtx = new org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator.NullEvaluationContext();
try {
v = new InstanceValidator(wrappedWorkerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
v.setBestPracticeWarningLevel(getBestPracticeWarningLevel());
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks());
v.setErrorForUnknownProfiles(isErrorForUnknownProfiles());
v.getExtensionDomains().addAll(extensionDomains);
List<ValidationMessage> messages = new ArrayList<>();
if (theEncoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();
m.setLevel(IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
return Collections.singletonList(m);
}
// Determine if meta/profiles are present...
ArrayList<String> resourceNames = determineIfProfilesSpecified(document);
if (resourceNames.isEmpty()) {
resourceNames.add(determineResourceName(document));
}
for (String resourceName : resourceNames) {
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, document, profile.getUrl());
} catch (Exception e) {
ourLog.error("Failure during validation", e);
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
} else {
profile = findStructureDefinitionForResourceName(theCtx, determineResourceName(document));
if (profile != null) {
try {
v.validate(null, messages, document, profile.getUrl());
} catch (Exception e) {
ourLog.error("Failure during validation", e);
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
}
}
} else if (theEncoding == EncodingEnum.JSON) {
Gson gson = new GsonBuilder().create();
JsonObject json = gson.fromJson(theInput, JsonObject.class);
ArrayList<String> resourceNames = new ArrayList<String>();
JsonArray profiles = null;
try {
profiles = json.getAsJsonObject("meta").getAsJsonArray("profile");
for (JsonElement element : profiles) {
resourceNames.add(element.getAsString());
}
} catch (Exception e) {
resourceNames.add(json.get("resourceType").getAsString());
}
for (String resourceName : resourceNames) {
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, json, profile.getUrl());
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
} else {
profile = findStructureDefinitionForResourceName(theCtx, json.get("resourceType").getAsString());
if (profile != null) {
try {
v.validate(null, messages, json, profile.getUrl());
} catch (Exception e) {
ourLog.error("Failure during validation", e);
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
}
}
} else {
throw new IllegalArgumentException("Unknown encoding: " + theEncoding);
}
for (int i = 0; i < messages.size(); i++) {
ValidationMessage next = messages.get(i);
String message = next.getMessage();
if ("Binding has no source, so can't be checked".equals(message) ||
"ValueSet http://hl7.org/fhir/ValueSet/mimetypes not found".equals(message)) {
messages.remove(i);
i--;
}
}
return messages;
return new ValidatorWrapper()
.setAnyExtensionsAllowed(isAnyExtensionsAllowed())
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
.setExtensionDomains(getExtensionDomains())
.setNoTerminologyChecks(isNoTerminologyChecks())
.validate(wrappedWorkerContext, theValidationCtx);
}
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
private List<String> getExtensionDomains() {
return extensionDomains;
}
private class WorkerContextWrapper implements IWorkerContext {
private static class WorkerContextWrapper implements IWorkerContext {
private final HapiWorkerContext myWrap;
private final VersionConvertor_40_50 myConverter;
private volatile List<org.hl7.fhir.r5.model.StructureDefinition> myAllStructures;
private LoadingCache<ResourceKey, org.hl7.fhir.r5.model.Resource> myFetchResourceCache;
private org.hl7.fhir.r5.model.Parameters myExpansionProfile;
WorkerContextWrapper(HapiWorkerContext theWorkerContext) {
myWrap = theWorkerContext;
myConverter = new VersionConvertor_40_50();
long timeoutMillis = 10 * DateUtils.MILLIS_PER_SECOND;
if (System.getProperties().containsKey(Constants.TEST_SYSTEM_PROP_VALIDATION_RESOURCE_CACHES_MS)) {
@ -404,36 +238,36 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
myFetchResourceCache = Caffeine.newBuilder()
.expireAfterWrite(timeoutMillis, TimeUnit.MILLISECONDS)
.maximumSize(10000)
.build(new CacheLoader<ResourceKey, org.hl7.fhir.r5.model.Resource>() {
@Override
public org.hl7.fhir.r5.model.Resource load(ResourceKey key) throws Exception {
Resource fetched;
switch (key.getResourceName()) {
case "StructureDefinition":
fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri());
break;
case "ValueSet":
fetched = myWrap.fetchResource(ValueSet.class, key.getUri());
break;
case "CodeSystem":
fetched = myWrap.fetchResource(CodeSystem.class, key.getUri());
break;
case "Questionnaire":
fetched = myWrap.fetchResource(Questionnaire.class, key.getUri());
break;
default:
throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName());
}
.build(key -> {
Resource fetched;
switch (key.getResourceName()) {
case "StructureDefinition":
fetched = myWrap.fetchResource(StructureDefinition.class, key.getUri());
break;
case "ValueSet":
fetched = myWrap.fetchResource(ValueSet.class, key.getUri());
break;
case "CodeSystem":
fetched = myWrap.fetchResource(CodeSystem.class, key.getUri());
break;
case "Questionnaire":
fetched = myWrap.fetchResource(Questionnaire.class, key.getUri());
break;
case "ImplementationGuide":
fetched = myWrap.fetchResource(ImplementationGuide.class, key.getUri());
break;
default:
throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName());
}
if (fetched == null) {
return null;
}
if (fetched == null) {
return null;
}
try {
return VersionConvertor_40_50.convertResource(fetched);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
try {
return VersionConvertor_40_50.convertResource(fetched);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
});
}
@ -487,19 +321,28 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
throw new UnsupportedOperationException();
}
private ValidationResult convertValidationResult(org.hl7.fhir.r4.context.IWorkerContext.ValidationResult theResult) {
IssueSeverity issueSeverity = theResult.getSeverity();
String message = theResult.getMessage();
org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null;
if (theResult.asConceptDefinition() != null) {
try {
conceptDefinition = org.hl7.fhir.convertors.conv40_50.CodeSystem.convertConceptDefinitionComponent(theResult.asConceptDefinition());
} catch (FHIRException e) {
throw new InternalErrorException(e);
@Nonnull
private ValidationResult convertValidationResult(@Nullable org.hl7.fhir.r4.context.IWorkerContext.ValidationResult theResult) {
ValidationResult retVal = null;
if (theResult != null) {
IssueSeverity issueSeverity = theResult.getSeverity();
String message = theResult.getMessage();
org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null;
if (theResult.asConceptDefinition() != null) {
try {
conceptDefinition = org.hl7.fhir.convertors.conv40_50.CodeSystem.convertConceptDefinitionComponent(theResult.asConceptDefinition());
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
retVal = new ValidationResult(issueSeverity, message, conceptDefinition);
}
if (retVal == null) {
retVal = new ValidationResult(IssueSeverity.ERROR, "Validation failed");
}
ValidationResult retVal = new ValidationResult(issueSeverity, message, conceptDefinition);
return retVal;
}
@ -554,8 +397,7 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
}
}
ValueSetExpander.ValueSetExpansionOutcome outcome = new ValueSetExpander.ValueSetExpansionOutcome(valueSetExpansion);
return outcome;
return new ValueSetExpander.ValueSetExpansionOutcome(valueSetExpansion);
}
@Override
@ -605,10 +447,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
return myWrap.getAbbreviation(name);
}
public VersionConvertor_40_50 getConverter() {
return myConverter;
}
@Override
public INarrativeGenerator getNarrativeGenerator(String prefix, String basePath) {
throw new UnsupportedOperationException();
@ -654,11 +492,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
}
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override
public List<String> getTypeNames() {
return myWrap.getTypeNames();
@ -669,6 +502,11 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
throw new UnsupportedOperationException();
}
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override
public String getVersion() {
return myWrap.getVersion();
@ -715,13 +553,13 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
}
@Override
public void setLogger(ILoggingService logger) {
throw new UnsupportedOperationException();
public ILoggingService getLogger() {
return null;
}
@Override
public ILoggingService getLogger() {
return null;
public void setLogger(ILoggingService logger) {
throw new UnsupportedOperationException();
}
@Override
@ -742,7 +580,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
@Override
public ValidationResult validateCode(TerminologyServiceOptions theOptions, String system, String code, String display) {
org.hl7.fhir.r4.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, system, code, display);
// TODO: converted code might be null -> NPE
return convertValidationResult(result);
}
@ -793,7 +630,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
throw new InternalErrorException(e);
}
// TODO: converted code might be null -> NPE
org.hl7.fhir.r4.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, convertedCode, convertedVs);
return convertValidationResult(result);
}
@ -814,7 +650,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r4.hapi.validation.BaseV
throw new InternalErrorException(e);
}
// TODO: converted code might be null -> NPE
org.hl7.fhir.r4.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, convertedCode, convertedVs);
return convertValidationResult(result);
}

View File

@ -12,6 +12,7 @@ import org.hl7.fhir.r5.model.ValueSet;
import org.hl7.fhir.r5.terminologies.ValueSetExpander;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
@ -59,7 +60,8 @@ public class CachingValidationSupport implements IValidationSupport {
@Override
public <T extends IBaseResource> T fetchResource(FhirContext theContext, Class<T> theClass, String theUri) {
return myWrap.fetchResource(theContext, theClass, theUri);
return loadFromCache("fetchResource " + theClass.getName() + " " + theUri,
t -> myWrap.fetchResource(theContext, theClass, theUri));
}
@Override
@ -92,6 +94,8 @@ public class CachingValidationSupport implements IValidationSupport {
@Nullable
private <T> T loadFromCache(String theKey, Function<String, T> theLoader) {
return (T) myCache.get(theKey, theLoader);
Function<String, Optional<T>> loaderWrapper = key -> Optional.ofNullable(theLoader.apply(theKey));
Optional<T> result = (Optional<T>) myCache.get(theKey, loaderWrapper);
return result.orElse(null);
}
}

View File

@ -1,21 +1,18 @@
package org.hl7.fhir.r5.hapi.validation;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.validation.IValidationContext;
import ca.uhn.fhir.validation.IValidatorModule;
import com.github.benmanes.caffeine.cache.CacheLoader;
import com.github.benmanes.caffeine.cache.Caffeine;
import com.github.benmanes.caffeine.cache.LoadingCache;
import com.google.gson.*;
import org.apache.commons.lang3.Validate;
import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.time.DateUtils;
import org.fhir.ucum.UcumService;
import org.hl7.fhir.common.hapi.validation.ValidatorWrapper;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.exceptions.TerminologyServiceException;
@ -27,44 +24,31 @@ import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.terminologies.ValueSetExpander;
import org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome;
import org.hl7.fhir.r5.utils.FHIRPathEngine;
import org.hl7.fhir.r5.utils.INarrativeGenerator;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.r5.utils.IResourceValidator.BestPracticeWarningLevel;
import org.hl7.fhir.r5.utils.IResourceValidator.IdStatus;
import org.hl7.fhir.r5.validation.InstanceValidator;
import org.hl7.fhir.utilities.TerminologyServiceOptions;
import org.hl7.fhir.utilities.TranslationServices;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.InputSource;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import java.io.StringReader;
import java.net.MalformedURLException;
import java.net.URL;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.*;
import java.util.concurrent.TimeUnit;
@SuppressWarnings({"PackageAccessibility", "Duplicates"})
public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseValidatorBridge implements IValidatorModule {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirInstanceValidator.class);
private boolean myAnyExtensionsAllowed = true;
private BestPracticeWarningLevel myBestPracticeWarningLevel;
private DocumentBuilderFactory myDocBuilderFactory;
private StructureDefinition myStructureDefintion;
private IValidationSupport myValidationSupport;
private boolean noTerminologyChecks = false;
private volatile WorkerContextWrapper myWrappedWorkerContext;
private boolean errorForUnknownProfiles;
private List<String> extensionDomains = Collections.emptyList();
private List<String> myExtensionDomains = Collections.emptyList();
/**
* Constructor
@ -81,8 +65,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
* @param theValidationSupport The validation support
*/
public FhirInstanceValidator(IValidationSupport theValidationSupport) {
myDocBuilderFactory = DocumentBuilderFactory.newInstance();
myDocBuilderFactory.setNamespaceAware(true);
myValidationSupport = theValidationSupport;
}
@ -90,8 +72,8 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
@ -101,7 +83,7 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(List<String> extensionDomains) {
this.extensionDomains = extensionDomains;
this.myExtensionDomains = extensionDomains;
return this;
}
@ -109,8 +91,8 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
* Every element in a resource or data type includes an optional <it>extension</it> child element
* which is identified by it's {@code url attribute}. There exists a number of predefined
* extension urls or extension domains:<ul>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* <li>any url which contains {@code example.org}, {@code nema.org}, or {@code acme.com}.</li>
* <li>any url which starts with {@code http://hl7.org/fhir/StructureDefinition/}.</li>
* </ul>
* It is possible to extend this list of known extension by defining custom extensions:
* Any url which starts which one of the elements in the list of custom extension domains is
@ -120,54 +102,10 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
* </p>
*/
public FhirInstanceValidator setCustomExtensionDomains(String... extensionDomains) {
this.extensionDomains = Arrays.asList(extensionDomains);
this.myExtensionDomains = Arrays.asList(extensionDomains);
return this;
}
private String determineResourceName(Document theDocument) {
NodeList list = theDocument.getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i) instanceof Element) {
return list.item(i).getLocalName();
}
}
return theDocument.getDocumentElement().getLocalName();
}
private ArrayList<String> determineIfProfilesSpecified(Document theDocument) {
ArrayList<String> profileNames = new ArrayList<String>();
NodeList list = theDocument.getChildNodes().item(0).getChildNodes();
for (int i = 0; i < list.getLength(); i++) {
if (list.item(i).getNodeName().compareToIgnoreCase("meta") == 0) {
NodeList metaList = list.item(i).getChildNodes();
for (int j = 0; j < metaList.getLength(); j++) {
if (metaList.item(j).getNodeName().compareToIgnoreCase("profile") == 0) {
profileNames.add(metaList.item(j).getAttributes().item(0).getNodeValue());
}
}
break;
}
}
return profileNames;
}
private StructureDefinition findStructureDefinitionForResourceName(final FhirContext theCtx, String resourceName) {
String sdName = null;
try {
// Test if a URL was passed in specifying the structure definition and test if "StructureDefinition" is part of the URL
URL testIfUrl = new URL(resourceName);
sdName = resourceName;
} catch (MalformedURLException e) {
sdName = "http://hl7.org/fhir/StructureDefinition/" + resourceName;
}
StructureDefinition profile = myStructureDefintion != null ? myStructureDefintion : myValidationSupport.fetchStructureDefinition(theCtx, sdName);
return profile;
}
public void flushCaches() {
myWrappedWorkerContext = null;
}
/**
* Returns the "best practice" warning level (default is {@link BestPracticeWarningLevel#Hint}).
* <p>
@ -225,14 +163,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
public boolean isAnyExtensionsAllowed() {
return myAnyExtensionsAllowed;
}
public boolean isErrorForUnknownProfiles() {
return errorForUnknownProfiles;
}
public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
this.errorForUnknownProfiles = errorForUnknownProfiles;
}
/**
* If set to {@literal true} (default is true) extensions which are not known to the
@ -243,13 +173,21 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
myAnyExtensionsAllowed = theAnyExtensionsAllowed;
}
public boolean isErrorForUnknownProfiles() {
return errorForUnknownProfiles;
}
public void setErrorForUnknownProfiles(boolean errorForUnknownProfiles) {
this.errorForUnknownProfiles = errorForUnknownProfiles;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
public boolean isNoTerminologyChecks() {
return noTerminologyChecks;
}
/**
* If set to {@literal true} (default is false) the valueSet will not be validate
*/
@ -257,135 +195,30 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
noTerminologyChecks = theNoTerminologyChecks;
}
public void setStructureDefintion(StructureDefinition theStructureDefintion) {
myStructureDefintion = theStructureDefintion;
public List<String> getExtensionDomains() {
return myExtensionDomains;
}
protected List<ValidationMessage> validate(final FhirContext theCtx, String theInput, EncodingEnum theEncoding) {
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theValidationCtx) {
WorkerContextWrapper wrappedWorkerContext = myWrappedWorkerContext;
if (wrappedWorkerContext == null) {
HapiWorkerContext workerContext = new HapiWorkerContext(theCtx, myValidationSupport);
HapiWorkerContext workerContext = new HapiWorkerContext(theValidationCtx.getFhirContext(), myValidationSupport);
wrappedWorkerContext = new WorkerContextWrapper(workerContext);
}
myWrappedWorkerContext = wrappedWorkerContext;
InstanceValidator v;
FHIRPathEngine.IEvaluationContext evaluationCtx = new org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator.NullEvaluationContext();
try {
v = new InstanceValidator(wrappedWorkerContext, evaluationCtx);
} catch (Exception e) {
throw new ConfigurationException(e);
}
v.setBestPracticeWarningLevel(getBestPracticeWarningLevel());
v.setAnyExtensionsAllowed(isAnyExtensionsAllowed());
v.setResourceIdRule(IdStatus.OPTIONAL);
v.setNoTerminologyChecks(isNoTerminologyChecks());
v.setErrorForUnknownProfiles(isErrorForUnknownProfiles());
v.getExtensionDomains().addAll(extensionDomains);
List<ValidationMessage> messages = new ArrayList<>();
if (theEncoding == EncodingEnum.XML) {
Document document;
try {
DocumentBuilder builder = myDocBuilderFactory.newDocumentBuilder();
InputSource src = new InputSource(new StringReader(theInput));
document = builder.parse(src);
} catch (Exception e2) {
ourLog.error("Failure to parse XML input", e2);
ValidationMessage m = new ValidationMessage();
m.setLevel(IssueSeverity.FATAL);
m.setMessage("Failed to parse input, it does not appear to be valid XML:" + e2.getMessage());
return Collections.singletonList(m);
}
// Determine if meta/profiles are present...
ArrayList<String> resourceNames = determineIfProfilesSpecified(document);
if (resourceNames.isEmpty()) {
resourceNames.add(determineResourceName(document));
}
for (String resourceName : resourceNames) {
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, document, profile.getUrl());
} catch (Exception e) {
ourLog.error("Failure during validation", e);
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
} else {
profile = findStructureDefinitionForResourceName(theCtx, determineResourceName(document));
if (profile != null) {
try {
v.validate(null, messages, document, profile.getUrl());
} catch (Exception e) {
ourLog.error("Failure during validation", e);
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
}
}
} else if (theEncoding == EncodingEnum.JSON) {
Gson gson = new GsonBuilder().create();
JsonObject json = gson.fromJson(theInput, JsonObject.class);
ArrayList<String> resourceNames = new ArrayList<String>();
JsonArray profiles = null;
try {
profiles = json.getAsJsonObject("meta").getAsJsonArray("profile");
for (JsonElement element : profiles) {
resourceNames.add(element.getAsString());
}
} catch (Exception e) {
resourceNames.add(json.get("resourceType").getAsString());
}
for (String resourceName : resourceNames) {
StructureDefinition profile = findStructureDefinitionForResourceName(theCtx, resourceName);
if (profile != null) {
try {
v.validate(null, messages, json, profile.getUrl());
} catch (Exception e) {
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
} else {
profile = findStructureDefinitionForResourceName(theCtx, json.get("resourceType").getAsString());
if (profile != null) {
try {
v.validate(null, messages, json, profile.getUrl());
} catch (Exception e) {
ourLog.error("Failure during validation", e);
throw new InternalErrorException("Unexpected failure while validating resource", e);
}
}
}
}
} else {
throw new IllegalArgumentException("Unknown encoding: " + theEncoding);
}
for (int i = 0; i < messages.size(); i++) {
ValidationMessage next = messages.get(i);
String message = next.getMessage();
if ("Binding has no source, so can't be checked".equals(message) ||
"ValueSet http://hl7.org/fhir/ValueSet/mimetypes not found".equals(message)) {
messages.remove(i);
i--;
}
}
return messages;
}
@Override
protected List<ValidationMessage> validate(IValidationContext<?> theCtx) {
return validate(theCtx.getFhirContext(), theCtx.getResourceAsString(), theCtx.getResourceAsStringEncoding());
return new ValidatorWrapper()
.setAnyExtensionsAllowed(isAnyExtensionsAllowed())
.setBestPracticeWarningLevel(getBestPracticeWarningLevel())
.setErrorForUnknownProfiles(isErrorForUnknownProfiles())
.setExtensionDomains(getExtensionDomains())
.setNoTerminologyChecks(isNoTerminologyChecks())
.validate(wrappedWorkerContext, theValidationCtx);
}
private class WorkerContextWrapper implements IWorkerContext {
private static class WorkerContextWrapper implements IWorkerContext {
private final HapiWorkerContext myWrap;
private volatile List<org.hl7.fhir.r5.model.StructureDefinition> myAllStructures;
private LoadingCache<ResourceKey, org.hl7.fhir.r5.model.Resource> myFetchResourceCache;
@ -419,6 +252,9 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
case "Questionnaire":
fetched = myWrap.fetchResource(Questionnaire.class, key.getUri());
break;
case "ImplementationGuide":
fetched = myWrap.fetchResource(ImplementationGuide.class, key.getUri());
break;
default:
throw new UnsupportedOperationException("Don't know how to fetch " + key.getResourceName());
}
@ -485,31 +321,40 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
throw new UnsupportedOperationException();
}
private ValidationResult convertValidationResult(org.hl7.fhir.r5.context.IWorkerContext.ValidationResult theResult) {
IssueSeverity issueSeverity = theResult.getSeverity();
String message = theResult.getMessage();
org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null;
if (theResult.asConceptDefinition() != null) {
try {
conceptDefinition = (theResult.asConceptDefinition());
} catch (FHIRException e) {
throw new InternalErrorException(e);
@Nonnull
private ValidationResult convertValidationResult(@Nullable org.hl7.fhir.r5.context.IWorkerContext.ValidationResult theResult) {
ValidationResult retVal = null;
if (theResult != null) {
IssueSeverity issueSeverity = theResult.getSeverity();
String message = theResult.getMessage();
org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent conceptDefinition = null;
if (theResult.asConceptDefinition() != null) {
try {
conceptDefinition = (theResult.asConceptDefinition());
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
}
retVal = new ValidationResult(issueSeverity, message, conceptDefinition);
}
if (retVal == null) {
retVal = new ValidationResult(IssueSeverity.ERROR, "Validation failed");
}
ValidationResult retVal = new ValidationResult(issueSeverity, message, conceptDefinition);
return retVal;
}
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean heiarchical) {
public ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet source, boolean cacheOk, boolean heiarchical) {
ValueSet convertedSource;
try {
convertedSource = (source);
} catch (FHIRException e) {
throw new InternalErrorException(e);
}
org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome expanded = myWrap.expandVS(convertedSource, cacheOk, heiarchical);
ValueSetExpansionOutcome expanded = myWrap.expandVS(convertedSource, cacheOk, heiarchical);
org.hl7.fhir.r5.model.ValueSet convertedResult = null;
if (expanded.getValueset() != null) {
@ -523,16 +368,16 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
String error = expanded.getError();
ValueSetExpander.TerminologyServiceErrorClass result = null;
return new ValueSetExpander.ValueSetExpansionOutcome(convertedResult, error, result);
return new ValueSetExpansionOutcome(convertedResult, error, result);
}
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) {
public ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ElementDefinition.ElementDefinitionBindingComponent binding, boolean cacheOk, boolean heiarchical) {
throw new UnsupportedOperationException();
}
@Override
public ValueSetExpander.ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException {
public ValueSetExpansionOutcome expandVS(org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent inc, boolean heirarchical) throws TerminologyServiceException {
ValueSet.ConceptSetComponent convertedInc = null;
if (inc != null) {
try {
@ -542,7 +387,7 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
}
}
org.hl7.fhir.r5.terminologies.ValueSetExpander.ValueSetExpansionOutcome expansion = myWrap.expandVS(convertedInc, heirarchical);
ValueSetExpansionOutcome expansion = myWrap.expandVS(convertedInc, heirarchical);
org.hl7.fhir.r5.model.ValueSet valueSetExpansion = null;
if (expansion != null) {
try {
@ -552,7 +397,7 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
}
}
ValueSetExpander.ValueSetExpansionOutcome outcome = new ValueSetExpander.ValueSetExpansionOutcome(valueSetExpansion);
ValueSetExpansionOutcome outcome = new ValueSetExpansionOutcome(valueSetExpansion);
return outcome;
}
@ -648,11 +493,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
return fetchResource(org.hl7.fhir.r5.model.StructureDefinition.class, "http://hl7.org/fhir/StructureDefinition/" + typeName);
}
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override
public List<String> getTypeNames() {
return myWrap.getTypeNames();
@ -663,6 +503,11 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
throw new UnsupportedOperationException();
}
@Override
public void setUcumService(UcumService ucumService) {
throw new UnsupportedOperationException();
}
@Override
public String getVersion() {
return myWrap.getVersion();
@ -709,13 +554,13 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
}
@Override
public void setLogger(ILoggingService logger) {
throw new UnsupportedOperationException();
public ILoggingService getLogger() {
return null;
}
@Override
public ILoggingService getLogger() {
return null;
public void setLogger(ILoggingService logger) {
throw new UnsupportedOperationException();
}
@Override
@ -736,7 +581,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
@Override
public ValidationResult validateCode(TerminologyServiceOptions theOptions, String system, String code, String display) {
org.hl7.fhir.r5.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, system, code, display);
// TODO: converted code might be null -> NPE
return convertValidationResult(result);
}
@ -787,7 +631,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
throw new InternalErrorException(e);
}
// TODO: converted code might be null -> NPE
org.hl7.fhir.r5.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, convertedCode, convertedVs);
return convertValidationResult(result);
}
@ -808,7 +651,6 @@ public class FhirInstanceValidator extends org.hl7.fhir.r5.hapi.validation.BaseV
throw new InternalErrorException(e);
}
// TODO: converted code might be null -> NPE
org.hl7.fhir.r5.context.IWorkerContext.ValidationResult result = myWrap.validateCode(theOptions, convertedCode, convertedVs);
return convertValidationResult(result);
}

View File

@ -27,7 +27,7 @@ public class ParserWithValidationDstu3Test {
// verify that InstanceValidator likes the format
{
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, origContent);
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, origContent, null);
new FhirInstanceValidator(validationSupport).validateResource(validationCtx);
ValidationResult result = validationCtx.toResult();
for (SingleValidationMessage msg : result.getMessages()) {
@ -42,7 +42,7 @@ public class ParserWithValidationDstu3Test {
// verify that InstanceValidator still likes the format
{
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, content);
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, content, null);
new FhirInstanceValidator(validationSupport).validateResource(validationCtx);
ValidationResult result = validationCtx.toResult();
for (SingleValidationMessage msg : result.getMessages()) {
@ -66,7 +66,7 @@ public class ParserWithValidationDstu3Test {
// verify that InstanceValidator likes the format
{
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, origContent);
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, origContent, null);
new FhirInstanceValidator(validationSupport).validateResource(validationCtx);
ValidationResult result = validationCtx.toResult();
for (SingleValidationMessage msg : result.getMessages()) {
@ -81,7 +81,7 @@ public class ParserWithValidationDstu3Test {
// verify that InstanceValidator still likes the format
{
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, content);
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, content, null);
new FhirInstanceValidator(validationSupport).validateResource(validationCtx);
ValidationResult result = validationCtx.toResult();
for (SingleValidationMessage msg : result.getMessages()) {
@ -102,7 +102,7 @@ public class ParserWithValidationDstu3Test {
// verify that InstanceValidator likes the format
{
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, origContent);
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, origContent, null);
new FhirInstanceValidator(validationSupport).validateResource(validationCtx);
ValidationResult result = validationCtx.toResult();
for (SingleValidationMessage msg : result.getMessages()) {
@ -117,7 +117,7 @@ public class ParserWithValidationDstu3Test {
// verify that InstanceValidator still likes the format
{
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, content);
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, content, null);
new FhirInstanceValidator(validationSupport).validateResource(validationCtx);
ValidationResult result = validationCtx.toResult();
for (SingleValidationMessage msg : result.getMessages()) {
@ -138,7 +138,7 @@ public class ParserWithValidationDstu3Test {
// verify that InstanceValidator likes the format
{
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, origContent);
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, origContent, null);
new FhirInstanceValidator(validationSupport).validateResource(validationCtx);
ValidationResult result = validationCtx.toResult();
for (SingleValidationMessage msg : result.getMessages()) {
@ -153,7 +153,7 @@ public class ParserWithValidationDstu3Test {
// verify that InstanceValidator still likes the format
{
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, content);
IValidationContext<IBaseResource> validationCtx = ValidationContext.forText(ourCtx, content, null);
new FhirInstanceValidator(validationSupport).validateResource(validationCtx);
ValidationResult result = validationCtx.toResult();
for (SingleValidationMessage msg : result.getMessages()) {

View File

@ -985,8 +985,7 @@ public class FhirInstanceValidatorDstu3Test {
myInstanceVal.setValidationSupport(myMockSupport);
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(errors.toString(), 1, errors.size());
assertEquals("StructureDefinition reference \"http://foo/structuredefinition/myprofile\" could not be resolved", errors.get(0).getMessage());
assertThat(errors.toString(), containsString("StructureDefinition reference \"http://foo/structuredefinition/myprofile\" could not be resolved"));
}
@Test

View File

@ -240,7 +240,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system::code1) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("Unknown code for 'http://codesystems.com/system#code1' - QuestionnaireResponse.item[0].answer[0].value.ofType(Coding)"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
qa = new QuestionnaireResponse();
@ -250,7 +250,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system2::code3) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("Validation failed for 'http://codesystems.com/system2#code3'"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
}
@ -271,7 +271,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("No LinkId, so can't be validated"));
assertThat(errors.toString(), containsString("Element 'QuestionnaireResponse.item[0].linkId': minimum required = 1, but only found 0"));
}
@Test
@ -1033,7 +1033,7 @@ public class QuestionnaireResponseValidatorDstu3Test {
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system::code1) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("Unknown code for 'http://codesystems.com/system#code1' - QuestionnaireResponse.item[0].answer[0].value.ofType(Coding)"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
// Partial code

View File

@ -979,8 +979,7 @@ public class FhirInstanceValidatorR4Test {
myInstanceVal.setValidationSupport(myMockSupport);
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(errors.toString(), 1, errors.size());
assertEquals("StructureDefinition reference \"http://foo/structuredefinition/myprofile\" could not be resolved", errors.get(0).getMessage());
assertThat(errors.toString(), containsString("StructureDefinition reference \"http://foo/structuredefinition/myprofile\" could not be resolved"));
}
@Test

View File

@ -237,7 +237,7 @@ public class QuestionnaireResponseValidatorR4Test {
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system::code1) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("Unknown code for 'http://codesystems.com/system#code1' - QuestionnaireResponse.item[0].answer[0].value.ofType(Coding)"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
qa = new QuestionnaireResponse();
@ -248,7 +248,7 @@ public class QuestionnaireResponseValidatorR4Test {
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system2::code3) is not in the options value set in the questionnaire"));
assertThat(errors.toString(), containsString("Validation failed for 'http://codesystems.com/system2#code3'"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
}
@ -269,7 +269,7 @@ public class QuestionnaireResponseValidatorR4Test {
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("ERROR - No LinkId, so can't be validated - QuestionnaireResponse"));
assertThat(errors.toString(), containsString("Element 'QuestionnaireResponse.item[0].linkId': minimum required = 1, but only found 0"));
}
@Test

View File

@ -777,8 +777,7 @@ public class FhirInstanceValidatorR5Test {
myInstanceVal.setValidationSupport(myMockSupport);
ValidationResult output = myVal.validateWithResult(input);
List<SingleValidationMessage> errors = logResultsAndReturnNonInformationalOnes(output);
assertEquals(errors.toString(), 1, errors.size());
assertEquals("StructureDefinition reference \"http://foo/structuredefinition/myprofile\" could not be resolved", errors.get(0).getMessage());
assertThat(errors.toString(), containsString("StructureDefinition reference \"http://foo/structuredefinition/myprofile\" could not be resolved"));
}
@Test
@ -971,6 +970,8 @@ public class FhirInstanceValidatorR5Test {
assertEquals(0, all.size());
}
@Test
@Ignore
public void testValidateStructureDefinition() throws IOException {

View File

@ -0,0 +1,740 @@
package org.hl7.fhir.r5.validation;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import ca.uhn.fhir.validation.SingleValidationMessage;
import ca.uhn.fhir.validation.ValidationResult;
import org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r5.hapi.ctx.IValidationSupport.CodeValidationResult;
import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r5.hapi.validation.ValidationSupportChain;
import org.hl7.fhir.r5.model.*;
import org.hl7.fhir.r5.model.CodeSystem.CodeSystemContentMode;
import org.hl7.fhir.r5.model.CodeSystem.ConceptDefinitionComponent;
import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemComponent;
import org.hl7.fhir.r5.model.Questionnaire.QuestionnaireItemType;
import org.hl7.fhir.r5.model.QuestionnaireResponse.QuestionnaireResponseItemComponent;
import org.hl7.fhir.r5.model.QuestionnaireResponse.QuestionnaireResponseStatus;
import org.hl7.fhir.utilities.validation.ValidationMessage;
import org.hl7.fhir.utilities.xhtml.XhtmlNode;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
public class QuestionnaireResponseValidatorR5Test {
public static final String ID_ICC_QUESTIONNAIRE_SETUP = "Questionnaire/profile";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(QuestionnaireResponseValidatorR5Test.class);
private static final String CODE_ICC_SCHOOLTYPE_PT = "PT";
private static final String ID_VS_SCHOOLTYPE = "ValueSet/schooltype";
private static final String SYSTEMURI_ICC_SCHOOLTYPE = "http://ehealthinnovation/icc/ns/schooltype";
private static DefaultProfileValidationSupport myDefaultValidationSupport = new DefaultProfileValidationSupport();
private static FhirContext ourCtx = FhirContext.forR5();
private FhirInstanceValidator myInstanceVal;
private FhirValidator myVal;
private IValidationSupport myValSupport;
private HapiWorkerContext myWorkerCtx;
@Before
public void before() {
myValSupport = mock(IValidationSupport.class);
// new DefaultProfileValidationSupport();
myWorkerCtx = new HapiWorkerContext(ourCtx, myValSupport);
myVal = ourCtx.newValidator();
myVal.setValidateAgainstStandardSchema(false);
myVal.setValidateAgainstStandardSchematron(false);
ValidationSupportChain validationSupport = new ValidationSupportChain(myValSupport, myDefaultValidationSupport);
myInstanceVal = new FhirInstanceValidator(validationSupport);
myVal.registerValidatorModule(myInstanceVal);
}
private ValidationResult stripBindingHasNoSourceMessage(ValidationResult theErrors) {
List<SingleValidationMessage> messages = new ArrayList<>(theErrors.getMessages());
for (int i = 0; i < messages.size(); i++) {
if (messages.get(i).getMessage().contains("has no source, so can't")) {
messages.remove(i);
i--;
}
}
return new ValidationResult(ourCtx, messages);
}
@Test
public void testAnswerWithCorrectType() {
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
codeSystem.setUrl("http://codesystems.com/system");
codeSystem.addConcept().setCode("code0");
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem);
ValueSet options = new ValueSet();
options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options);
int itemCnt = 16;
QuestionnaireItemType[] questionnaireItemTypes = new QuestionnaireItemType[itemCnt];
questionnaireItemTypes[0] = QuestionnaireItemType.BOOLEAN;
questionnaireItemTypes[1] = QuestionnaireItemType.DECIMAL;
questionnaireItemTypes[2] = QuestionnaireItemType.INTEGER;
questionnaireItemTypes[3] = QuestionnaireItemType.DATE;
questionnaireItemTypes[4] = QuestionnaireItemType.DATETIME;
questionnaireItemTypes[5] = QuestionnaireItemType.TIME;
questionnaireItemTypes[6] = QuestionnaireItemType.STRING;
questionnaireItemTypes[7] = QuestionnaireItemType.TEXT;
questionnaireItemTypes[8] = QuestionnaireItemType.TEXT;
questionnaireItemTypes[9] = QuestionnaireItemType.URL;
questionnaireItemTypes[10] = QuestionnaireItemType.CHOICE;
questionnaireItemTypes[11] = QuestionnaireItemType.OPENCHOICE;
questionnaireItemTypes[12] = QuestionnaireItemType.OPENCHOICE;
questionnaireItemTypes[13] = QuestionnaireItemType.ATTACHMENT;
questionnaireItemTypes[14] = QuestionnaireItemType.REFERENCE;
questionnaireItemTypes[15] = QuestionnaireItemType.QUANTITY;
Type[] answerValues = new Type[itemCnt];
answerValues[0] = new BooleanType(true);
answerValues[1] = new DecimalType(42.0);
answerValues[2] = new IntegerType(42);
answerValues[3] = new DateType(new Date());
answerValues[4] = new DateTimeType(new Date());
answerValues[5] = new TimeType("04:47:12");
answerValues[6] = new StringType("some text");
answerValues[7] = new StringType("some text");
answerValues[8] = new MarkdownType("some text");
answerValues[9] = new UriType("http://example.com");
answerValues[10] = new Coding().setSystem("http://codesystems.com/system").setCode("code0");
answerValues[11] = new Coding().setSystem("http://codesystems.com/system").setCode("code0");
answerValues[12] = new StringType("some value");
answerValues[13] = new Attachment().setData("some data".getBytes()).setContentType("txt");
answerValues[14] = new Reference("http://example.com/Questionnaire/q1");
answerValues[15] = new Quantity(42);
for (int i = 0; i < itemCnt; i++) {
ourLog.info("Testing item {}: {}", i, questionnaireItemTypes[i]);
Questionnaire q = new Questionnaire();
if (questionnaireItemTypes[i] == null) continue;
String linkId = "link" + i;
QuestionnaireItemComponent questionnaireItemComponent =
q.addItem().setLinkId(linkId).setRequired(true).setType(questionnaireItemTypes[i]);
if (i == 10 || i == 11) {
questionnaireItemComponent.setAnswerValueSet("http://somevalueset");
} else if (i == 12) {
questionnaireItemComponent.setAnswerOption(
Arrays.asList(new Questionnaire.QuestionnaireItemAnswerOptionComponent(new StringType("some value"))));
}
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.INPROGRESS);
qa.setQuestionnaire("http://example.com/Questionnaire/q" + i);
qa.addItem().setLinkId(linkId).addAnswer().setValue(answerValues[i]);
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class),
eq(qa.getQuestionnaire()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat("index[" + i + "]: " + errors.toString(), errors.getMessages(), empty());
}
}
@Test
public void testAnswerWithWrongType() {
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1");
qa.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaireElement().getValue()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Answer value must be of type boolean"));
}
@Test
public void testCodedAnswer() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.CHOICE).setAnswerValueSet("http://somevalueset");
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq("http://example.com/Questionnaire/q1"))).thenReturn(q);
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system"))).thenReturn(true);
when(myValSupport.isCodeSystemSupported(any(), eq("http://codesystems.com/system2"))).thenReturn(true);
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code0"), any()))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent().setCode("code0")));
when(myValSupport.validateCode(any(), eq("http://codesystems.com/system"), eq("code1"), any()))
.thenReturn(new CodeValidationResult(ValidationMessage.IssueSeverity.ERROR, "Unknown code"));
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
codeSystem.setUrl("http://codesystems.com/system");
codeSystem.addConcept().setCode("code0");
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem);
CodeSystem codeSystem2 = new CodeSystem();
codeSystem2.setContent(CodeSystemContentMode.COMPLETE);
codeSystem2.setUrl("http://codesystems.com/system2");
codeSystem2.addConcept().setCode("code2");
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2);
ValueSet options = new ValueSet();
options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options);
QuestionnaireResponse qa;
ValidationResult errors;
// Good code
qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system").setCode("code0"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
assertEquals(errors.toString(), 0, errors.getMessages().size());
// Bad code
qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system").setCode("code1"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Unknown code for 'http://codesystems.com/system#code1' - QuestionnaireResponse.item[0].answer[0].value.ofType(Coding)"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system2").setCode("code3"));
errors = myVal.validateWithResult(qa);
errors = stripBindingHasNoSourceMessage(errors);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Validation failed for 'http://codesystems.com/system2#code3'"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
}
@Test
public void testGroupWithNoLinkIdInQuestionnaireResponse() {
Questionnaire q = new Questionnaire();
QuestionnaireItemComponent qGroup = q.addItem().setType(QuestionnaireItemType.GROUP);
qGroup.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1");
QuestionnaireResponseItemComponent qaGroup = qa.addItem();
qaGroup.addItem().setLinkId("link0").addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Element 'QuestionnaireResponse.item[0].linkId': minimum required = 1, but only found 0"));
}
@Test
public void testItemWithNoType() {
Questionnaire q = new Questionnaire();
QuestionnaireItemComponent qItem = q.addItem();
qItem.setLinkId("link0");
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1");
QuestionnaireResponseItemComponent qaItem = qa.addItem().setLinkId("link0");
qaItem.addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Definition for item link0 does not contain a type"));
assertEquals(1, errors.getMessages().size());
}
@Test
public void testMissingRequiredQuestion() {
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.STRING);
q.addItem().setLinkId("link1").setRequired(true).setType(QuestionnaireItemType.STRING);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1");
qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
String reference = qa.getQuestionnaire();
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("No response found for required item with id = 'link0'"));
}
@Test
public void testEmbeddedItemInChoice() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
String valueSetRef = "http://somevalueset";
String codeSystemUrl = "http://codesystems.com/system";
String codeValue = "code0";
// create the questionnaire
QuestionnaireItemComponent item1 = new QuestionnaireItemComponent();
item1.setLinkId("link1")
.setType(QuestionnaireItemType.CHOICE)
.setAnswerValueSet(valueSetRef);
item1.addItem().setLinkId("link11")
.setType(QuestionnaireItemType.TEXT);
Questionnaire q = new Questionnaire();
q.addItem(item1);
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef)))
.thenReturn(q);
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
codeSystem.setUrl(codeSystemUrl);
codeSystem.addConcept().setCode(codeValue);
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq(codeSystemUrl)))
.thenReturn(codeSystem);
ValueSet options = new ValueSet();
options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef)))
.thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class)))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue))));
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
String qXml = xmlParser.encodeResourceToString(q);
ourLog.info(qXml);
// create the response
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.INPROGRESS);
qa.setQuestionnaire(questionnaireRef);
qa.addItem().setLinkId("link1")
.addAnswer()
.addItem().setLinkId("link11");
String rXml = xmlParser.encodeResourceToString(qa);
ourLog.info(rXml);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.getMessages(), empty());
}
@Test
public void testEmbeddedItemInOpenChoice() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
String valueSetRef = "http://somevalueset";
String codeSystemUrl = "http://codesystems.com/system";
String codeValue = "code0";
// create the questionnaire
QuestionnaireItemComponent item1 = new QuestionnaireItemComponent();
item1.setLinkId("link1")
.setType(QuestionnaireItemType.OPENCHOICE)
.setAnswerValueSet(valueSetRef);
item1.addItem().setLinkId("link11")
.setType(QuestionnaireItemType.TEXT);
Questionnaire q = new Questionnaire();
q.addItem(item1);
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef)))
.thenReturn(q);
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
codeSystem.setUrl(codeSystemUrl);
codeSystem.addConcept().setCode(codeValue);
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq(codeSystemUrl)))
.thenReturn(codeSystem);
ValueSet options = new ValueSet();
options.getCompose().addInclude().setSystem(codeSystemUrl).addConcept().setCode(codeValue);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(valueSetRef)))
.thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq(codeSystemUrl), eq(codeValue), any(String.class)))
.thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType(codeValue))));
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
String qXml = xmlParser.encodeResourceToString(q);
ourLog.info(qXml);
// create the response
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.INPROGRESS);
qa.setQuestionnaire(questionnaireRef);
qa.addItem().setLinkId("link1")
.addAnswer()
.addItem().setLinkId("link11");
String rXml = xmlParser.encodeResourceToString(qa);
ourLog.info(rXml);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.getMessages(), empty());
}
@Test
public void testEmbeddedItemInString() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
// create the questionnaire
QuestionnaireItemComponent item1 = new QuestionnaireItemComponent();
item1.setLinkId("link1")
.setType(QuestionnaireItemType.TEXT);
item1.addItem().setLinkId("link11")
.setType(QuestionnaireItemType.TEXT);
Questionnaire q = new Questionnaire();
q.addItem(item1);
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef)))
.thenReturn(q);
IParser xmlParser = ourCtx.newXmlParser().setPrettyPrint(true);
String qXml = xmlParser.encodeResourceToString(q);
ourLog.info(qXml);
// create the response
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.INPROGRESS);
qa.setQuestionnaire(questionnaireRef);
qa.addItem().setLinkId("link1")
.addAnswer()
.addItem().setLinkId("link11");
String rXml = xmlParser.encodeResourceToString(qa);
ourLog.info(rXml);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.getMessages(), empty());
}
@Test
public void testMissingRequiredAnswer() {
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0")
.setType(QuestionnaireItemType.STRING)
.setRequired(true);
String reference = "http://example.com/Questionnaire/q1";
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(reference)))
.thenReturn(q);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setQuestionnaire(reference);
qa.addItem().setLinkId("link0");
qa.setStatus(QuestionnaireResponseStatus.INPROGRESS);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.getMessages(), hasSize(1));
assertEquals(ResultSeverityEnum.WARNING, errors.getMessages().get(0).getSeverity());
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.getMessages(), hasSize(1));
assertEquals(ResultSeverityEnum.ERROR, errors.getMessages().get(0).getSeverity());
}
@Test
public void testOpenchoiceAnswer() {
String questionnaireRef = "http://example.com/Questionnaire/q1";
Questionnaire q = new Questionnaire();
QuestionnaireItemComponent item = q.addItem();
item.setLinkId("link0").setRequired(true).setType(QuestionnaireItemType.OPENCHOICE).setAnswerValueSet("http://somevalueset");
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(questionnaireRef))).thenReturn(q);
CodeSystem codeSystem = new CodeSystem();
codeSystem.setContent(CodeSystemContentMode.COMPLETE);
codeSystem.setUrl("http://codesystems.com/system");
codeSystem.addConcept().setCode("code0");
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system"))).thenReturn(codeSystem);
CodeSystem codeSystem2 = new CodeSystem();
codeSystem2.setContent(CodeSystemContentMode.COMPLETE);
codeSystem2.setUrl("http://codesystems.com/system2");
codeSystem2.addConcept().setCode("code2");
when(myValSupport.fetchCodeSystem(any(FhirContext.class), eq("http://codesystems.com/system2"))).thenReturn(codeSystem2);
ValueSet options = new ValueSet();
options.getCompose().addInclude().setSystem("http://codesystems.com/system").addConcept().setCode("code0");
options.getCompose().addInclude().setSystem("http://codesystems.com/system2").addConcept().setCode("code2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq("http://somevalueset"))).thenReturn(options);
when(myValSupport.validateCode(any(FhirContext.class), eq("http://codesystems.com/system"), eq("code0"), any(String.class))).thenReturn(new CodeValidationResult(new ConceptDefinitionComponent(new CodeType("code0"))));
QuestionnaireResponse qa;
ValidationResult errors;
// // Good code
//
// qa = new QuestionnaireResponse();
// qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
// qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
// qa.getQuestionnaireElement().setValue(questionnaireRef);
// qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system").setCode("code0"));
// errors = myVal.validateWithResult(qa);
// errors = stripBindingHasNoSourceMessage(errors);
// assertEquals(errors.getMessages().toString(), 0, errors.getMessages().size());
//
// // Bad code
//
// qa = new QuestionnaireResponse();
// qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
// qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
// qa.getQuestionnaireElement().setValue(questionnaireRef);
// qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system").setCode("code1"));
// errors = myVal.validateWithResult(qa);
// errors = stripBindingHasNoSourceMessage(errors);
// ourLog.info(errors.toString());
// assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system::code1) is not in the options value set in the questionnaire"));
// assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
//
// // Partial code
//
// qa = new QuestionnaireResponse();
// qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
// qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
// qa.getQuestionnaireElement().setValue(questionnaireRef);
// qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem(null).setCode("code1"));
// errors = myVal.validateWithResult(qa);
// errors = stripBindingHasNoSourceMessage(errors);
// ourLog.info(errors.toString());
// assertThat(errors.toString(), containsString("The value provided (null::code1) is not in the options value set in the questionnaire"));
// assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
//
// qa = new QuestionnaireResponse();
// qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
// qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
// qa.getQuestionnaireElement().setValue(questionnaireRef);
// qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("").setCode("code1"));
// errors = myVal.validateWithResult(qa);
// errors = stripBindingHasNoSourceMessage(errors);
// ourLog.info(errors.toString());
// assertThat(errors.toString(), containsString("The value provided (null::code1) is not in the options value set in the questionnaire"));
// assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
//
// // System only in known codesystem
// qa = new QuestionnaireResponse();
// qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
// qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
// qa.getQuestionnaireElement().setValue(questionnaireRef);
// qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://codesystems.com/system").setCode(null));
// errors = myVal.validateWithResult(qa);
// ourLog.info(errors.toString());
// assertThat(errors.toString(), containsString("The value provided (http://codesystems.com/system::null) is not in the options value set in the questionnaire"));
// assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setSystem("http://system").setCode(null));
errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
// This is set in InstanceValidator#validateAnswerCode
assertThat(errors.toString(), containsString("INFORMATION - Code http://system/null was not validated because the code system is not present"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
// Wrong type
qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new IntegerType(123));
errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("Cannot validate integer answer option because no option list is provided"));
assertThat(errors.toString(), containsString("QuestionnaireResponse.item[0].answer[0]"));
// String answer
qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setDisplay("Hello"));
errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.getMessages(), empty());
// Missing String answer
qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue(questionnaireRef);
qa.addItem().setLinkId("link0").addAnswer().setValue(new Coding().setDisplay(""));
errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("No response found for required item with id = 'link0'"));
}
@Test
public void testUnexpectedAnswer() {
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1");
qa.addItem().setLinkId("link1").addAnswer().setValue(new StringType("FOO"));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString(" - QuestionnaireResponse"));
assertThat(errors.toString(), containsString("LinkId \"link1\" not found in questionnaire"));
}
@Test
public void testUnexpectedGroup() {
Questionnaire q = new Questionnaire();
q.addItem().setLinkId("link0").setRequired(false).setType(QuestionnaireItemType.BOOLEAN);
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.getQuestionnaireElement().setValue("http://example.com/Questionnaire/q1");
qa.addItem().setLinkId("link1").addItem().setLinkId("link2");
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(q);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString(" - QuestionnaireResponse"));
assertThat(errors.toString(), containsString("LinkId \"link1\" not found in questionnaire"));
}
@Test
public void testValidateQuestionnaireResponseWithValueSetChoiceAnswer() {
/*
* Create valueset
*/
ValueSet iccSchoolTypeVs = new ValueSet();
iccSchoolTypeVs.setId(ID_VS_SCHOOLTYPE);
iccSchoolTypeVs.getCompose().getIncludeFirstRep().setSystem(SYSTEMURI_ICC_SCHOOLTYPE);
iccSchoolTypeVs
.getCompose()
.getIncludeFirstRep()
.addConcept()
.setCode(CODE_ICC_SCHOOLTYPE_PT)
.setDisplay("Part Time");
/*
* Create Questionnaire
*/
Questionnaire questionnaire = new Questionnaire();
{
questionnaire.setId(ID_ICC_QUESTIONNAIRE_SETUP);
QuestionnaireItemComponent basicGroup = questionnaire.addItem();
basicGroup.setLinkId("basic");
basicGroup.setType(QuestionnaireItemType.GROUP);
basicGroup
.addItem()
.setLinkId("schoolType")
.setType(QuestionnaireItemType.CHOICE)
.setAnswerValueSet(ID_VS_SCHOOLTYPE)
.setRequired(true);
}
/*
* Create response
*/
QuestionnaireResponse qa = new QuestionnaireResponse();
qa.getText().setDiv(new XhtmlNode().setValue("<div>AA</div>")).setStatus(Narrative.NarrativeStatus.GENERATED);
qa.setStatus(QuestionnaireResponseStatus.COMPLETED);
qa.setQuestionnaire(ID_ICC_QUESTIONNAIRE_SETUP);
qa.getSubject().setReference("Patient/123");
QuestionnaireResponseItemComponent basicGroup = qa
.addItem();
basicGroup.setLinkId("basic");
basicGroup
.addItem()
.setLinkId("schoolType")
.addAnswer()
.setValue(new Coding(SYSTEMURI_ICC_SCHOOLTYPE, CODE_ICC_SCHOOLTYPE_PT, ""));
when(myValSupport.fetchResource(any(FhirContext.class), eq(Questionnaire.class), eq(qa.getQuestionnaire()))).thenReturn(questionnaire);
when(myValSupport.fetchResource(any(FhirContext.class), eq(ValueSet.class), eq(ID_VS_SCHOOLTYPE))).thenReturn(iccSchoolTypeVs);
ValidationResult errors = myVal.validateWithResult(qa);
ourLog.info(errors.toString());
assertThat(errors.toString(), containsString("No issues"));
}
@AfterClass
public static void afterClassClearContext() {
myDefaultValidationSupport.flush();
myDefaultValidationSupport = null;
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -833,6 +833,11 @@
<artifactId>gson</artifactId>
<version>${gson_version}</version>
</dependency>
<dependency>
<groupId>com.google.code.findbugs</groupId>
<artifactId>jsr305</artifactId>
<version>3.0.2</version>
</dependency>
<dependency>
<groupId>com.microsoft.sqlserver</groupId>
<artifactId>mssql-jdbc</artifactId>

View File

@ -59,12 +59,20 @@
a type level operation (i.e. called on the CodeSystem type).
]]>
</action>
<action type="change">
<![CDATA[
<b>Breaking Change</b>:
The FhirValidator#validate(IResource) method has been removed. It was deprecated in HAPI FHIR 0.7 and replaced with
FhirValidator#validateWithResults(IBaseResource) so it is unlikely anyone is still depending on the
old method.
]]>
</action>
<action type="add">
Support for the new R5 draft resources has been added. This support includes the client,
server, and JPA server. Note that these definitions will change as the R5 standard is
modified until it is released, so use with caution!
</action>
<action type="add">
<action type="add">
A new interceptor called
<![CDATA[<code>ConsentInterceptor</code>]]> has been added. This interceptor allows
JPA based servers to make appropriate consent decisions related to resources that
@ -373,6 +381,15 @@
operations had ambiguous paths that could lead to the wrong method
being called. Thanks to Seth Rylan Gainey for the pull request!
</action>
<action type="fix" issue="1414">
The profile validator (FhirInstanceValidator) can now be used to validate a resource
using an explicit profile declaration rather than simply relying on the declared
URL in the resource itself.
</action>
<action type="fix">
When using the ResponseHighlighterInterceptor, some invalid requests that would normally generate an HTTP
400 response (e.g. an invalid _elements value) would cause an HTTP 500 crash.
</action>
</release>
<release version="3.8.0" date="2019-05-30" description="Hippo">
<action type="fix">

View File

@ -124,7 +124,73 @@
</macro>
</section>
<section name="Resource Validation (Profile/StructureDefinition)">
<p>
HAPI also supports validation against StructureDefinition
resources. This functionality uses the HL7 "InstanceValidator", which is able
to check a resource for conformance to FHIR profiles
(StructureDefinitions, ValueSets, and CodeSystems),
including validating fields, extensions, and codes for conformance to their given ValueSets.
</p>
<p>
StructureDefinition validation can be used to validate a resource against the
official structure definitions (produced by HL7) as well as against custom
definitions provided either by HL7 or by the user.
</p>
<p class="doc_info_bubble">
The instance validator is experimental in the DSTU2 mode, but has become very stable
and full-featured in DSTU3+ mode. Use with caution when validating DSTU2 resources using
instance validator.
</p>
<subsection name="Running the Validator">
<p>
To execute the validator, you simply create an instance of
<a href="./apidocs-dstu3/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.html">FhirInstanceValidator</a>
and register it to new validator, as shown in the example below.
</p>
<p>
Note that the example below uses the official FHIR StructureDefintions and ValueSets
to validate the resource. It will not work unless you include the
<code>hapi-fhir-validation-resources-[version].jar</code> to your classpath.
</p>
<macro name="snippet">
<param name="id" value="instanceValidator" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
</subsection>
<subsection name="Supplying your own StructureDefinitions">
<p>
The FhirInstanceValidator relies on the
<a href="./apidocs-hl7org-dstu2/ca/uhn/fhir/validation/IValidationSupport.html">IValidationSupport</a>
interface to load StructureDefinitions, and validate codes.
</p>
<p>
By default, the
<a href="./apidocs-hl7org-dstu2/ca/uhn/fhir/validation/DefaultProfileValidationSupport.html">DefaultProfileValidationSupport</a>
implementation is used. This implementation loads the FHIR profiles from the
validator resources JAR. If you want to use your own profiles, you may wish to
supply your own implementation.
</p>
<macro name="snippet">
<param name="id" value="instanceValidatorCustom" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
</subsection>
</section>
<section name="Resource Validation Module: Schema/Schematron">
<p>
@ -189,100 +255,6 @@
<a name="structure_definition_validation" />
</section>
<section name="Resource Validation (Profile/StructureDefinition)">
<p>
HAPI also supports validation against StructureDefinition
resources. This functionality uses the HL7 "InstanceValidator", which is able
to check a resource for conformance to FHIR profiles
(StructureDefinitions, ValueSets, and CodeSystems),
including validating fields, extensions, and codes for conformance to their given ValueSets.
</p>
<p>
StructureDefinition validation can be used to validate a resource against the
official structure definitions (produced by HL7) as well as against custom
definitions provided either by HL7 or by the user.
</p>
<p class="doc_info_bubble">
The instance validator is experimental in the DSTU2 mode, but has become very stable
and full-featured in DSTU3 mode. Use with caution when validating DSTU2 resources using
instance validator.
</p>
<subsection name="Preparation">
<p>
To use this functionality, you must add the following two dependencies
to your classpath (or Maven POM file, Gradle file, etc.):
</p>
<ul>
<li>
<!-- TODO: add search.maven.org links to these -->
<b>hapi-fhir-structures-hl7org-dstu2</b>
: This file contains the "reference implementation"
structures and tooling. You need to include it even if you are not using the RI model
(the StructureDefinition validation will work against HAPI structures as well)
</li>
<li>
<b>hapi-fhir-validation-resources-dstu2</b>
: This file contains the official FHIR
StructureDefinition files, and the ValueSets needed to support them.
</li>
</ul>
<p>
See the
<a href="./download.html">download page</a>
for more information.
</p>
</subsection>
<subsection name="Running the Validator">
<p>
To execute the validator, you simply create an instance of
<a href="./apidocs-dstu3/org/hl7/fhir/dstu3/hapi/validation/FhirInstanceValidator.html">FhirInstanceValidator</a>
and register it to new validator, as shown in the example below.
</p>
<p>
Note that the example below uses the official FHIR StructureDefintions and ValueSets
to validate the resource. It will not work unless you include the
<code>hapi-fhir-validation-resources-[version].jar</code> to your classpath.
</p>
<macro name="snippet">
<param name="id" value="instanceValidator" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
</subsection>
<subsection name="Supplying your own StructureDefinitions">
<p>
The FhirInstanceValidator relies on the
<a href="./apidocs-hl7org-dstu2/ca/uhn/fhir/validation/IValidationSupport.html">IValidationSupport</a>
interface to load StructureDefinitions, and validate codes.
</p>
<p>
By default, the
<a href="./apidocs-hl7org-dstu2/ca/uhn/fhir/validation/DefaultProfileValidationSupport.html">DefaultProfileValidationSupport</a>
implementation is used. This implementation loads the FHIR profiles from the
validator resources JAR. If you want to use your own profiles, you may wish to
supply your own implementation.
</p>
<macro name="snippet">
<param name="id" value="instanceValidatorCustom" />
<param name="file" value="examples/src/main/java/example/ValidatorExamples.java" />
</macro>
</subsection>
</section>
</body>
</document>