Initial commit
This commit is contained in:
parent
19ff4862ec
commit
961f16a1be
|
@ -31,7 +31,7 @@ public interface IAddressValidator {
|
||||||
/**
|
/**
|
||||||
* URL for validation results that should be placed on addresses
|
* 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)
|
* Extension value confirming that address can be considered valid (it exists and can be traced to the building)
|
||||||
|
|
|
@ -37,6 +37,7 @@ import java.util.Properties;
|
||||||
public abstract class BaseRestfulValidator implements IAddressValidator {
|
public abstract class BaseRestfulValidator implements IAddressValidator {
|
||||||
|
|
||||||
public static final String PROPERTY_SERVICE_KEY = "service.key";
|
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);
|
private static final Logger ourLog = LoggerFactory.getLogger(BaseRestfulValidator.class);
|
||||||
|
|
||||||
|
@ -74,7 +75,7 @@ public abstract class BaseRestfulValidator implements IAddressValidator {
|
||||||
retVal.setRawResponse(responseBody);
|
retVal.setRawResponse(responseBody);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
JsonNode response = new ObjectMapper().readTree(responseBody);
|
JsonNode response = new ObjectMapper().readTree(responseBody);
|
||||||
ourLog.debug("Parsed address validator response {}", response);
|
ourLog.debug("Parsed address validator response {}", response);
|
||||||
return getValidationResult(retVal, response, theFhirContext);
|
return getValidationResult(retVal, response, theFhirContext);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
|
@ -97,4 +98,8 @@ public abstract class BaseRestfulValidator implements IAddressValidator {
|
||||||
protected String getApiKey() {
|
protected String getApiKey() {
|
||||||
return getProperties().getProperty(PROPERTY_SERVICE_KEY);
|
return getProperties().getProperty(PROPERTY_SERVICE_KEY);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getApiEndpoint() {
|
||||||
|
return getProperties().getProperty(PROPERTY_SERVICE_ENDPOINT);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ import com.fasterxml.jackson.databind.JsonNode;
|
||||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
import com.fasterxml.jackson.databind.node.ArrayNode;
|
import com.fasterxml.jackson.databind.node.ArrayNode;
|
||||||
import com.fasterxml.jackson.databind.node.ObjectNode;
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
import org.hl7.fhir.instance.model.api.IBase;
|
import org.hl7.fhir.instance.model.api.IBase;
|
||||||
import org.slf4j.Logger;
|
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[] 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;
|
private static final int MAX_ADDRESS_LINES = 8;
|
||||||
|
|
||||||
public LoquateAddressValidator(Properties theProperties) {
|
public LoquateAddressValidator(Properties theProperties) {
|
||||||
|
@ -171,7 +172,13 @@ public class LoquateAddressValidator extends BaseRestfulValidator {
|
||||||
|
|
||||||
String requestBody = getRequestBody(theFhirContext, theAddress);
|
String requestBody = getRequestBody(theFhirContext, theAddress);
|
||||||
HttpEntity<String> request = new HttpEntity<>(requestBody, headers);
|
HttpEntity<String> 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 {
|
protected String getRequestBody(FhirContext theFhirContext, IBase... theAddresses) throws JsonProcessingException {
|
||||||
|
|
|
@ -56,7 +56,13 @@ public class MelissaAddressValidator extends BaseRestfulValidator {
|
||||||
@Override
|
@Override
|
||||||
protected ResponseEntity<String> getResponseEntity(IBase theAddress, FhirContext theFhirContext) throws Exception {
|
protected ResponseEntity<String> getResponseEntity(IBase theAddress, FhirContext theFhirContext) throws Exception {
|
||||||
Map<String, String> requestParams = getRequestParams(theAddress);
|
Map<String, String> 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<String, String> getRequestParams(IBase theAddress) {
|
protected Map<String, String> getRequestParams(IBase theAddress) {
|
||||||
|
|
|
@ -27,7 +27,9 @@ import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.ConfigLoader;
|
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.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
|
@ -46,12 +48,11 @@ public class FieldValidatingInterceptor {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(FieldValidatingInterceptor.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(FieldValidatingInterceptor.class);
|
||||||
|
|
||||||
public static final String VALIDATION_DISABLED_HEADER = "HAPI-Field-Validation-Disabled";
|
public static final String VALIDATION_DISABLED_HEADER = "HAPI-Field-Validation-Disabled";
|
||||||
|
public static final String VALIDATION_EXTENSION_URL = "https://hapifhir.org/StructureDefinition/ext-validation-field-error";
|
||||||
private IAddressValidator myAddressValidator;
|
public static final String PROPERTY_EXTENSION_URL = "validation.extension.url";
|
||||||
|
|
||||||
private Map<String, String> myConfig;
|
private Map<String, String> myConfig;
|
||||||
|
|
||||||
|
|
||||||
public FieldValidatingInterceptor() {
|
public FieldValidatingInterceptor() {
|
||||||
super();
|
super();
|
||||||
|
|
||||||
|
@ -84,20 +85,52 @@ public class FieldValidatingInterceptor {
|
||||||
|
|
||||||
FhirContext ctx = theRequest.getFhirContext();
|
FhirContext ctx = theRequest.getFhirContext();
|
||||||
IFhirPath fhirPath = ctx.newFhirPath();
|
IFhirPath fhirPath = ctx.newFhirPath();
|
||||||
|
|
||||||
for (Map.Entry<String, String> e : myConfig.entrySet()) {
|
for (Map.Entry<String, String> e : myConfig.entrySet()) {
|
||||||
IValidator validator = getValidator(e.getValue());
|
IValidator validator = getValidator(e.getValue());
|
||||||
|
if (validator == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
List<IPrimitiveType> values = fhirPath.evaluate(theResource, e.getKey(), IPrimitiveType.class);
|
List<IBase> fields = fhirPath.evaluate(theResource, e.getKey(), IBase.class);
|
||||||
for (IPrimitiveType value : values) {
|
for (IBase field : fields) {
|
||||||
String valueAsString = value.getValueAsString();
|
|
||||||
if (!validator.isValid(valueAsString)) {
|
List<IPrimitiveType> values = fhirPath.evaluate(field, "value", IPrimitiveType.class);
|
||||||
throw new IllegalArgumentException(String.format("Invalid resource %s", valueAsString));
|
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) {
|
private IValidator getValidator(String theValue) {
|
||||||
|
if (PROPERTY_EXTENSION_URL.equals(theValue)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
if (ValidatorType.EMAIL.name().equals(theValue)) {
|
if (ValidatorType.EMAIL.name().equals(theValue)) {
|
||||||
return new EmailValidator();
|
return new EmailValidator();
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,3 +1,3 @@
|
||||||
{
|
{
|
||||||
"telecom.where(system='email').value" : "EMAIL"
|
"telecom.where(system='email')" : "EMAIL"
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,9 +4,12 @@ import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.r4.model.ContactPoint;
|
import org.hl7.fhir.r4.model.ContactPoint;
|
||||||
|
import org.hl7.fhir.r4.model.Extension;
|
||||||
import org.hl7.fhir.r4.model.Person;
|
import org.hl7.fhir.r4.model.Person;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
|
@ -19,6 +22,8 @@ import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
class FieldValidatingInterceptorTest {
|
class FieldValidatingInterceptorTest {
|
||||||
|
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(FieldValidatingInterceptorTest.class);
|
||||||
|
|
||||||
private FhirContext myFhirContext = FhirContext.forR4();
|
private FhirContext myFhirContext = FhirContext.forR4();
|
||||||
private FieldValidatingInterceptor myInterceptor = new FieldValidatingInterceptor();
|
private FieldValidatingInterceptor myInterceptor = new FieldValidatingInterceptor();
|
||||||
|
|
||||||
|
@ -61,12 +66,22 @@ class FieldValidatingInterceptorTest {
|
||||||
public void testInvalidEmailValidation() {
|
public void testInvalidEmailValidation() {
|
||||||
Person person = new Person();
|
Person person = new Person();
|
||||||
person.addTelecom().setSystem(ContactPoint.ContactPointSystem.EMAIL).setValue("@garbage");
|
person.addTelecom().setSystem(ContactPoint.ContactPointSystem.EMAIL).setValue("@garbage");
|
||||||
|
person.addTelecom().setSystem(ContactPoint.ContactPointSystem.EMAIL).setValue("my@email.com");
|
||||||
|
|
||||||
try {
|
try {
|
||||||
myInterceptor.handleRequest(newRequestDetails(), person);
|
myInterceptor.handleRequest(newRequestDetails(), person);
|
||||||
fail();
|
|
||||||
} catch (Exception e) {
|
} 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
|
@Test
|
||||||
|
|
Loading…
Reference in New Issue