diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtil.java index 2b66a864054..adc6e11a2c7 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtil.java @@ -41,7 +41,7 @@ import java.util.stream.Stream; import static org.slf4j.LoggerFactory.getLogger; public final class TerserUtil { - + private static final Logger ourLog = getLogger(TerserUtil.class); public static final String FIELD_NAME_IDENTIFIER = "identifier"; @@ -118,15 +118,18 @@ public final class TerserUtil { * Clones specified composite field (collection). Composite field values must conform to the collections * contract. * - * @param theFrom Resource to clone the specified field from - * @param theTo Resource to clone the specified field to - * @param field Field name to be copied + * @param theFrom Resource to clone the specified field from + * @param theTo Resource to clone the specified field to + * @param theField Field name to be copied */ - public static void cloneCompositeField(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo, String field) { + public static void cloneCompositeField(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo, String theField) { FhirTerser terser = theFhirContext.newTerser(); RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theFrom); - BaseRuntimeChildDefinition childDefinition = definition.getChildByName(field); + BaseRuntimeChildDefinition childDefinition = definition.getChildByName(theField); + if (childDefinition == null) { + throw new IllegalArgumentException(String.format("Unable to find child definition %s in %s", theField, theFrom)); + } List theFromFieldValues = childDefinition.getAccessor().getValues(theFrom); List theToFieldValues = childDefinition.getAccessor().getValues(theTo); @@ -136,7 +139,7 @@ public final class TerserUtil { continue; } - IBase newFieldValue = childDefinition.getChildByName(field).newInstance(); + IBase newFieldValue = childDefinition.getChildByName(theField).newInstance(); terser.cloneInto(theFromFieldValue, newFieldValue, true); try { @@ -176,10 +179,27 @@ public final class TerserUtil { }); } + /** + * Merges all fields on the provided instance. theTo will contain a union of all values from theFrom + * instance and theTo instance. + * + * @param theFhirContext Context holding resource definition + * @param theFrom The resource to merge the fields from + * @param theTo The resource to merge the fields into + */ public static void mergeAllFields(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) { mergeFields(theFhirContext, theFrom, theTo, INCLUDE_ALL); } + /** + * Replaces all fields that test positive by the given inclusion strategy. theTo will contain a copy of the + * values from theFrom instance. + * + * @param theFhirContext Context holding resource definition + * @param theFrom The resource to merge the fields from + * @param theTo The resource to merge the fields into + * @param inclusionStrategy Inclusion strategy that checks if a given field should be replaced by checking {@link Predicate#test(Object)} + */ public static void replaceFields(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo, Predicate inclusionStrategy) { FhirTerser terser = theFhirContext.newTerser(); @@ -193,15 +213,33 @@ public final class TerserUtil { } } + /** + * Checks if the field exists on the resource + * + * @param theFhirContext Context holding resource definition + * @param theFieldName Name of the field to check + * @param theInstance Resource instance to check + * @return Returns true if resource definition has a child with the specified name and false otherwise + */ public static boolean fieldExists(FhirContext theFhirContext, String theFieldName, IBaseResource theInstance) { RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance); return definition.getChildByName(theFieldName) != null; } + /** + * Replaces the specified fields on theTo resource with the value from theFrom resource. + * + * @param theFhirContext Context holding resource definition + * @param theFrom The resource to replace the field from + * @param theTo The resource to replace the field on + */ public static void replaceField(FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { replaceField(theFhirContext, theFhirContext.newTerser(), theFieldName, theFrom, theTo); } + /** + * @deprecated Use {@link #replaceField(FhirContext, String, IBaseResource, IBaseResource)} instead + */ public static void replaceField(FhirContext theFhirContext, FhirTerser theTerser, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { replaceField(theFrom, theTo, getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theFrom)); } @@ -209,7 +247,7 @@ public final class TerserUtil { /** * Clears the specified field on the resource provided * - * @param theFhirContext + * @param theFhirContext Context holding resource definition * @param theFieldName * @param theResource */ @@ -223,11 +261,11 @@ public final class TerserUtil { * in case of multiple cardinality. Use {@link #clearField(FhirContext, FhirTerser, String, IBaseResource, IBase...)} * to remove values before setting * - * @param theFhirContext - * @param theTerser - * @param theFieldName - * @param theResource - * @param theValues + * @param theFhirContext Context holding resource definition + * @param theTerser Terser to be used when cloning field values + * @param theFieldName Child field name of the resource to set + * @param theResource The resource to set the values on + * @param theValues The values to set on the resource child field name */ public static void setField(FhirContext theFhirContext, FhirTerser theTerser, String theFieldName, IBaseResource theResource, IBase... theValues) { BaseRuntimeChildDefinition childDefinition = getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theResource); @@ -238,13 +276,33 @@ public final class TerserUtil { mergeFields(theTerser, theResource, childDefinition, theFromFieldValues, theToFieldValues); } - public static void setFieldByFhirPath(FhirContext theFhirContext, FhirTerser theTerser, String theFhirPath, IBaseResource theResource, IBase theValue) { + /** + * Sets the specified value at the FHIR path provided. + * + * @param theTerser The terser that should be used for cloning the field value. + * @param theFhirPath The FHIR path to set the field at + * @param theResource The resource on which the value should be set + * @param theValue The value to set + */ + public static void setFieldByFhirPath(FhirTerser theTerser, String theFhirPath, IBaseResource theResource, IBase theValue) { List theFromFieldValues = theTerser.getValues(theResource, theFhirPath, true, false); for (IBase theFromFieldValue : theFromFieldValues) { theTerser.cloneInto(theFromFieldValue, theValue, true); } } + /** + * Sets the specified value at the FHIR path provided. + * + * @param theFhirContext Context holding resource definition + * @param theFhirPath The FHIR path to set the field at + * @param theResource The resource on which the value should be set + * @param theValue The value to set + */ + public static void setFieldByFhirPath(FhirContext theFhirContext, String theFhirPath, IBaseResource theResource, IBase theValue) { + setFieldByFhirPath(theFhirContext.newTerser(), theFhirPath, theResource, theValue); + } + private static void replaceField(IBaseResource theFrom, IBaseResource theTo, BaseRuntimeChildDefinition childDefinition) { childDefinition.getAccessor().getFirstValueOrNull(theFrom).ifPresent(v -> { childDefinition.getMutator().setValue(theTo, v); @@ -252,10 +310,28 @@ public final class TerserUtil { ); } + /** + * Merges values of all fields except for "identifier" and "meta" from theFrom resource to + * theTo resource. Fields values are compared via the equalsDeep method, or via object identity if this + * method is not available. + * + * @param theFhirContext Context holding resource definition + * @param theFrom Resource to merge the specified field from + * @param theTo Resource to merge the specified field into + */ public static void mergeFieldsExceptIdAndMeta(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo) { mergeFields(theFhirContext, theFrom, theTo, EXCLUDE_IDS_AND_META); } + /** + * Merges values of all field from theFrom resource to theTo resource. Fields + * values are compared via the equalsDeep method, or via object identity if this method is not available. + * + * @param theFhirContext Context holding resource definition + * @param theFrom Resource to merge the specified field from + * @param theTo Resource to merge the specified field into + * @param inclusionStrategy Predicate to test which fields should be merged + */ public static void mergeFields(FhirContext theFhirContext, IBaseResource theFrom, IBaseResource theTo, Predicate inclusionStrategy) { FhirTerser terser = theFhirContext.newTerser(); @@ -273,27 +349,27 @@ public final class TerserUtil { } /** - * Merges value of the specified field from theFrom resource to theTo resource. Fields values are compared via - * the equalsDeep method, or via object identity if this method is not available. + * Merges value of the specified field from theFrom resource to theTo resource. Fields + * values are compared via the equalsDeep method, or via object identity if this method is not available. * - * @param theFhirContext - * @param theFieldName - * @param theFrom - * @param theTo + * @param theFhirContext Context holding resource definition + * @param theFieldName Name of the child filed to merge + * @param theFrom Resource to merge the specified field from + * @param theTo Resource to merge the specified field into */ public static void mergeField(FhirContext theFhirContext, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { mergeField(theFhirContext, theFhirContext.newTerser(), theFieldName, theFrom, theTo); } /** - * Merges value of the specified field from theFrom resource to theTo resource. Fields values are compared via - * the equalsDeep method, or via object identity if this method is not available. + * Merges value of the specified field from theFrom resource to theTo resource. Fields + * values are compared via the equalsDeep method, or via object identity if this method is not available. * - * @param theFhirContext - * @param theTerser - * @param theFieldName - * @param theFrom - * @param theTo + * @param theFhirContext Context holding resource definition + * @param theTerser Terser to be used when cloning the field values + * @param theFieldName Name of the child filed to merge + * @param theFrom Resource to merge the specified field from + * @param theTo Resource to merge the specified field into */ public static void mergeField(FhirContext theFhirContext, FhirTerser theTerser, String theFieldName, IBaseResource theFrom, IBaseResource theTo) { BaseRuntimeChildDefinition childDefinition = getBaseRuntimeChildDefinition(theFhirContext, theFieldName, theFrom); @@ -331,6 +407,14 @@ public final class TerserUtil { } } + /** + * Clones the specified resource. + * + * @param theFhirContext Context holding resource definition + * @param theInstance The instance to be cloned + * @param Base resource type + * @return Returns a cloned instance + */ public static T clone(FhirContext theFhirContext, T theInstance) { RuntimeResourceDefinition definition = theFhirContext.getResourceDefinition(theInstance.getClass()); T retVal = (T) definition.newInstance(); @@ -340,21 +424,56 @@ public final class TerserUtil { return retVal; } + /** + * Creates a new element instance + * + * @param theFhirContext Context holding resource definition + * @param theElementType Element type name + * @param Base element type + * @return Returns a new instance of the element + */ public static T newElement(FhirContext theFhirContext, String theElementType) { BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType); return (T) def.newInstance(); } + /** + * Creates a new element instance + * + * @param theFhirContext Context holding resource definition + * @param theElementType Element type name + * @param theConstructorParam Initialization parameter for the element + * @param Base element type + * @return Returns a new instance of the element with the specified initial value + */ public static T newElement(FhirContext theFhirContext, String theElementType, Object theConstructorParam) { BaseRuntimeElementDefinition def = theFhirContext.getElementDefinition(theElementType); return (T) def.newInstance(theConstructorParam); } + /** + * Creates a new resource definition. + * + * @param theFhirContext Context holding resource definition + * @param theResourceName Name of the resource in the context + * @param Type of the resource + * @return Returns a new instance of the resource + */ public static T newResource(FhirContext theFhirContext, String theResourceName) { RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName); return (T) def.newInstance(); } + + /** + * Creates a new resource definition. + * + * @param theFhirContext Context holding resource definition + * @param theResourceName Name of the resource in the context + * @param theConstructorParam Initialization parameter for the new instance + * @param Type of the resource + * @return Returns a new instance of the resource + */ public static T newResource(FhirContext theFhirContext, String theResourceName, Object theConstructorParam) { RuntimeResourceDefinition def = theFhirContext.getResourceDefinition(theResourceName); return (T) def.newInstance(theConstructorParam); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtilHelper.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtilHelper.java index 667563797d5..97b8f2d7f6c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtilHelper.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/TerserUtilHelper.java @@ -28,22 +28,44 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import java.util.List; /** - * Wrapper class holding context-related instances, and the resource being operated on. + * Wrapper class holding context-related instances, and the resource being operated on. Sample use case is + * + *
{@code
+ * TerserUtilHelper helper = TerserUtilHelper.newHelper(ourFhirContext, "Patient");
+ * helper.setField("identifier.system", "http://org.com/sys");
+ * helper.setField("identifier.value", "123");
+ * ...
+ * Patient patient = helper.getResource();
+ * }
*/ public class TerserUtilHelper { - public static TerserUtilHelper newHelper(FhirContext theFhirContext, String theResource) { - return newHelper(theFhirContext, (IBaseResource) TerserUtil.newResource(theFhirContext, theResource)); + /** + * Factory method for creating a new instance of the wrapper + * + * @param theFhirContext FHIR Context to be used for all further operations + * @param theResourceName Name of the resource type + * @return Returns a new helper instance + */ + public static TerserUtilHelper newHelper(FhirContext theFhirContext, String theResourceName) { + return newHelper(theFhirContext, (IBaseResource) TerserUtil.newResource(theFhirContext, theResourceName)); } + /** + * Factory method for creating a new instance of the wrapper + * + * @param theFhirContext FHIR Context to be used for all further operations + * @param theResource The resource to operate on + * @return Returns a new helper instance + */ public static TerserUtilHelper newHelper(FhirContext theFhirContext, IBaseResource theResource) { TerserUtilHelper retVal = new TerserUtilHelper(theFhirContext, theResource); return retVal; } - FhirContext myContext; - FhirTerser myTerser; - IBaseResource myResource; + private FhirContext myContext; + private FhirTerser myTerser; + private IBaseResource myResource; protected TerserUtilHelper(FhirContext theFhirContext, IBaseResource theResource) { myContext = theFhirContext; @@ -59,7 +81,7 @@ public class TerserUtilHelper { */ public TerserUtilHelper setField(String theField, String theValue) { IBase value = newStringElement(theValue); - TerserUtil.setFieldByFhirPath(myContext, getTerser(), theField, myResource, value); + TerserUtil.setFieldByFhirPath(getTerser(), theField, myResource, value); return this; } @@ -68,10 +90,21 @@ public class TerserUtilHelper { return value; } + /** + * Gets values of the specified field. + * + * @param theField The field to get values from + * @return Returns a collection of values containing values or null if the spefied field doesn't exist + */ public List getFieldValues(String theField) { return TerserUtil.getValues(myContext, myResource, theField); } + /** + * Gets the terser instance, creating one if necessary. + * + * @return Returns the terser + */ public FhirTerser getTerser() { if (myTerser == null) { myTerser = myContext.newTerser(); @@ -98,10 +131,21 @@ public class TerserUtilHelper { return myContext.getResourceDefinition(myResource); } + /** + * Creates a new element + * + * @param theElementName Name of the element to create + * @return Returns a new element + */ public IBase newElement(String theElementName) { return TerserUtil.newElement(myContext, theElementName); } + /** + * Gets context holding resource definition. + * + * @return Returns the current FHIR context. + */ public FhirContext getContext() { return myContext; } diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/LoquateAddressValidatorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/LoquateAddressValidatorTest.java index 9330df27290..9d62b63b06d 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/LoquateAddressValidatorTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/LoquateAddressValidatorTest.java @@ -1,20 +1,23 @@ package ca.uhn.fhir.rest.server.interceptor.validation.address.impl; import ca.uhn.fhir.context.FhirContext; -import com.fasterxml.jackson.core.JsonProcessingException; -import com.fasterxml.jackson.databind.ObjectMapper; import ca.uhn.fhir.rest.server.interceptor.validation.address.AddressValidationException; import ca.uhn.fhir.rest.server.interceptor.validation.address.AddressValidationResult; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.ObjectMapper; import org.hl7.fhir.r4.model.Address; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.springframework.http.HttpEntity; import org.springframework.web.client.RestTemplate; -//import javax.validation.constraints.NotNull; import java.util.Properties; -import static org.junit.jupiter.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.junit.jupiter.api.Assertions.fail; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/MelissaAddressValidatorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/MelissaAddressValidatorTest.java index 020e765f98b..2b229860b74 100644 --- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/MelissaAddressValidatorTest.java +++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/validation/address/impl/MelissaAddressValidatorTest.java @@ -1,9 +1,9 @@ package ca.uhn.fhir.rest.server.interceptor.validation.address.impl; import ca.uhn.fhir.context.FhirContext; -import com.fasterxml.jackson.databind.ObjectMapper; import ca.uhn.fhir.rest.server.interceptor.validation.address.AddressValidationException; import ca.uhn.fhir.rest.server.interceptor.validation.address.AddressValidationResult; +import com.fasterxml.jackson.databind.ObjectMapper; import org.hl7.fhir.r4.model.Address; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test;