Refactored based on the code review

This commit is contained in:
Nick Goupinets 2021-03-11 17:14:01 -05:00
parent 0013910c53
commit 8b8bbcc91f
15 changed files with 151 additions and 128 deletions

View File

@ -1,33 +1,16 @@
package ca.uhn.fhir.rest.server.interceptor.validation.helpers; package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseDatatype; import org.hl7.fhir.instance.model.api.IBaseDatatype;
import org.hl7.fhir.instance.model.api.IBaseExtension; import org.hl7.fhir.instance.model.api.IBaseExtension;
import org.hl7.fhir.instance.model.api.IBaseHasExtensions; import org.hl7.fhir.instance.model.api.IBaseHasExtensions;
public class ExtensionHelper { /**
* Utility for modifying with extensions in a FHIR version-independent approach.
*/
public class ExtensionUtil {
/** /**
* Returns an extension with the specified URL creating one if it doesn't exist. * Returns an extension with the specified URL creating one if it doesn't exist.
@ -37,9 +20,9 @@ public class ExtensionHelper {
* @return Returns a extension with the specified URL. * @return Returns a extension with the specified URL.
* @throws IllegalArgumentException IllegalArgumentException is thrown in case resource doesn't support extensions * @throws IllegalArgumentException IllegalArgumentException is thrown in case resource doesn't support extensions
*/ */
public IBaseExtension<?, ?> getOrCreateExtension(IBase theBase, String theUrl) { public static IBaseExtension<?, ?> getOrCreateExtension(IBase theBase, String theUrl) {
IBaseHasExtensions baseHasExtensions = validateExtensionSupport(theBase); IBaseHasExtensions baseHasExtensions = validateExtensionSupport(theBase);
IBaseExtension extension = getExtension(baseHasExtensions, theUrl); IBaseExtension extension = getExtensionByUrl(baseHasExtensions, theUrl);
if (extension == null) { if (extension == null) {
extension = baseHasExtensions.addExtension(); extension = baseHasExtensions.addExtension();
extension.setUrl(theUrl); extension.setUrl(theUrl);
@ -47,7 +30,7 @@ public class ExtensionHelper {
return extension; return extension;
} }
private IBaseHasExtensions validateExtensionSupport(IBase theBase) { private static IBaseHasExtensions validateExtensionSupport(IBase theBase) {
if (!(theBase instanceof IBaseHasExtensions)) { if (!(theBase instanceof IBaseHasExtensions)) {
throw new IllegalArgumentException(String.format("Expected instance that supports extensions, but got %s", theBase)); throw new IllegalArgumentException(String.format("Expected instance that supports extensions, but got %s", theBase));
} }
@ -61,7 +44,7 @@ public class ExtensionHelper {
* @param theExtensionUrl URL of the extension * @param theExtensionUrl URL of the extension
* @return Returns true if extension is exists and false otherwise * @return Returns true if extension is exists and false otherwise
*/ */
public boolean hasExtension(IBase theBase, String theExtensionUrl) { public static boolean hasExtension(IBase theBase, String theExtensionUrl) {
IBaseHasExtensions baseHasExtensions; IBaseHasExtensions baseHasExtensions;
try { try {
baseHasExtensions = validateExtensionSupport(theBase); baseHasExtensions = validateExtensionSupport(theBase);
@ -69,7 +52,7 @@ public class ExtensionHelper {
return false; return false;
} }
return getExtension(baseHasExtensions, theExtensionUrl) != null; return getExtensionByUrl(baseHasExtensions, theExtensionUrl) != null;
} }
/** /**
@ -79,18 +62,18 @@ public class ExtensionHelper {
* @param theExtensionUrl URL of the extension * @param theExtensionUrl URL of the extension
* @return Returns true if extension is exists and false otherwise * @return Returns true if extension is exists and false otherwise
*/ */
public boolean hasExtension(IBase theBase, String theExtensionUrl, String theExtensionValue) { public static boolean hasExtension(IBase theBase, String theExtensionUrl, String theExtensionValue) {
if (!hasExtension(theBase, theExtensionUrl)) { if (!hasExtension(theBase, theExtensionUrl)) {
return false; return false;
} }
IBaseDatatype value = getExtension((IBaseHasExtensions) theBase, theExtensionUrl).getValue(); IBaseDatatype value = getExtensionByUrl((IBaseHasExtensions) theBase, theExtensionUrl).getValue();
if (value == null) { if (value == null) {
return theExtensionValue == null; return theExtensionValue == null;
} }
return value.toString().equals(theExtensionValue); return value.toString().equals(theExtensionValue);
} }
private IBaseExtension<?, ?> getExtension(IBaseHasExtensions theBase, String theExtensionUrl) { private static IBaseExtension<?, ?> getExtensionByUrl(IBaseHasExtensions theBase, String theExtensionUrl) {
return theBase.getExtension() return theBase.getExtension()
.stream() .stream()
.filter(e -> theExtensionUrl.equals(e.getUrl())) .filter(e -> theExtensionUrl.equals(e.getUrl()))
@ -98,18 +81,28 @@ public class ExtensionHelper {
.orElse(null); .orElse(null);
} }
public void setValue(IBaseExtension theExtension, String theValue, FhirContext theFhirContext) { /**
theExtension.setValue(newString(theValue, theFhirContext)); * Sets value of the extension
*
* @param theExtension The extension to set the value on
* @param theValue The value to set
* @param theFhirContext The context containing FHIR resource definitions
*/
public static void setExtension(FhirContext theFhirContext, IBaseExtension theExtension, String theValue) {
theExtension.setValue(TerserUtil.newElement(theFhirContext, "string", theValue));
} }
public void setValue(IBase theBase, String theUrl, String theValue, FhirContext theFhirContext) { /**
* Sets or replaces existing extension with the specified value
*
* @param theBase The resource to update extension on
* @param theUrl Extension URL
* @param theValue Extension value
* @param theFhirContext The context containing FHIR resource definitions
*/
public static void setExtension(FhirContext theFhirContext, IBase theBase, String theUrl, String theValue) {
IBaseExtension ext = getOrCreateExtension(theBase, theUrl); IBaseExtension ext = getOrCreateExtension(theBase, theUrl);
setValue(ext, theValue, theFhirContext); setExtension(theFhirContext, ext, theValue);
}
public IBaseDatatype newString(String theValue, FhirContext theFhirContext) {
BaseRuntimeElementDefinition<?> def = theFhirContext.getElementDefinition("string");
return (IBaseDatatype) def.newInstance(theValue);
} }
} }

View File

@ -1,24 +1,4 @@
package ca.uhn.fhir.rest.server.interceptor.validation.helpers; package ca.uhn.fhir.util;
/*-
* #%L
* HAPI FHIR - Server Framework
* %%
* Copyright (C) 2014 - 2021 Smile CDR, Inc.
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
@ -29,7 +9,11 @@ import java.util.Arrays;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class BaseHelper { /**
* Helper class for handling updates of the instances that support property modification via <code>setProperty</code>
* and <code>getProperty</code> methods.
*/
public class PropertyModifyingHelper {
public static final String GET_PROPERTY_METHOD_NAME = "getProperty"; public static final String GET_PROPERTY_METHOD_NAME = "getProperty";
public static final String SET_PROPERTY_METHOD_NAME = "setProperty"; public static final String SET_PROPERTY_METHOD_NAME = "setProperty";
@ -41,7 +25,13 @@ public class BaseHelper {
private FhirContext myFhirContext; private FhirContext myFhirContext;
public BaseHelper(IBase theBase, FhirContext theFhirContext) { /**
* Creates a new instance initializing the dependencies.
*
* @param theFhirContext FHIR context holding the resource definitions
* @param theBase The base class to set properties on
*/
public PropertyModifyingHelper(FhirContext theFhirContext, IBase theBase) {
if (findGetPropertyMethod(theBase) == null) { if (findGetPropertyMethod(theBase) == null) {
throw new IllegalArgumentException("Specified base instance does not support property retrieval."); throw new IllegalArgumentException("Specified base instance does not support property retrieval.");
} }
@ -49,6 +39,14 @@ public class BaseHelper {
myFhirContext = theFhirContext; myFhirContext = theFhirContext;
} }
/**
* Gets the method with the specified name and parameter types.
*
* @param theObject Non-null instance to get the method from
* @param theMethodName Name of the method to get
* @param theParamClasses Parameters types that method parameters should be assignable as
* @return Returns the method with the given name and parameters or null if it can't be found
*/
protected Method getMethod(Object theObject, String theMethodName, Class... theParamClasses) { protected Method getMethod(Object theObject, String theMethodName, Class... theParamClasses) {
for (Method m : theObject.getClass().getDeclaredMethods()) { for (Method m : theObject.getClass().getDeclaredMethods()) {
if (m.getName().equals(theMethodName)) { if (m.getName().equals(theMethodName)) {
@ -69,6 +67,12 @@ public class BaseHelper {
return null; return null;
} }
/**
* Gets all non-blank fields as a single string joined with the delimiter provided by {@link #getDelimiter()}
*
* @param theFiledNames Field names to retrieve values for
* @return Returns all specified non-blank fileds as a single string.
*/
public String getFields(String... theFiledNames) { public String getFields(String... theFiledNames) {
return Arrays.stream(theFiledNames) return Arrays.stream(theFiledNames)
.map(this::get) .map(this::get)
@ -110,11 +114,10 @@ public class BaseHelper {
} }
/** /**
* Gets property with the specified name from the provided base class. * Gets property values with the specified name from the provided base class.
* *
* @param thePropertyName Name of the property to get * @param thePropertyName Name of the property to get
* @return Returns property value converted to string. In case of multiple values, they are joined with the * @return Returns property values converted to string.
* specified delimiter.
*/ */
public List<String> getMultiple(String thePropertyName) { public List<String> getMultiple(String thePropertyName) {
Method getPropertyMethod = findGetPropertyMethod(myBase); Method getPropertyMethod = findGetPropertyMethod(myBase);
@ -139,14 +142,29 @@ public class BaseHelper {
return getMethod(theAddress, SET_PROPERTY_METHOD_NAME, theParamClasses); return getMethod(theAddress, SET_PROPERTY_METHOD_NAME, theParamClasses);
} }
/**
* Gets the delimiter used when concatenating multiple field values
*
* @return Returns the delimiter
*/
public String getDelimiter() { public String getDelimiter() {
return myDelimiter; return myDelimiter;
} }
/**
* Sets the delimiter used when concatenating multiple field values
*
* @param theDelimiter The delimiter to set
*/
public void setDelimiter(String theDelimiter) { public void setDelimiter(String theDelimiter) {
this.myDelimiter = theDelimiter; this.myDelimiter = theDelimiter;
} }
/**
* Gets the base instance that this helper operates on
*
* @return Returns the base instance
*/
public IBase getBase() { public IBase getBase() {
return myBase; return myBase;
} }

View File

@ -464,7 +464,6 @@ public final class TerserUtil {
return (T) def.newInstance(); return (T) def.newInstance();
} }
/** /**
* Creates a new resource definition. * Creates a new resource definition.
* *

View File

@ -253,7 +253,7 @@ A sample configuration file can be found below:
} }
``` ```
Standardization can be disabled for a given request by providing ```HAPI-Standardization-Disabled``` request header. Standardization can be disabled for a given request by providing ```HAPI-Standardization-Disabled: *``` request header. Header value can be any string, it is the presence of the header that disables the s13n.
# Validation: Address Validation # Validation: Address Validation
@ -262,7 +262,7 @@ Standardization can be disabled for a given request by providing ```HAPI-Standar
This interceptor is configured in ```address-validation.properties``` file that should be made available on the classpath. This file must contain ```validator.class``` property, which defines a fully qualified class implementing ```ca.uhn.fhir.rest.server.interceptor.validation.address.IAddressValidator``` interface. The specified implementation must provide service-specific logic for validating an Address instance. An example implementation can be found in ```ca.uhn.fhir.rest.server.interceptor.validation.address.impl.LoquateAddressValidator``` class which validates addresses by using Loquate Data Cleanse service. This interceptor is configured in ```address-validation.properties``` file that should be made available on the classpath. This file must contain ```validator.class``` property, which defines a fully qualified class implementing ```ca.uhn.fhir.rest.server.interceptor.validation.address.IAddressValidator``` interface. The specified implementation must provide service-specific logic for validating an Address instance. An example implementation can be found in ```ca.uhn.fhir.rest.server.interceptor.validation.address.impl.LoquateAddressValidator``` class which validates addresses by using Loquate Data Cleanse service.
Address validation can be disabled for a given request by providing ```HAPI-Address-Validation-Disabled``` request header. Address validation can be disabled for a given request by providing ```HAPI-Address-Validation-Disabled: *``` request header. Header value can be any string, it is the presence of the header that disables the validation.
# Validation: Field-Level Validation # Validation: Field-Level Validation
@ -275,4 +275,4 @@ Address validation can be disabled for a given request by providing ```HAPI-Addr
} }
``` ```
Field validation can be disabled for a given request by providing ```HAPI-Field-Validation-Disabled``` request header. Field validation can be disabled for a given request by providing ```HAPI-Field-Validation-Disabled: *``` request header. Header value can be any string, it is the presence of the header that disables the validation.

View File

@ -23,9 +23,11 @@ package ca.uhn.fhir.rest.server.interceptor.s13n;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.fhirpath.FhirPathExecutionException; import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
import ca.uhn.fhir.fhirpath.IFhirPath; import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.interceptor.api.Hook;
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.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.ServerOperationInterceptorAdapter;
import ca.uhn.fhir.rest.server.interceptor.s13n.standardizers.EmailStandardizer; import ca.uhn.fhir.rest.server.interceptor.s13n.standardizers.EmailStandardizer;
import ca.uhn.fhir.rest.server.interceptor.s13n.standardizers.FirstNameStandardizer; import ca.uhn.fhir.rest.server.interceptor.s13n.standardizers.FirstNameStandardizer;
import ca.uhn.fhir.rest.server.interceptor.s13n.standardizers.IStandardizer; import ca.uhn.fhir.rest.server.interceptor.s13n.standardizers.IStandardizer;
@ -43,7 +45,8 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class StandardizingInterceptor extends ServerOperationInterceptorAdapter { @Interceptor
public class StandardizingInterceptor {
/** /**
* Pre-defined standardizers * Pre-defined standardizers
@ -85,13 +88,13 @@ public class StandardizingInterceptor extends ServerOperationInterceptorAdapter
ourLog.info("Initialized standardizers {}", myStandardizers); ourLog.info("Initialized standardizers {}", myStandardizers);
} }
@Override @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) { public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) {
ourLog.debug("Standardizing on pre-create for - {}, {}", theRequest, theResource); ourLog.debug("Standardizing on pre-create for - {}, {}", theRequest, theResource);
standardize(theRequest, theResource); standardize(theRequest, theResource);
} }
@Override @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
ourLog.debug("Standardizing on pre-update for - {}, {}, {}", theRequest, theOldResource, theNewResource); ourLog.debug("Standardizing on pre-update for - {}, {}, {}", theRequest, theOldResource, theNewResource);
standardize(theRequest, theNewResource); standardize(theRequest, theNewResource);
@ -166,7 +169,7 @@ public class StandardizingInterceptor extends ServerOperationInterceptorAdapter
} }
private boolean appliesToResource(String theResourceFromConfig, String theActualResourceType) { private boolean appliesToResource(String theResourceFromConfig, String theActualResourceType) {
return theResourceFromConfig.equals("*") || theResourceFromConfig.equals(theActualResourceType); return theResourceFromConfig.equals(theActualResourceType);
} }
public Map<String, Map<String, String>> getConfig() { public Map<String, Map<String, String>> getConfig() {

View File

@ -21,7 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor.s13n.standardizers;
*/ */
/** /**
* Standardizes phone numbers to fit 123-456-7890 patter. * Standardizes phone numbers to fit 123-456-7890 pattern.
*/ */
public class PhoneStandardizer implements IStandardizer { public class PhoneStandardizer implements IStandardizer {

View File

@ -23,10 +23,12 @@ package ca.uhn.fhir.rest.server.interceptor.validation.address;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.interceptor.api.Hook;
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.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.ServerOperationInterceptorAdapter; import ca.uhn.fhir.util.ExtensionUtil;
import ca.uhn.fhir.rest.server.interceptor.validation.helpers.ExtensionHelper;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
@ -38,7 +40,8 @@ import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.stream.Collectors; import java.util.stream.Collectors;
public class AddressValidatingInterceptor extends ServerOperationInterceptorAdapter { @Interceptor
public class AddressValidatingInterceptor {
private static final Logger ourLog = LoggerFactory.getLogger(AddressValidatingInterceptor.class); private static final Logger ourLog = LoggerFactory.getLogger(AddressValidatingInterceptor.class);
@ -47,8 +50,6 @@ public class AddressValidatingInterceptor extends ServerOperationInterceptorAdap
public static final String ADDRESS_VALIDATION_DISABLED_HEADER = "HAPI-Address-Validation-Disabled"; public static final String ADDRESS_VALIDATION_DISABLED_HEADER = "HAPI-Address-Validation-Disabled";
private ExtensionHelper myExtensionHelper = new ExtensionHelper();
private IAddressValidator myAddressValidator; private IAddressValidator myAddressValidator;
private Properties myProperties; private Properties myProperties;
@ -92,13 +93,13 @@ public class AddressValidatingInterceptor extends ServerOperationInterceptorAdap
} }
} }
@Override @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) { public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) {
ourLog.debug("Validating address on for create {}, {}", theResource, theRequest); ourLog.debug("Validating address on for create {}, {}", theResource, theRequest);
handleRequest(theRequest, theResource); handleRequest(theRequest, theResource);
} }
@Override @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
ourLog.debug("Validating address on for update {}, {}, {}", theOldResource, theNewResource, theRequest); ourLog.debug("Validating address on for update {}, {}, {}", theOldResource, theNewResource, theRequest);
handleRequest(theRequest, theNewResource); handleRequest(theRequest, theNewResource);
@ -117,8 +118,8 @@ public class AddressValidatingInterceptor extends ServerOperationInterceptorAdap
getAddresses(theResource, ctx) getAddresses(theResource, ctx)
.stream() .stream()
.filter(a -> { .filter(a -> {
return !myExtensionHelper.hasExtension(a, IAddressValidator.ADDRESS_VALIDATION_EXTENSION_URL) || return !ExtensionUtil.hasExtension(a, IAddressValidator.ADDRESS_VALIDATION_EXTENSION_URL) ||
myExtensionHelper.hasExtension(a, IAddressValidator.ADDRESS_VALIDATION_EXTENSION_URL, IAddressValidator.EXT_UNABLE_TO_VALIDATE); ExtensionUtil.hasExtension(a, IAddressValidator.ADDRESS_VALIDATION_EXTENSION_URL, IAddressValidator.EXT_UNABLE_TO_VALIDATE);
}) })
.forEach(a -> validateAddress(a, ctx)); .forEach(a -> validateAddress(a, ctx));
} }
@ -128,11 +129,11 @@ public class AddressValidatingInterceptor extends ServerOperationInterceptorAdap
AddressValidationResult validationResult = getAddressValidator().isValid(theAddress, theFhirContext); AddressValidationResult validationResult = getAddressValidator().isValid(theAddress, theFhirContext);
ourLog.debug("Validated address {}", validationResult); ourLog.debug("Validated address {}", validationResult);
myExtensionHelper.setValue(theAddress, IAddressValidator.ADDRESS_VALIDATION_EXTENSION_URL, ExtensionUtil.setExtension(theFhirContext, theAddress, IAddressValidator.ADDRESS_VALIDATION_EXTENSION_URL,
validationResult.isValid() ? IAddressValidator.EXT_VALUE_VALID : IAddressValidator.EXT_VALUE_INVALID, theFhirContext); validationResult.isValid() ? IAddressValidator.EXT_VALUE_VALID : IAddressValidator.EXT_VALUE_INVALID);
} catch (Exception ex) { } catch (Exception ex) {
ourLog.warn("Unable to validate address", ex); ourLog.warn("Unable to validate address", ex);
myExtensionHelper.setValue(theAddress, IAddressValidator.ADDRESS_VALIDATION_EXTENSION_URL, IAddressValidator.EXT_UNABLE_TO_VALIDATE, theFhirContext); ExtensionUtil.setExtension(theFhirContext, theAddress, IAddressValidator.ADDRESS_VALIDATION_EXTENSION_URL, IAddressValidator.EXT_UNABLE_TO_VALIDATE);
} }
} }

View File

@ -109,7 +109,7 @@ public class LoquateAddressValidator extends BaseRestfulValidator {
protected IBase toAddress(JsonNode match, FhirContext theFhirContext) { protected IBase toAddress(JsonNode match, FhirContext theFhirContext) {
IBase addressBase = theFhirContext.getElementDefinition("Address").newInstance(); IBase addressBase = theFhirContext.getElementDefinition("Address").newInstance();
AddressHelper helper = new AddressHelper(addressBase, theFhirContext); AddressHelper helper = new AddressHelper(theFhirContext, addressBase);
helper.setText(getString(match, "Address")); helper.setText(getString(match, "Address"));
String str = getString(match, "Address1"); String str = getString(match, "Address1");
@ -192,7 +192,7 @@ public class LoquateAddressValidator extends BaseRestfulValidator {
} }
protected ObjectNode toJsonNode(IBase theAddress, ObjectMapper mapper, FhirContext theFhirContext) { protected ObjectNode toJsonNode(IBase theAddress, ObjectMapper mapper, FhirContext theFhirContext) {
AddressHelper helper = new AddressHelper(theAddress, theFhirContext); AddressHelper helper = new AddressHelper(theFhirContext, theAddress);
ObjectNode addressNode = mapper.createObjectNode(); ObjectNode addressNode = mapper.createObjectNode();
int count = 1; int count = 1;

View File

@ -60,7 +60,7 @@ public class MelissaAddressValidator extends BaseRestfulValidator {
} }
protected Map<String, String> getRequestParams(IBase theAddress) { protected Map<String, String> getRequestParams(IBase theAddress) {
AddressHelper helper = new AddressHelper(theAddress, null); AddressHelper helper = new AddressHelper(null, theAddress);
Map<String, String> requestParams = new HashMap<>(); Map<String, String> requestParams = new HashMap<>();
requestParams.put("t", UUID.randomUUID().toString()); requestParams.put("t", UUID.randomUUID().toString());

View File

@ -24,11 +24,11 @@ import java.util.regex.Pattern;
public class EmailValidator implements IValidator { public class EmailValidator implements IValidator {
private Pattern myPattern = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", private Pattern myEmailPattern = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$",
Pattern.CASE_INSENSITIVE); Pattern.CASE_INSENSITIVE);
@Override @Override
public boolean isValid(String theString) { public boolean isValid(String theString) {
return myPattern.matcher(theString).matches(); return myEmailPattern.matcher(theString).matches();
} }
} }

View File

@ -22,6 +22,9 @@ package ca.uhn.fhir.rest.server.interceptor.validation.fields;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.fhirpath.IFhirPath; import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.interceptor.api.Hook;
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.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.ServerOperationInterceptorAdapter; import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
@ -34,7 +37,8 @@ import org.slf4j.LoggerFactory;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class FieldValidatingInterceptor extends ServerOperationInterceptorAdapter { @Interceptor
public class FieldValidatingInterceptor {
public enum ValidatorType { public enum ValidatorType {
EMAIL; EMAIL;
@ -56,15 +60,15 @@ public class FieldValidatingInterceptor extends ServerOperationInterceptorAdapte
myConfig = ConfigLoader.loadJson("classpath:field-validation-rules.json", Map.class); myConfig = ConfigLoader.loadJson("classpath:field-validation-rules.json", Map.class);
} }
@Override @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED)
public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) { public void resourcePreCreate(RequestDetails theRequest, IBaseResource theResource) {
ourLog.debug("Validating address on for create {}, {}", theResource, theRequest); ourLog.debug("Validating address on create for resource {} / request {}", theResource, theRequest);
handleRequest(theRequest, theResource); handleRequest(theRequest, theResource);
} }
@Override @Hook(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED)
public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) { public void resourcePreUpdate(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
ourLog.debug("Validating address on for update {}, {}, {}", theOldResource, theNewResource, theRequest); ourLog.debug("Validating address on update for resource {} / old resource {} / request {}", theOldResource, theNewResource, theRequest);
handleRequest(theRequest, theNewResource); handleRequest(theRequest, theNewResource);
} }

View File

@ -21,6 +21,7 @@ package ca.uhn.fhir.rest.server.interceptor.validation.helpers;
*/ */
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.PropertyModifyingHelper;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBase; import org.hl7.fhir.instance.model.api.IBase;
@ -31,7 +32,7 @@ import java.util.stream.Collectors;
/** /**
* Helper class for working with FHIR Address element * Helper class for working with FHIR Address element
*/ */
public class AddressHelper extends BaseHelper { public class AddressHelper extends PropertyModifyingHelper {
public static final String FIELD_LINE = "line"; public static final String FIELD_LINE = "line";
public static final String FIELD_CITY = "city"; public static final String FIELD_CITY = "city";
@ -46,8 +47,8 @@ public class AddressHelper extends BaseHelper {
public static final String[] ADDRESS_PARTS = {FIELD_CITY, FIELD_DISTRICT, FIELD_STATE, FIELD_POSTAL}; public static final String[] ADDRESS_PARTS = {FIELD_CITY, FIELD_DISTRICT, FIELD_STATE, FIELD_POSTAL};
public AddressHelper(IBase theBase, FhirContext theFhirContext) { public AddressHelper(FhirContext theFhirContext, IBase theBase) {
super(theBase, theFhirContext); super(theFhirContext, theBase);
} }
public String getCountry() { public String getCountry() {

View File

@ -19,13 +19,13 @@ class AddressHelperTest {
HumanName name = new HumanName(); HumanName name = new HumanName();
name.setFamily("Test"); name.setFamily("Test");
final AddressHelper helper = new AddressHelper(name, null); final AddressHelper helper = new AddressHelper(null, name);
assertThrows(IllegalStateException.class, () -> { assertThrows(IllegalStateException.class, () -> {
helper.getCountry(); helper.getCountry();
}); });
assertThrows(IllegalArgumentException.class, () -> { assertThrows(IllegalArgumentException.class, () -> {
new AddressHelper(new StringType("this will blow up"), null); new AddressHelper(null, new StringType("this will blow up"));
}); });
} }
@ -34,7 +34,7 @@ class AddressHelperTest {
Address a = new Address(); Address a = new Address();
a.setCountry("Test"); a.setCountry("Test");
AddressHelper helper = new AddressHelper(a, null); AddressHelper helper = new AddressHelper(null, a);
assertEquals("Test", helper.getCountry()); assertEquals("Test", helper.getCountry());
} }
@ -43,7 +43,7 @@ class AddressHelperTest {
Address a = new Address(); Address a = new Address();
a.setCity("Hammer"); a.setCity("Hammer");
AddressHelper helper = new AddressHelper(a, null); AddressHelper helper = new AddressHelper(null, a);
helper.setDelimiter("; "); helper.setDelimiter("; ");
assertEquals("Hammer", helper.getParts()); assertEquals("Hammer", helper.getParts());
@ -58,7 +58,7 @@ class AddressHelperTest {
a.addLine("Unit 10"); a.addLine("Unit 10");
a.setCity("Hammer"); a.setCity("Hammer");
AddressHelper helper = new AddressHelper(a, null); AddressHelper helper = new AddressHelper(null, a);
assertEquals("Unit 10", helper.getLine()); assertEquals("Unit 10", helper.getLine());
a.addLine("100 Main St."); a.addLine("100 Main St.");
@ -69,7 +69,7 @@ class AddressHelperTest {
void testSetFields() { void testSetFields() {
Address a = new Address(); Address a = new Address();
AddressHelper helper = new AddressHelper(a, ourContext); AddressHelper helper = new AddressHelper(ourContext, a);
helper.addLine("Line 1").addLine("Line 2"); helper.addLine("Line 1").addLine("Line 2");
helper.setCity("Hammer"); helper.setCity("Hammer");
helper.setState("State"); helper.setState("State");

View File

@ -27,17 +27,10 @@ class StandardizingInterceptorTest {
"\t\t\"Person.name.given\" : \"NAME_GIVEN\",\n" + "\t\t\"Person.name.given\" : \"NAME_GIVEN\",\n" +
"\t\t\"Person.telecom.where(system='phone').value\" : \"PHONE\"\n" + "\t\t\"Person.telecom.where(system='phone').value\" : \"PHONE\"\n" +
"\t\t},\n" + "\t\t},\n" +
"\t\"*\" : {\n" +
"\t\t\"telecom.where(system='email').value\" : \"EMAIL\"\n" +
"\t},\n" +
"\t\"Patient\" : {\n" + "\t\"Patient\" : {\n" +
"\t\t\"name.given\" : \"NAME_GIVEN\",\n" + "\t\t\"name.given\" : \"NAME_GIVEN\",\n" +
"\t\t\"telecom.where(system='phone').value\" : \"PHONE\"\n" + "\t\t\"telecom.where(system='phone').value\" : \"PHONE\"\n" +
"\t\t},\n" + "\t\t}\n" +
"\t\"*\" : {\n" +
"\t\t\"telecom.where(system='email').value\" : \"EMAIL\"\n" +
"\t}\n" +
"\n" +
"}"; "}";
private static FhirContext ourCtx = FhirContext.forR4(); private static FhirContext ourCtx = FhirContext.forR4();
@ -74,20 +67,7 @@ class StandardizingInterceptorTest {
myInterceptor.resourcePreUpdate(myRequestDetails, null, p); myInterceptor.resourcePreUpdate(myRequestDetails, null, p);
assertEquals("email@email.com", p.getTelecom().get(0).getValue()); assertEquals(" Email@email.com", p.getTelecom().get(0).getValue(), "Expected email to remain the same");
assertEquals("123-456-7890", p.getTelecom().get(1).getValue()); assertEquals("123-456-7890", p.getTelecom().get(1).getValue());
} }
@Test
public void testUniversalOtherTypes() throws Exception {
Patient p = new Patient();
p.addName().setFamily("FAM").addGiven("GIV");
p.addTelecom().setSystem(ContactPoint.ContactPointSystem.EMAIL).setValue(" Test@TEST.com ");
myInterceptor.resourcePreUpdate(myRequestDetails, null, p);
assertEquals("Giv FAM", p.getName().get(0).getNameAsSingleString());
assertEquals("test@test.com", p.getTelecom().get(0).getValue());
}
} }

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.util;
import ca.uhn.fhir.context.FhirContext;
import org.hl7.fhir.r4.model.Patient;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.hasSize;
import static org.junit.jupiter.api.Assertions.*;
class ExtensionUtilTest {
private static final String EXT_URL = "http://magic.com/extensions";
private static FhirContext ourFhirContext = FhirContext.forR4();
@Test
void testExtensionsWork() {
Patient p1 = new Patient();
assertFalse(ExtensionUtil.hasExtension(p1, EXT_URL));
ExtensionUtil.setExtension(ourFhirContext, p1, EXT_URL, "value");
assertTrue(ExtensionUtil.hasExtension(p1, EXT_URL));
}
}