Initial commit

This commit is contained in:
Nick Goupinets 2021-04-22 09:48:40 -04:00
parent 19ff4862ec
commit 961f16a1be
7 changed files with 82 additions and 16 deletions

View File

@ -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)

View File

@ -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);
}
}

View File

@ -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<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 {

View File

@ -56,7 +56,13 @@ public class MelissaAddressValidator extends BaseRestfulValidator {
@Override
protected ResponseEntity<String> getResponseEntity(IBase theAddress, FhirContext theFhirContext) throws Exception {
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) {

View File

@ -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<String, String> myConfig;
public FieldValidatingInterceptor() {
super();
@ -84,20 +85,52 @@ public class FieldValidatingInterceptor {
FhirContext ctx = theRequest.getFhirContext();
IFhirPath fhirPath = ctx.newFhirPath();
for (Map.Entry<String, String> e : myConfig.entrySet()) {
IValidator validator = getValidator(e.getValue());
if (validator == null) {
continue;
}
List<IPrimitiveType> 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<IBase> fields = fhirPath.evaluate(theResource, e.getKey(), IBase.class);
for (IBase field : fields) {
List<IPrimitiveType> 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();
}

View File

@ -1,3 +1,3 @@
{
"telecom.where(system='email').value" : "EMAIL"
"telecom.where(system='email')" : "EMAIL"
}

View File

@ -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