diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/IAddressValidator.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/IAddressValidator.java index 4f6afcbe9f7..10903dab3f2 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/IAddressValidator.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/IAddressValidator.java @@ -31,7 +31,7 @@ public interface IAddressValidator { /** * URL for validation results that should be placed on addresses */ - public static final String ADDRESS_VALIDATION_EXTENSION_URL = "https://hapifhir.org/AddressValidation/"; + public static final String ADDRESS_VALIDATION_EXTENSION_URL = "https://hapifhir.org/StructureDefinition/ext-validation-address-error"; /** * Extension value confirming that address can be considered valid (it exists and can be traced to the building) diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/BaseRestfulValidator.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/BaseRestfulValidator.java index 7336f439cfa..799ef5877a1 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/BaseRestfulValidator.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/BaseRestfulValidator.java @@ -37,6 +37,7 @@ import java.util.Properties; public abstract class BaseRestfulValidator implements IAddressValidator { public static final String PROPERTY_SERVICE_KEY = "service.key"; + public static final String PROPERTY_SERVICE_ENDPOINT = "service.endpoint"; private static final Logger ourLog = LoggerFactory.getLogger(BaseRestfulValidator.class); @@ -74,7 +75,7 @@ public abstract class BaseRestfulValidator implements IAddressValidator { retVal.setRawResponse(responseBody); try { - JsonNode response = new ObjectMapper().readTree(responseBody); + JsonNode response = new ObjectMapper().readTree(responseBody); ourLog.debug("Parsed address validator response {}", response); return getValidationResult(retVal, response, theFhirContext); } catch (Exception e) { @@ -97,4 +98,8 @@ public abstract class BaseRestfulValidator implements IAddressValidator { protected String getApiKey() { return getProperties().getProperty(PROPERTY_SERVICE_KEY); } + + protected String getApiEndpoint() { + return getProperties().getProperty(PROPERTY_SERVICE_ENDPOINT); + } } diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/LoquateAddressValidator.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/LoquateAddressValidator.java index 6a4e4521542..2749816aac9 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/LoquateAddressValidator.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/LoquateAddressValidator.java @@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.ArrayNode; import com.fasterxml.jackson.databind.node.ObjectNode; +import org.apache.commons.lang3.StringUtils; import org.apache.http.entity.ContentType; import org.hl7.fhir.instance.model.api.IBase; import org.slf4j.Logger; @@ -52,7 +53,7 @@ public class LoquateAddressValidator extends BaseRestfulValidator { private static final String[] DUPLICATE_FIELDS_IN_ADDRESS_LINES = {"Locality", "AdministrativeArea", "PostalCode"}; - private static final String DATA_CLEANSE_ENDPOINT = "https://api.addressy.com/Cleansing/International/Batch/v1.00/json4.ws"; + private static final String DEFAULT_DATA_CLEANSE_ENDPOINT = "https://api.addressy.com/Cleansing/International/Batch/v1.00/json4.ws"; private static final int MAX_ADDRESS_LINES = 8; public LoquateAddressValidator(Properties theProperties) { @@ -171,7 +172,13 @@ public class LoquateAddressValidator extends BaseRestfulValidator { String requestBody = getRequestBody(theFhirContext, theAddress); HttpEntity request = new HttpEntity<>(requestBody, headers); - return newTemplate().postForEntity(DATA_CLEANSE_ENDPOINT, request, String.class); + return newTemplate().postForEntity(getApiEndpoint(), request, String.class); + } + + @Override + protected String getApiEndpoint() { + String endpoint = super.getApiEndpoint(); + return StringUtils.isEmpty(endpoint) ? DEFAULT_DATA_CLEANSE_ENDPOINT : endpoint; } protected String getRequestBody(FhirContext theFhirContext, IBase... theAddresses) throws JsonProcessingException { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/MelissaAddressValidator.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/MelissaAddressValidator.java index 2a886487792..577343a5502 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/MelissaAddressValidator.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/MelissaAddressValidator.java @@ -56,7 +56,13 @@ public class MelissaAddressValidator extends BaseRestfulValidator { @Override protected ResponseEntity getResponseEntity(IBase theAddress, FhirContext theFhirContext) throws Exception { Map requestParams = getRequestParams(theAddress); - return newTemplate().getForEntity(GLOBAL_ADDRESS_VALIDATION_ENDPOINT, String.class, requestParams); + return newTemplate().getForEntity(getApiEndpoint(), String.class, requestParams); + } + + @Override + protected String getApiEndpoint() { + String endpoint = super.getApiEndpoint(); + return StringUtils.isEmpty(endpoint) ? GLOBAL_ADDRESS_VALIDATION_ENDPOINT : endpoint; } protected Map getRequestParams(IBase theAddress) { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/fields/FieldValidatingInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/fields/FieldValidatingInterceptor.java index 730347748ef..c97c6b07c40 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/fields/FieldValidatingInterceptor.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/validation/fields/FieldValidatingInterceptor.java @@ -27,7 +27,9 @@ import ca.uhn.fhir.interceptor.api.Interceptor; import ca.uhn.fhir.interceptor.api.Pointcut; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.interceptor.ConfigLoader; -import ca.uhn.fhir.rest.server.interceptor.validation.address.IAddressValidator; +import ca.uhn.fhir.util.ExtensionUtil; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.slf4j.Logger; @@ -46,12 +48,11 @@ public class FieldValidatingInterceptor { private static final Logger ourLog = LoggerFactory.getLogger(FieldValidatingInterceptor.class); public static final String VALIDATION_DISABLED_HEADER = "HAPI-Field-Validation-Disabled"; - - private IAddressValidator myAddressValidator; + public static final String VALIDATION_EXTENSION_URL = "https://hapifhir.org/StructureDefinition/ext-validation-field-error"; + public static final String PROPERTY_EXTENSION_URL = "validation.extension.url"; private Map myConfig; - public FieldValidatingInterceptor() { super(); @@ -84,20 +85,52 @@ public class FieldValidatingInterceptor { FhirContext ctx = theRequest.getFhirContext(); IFhirPath fhirPath = ctx.newFhirPath(); + for (Map.Entry e : myConfig.entrySet()) { IValidator validator = getValidator(e.getValue()); + if (validator == null) { + continue; + } - List values = fhirPath.evaluate(theResource, e.getKey(), IPrimitiveType.class); - for (IPrimitiveType value : values) { - String valueAsString = value.getValueAsString(); - if (!validator.isValid(valueAsString)) { - throw new IllegalArgumentException(String.format("Invalid resource %s", valueAsString)); + List fields = fhirPath.evaluate(theResource, e.getKey(), IBase.class); + for (IBase field : fields) { + + List values = fhirPath.evaluate(field, "value", IPrimitiveType.class); + boolean isValid = true; + for (IPrimitiveType value : values) { + String valueAsString = value.getValueAsString(); + isValid = validator.isValid(valueAsString); + ourLog.debug("Field {} at path {} validated {}", value, e.getKey(), isValid); + if (!isValid) { + break; + } } + setValidationStatus(ctx, field, isValid); } } } + private void setValidationStatus(FhirContext ctx, IBase theBase, boolean isValid) { + if (isValid) { + ExtensionUtil.clearExtensionsByUrl(theBase, getValidationExtensionUrl()); + } else { + IBaseExtension validationResultExtension = ExtensionUtil.addExtension(theBase, getValidationExtensionUrl()); + ExtensionUtil.setExtension(ctx, theBase, getValidationExtensionUrl(), "boolean", Boolean.TRUE); + } + } + + private String getValidationExtensionUrl() { + if (myConfig.containsKey(PROPERTY_EXTENSION_URL)) { + return myConfig.get(PROPERTY_EXTENSION_URL); + } + return VALIDATION_EXTENSION_URL; + } + private IValidator getValidator(String theValue) { + if (PROPERTY_EXTENSION_URL.equals(theValue)) { + return null; + } + if (ValidatorType.EMAIL.name().equals(theValue)) { return new EmailValidator(); } diff --git a/hapi-fhir-server/src/main/resources/field-validation-rules.json b/hapi-fhir-server/src/main/resources/field-validation-rules.json index edb9a463089..d5a304554af 100644 --- a/hapi-fhir-server/src/main/resources/field-validation-rules.json +++ b/hapi-fhir-server/src/main/resources/field-validation-rules.json @@ -1,3 +1,3 @@ { - "telecom.where(system='email').value" : "EMAIL" + "telecom.where(system='email')" : "EMAIL" } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/fields/FieldValidatingInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/fields/FieldValidatingInterceptorTest.java index 533fc814777..e2db448fc53 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/fields/FieldValidatingInterceptorTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/fields/FieldValidatingInterceptorTest.java @@ -4,9 +4,12 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.server.RequestDetails; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.r4.model.ContactPoint; +import org.hl7.fhir.r4.model.Extension; import org.hl7.fhir.r4.model.Person; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import java.util.Arrays; @@ -19,6 +22,8 @@ import static org.mockito.Mockito.when; class FieldValidatingInterceptorTest { + private static final Logger ourLog = LoggerFactory.getLogger(FieldValidatingInterceptorTest.class); + private FhirContext myFhirContext = FhirContext.forR4(); private FieldValidatingInterceptor myInterceptor = new FieldValidatingInterceptor(); @@ -61,12 +66,22 @@ class FieldValidatingInterceptorTest { public void testInvalidEmailValidation() { Person person = new Person(); person.addTelecom().setSystem(ContactPoint.ContactPointSystem.EMAIL).setValue("@garbage"); + person.addTelecom().setSystem(ContactPoint.ContactPointSystem.EMAIL).setValue("my@email.com"); try { myInterceptor.handleRequest(newRequestDetails(), person); - fail(); } catch (Exception e) { + fail(); } + + ourLog.info("Resource looks like {}", myFhirContext.newJsonParser().setPrettyPrint(true).encodeResourceToString(person)); + + ContactPoint invalidEmail = person.getTelecomFirstRep(); + assertTrue(invalidEmail.hasExtension()); + assertEquals("true", invalidEmail.getExtensionString("https://hapifhir.org/StructureDefinition/ext-validation-field-error")); + + ContactPoint validEmail = person.getTelecom().get(1); + assertFalse(validEmail.hasExtension()); } @Test