diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index 11e66968879..9dab60b4e36 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -21,13 +21,32 @@ package ca.uhn.fhir.context; */ import ca.uhn.fhir.context.RuntimeSearchParam.RuntimeSearchParamStatusEnum; -import ca.uhn.fhir.model.api.*; -import ca.uhn.fhir.model.api.annotation.*; +import ca.uhn.fhir.model.api.BaseIdentifiableElement; +import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.model.api.IDatatype; +import ca.uhn.fhir.model.api.IElement; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.IResourceBlock; +import ca.uhn.fhir.model.api.IValueSetEnumBinder; +import ca.uhn.fhir.model.api.annotation.Block; +import ca.uhn.fhir.model.api.annotation.Child; +import ca.uhn.fhir.model.api.annotation.Compartment; +import ca.uhn.fhir.model.api.annotation.DatatypeDef; +import ca.uhn.fhir.model.api.annotation.ResourceDef; +import ca.uhn.fhir.model.api.annotation.SearchParamDefinition; import ca.uhn.fhir.model.primitive.BoundCodeDt; import ca.uhn.fhir.model.primitive.XhtmlDt; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.util.ReflectionUtil; -import org.hl7.fhir.instance.model.api.*; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseBackboneElement; +import org.hl7.fhir.instance.model.api.IBaseDatatype; +import org.hl7.fhir.instance.model.api.IBaseDatatypeElement; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IBaseXhtml; +import org.hl7.fhir.instance.model.api.ICompositeType; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; import javax.annotation.Nonnull; import java.io.IOException; @@ -37,8 +56,18 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; import java.util.Map.Entry; +import java.util.Properties; +import java.util.Set; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -390,7 +419,7 @@ class ModelScanner { for (String nextName : searchParam.compositeOf()) { RuntimeSearchParam param = nameToParam.get(nextName); if (param == null) { - ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parametr name itself. Valid values are: {}", + ourLog.warn("Search parameter {}.{} declares that it is a composite with compositeOf value '{}' but that is not a valid parameter name itself. Valid values are: {}", theResourceDef.getName(), searchParam.name(), nextName, nameToParam.keySet()); continue; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java index bd8a538121f..c15cb6aa874 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeResourceDefinition.java @@ -20,6 +20,13 @@ package ca.uhn.fhir.context; * #L% */ +import ca.uhn.fhir.model.api.annotation.ResourceDef; +import ca.uhn.fhir.util.UrlUtil; +import org.hl7.fhir.instance.model.api.IAnyResource; +import org.hl7.fhir.instance.model.api.IBase; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IDomainResource; + import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -28,15 +35,6 @@ import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import org.apache.commons.lang3.StringUtils; -import org.hl7.fhir.instance.model.api.IAnyResource; -import org.hl7.fhir.instance.model.api.IBase; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.hl7.fhir.instance.model.api.IDomainResource; - -import ca.uhn.fhir.model.api.annotation.ResourceDef; -import ca.uhn.fhir.util.UrlUtil; - public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefinition { private Class myBaseType; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java index dbae7e49544..cdee81aa052 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/RuntimeSearchParam.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.context; +import ca.uhn.fhir.context.phonetic.IPhoneticEncoder; import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import org.apache.commons.lang3.builder.EqualsBuilder; import org.apache.commons.lang3.builder.HashCodeBuilder; @@ -55,6 +56,7 @@ public class RuntimeSearchParam { private final RuntimeSearchParamStatusEnum myStatus; private final String myUri; private final Map>> myExtensions = new HashMap<>(); + private IPhoneticEncoder myPhoneticEncoder; /** * Constructor @@ -245,7 +247,6 @@ public class RuntimeSearchParam { return myProvidesMembershipInCompartments; } - public enum RuntimeSearchParamStatusEnum { ACTIVE, DRAFT, @@ -253,4 +254,15 @@ public class RuntimeSearchParam { UNKNOWN } + public RuntimeSearchParam setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) { + myPhoneticEncoder = thePhoneticEncoder; + return this; + } + + public String encode(String theString) { + if (myPhoneticEncoder == null || theString == null) { + return theString; + } + return myPhoneticEncoder.encode(theString); + } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/phonetic/ApacheEncoder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/phonetic/ApacheEncoder.java new file mode 100644 index 00000000000..16706cdb5cb --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/phonetic/ApacheEncoder.java @@ -0,0 +1,33 @@ +package ca.uhn.fhir.context.phonetic; + +import org.apache.commons.codec.EncoderException; +import org.apache.commons.codec.StringEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class ApacheEncoder implements IPhoneticEncoder { + private static final Logger ourLog = LoggerFactory.getLogger(ApacheEncoder.class); + + private final String myName; + private final StringEncoder myStringEncoder; + + public ApacheEncoder(String theName, StringEncoder theStringEncoder) { + myName = theName; + myStringEncoder = theStringEncoder; + } + + @Override + public String name() { + return myName; + } + + @Override + public String encode(String theString) { + try { + return myStringEncoder.encode(theString); + } catch (EncoderException e) { + ourLog.error("Failed to encode string " + theString, e); + return theString; + } + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/phonetic/IPhoneticEncoder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/phonetic/IPhoneticEncoder.java new file mode 100644 index 00000000000..b2e316e9a01 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/phonetic/IPhoneticEncoder.java @@ -0,0 +1,12 @@ +package ca.uhn.fhir.context.phonetic; + +public interface IPhoneticEncoder { + String name(); + + /** + * Encode the provided string using a phonetic encoder like Soundex + * @param theString + * @return + */ + String encode(String theString); +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/phonetic/PhoneticEncoderEnum.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/phonetic/PhoneticEncoderEnum.java new file mode 100644 index 00000000000..b535ef694fc --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/phonetic/PhoneticEncoderEnum.java @@ -0,0 +1,33 @@ +package ca.uhn.fhir.context.phonetic; + +import org.apache.commons.codec.language.Caverphone1; +import org.apache.commons.codec.language.Caverphone2; +import org.apache.commons.codec.language.ColognePhonetic; +import org.apache.commons.codec.language.DoubleMetaphone; +import org.apache.commons.codec.language.MatchRatingApproachEncoder; +import org.apache.commons.codec.language.Metaphone; +import org.apache.commons.codec.language.Nysiis; +import org.apache.commons.codec.language.RefinedSoundex; +import org.apache.commons.codec.language.Soundex; + +public enum PhoneticEncoderEnum { + CAVERPHONE1(new ApacheEncoder("CAVERPHONE1", new Caverphone1())), + CAVERPHONE2(new ApacheEncoder("CAVERPHONE2", new Caverphone2())), + COLOGNE(new ApacheEncoder("COLOGNE", new ColognePhonetic())), + DOUBLE_METAPHONE(new ApacheEncoder("DOUBLE_METAPHONE", new DoubleMetaphone())), + MATCH_RATING_APPROACH(new ApacheEncoder("MATCH_RATING_APPROACH", new MatchRatingApproachEncoder())), + METAPHONE(new ApacheEncoder("METAPHONE", new Metaphone())), + NYSIIS(new ApacheEncoder("NYSIIS", new Nysiis())), + REFINED_SOUNDEX(new ApacheEncoder("REFINED_SOUNDEX", new RefinedSoundex())), + SOUNDEX(new ApacheEncoder("SOUNDEX", new Soundex())); + + private final IPhoneticEncoder myPhoneticEncoder; + + PhoneticEncoderEnum(IPhoneticEncoder thePhoneticEncoder) { + myPhoneticEncoder = thePhoneticEncoder; + } + + public IPhoneticEncoder getPhoneticEncoder() { + return myPhoneticEncoder; + } +} diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1936-phonetic-search.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1936-phonetic-search.yaml new file mode 100644 index 00000000000..a872ad22caf --- /dev/null +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/5_1_0/1936-phonetic-search.yaml @@ -0,0 +1,4 @@ +--- +type: add +issue: 1936 +title: "Phonetic name search is now supported via ISearchParamRegistry.setStringEncoder()." diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_rules.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_rules.md index d6722b08f6b..43f097b77aa 100644 --- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_rules.md +++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_jpa_empi/empi_rules.md @@ -158,46 +158,6 @@ The following metrics are currently supported: - - STRING - matcher - - Match the values as strings. This matcher should be used with tokens (e.g. gender). - - MCTAVISH = McTavish when exact = false, MCTAVISH != McTavish when exact = true - - - SUBSTRING - matcher - - True if one string starts with the other. - - Bill = Billy, Egbert = Bert - - - METAPHONE - matcher - - Apache Metaphone - - Dury = Durie, Allsop != Allsob, Smith != Schmidt - - - DOUBLE_METAPHONE - matcher - - Apache Double Metaphone - - Dury = Durie, Allsop = Allsob, Smith != Schmidt - - - SOUNDEX - matcher - - Apache Soundex - - Jon = John, Thomas != Tom - CAVERPHONE1 matcher @@ -214,6 +174,78 @@ The following metrics are currently supported: Gail = Gael, Gail = Gale, Thomas != Tom + + COLOGNE + matcher + + Apache Cologne Phonetic + + + + + DOUBLE_METAPHONE + matcher + + Apache Double Metaphone + + Dury = Durie, Allsop = Allsob, Smith != Schmidt + + + MATCH_RATING_APPROACH + matcher + + Apache Match Rating Approach Encoder + + + + + METAPHONE + matcher + + Apache Metaphone + + Dury = Durie, Allsop != Allsob, Smith != Schmidt + + + NYSIIS + matcher + + Apache Nysiis + + + + + REFINED_SOUNDEX + matcher + + Apache Refined Soundex + + + + + SOUNDEX + matcher + + Apache Soundex + + Jon = John, Thomas != Tom + + + STRING + matcher + + Match the values as strings. This matcher should be used with tokens (e.g. gender). + + MCTAVISH = McTavish when exact = false, MCTAVISH != McTavish when exact = true + + + SUBSTRING + matcher + + True if one string starts with the other. + + Bill = Billy, Egbert = Bert + DATE matcher diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java index 385770634b3..282afbfde24 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/IPredicateBuilder.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.model.api.IQueryParameterType; @@ -30,7 +31,7 @@ import java.util.List; public interface IPredicateBuilder { @Nullable Predicate addPredicate(String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, List theList, SearchFilterParser.CompareOperation operation, RequestPartitionId theRequestPartitionId); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java index b6a4c9edd5b..26b5ffe5bf0 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilder.java @@ -20,13 +20,22 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; -import ca.uhn.fhir.jpa.model.entity.*; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; +import ca.uhn.fhir.jpa.model.entity.ResourceLink; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.rest.api.server.RequestDetails; -import javax.persistence.criteria.*; +import javax.persistence.criteria.CriteriaBuilder; +import javax.persistence.criteria.From; +import javax.persistence.criteria.Join; +import javax.persistence.criteria.Predicate; +import javax.persistence.criteria.Subquery; import java.util.ArrayList; import java.util.Collection; import java.util.List; @@ -56,40 +65,40 @@ public class PredicateBuilder { myPredicateBuilderUri = thePredicateBuilderFactory.newPredicateBuilderUri(theSearchBuilder); } - void addPredicateCoords(String theResourceName, String theParamName, List theNextAnd, RequestPartitionId theRequestPartitionId) { - myPredicateBuilderCoords.addPredicate(theResourceName, theParamName, theNextAnd, null, theRequestPartitionId); + void addPredicateCoords(String theResourceName, RuntimeSearchParam theSearchParam, List theNextAnd, RequestPartitionId theRequestPartitionId) { + myPredicateBuilderCoords.addPredicate(theResourceName, theSearchParam, theNextAnd, null, theRequestPartitionId); } - Predicate addPredicateDate(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - return myPredicateBuilderDate.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); + Predicate addPredicateDate(String theResourceName, RuntimeSearchParam theSearchParam, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderDate.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId); } - Predicate addPredicateNumber(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - return myPredicateBuilderNumber.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); + Predicate addPredicateNumber(String theResourceName, RuntimeSearchParam theSearchParam, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderNumber.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId); } - Predicate addPredicateQuantity(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - return myPredicateBuilderQuantity.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); + Predicate addPredicateQuantity(String theResourceName, RuntimeSearchParam theSearchParam, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderQuantity.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId); } - void addPredicateString(String theResourceName, String theParamName, List theNextAnd, RequestPartitionId theRequestPartitionId) { - myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId); + void addPredicateString(String theResourceName, RuntimeSearchParam theSearchParam, List theNextAnd, RequestPartitionId theRequestPartitionId) { + myPredicateBuilderString.addPredicate(theResourceName, theSearchParam, theNextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId); } - Predicate addPredicateString(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - return myPredicateBuilderString.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); + Predicate addPredicateString(String theResourceName,RuntimeSearchParam theSearchParam, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderString.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId); } void addPredicateTag(List> theAndOrParams, String theParamName, RequestPartitionId theRequestPartitionId) { myPredicateBuilderTag.addPredicateTag(theAndOrParams, theParamName, theRequestPartitionId); } - Predicate addPredicateToken(String theResourceName, String theParamName, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - return myPredicateBuilderToken.addPredicate(theResourceName, theParamName, theNextAnd, theOperation, theRequestPartitionId); + Predicate addPredicateToken(String theResourceName, RuntimeSearchParam theSearchParam, List theNextAnd, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderToken.addPredicate(theResourceName, theSearchParam, theNextAnd, theOperation, theRequestPartitionId); } - Predicate addPredicateUri(String theResourceName, String theName, List theSingletonList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - return myPredicateBuilderUri.addPredicate(theResourceName, theName, theSingletonList, theOperation, theRequestPartitionId); + Predicate addPredicateUri(String theResourceName, RuntimeSearchParam theSearchParam, List theSingletonList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderUri.addPredicate(theResourceName, theSearchParam, theSingletonList, theOperation, theRequestPartitionId); } public void searchForIdsWithAndOr(String theResourceName, String theNextParamName, List> theAndOrParams, RequestDetails theRequest, RequestPartitionId theRequestPartitionId) { @@ -112,12 +121,12 @@ public class PredicateBuilder { return myPredicateBuilderResourceId.addPredicateResourceId(theValues, theResourceName, theOperation, theRequestPartitionId); } - Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From theStringJoin, RequestPartitionId theRequestPartitionId) { - return myPredicateBuilderString.createPredicateString(theLeftValue, theResourceName, theName, theBuilder, theStringJoin, theRequestPartitionId); + Predicate createPredicateString(IQueryParameterType theLeftValue, String theResourceName, RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From theStringJoin, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderString.createPredicateString(theLeftValue, theResourceName, theSearchParam, theBuilder, theStringJoin, theRequestPartitionId); } - Collection createPredicateToken(List theTokens, String theResourceName, String theName, CriteriaBuilder theBuilder, From theTokenJoin, RequestPartitionId theRequestPartitionId) { - return myPredicateBuilderToken.createPredicateToken(theTokens, theResourceName, theName, theBuilder, theTokenJoin, theRequestPartitionId); + Collection createPredicateToken(List theTokens, String theResourceName, RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From theTokenJoin, RequestPartitionId theRequestPartitionId) { + return myPredicateBuilderToken.createPredicateToken(theTokens, theResourceName, theSearchParam, theBuilder, theTokenJoin, theRequestPartitionId); } Predicate createPredicateDate(IQueryParameterType theLeftValue, String theResourceName, String theName, CriteriaBuilder theBuilder, From theDateJoin, RequestPartitionId theRequestPartitionId) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java index 3e64048178f..1c3b2e74ca8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderCoords.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamCoords; @@ -55,7 +56,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre private Predicate createPredicateCoords(IQueryParameterType theParam, String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From theFrom, RequestPartitionId theRequestPartitionId) { @@ -119,7 +120,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre longitudePredicate = longitudePredicateFromBox(theBuilder, theFrom, box); } Predicate singleCode = theBuilder.and(latitudePredicate, longitudePredicate); - return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); + return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theSearchParam.getName(), theFrom, singleCode, theRequestPartitionId); } private Predicate latitudePredicateFromBox(CriteriaBuilder theBuilder, From theFrom, SearchBox theBox) { @@ -145,14 +146,14 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre @Override public Predicate addPredicate(String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, List theList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - From join = myQueryStack.createJoin(SearchBuilderJoinEnum.COORDS, theParamName); + From join = myQueryStack.createJoin(SearchBuilderJoinEnum.COORDS, theSearchParam.getName()); if (theList.get(0).getMissing() != null) { - addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); + addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId); return null; } @@ -163,7 +164,7 @@ public class PredicateBuilderCoords extends BasePredicateBuilder implements IPre Predicate singleCode = createPredicateCoords(nextOr, theResourceName, - theParamName, + theSearchParam, myCriteriaBuilder, join, theRequestPartitionId); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java index 702819eaa9d..100927c2300 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderDate.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate; @@ -56,27 +57,28 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi @Override public Predicate addPredicate(String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, List theList, SearchFilterParser.CompareOperation operation, RequestPartitionId theRequestPartitionId) { + String paramName = theSearchParam.getName(); boolean newJoin = false; if (myJoinMap == null) { myJoinMap = new HashMap<>(); } - String key = theResourceName + " " + theParamName; + String key = theResourceName + " " + paramName; From join = myJoinMap.get(key); if (join == null) { - join = myQueryStack.createJoin(SearchBuilderJoinEnum.DATE, theParamName); + join = myQueryStack.createJoin(SearchBuilderJoinEnum.DATE, paramName); myJoinMap.put(key, join); newJoin = true; } if (theList.get(0).getMissing() != null) { Boolean missing = theList.get(0).getMissing(); - addPredicateParamMissingForNonReference(theResourceName, theParamName, missing, join, theRequestPartitionId); + addPredicateParamMissingForNonReference(theResourceName, paramName, missing, join, theRequestPartitionId); return null; } @@ -94,7 +96,7 @@ public class PredicateBuilderDate extends BasePredicateBuilder implements IPredi Predicate orPredicates = myCriteriaBuilder.or(toArray(codePredicates)); if (newJoin) { - Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, orPredicates, theRequestPartitionId); + Predicate identityAndValuePredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, orPredicates, theRequestPartitionId); myQueryStack.addPredicateWithImplicitTypeSelection(identityAndValuePredicate); } else { myQueryStack.addPredicateWithImplicitTypeSelection(orPredicates); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java index 24466e085ff..6359fef84d4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderNumber.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamNumber; @@ -51,15 +52,15 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB @Override public Predicate addPredicate(String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, List theList, SearchFilterParser.CompareOperation operation, RequestPartitionId theRequestPartitionId) { - From join = myQueryStack.createJoin(SearchBuilderJoinEnum.NUMBER, theParamName); + From join = myQueryStack.createJoin(SearchBuilderJoinEnum.NUMBER, theSearchParam.getName()); if (theList.get(0).getMissing() != null) { - addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); + addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId); return null; } @@ -97,8 +98,8 @@ class PredicateBuilderNumber extends BasePredicateBuilder implements IPredicateB String invalidMessageName = "invalidNumberPrefix"; - Predicate predicateNumeric = createPredicateNumeric(theResourceName, theParamName, join, myCriteriaBuilder, nextOr, prefix, value, fromObj, invalidMessageName, theRequestPartitionId); - Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, predicateNumeric, theRequestPartitionId); + Predicate predicateNumeric = createPredicateNumeric(theResourceName, theSearchParam.getName(), join, myCriteriaBuilder, nextOr, prefix, value, fromObj, invalidMessageName, theRequestPartitionId); + Predicate predicateOuter = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theSearchParam.getName(), join, predicateNumeric, theRequestPartitionId); codePredicates.add(predicateOuter); } else { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java index 2a98b9f7711..4096f478ab7 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderQuantity.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; @@ -52,15 +53,15 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat @Override public Predicate addPredicate(String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, List theList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - From join = myQueryStack.createJoin(SearchBuilderJoinEnum.QUANTITY, theParamName); + From join = myQueryStack.createJoin(SearchBuilderJoinEnum.QUANTITY, theSearchParam.getName()); if (theList.get(0).getMissing() != null) { - addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); + addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId); return null; } @@ -68,7 +69,7 @@ class PredicateBuilderQuantity extends BasePredicateBuilder implements IPredicat addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); for (IQueryParameterType nextOr : theList) { - Predicate singleCode = createPredicateQuantity(nextOr, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, theRequestPartitionId); + Predicate singleCode = createPredicateQuantity(nextOr, theResourceName, theSearchParam.getName(), myCriteriaBuilder, join, theOperation, theRequestPartitionId); codePredicates.add(singleCode); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java index 3703a360fa4..be9c9d5314a 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java @@ -62,7 +62,6 @@ import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.api.server.storage.ResourcePersistentId; import ca.uhn.fhir.rest.param.CompositeParam; import ca.uhn.fhir.rest.param.DateParam; -import ca.uhn.fhir.rest.param.HasOrListParam; import ca.uhn.fhir.rest.param.HasParam; import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.ParameterUtil; @@ -599,12 +598,12 @@ class PredicateBuilderReference extends BasePredicateBuilder { switch (nextParamDef.getParamType()) { case DATE: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateDate(theResourceName, theParamName, nextAnd, null, theRequestPartitionId); + myPredicateBuilder.addPredicateDate(theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId); } break; case QUANTITY: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateQuantity(theResourceName, theParamName, nextAnd, null, theRequestPartitionId); + myPredicateBuilder.addPredicateQuantity(theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId); } break; case REFERENCE: @@ -614,21 +613,21 @@ class PredicateBuilderReference extends BasePredicateBuilder { break; case STRING: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateString(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId); + myPredicateBuilder.addPredicateString(theResourceName, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.sw, theRequestPartitionId); } break; case TOKEN: for (List nextAnd : theAndOrParams) { if ("Location.position".equals(nextParamDef.getPath())) { - myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd, theRequestPartitionId); + myPredicateBuilder.addPredicateCoords(theResourceName, nextParamDef, nextAnd, theRequestPartitionId); } else { - myPredicateBuilder.addPredicateToken(theResourceName, theParamName, nextAnd, null, theRequestPartitionId); + myPredicateBuilder.addPredicateToken(theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId); } } break; case NUMBER: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateNumber(theResourceName, theParamName, nextAnd, null, theRequestPartitionId); + myPredicateBuilder.addPredicateNumber(theResourceName, nextParamDef, nextAnd, null, theRequestPartitionId); } break; case COMPOSITE: @@ -638,14 +637,14 @@ class PredicateBuilderReference extends BasePredicateBuilder { break; case URI: for (List nextAnd : theAndOrParams) { - myPredicateBuilder.addPredicateUri(theResourceName, theParamName, nextAnd, SearchFilterParser.CompareOperation.eq, theRequestPartitionId); + myPredicateBuilder.addPredicateUri(theResourceName, nextParamDef, nextAnd, SearchFilterParser.CompareOperation.eq, theRequestPartitionId); } break; case HAS: case SPECIAL: for (List nextAnd : theAndOrParams) { if ("Location.position".equals(nextParamDef.getPath())) { - myPredicateBuilder.addPredicateCoords(theResourceName, theParamName, nextAnd, theRequestPartitionId); + myPredicateBuilder.addPredicateCoords(theResourceName, nextParamDef, nextAnd, theRequestPartitionId); } } break; @@ -755,13 +754,13 @@ class PredicateBuilderReference extends BasePredicateBuilder { } else { RestSearchParameterTypeEnum typeEnum = searchParam.getParamType(); if (typeEnum == RestSearchParameterTypeEnum.URI) { - return myPredicateBuilder.addPredicateUri(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); + return myPredicateBuilder.addPredicateUri(theResourceName, searchParam, Collections.singletonList(new UriParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.STRING) { - return myPredicateBuilder.addPredicateString(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); + return myPredicateBuilder.addPredicateString(theResourceName, searchParam, Collections.singletonList(new StringParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.DATE) { - return myPredicateBuilder.addPredicateDate(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new DateParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); + return myPredicateBuilder.addPredicateDate(theResourceName, searchParam, Collections.singletonList(new DateParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.NUMBER) { - return myPredicateBuilder.addPredicateNumber(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); + return myPredicateBuilder.addPredicateNumber(theResourceName, searchParam, Collections.singletonList(new NumberParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.REFERENCE) { String paramName = theFilter.getParamPath().getName(); SearchFilterParser.CompareOperation operation = theFilter.getOperation(); @@ -771,7 +770,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { ReferenceParam referenceParam = new ReferenceParam(resourceType, chain, value); return addPredicate(theResourceName, paramName, Collections.singletonList(referenceParam), operation, theRequest, theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.QUANTITY) { - return myPredicateBuilder.addPredicateQuantity(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); + return myPredicateBuilder.addPredicateQuantity(theResourceName, searchParam, Collections.singletonList(new QuantityParam(theFilter.getValue())), theFilter.getOperation(), theRequestPartitionId); } else if (typeEnum == RestSearchParameterTypeEnum.COMPOSITE) { throw new InvalidRequestException("Composite search parameters not currently supported with _filter clauses"); } else if (typeEnum == RestSearchParameterTypeEnum.TOKEN) { @@ -780,7 +779,7 @@ class PredicateBuilderReference extends BasePredicateBuilder { null, null, theFilter.getValue()); - return myPredicateBuilder.addPredicateToken(theResourceName, theFilter.getParamPath().getName(), Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId); + return myPredicateBuilder.addPredicateToken(theResourceName, searchParam, Collections.singletonList(param), theFilter.getOperation(), theRequestPartitionId); } } return null; @@ -1033,13 +1032,13 @@ class PredicateBuilderReference extends BasePredicateBuilder { switch (theParam.getParamType()) { case STRING: { From stringJoin = theRoot.join("myParamsString", JoinType.INNER); - retVal = myPredicateBuilder.createPredicateString(leftValue, theResourceName, theParam.getName(), myCriteriaBuilder, stringJoin, theRequestPartitionId); + retVal = myPredicateBuilder.createPredicateString(leftValue, theResourceName, theParam, myCriteriaBuilder, stringJoin, theRequestPartitionId); break; } case TOKEN: { From tokenJoin = theRoot.join("myParamsToken", JoinType.INNER); List tokens = Collections.singletonList(leftValue); - Collection tokenPredicates = myPredicateBuilder.createPredicateToken(tokens, theResourceName, theParam.getName(), myCriteriaBuilder, tokenJoin, theRequestPartitionId); + Collection tokenPredicates = myPredicateBuilder.createPredicateToken(tokens, theResourceName, theParam, myCriteriaBuilder, tokenJoin, theRequestPartitionId); retVal = myCriteriaBuilder.and(tokenPredicates.toArray(new Predicate[0])); break; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java index 4b4266e19ac..727ec2b1de8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderString.java @@ -20,8 +20,8 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.model.RequestPartitionId; -import ca.uhn.fhir.jpa.api.config.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.model.api.IPrimitiveDatatype; @@ -31,7 +31,6 @@ import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; import ca.uhn.fhir.util.StringUtil; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; @@ -44,25 +43,21 @@ import java.util.List; @Component @Scope("prototype") class PredicateBuilderString extends BasePredicateBuilder implements IPredicateBuilder { - - @Autowired - DaoConfig myDaoConfig; - PredicateBuilderString(SearchBuilder theSearchBuilder) { super(theSearchBuilder); } @Override public Predicate addPredicate(String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, List theList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { - From join = myQueryStack.createJoin(SearchBuilderJoinEnum.STRING, theParamName); + From join = myQueryStack.createJoin(SearchBuilderJoinEnum.STRING, theSearchParam.getName()); if (theList.get(0).getMissing() != null) { - addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); + addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId); return null; } @@ -70,7 +65,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); for (IQueryParameterType nextOr : theList) { - Predicate singleCode = createPredicateString(nextOr, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, theRequestPartitionId); + Predicate singleCode = createPredicateString(nextOr, theResourceName, theSearchParam, myCriteriaBuilder, join, theOperation, theRequestPartitionId); codePredicates.add(singleCode); } @@ -83,13 +78,13 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB public Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From theFrom, RequestPartitionId theRequestPartitionId) { return createPredicateString(theParameter, theResourceName, - theParamName, + theSearchParam, theBuilder, theFrom, null, @@ -98,12 +93,13 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB private Predicate createPredicateString(IQueryParameterType theParameter, String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From theFrom, SearchFilterParser.CompareOperation operation, RequestPartitionId theRequestPartitionId) { String rawSearchTerm; + String paramName = theSearchParam.getName(); if (theParameter instanceof TokenParam) { TokenParam id = (TokenParam) theParameter; if (!id.isText()) { @@ -117,6 +113,8 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB if (!myDaoConfig.isAllowContainsSearches()) { throw new MethodNotAllowedException(":contains modifier is disabled on this server"); } + } else { + rawSearchTerm = theSearchParam.encode(rawSearchTerm); } } else if (theParameter instanceof IPrimitiveDatatype) { IPrimitiveDatatype id = (IPrimitiveDatatype) theParameter; @@ -126,7 +124,7 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB } if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { - throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + throw new InvalidRequestException("Parameter[" + paramName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); } @@ -152,12 +150,12 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB singleCode = theBuilder.and(singleCode, exactCode); } - return combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); + return combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); } boolean exactMatch = theParameter instanceof StringParam && ((StringParam) theParameter).isExact(); if (exactMatch) { // Exact match - Long hash = ResourceIndexedSearchParamString.calculateHashExact(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, rawSearchTerm); + Long hash = ResourceIndexedSearchParamString.calculateHashExact(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName, rawSearchTerm); return theBuilder.equal(theFrom.get("myHashExact").as(Long.class), hash); } else { // Normalized Match @@ -185,34 +183,34 @@ class PredicateBuilderString extends BasePredicateBuilder implements IPredicateB Predicate predicate; if ((operation == null) || (operation == SearchFilterParser.CompareOperation.sw)) { - Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); + Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, paramName, normalizedString); Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); predicate = theBuilder.and(hashCode, singleCode); } else if ((operation == SearchFilterParser.CompareOperation.ew) || (operation == SearchFilterParser.CompareOperation.co)) { Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.eq) { - Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, theParamName, normalizedString); + Long hash = ResourceIndexedSearchParamString.calculateHashNormalized(getPartitionSettings(), theRequestPartitionId, myDaoConfig.getModelConfig(), theResourceName, paramName, normalizedString); Predicate hashCode = theBuilder.equal(theFrom.get("myHashNormalizedPrefix").as(Long.class), hash); Predicate singleCode = theBuilder.like(theFrom.get("myValueNormalized").as(String.class), normalizedString); predicate = theBuilder.and(hashCode, singleCode); } else if (operation == SearchFilterParser.CompareOperation.ne) { Predicate singleCode = theBuilder.notEqual(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.gt) { Predicate singleCode = theBuilder.greaterThan(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.lt) { Predicate singleCode = theBuilder.lessThan(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.ge) { Predicate singleCode = theBuilder.greaterThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); } else if (operation == SearchFilterParser.CompareOperation.le) { Predicate singleCode = theBuilder.lessThanOrEqualTo(theFrom.get("myValueNormalized").as(String.class), likeExpression); - predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, theFrom, singleCode, theRequestPartitionId); + predicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, theFrom, singleCode, theRequestPartitionId); } else { throw new IllegalArgumentException("Don't yet know how to handle operation " + operation + " on a string"); } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java index cd4fe0e4079..a34ec65a7ab 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderToken.java @@ -22,7 +22,6 @@ package ca.uhn.fhir.jpa.dao.predicate; import ca.uhn.fhir.context.BaseRuntimeChildDefinition; import ca.uhn.fhir.context.BaseRuntimeDeclaredChildDefinition; -import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.support.ValueSetExpansionOptions; import ca.uhn.fhir.interceptor.model.RequestPartitionId; @@ -31,7 +30,6 @@ import ca.uhn.fhir.jpa.model.entity.BaseResourceIndexedSearchParam; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.searchparam.extractor.BaseSearchParamExtractor; -import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.term.api.ITermReadSvc; import ca.uhn.fhir.model.api.IQueryParameterType; import ca.uhn.fhir.model.base.composite.BaseCodingDt; @@ -72,8 +70,6 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu @Autowired private ITermReadSvc myTerminologySvc; @Autowired - private ISearchParamRegistry mySearchParamRegistry; - @Autowired private ModelConfig myModelConfig; PredicateBuilderToken(SearchBuilder theSearchBuilder, PredicateBuilder thePredicateBuilder) { @@ -83,14 +79,14 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu @Override public Predicate addPredicate(String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, List theList, SearchFilterParser.CompareOperation theOperation, RequestPartitionId theRequestPartitionId) { if (theList.get(0).getMissing() != null) { - From join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theParamName); - addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); + From join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theSearchParam.getName()); + addPredicateParamMissingForNonReference(theResourceName, theSearchParam.getName(), theList.get(0).getMissing(), join, theRequestPartitionId); return null; } @@ -104,8 +100,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu if (id.isText()) { // Check whether the :text modifier is actually enabled here - RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam(theResourceName, theParamName); - boolean tokenTextIndexingEnabled = BaseSearchParamExtractor.tokenTextIndexingEnabledForSearchParam(myModelConfig, param); + boolean tokenTextIndexingEnabled = BaseSearchParamExtractor.tokenTextIndexingEnabledForSearchParam(myModelConfig, theSearchParam); if (!tokenTextIndexingEnabled) { String msg; if (myModelConfig.isSuppressStringIndexingInTokens()) { @@ -116,7 +111,7 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu throw new MethodNotAllowedException(msg); } - myPredicateBuilder.addPredicateString(theResourceName, theParamName, theList, theRequestPartitionId); + myPredicateBuilder.addPredicateString(theResourceName, theSearchParam, theList, theRequestPartitionId); break; } } @@ -128,10 +123,10 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu return null; } - From join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theParamName); + From join = myQueryStack.createJoin(SearchBuilderJoinEnum.TOKEN, theSearchParam.getName()); addPartitionIdPredicate(theRequestPartitionId, join, codePredicates); - Collection singleCode = createPredicateToken(tokens, theResourceName, theParamName, myCriteriaBuilder, join, theOperation, theRequestPartitionId); + Collection singleCode = createPredicateToken(tokens, theResourceName, theSearchParam, myCriteriaBuilder, join, theOperation, theRequestPartitionId); assert singleCode != null; codePredicates.addAll(singleCode); @@ -144,14 +139,14 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu public Collection createPredicateToken(Collection theParameters, String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From theFrom, RequestPartitionId theRequestPartitionId) { return createPredicateToken( theParameters, theResourceName, - theParamName, + theSearchParam, theBuilder, theFrom, null, @@ -160,12 +155,13 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu private Collection createPredicateToken(Collection theParameters, String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, CriteriaBuilder theBuilder, From theFrom, SearchFilterParser.CompareOperation operation, RequestPartitionId theRequestPartitionId) { final List codes = new ArrayList<>(); + String paramName = theSearchParam.getName(); TokenParamModifier modifier = null; for (IQueryParameterType nextParameter : theParameters) { @@ -195,12 +191,12 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu if (system != null && system.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { throw new InvalidRequestException( - "Parameter[" + theParamName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system); + "Parameter[" + paramName + "] has system (" + system.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + system); } if (code != null && code.length() > ResourceIndexedSearchParamToken.MAX_LENGTH) { throw new InvalidRequestException( - "Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code); + "Parameter[" + paramName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code); } /* @@ -210,12 +206,12 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu if (modifier == TokenParamModifier.IN) { codes.addAll(myTerminologySvc.expandValueSet(null, code)); } else if (modifier == TokenParamModifier.ABOVE) { - system = determineSystemIfMissing(theParamName, code, system); - validateHaveSystemAndCodeForToken(theParamName, code, system); + system = determineSystemIfMissing(theSearchParam, code, system); + validateHaveSystemAndCodeForToken(paramName, code, system); codes.addAll(myTerminologySvc.findCodesAbove(system, code)); } else if (modifier == TokenParamModifier.BELOW) { - system = determineSystemIfMissing(theParamName, code, system); - validateHaveSystemAndCodeForToken(theParamName, code, system); + system = determineSystemIfMissing(theSearchParam, code, system); + validateHaveSystemAndCodeForToken(paramName, code, system); codes.addAll(myTerminologySvc.findCodesBelow(system, code)); } else { codes.add(new VersionIndependentConcept(system, code)); @@ -240,32 +236,30 @@ class PredicateBuilderToken extends BasePredicateBuilder implements IPredicateBu // System only List systemOnlyCodes = sortedCodesList.stream().filter(t -> isBlank(t.getCode())).collect(Collectors.toList()); if (!systemOnlyCodes.isEmpty()) { - retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemOnlyCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_ONLY, theRequestPartitionId)); + retVal.add(addPredicate(theResourceName, paramName, theBuilder, theFrom, systemOnlyCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_ONLY, theRequestPartitionId)); } // Code only List codeOnlyCodes = sortedCodesList.stream().filter(t -> t.getSystem() == null).collect(Collectors.toList()); if (!codeOnlyCodes.isEmpty()) { - retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, codeOnlyCodes, modifier, SearchBuilderTokenModeEnum.VALUE_ONLY, theRequestPartitionId)); + retVal.add(addPredicate(theResourceName, paramName, theBuilder, theFrom, codeOnlyCodes, modifier, SearchBuilderTokenModeEnum.VALUE_ONLY, theRequestPartitionId)); } // System and code List systemAndCodeCodes = sortedCodesList.stream().filter(t -> isNotBlank(t.getCode()) && t.getSystem() != null).collect(Collectors.toList()); if (!systemAndCodeCodes.isEmpty()) { - retVal.add(addPredicate(theResourceName, theParamName, theBuilder, theFrom, systemAndCodeCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_AND_VALUE, theRequestPartitionId)); + retVal.add(addPredicate(theResourceName, paramName, theBuilder, theFrom, systemAndCodeCodes, modifier, SearchBuilderTokenModeEnum.SYSTEM_AND_VALUE, theRequestPartitionId)); } return retVal; } - private String determineSystemIfMissing(String theParamName, String code, String theSystem) { + private String determineSystemIfMissing(RuntimeSearchParam theSearchParam, String code, String theSystem) { String retVal = theSystem; if (retVal == null) { - RuntimeResourceDefinition resourceDef = myContext.getResourceDefinition(myResourceName); - RuntimeSearchParam param = mySearchParamRegistry.getSearchParamByName(resourceDef, theParamName); - if (param != null) { + if (theSearchParam != null) { Set valueSetUris = Sets.newHashSet(); - for (String nextPath : param.getPathsSplit()) { + for (String nextPath : theSearchParam.getPathsSplit()) { BaseRuntimeChildDefinition def = myContext.newTerser().getDefinition(myResourceType, nextPath); if (def instanceof BaseRuntimeDeclaredChildDefinition) { String valueSet = ((BaseRuntimeDeclaredChildDefinition) def).getBindingValueSet(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java index 52f7503d1a2..1e4673b05fa 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderUri.java @@ -20,6 +20,7 @@ package ca.uhn.fhir.jpa.dao.predicate; * #L% */ +import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.interceptor.model.RequestPartitionId; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.dao.data.IResourceIndexedSearchParamUriDao; @@ -53,15 +54,16 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil @Override public Predicate addPredicate(String theResourceName, - String theParamName, + RuntimeSearchParam theSearchParam, List theList, SearchFilterParser.CompareOperation operation, RequestPartitionId theRequestPartitionId) { - From join = myQueryStack.createJoin(SearchBuilderJoinEnum.URI, theParamName); + String paramName = theSearchParam.getName(); + From join = myQueryStack.createJoin(SearchBuilderJoinEnum.URI, paramName); if (theList.get(0).getMissing() != null) { - addPredicateParamMissingForNonReference(theResourceName, theParamName, theList.get(0).getMissing(), join, theRequestPartitionId); + addPredicateParamMissingForNonReference(theResourceName, paramName, theList.get(0).getMissing(), join, theRequestPartitionId); return null; } @@ -91,8 +93,8 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil * * If we ever need to make this more efficient, lucene could certainly be used as an optimization. */ - ourLog.info("Searching for candidate URI:above parameters for Resource[{}] param[{}]", myResourceName, theParamName); - Collection candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, theParamName); + ourLog.info("Searching for candidate URI:above parameters for Resource[{}] param[{}]", myResourceName, paramName); + Collection candidates = myResourceIndexedSearchParamUriDao.findAllByResourceTypeAndParamName(myResourceName, paramName); List toFind = new ArrayList<>(); for (String next : candidates) { if (value.length() >= next.length()) { @@ -107,13 +109,13 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil } Predicate uriPredicate = join.get("myUri").as(String.class).in(toFind); - Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate, theRequestPartitionId); + Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, uriPredicate, theRequestPartitionId); codePredicates.add(hashAndUriPredicate); } else if (param.getQualifier() == UriParamQualifierEnum.BELOW) { Predicate uriPredicate = myCriteriaBuilder.like(join.get("myUri").as(String.class), createLeftMatchLikeExpression(value)); - Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, theParamName, join, uriPredicate, theRequestPartitionId); + Predicate hashAndUriPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, paramName, join, uriPredicate, theRequestPartitionId); codePredicates.add(hashAndUriPredicate); } else { @@ -124,7 +126,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil Predicate uriPredicate = null; if (operation == null || operation == SearchFilterParser.CompareOperation.eq) { - long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName, value); + long hashUri = ResourceIndexedSearchParamUri.calculateHashUri(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName, value); Predicate hashPredicate = myCriteriaBuilder.equal(join.get("myHashUri"), hashUri); codePredicates.add(hashPredicate); } else if (operation == SearchFilterParser.CompareOperation.ne) { @@ -149,7 +151,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil } if (uriPredicate != null) { - long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, theParamName); + long hashIdentity = BaseResourceIndexedSearchParam.calculateHashIdentity(getPartitionSettings(), theRequestPartitionId, theResourceName, paramName); Predicate hashIdentityPredicate = myCriteriaBuilder.equal(join.get("myHashIdentity"), hashIdentity); codePredicates.add(myCriteriaBuilder.and(hashIdentityPredicate, uriPredicate)); } @@ -175,7 +177,7 @@ class PredicateBuilderUri extends BasePredicateBuilder implements IPredicateBuil Predicate orPredicate = myCriteriaBuilder.or(toArray(codePredicates)); Predicate outerPredicate = combineParamIndexPredicateWithParamNamePredicate(theResourceName, - theParamName, + paramName, join, orPredicate, theRequestPartitionId); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3PhoneticSearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3PhoneticSearchNoFtTest.java new file mode 100644 index 00000000000..231a8ed3203 --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3PhoneticSearchNoFtTest.java @@ -0,0 +1,135 @@ +package ca.uhn.fhir.jpa.dao.dstu3; + +import ca.uhn.fhir.context.phonetic.ApacheEncoder; +import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum; +import ca.uhn.fhir.jpa.api.config.DaoConfig; +import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; +import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.rest.param.StringParam; +import org.apache.commons.codec.language.Soundex; +import org.hl7.fhir.dstu3.model.Enumerations; +import org.hl7.fhir.dstu3.model.Patient; +import org.hl7.fhir.dstu3.model.SearchParameter; +import org.hl7.fhir.dstu3.model.StringType; +import org.hl7.fhir.instance.model.api.IIdType; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.beans.factory.annotation.Autowired; + +import java.util.List; +import java.util.stream.Collectors; + +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.containsInAnyOrder; +import static org.hamcrest.Matchers.hasSize; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertThat; + +public class FhirResourceDaoDstu3PhoneticSearchNoFtTest extends BaseJpaDstu3Test { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirResourceDaoDstu3PhoneticSearchNoFtTest.class); + public static final String GALE = "Gale"; + public static final String GAIL = "Gail"; + public static final String NAME_SOUNDEX_SP = "nameSoundex"; + public static final String ADDRESS_LINE_SOUNDEX_SP = "addressLineSoundex"; + private static final String BOB = "BOB"; + private static final String ADDRESS = "123 Nohili St"; + private static final String ADDRESS_CLOSE = "123 Nohily St"; + private static final String ADDRESS_FAR = "123 College St"; + + @Autowired + ISearchParamRegistry mySearchParamRegistry; + + @Before + public void beforeDisableResultReuse() { + myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED); + myDaoConfig.setReuseCachedSearchResultsForMillis(null); + myDaoConfig.setFetchSizeDefaultMaximum(new DaoConfig().getFetchSizeDefaultMaximum()); + + createSoundexSearchParameter(NAME_SOUNDEX_SP, PhoneticEncoderEnum.SOUNDEX, "Patient.name"); + createSoundexSearchParameter(ADDRESS_LINE_SOUNDEX_SP, PhoneticEncoderEnum.SOUNDEX, "Patient.address.line"); + mySearchParamRegistry.forceRefresh(); + mySearchParamRegistry.setPhoneticEncoder(new ApacheEncoder(PhoneticEncoderEnum.SOUNDEX.name(), new Soundex())); + } + + @After + public void resetStringEncoder() { + mySearchParamRegistry.setPhoneticEncoder(null); + } + + @Test + public void testSoundex() { + Soundex soundex = new Soundex(); + assertEquals(soundex.encode(GALE), soundex.encode(GAIL)); + assertNotEquals(soundex.encode(GALE), soundex.encode(BOB)); + assertEquals(soundex.encode(ADDRESS), soundex.encode(ADDRESS_CLOSE)); + assertNotEquals(soundex.encode(ADDRESS), soundex.encode(ADDRESS_FAR)); + ourLog.info("Encoded address: {}", soundex.encode(ADDRESS)); + } + + @Test + public void phoneticMatch() { + Patient patient; + + patient = new Patient(); + patient.addName().addGiven(GALE); + patient.addAddress().addLine(ADDRESS); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient)); + + IIdType pId = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); + + List stringParams = myResourceIndexedSearchParamStringDao.findAll(); + + assertThat(stringParams, hasSize(6)); + List stringParamNames = stringParams.stream().map(ResourceIndexedSearchParamString::getParamName).collect(Collectors.toList()); + assertThat(stringParamNames, containsInAnyOrder(Patient.SP_NAME, Patient.SP_GIVEN, Patient.SP_PHONETIC, NAME_SOUNDEX_SP, Patient.SP_ADDRESS, ADDRESS_LINE_SOUNDEX_SP)); + + assertSearchMatch(pId, Patient.SP_PHONETIC, GALE); + assertSearchMatch(pId, Patient.SP_PHONETIC, GAIL); + assertNoMatch(Patient.SP_PHONETIC, BOB); + + assertSearchMatch(pId, NAME_SOUNDEX_SP, GAIL); + assertSearchMatch(pId, NAME_SOUNDEX_SP, GALE); + assertNoMatch(NAME_SOUNDEX_SP, BOB); + + assertSearchMatch(pId, ADDRESS_LINE_SOUNDEX_SP, ADDRESS); + assertSearchMatch(pId, ADDRESS_LINE_SOUNDEX_SP, ADDRESS_CLOSE); + assertNoMatch(ADDRESS_LINE_SOUNDEX_SP, ADDRESS_FAR); + } + + private void assertSearchMatch(IIdType thePId1, String theSp, String theValue) { + SearchParameterMap map; + map = new SearchParameterMap(); + map.add(theSp, new StringParam(theValue)); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), contains(toValues(thePId1))); + } + + private void assertNoMatch(String theSp, String theValue) { + SearchParameterMap map; + map = new SearchParameterMap(); + map.add(theSp, new StringParam(theValue)); + assertThat(toUnqualifiedVersionlessIdValues(myPatientDao.search(map)), hasSize(0)); + } + + private void createSoundexSearchParameter(String theCode, PhoneticEncoderEnum theEncoder, String theFhirPath) { + SearchParameter searchParameter = new SearchParameter(); + searchParameter.addBase("Patient"); + searchParameter.setCode(theCode); + searchParameter.setType(Enumerations.SearchParamType.STRING); + searchParameter.setTitle("Test Soundex"); + searchParameter.setExpression(theFhirPath); +// Maybe use in the future? RuntimeSearchParam doesn't store this... +// searchParameter.setXpathUsage(SearchParameter.XPathUsageType.PHONETIC); + searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE); + searchParameter.addExtension() + .setUrl(JpaConstants.EXT_SEARCHPARAM_PHONETIC_ENCODER) + .setValue(new StringType(theEncoder.name())); + ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(searchParameter)); + mySearchParameterDao.create(searchParameter, mySrd).getId().toUnqualifiedVersionless(); + } + + +} diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java index 37f2a31b7aa..c38069fb90c 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.dao.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.phonetic.IPhoneticEncoder; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.model.config.PartitionSettings; @@ -401,6 +402,11 @@ public class SearchParamExtractorR4Test { public Collection getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) { return null; } + + @Override + public void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) { + // nothing + } } @AfterClass diff --git a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java index 79862713f3c..d2bea67063a 100644 --- a/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java +++ b/hapi-fhir-jpaserver-model/src/main/java/ca/uhn/fhir/jpa/model/util/JpaConstants.java @@ -187,10 +187,15 @@ public class JpaConstants { */ public static final String PARAM_EXPORT_TYPE_FILTER = "_typeFilter"; /** - * Placed in system-generated extensions + * URL for extension on a SearchParameter indicating that text values should not be indexed */ public static final String EXTENSION_EXT_SYSTEMDEFINED = JpaConstants.class.getName() + "_EXTENSION_EXT_SYSTEMDEFINED"; + /** + * URL for extension on a Phonetic String SearchParameter indicating that text values should be phonetically indexed with the named encoder + */ + public static final String EXT_SEARCHPARAM_PHONETIC_ENCODER = "http://hapifhir.io/fhir/StructureDefinition/searchparameter-phonetic-encoder"; + /** * Non-instantiable */ diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamConfig.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamConfig.java index 88d2acc3e8f..7f50902132b 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamConfig.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/config/SearchParamConfig.java @@ -33,6 +33,7 @@ import ca.uhn.fhir.jpa.searchparam.matcher.IndexedSearchParamExtractor; import ca.uhn.fhir.jpa.searchparam.matcher.SearchParamMatcher; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryImpl; +import ca.uhn.fhir.jpa.searchparam.registry.SearchParameterCanonicalizer; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; @@ -81,6 +82,12 @@ public class SearchParamConfig { return new SearchParamExtractorService(); } + @Bean + @Lazy + public SearchParameterCanonicalizer searchParameterCanonicalizer(FhirContext theFhirContext) { + return new SearchParameterCanonicalizer(theFhirContext); + } + @Bean public IndexedSearchParamExtractor indexedSearchParamExtractor() { return new IndexedSearchParamExtractor(); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java index aea7b12aaf6..e9d8d3c301b 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java @@ -81,8 +81,8 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank; import static org.apache.commons.lang3.StringUtils.trim; public abstract class BaseSearchParamExtractor implements ISearchParamExtractor { - private static final Pattern SPLIT = Pattern.compile("\\||( or )"); + private static final Pattern SPLIT = Pattern.compile("\\||( or )"); private static final Pattern SPLIT_R4 = Pattern.compile("\\|"); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSearchParamExtractor.class); @Autowired @@ -95,6 +95,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor private ModelConfig myModelConfig; @Autowired private PartitionSettings myPartitionSettings; + private Set myIgnoredForSearchDatatypes; private BaseRuntimeChildDefinition myQuantityValueValueChild; private BaseRuntimeChildDefinition myQuantitySystemValueChild; @@ -252,7 +253,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor @Override public List extractParamValuesAsStrings(RuntimeSearchParam theSearchParam, IBaseResource theResource) { IExtractor extractor; - switch(theSearchParam.getParamType()) { + switch (theSearchParam.getParamType()) { case DATE: extractor = createDateExtractor(theResource); break; @@ -972,12 +973,10 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor for (String next : families) { createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, next); } - List givens = extractValuesAsStrings(myHumanNameGivenValueChild, theValue); for (String next : givens) { createStringIndexIfNotBlank(theResourceType, theParams, theSearchParam, next); } - } private void addString_Quantity(String theResourceType, Set theParams, RuntimeSearchParam theSearchParam, IBase theValue) { @@ -1099,11 +1098,13 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor String searchParamName = theSearchParam.getName(); String valueNormalized = StringUtil.normalizeStringForSearchIndexing(value); - if (valueNormalized.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { - valueNormalized = valueNormalized.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); + String valueEncoded = theSearchParam.encode(valueNormalized); + + if (valueEncoded.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { + valueEncoded = valueEncoded.substring(0, ResourceIndexedSearchParamString.MAX_LENGTH); } - ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(myPartitionSettings, getModelConfig(), theResourceType, searchParamName, valueNormalized, value); + ResourceIndexedSearchParamString nextEntity = new ResourceIndexedSearchParamString(myPartitionSettings, getModelConfig(), theResourceType, searchParamName, valueEncoded, value); Set params = theParams; params.add(nextEntity); diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java index abf9d099ba0..48d2f546737 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorService.java @@ -85,6 +85,7 @@ public class SearchParamExtractorService { @Autowired(required = false) private IResourceLinkResolver myResourceLinkResolver; + /** * This method is responsible for scanning a resource for all of the search parameter instances. I.e. for all search parameters defined for * a given resource type, it extracts the associated indexes and populates {@literal theParams}. diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java index 59019b4c798..9bc7707ff78 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/ISearchParamRegistry.java @@ -22,6 +22,7 @@ package ca.uhn.fhir.jpa.searchparam.registry; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.phonetic.IPhoneticEncoder; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import java.util.Collection; @@ -59,4 +60,12 @@ public interface ISearchParamRegistry { RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName); Collection getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef); + + /** + * When indexing a HumanName, if a StringEncoder is set in the context, then the "phonetic" search parameter will normalize + * the String using this encoder. + * + * @since 5.1.0 + */ + void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java index 2d9441a315b..46b41128f83 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImpl.java @@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.searchparam.registry; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.phonetic.IPhoneticEncoder; import ca.uhn.fhir.interceptor.api.HookParams; import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; import ca.uhn.fhir.interceptor.api.Pointcut; @@ -34,8 +35,6 @@ import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.retry.Retrier; -import ca.uhn.fhir.model.api.ExtensionDt; -import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.rest.api.server.IBundleProvider; import ca.uhn.fhir.rest.api.server.RequestDetails; import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; @@ -43,24 +42,26 @@ import ca.uhn.fhir.util.DatatypeUtil; import ca.uhn.fhir.util.HapiExtensions; import ca.uhn.fhir.util.SearchParameterUtil; import ca.uhn.fhir.util.StopWatch; -import com.google.common.annotations.VisibleForTesting; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.time.DateUtils; -import org.hl7.fhir.dstu3.model.Extension; -import org.hl7.fhir.dstu3.model.SearchParameter; -import org.hl7.fhir.instance.model.api.*; -import org.hl7.fhir.r4.model.Reference; +import org.hl7.fhir.instance.model.api.IBaseResource; import org.quartz.JobExecutionContext; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import javax.annotation.PostConstruct; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; public class SearchParamRegistryImpl implements ISearchParamRegistry { @@ -76,8 +77,12 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry { private FhirContext myFhirContext; @Autowired private ISchedulerService mySchedulerService; + @Autowired + private SearchParameterCanonicalizer mySearchParameterCanonicalizer; private Map> myBuiltInSearchParams; + private IPhoneticEncoder myPhoneticEncoder; + private volatile Map> myActiveUniqueSearchParams = Collections.emptyMap(); private volatile Map, List>> myActiveParamNamesToUniqueSearchParams = Collections.emptyMap(); private volatile Map> myActiveSearchParams; @@ -175,6 +180,8 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry { uniqueSearchParams.add(nextCandidateCasted); } } + + setPhoneticEncoder(nextCandidate); } } @@ -228,21 +235,11 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry { myActiveParamNamesToUniqueSearchParams = activeParamNamesToUniqueSearchParams; } - @VisibleForTesting - void setFhirContextForUnitTest(FhirContext theFhirContext) { - myFhirContext = theFhirContext; - } - @PostConstruct public void postConstruct() { myBuiltInSearchParams = createBuiltInSearchParamMap(myFhirContext); } - @VisibleForTesting - public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) { - mySearchParamProvider = theSearchParamProvider; - } - public int doRefresh(long theRefreshInterval) { if (System.currentTimeMillis() - theRefreshInterval > myLastRefresh) { StopWatch sw = new StopWatch(); @@ -280,7 +277,7 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry { continue; } - RuntimeSearchParam runtimeSp = canonicalizeSearchParameter(nextSp); + RuntimeSearchParam runtimeSp = mySearchParameterCanonicalizer.canonicalizeSearchParameter(nextSp); if (runtimeSp == null) { continue; } @@ -337,361 +334,6 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry { } } - protected RuntimeSearchParam canonicalizeSearchParameter(IBaseResource theSearchParameter) { - switch (myFhirContext.getVersion().getVersion()) { - case DSTU2: - return canonicalizeSearchParameterDstu2((ca.uhn.fhir.model.dstu2.resource.SearchParameter) theSearchParameter); - case DSTU3: - return canonicalizeSearchParameterDstu3((org.hl7.fhir.dstu3.model.SearchParameter) theSearchParameter); - case R4: - return canonicalizeSearchParameterR4((org.hl7.fhir.r4.model.SearchParameter) theSearchParameter); - case DSTU2_HL7ORG: - case DSTU2_1: - // Non-supported - these won't happen so just fall through - case R5: - default: - return canonicalizeSearchParameterR5((org.hl7.fhir.r5.model.SearchParameter) theSearchParameter); - } - } - - private RuntimeSearchParam canonicalizeSearchParameterDstu2(ca.uhn.fhir.model.dstu2.resource.SearchParameter theNextSp) { - String name = theNextSp.getCode(); - String description = theNextSp.getDescription(); - String path = theNextSp.getXpath(); - RestSearchParameterTypeEnum paramType = null; - RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; - switch (theNextSp.getTypeElement().getValueAsEnum()) { - case COMPOSITE: - paramType = RestSearchParameterTypeEnum.COMPOSITE; - break; - case DATE_DATETIME: - paramType = RestSearchParameterTypeEnum.DATE; - break; - case NUMBER: - paramType = RestSearchParameterTypeEnum.NUMBER; - break; - case QUANTITY: - paramType = RestSearchParameterTypeEnum.QUANTITY; - break; - case REFERENCE: - paramType = RestSearchParameterTypeEnum.REFERENCE; - break; - case STRING: - paramType = RestSearchParameterTypeEnum.STRING; - break; - case TOKEN: - paramType = RestSearchParameterTypeEnum.TOKEN; - break; - case URI: - paramType = RestSearchParameterTypeEnum.URI; - break; - } - if (theNextSp.getStatus() != null) { - switch (theNextSp.getStatusElement().getValueAsEnum()) { - case ACTIVE: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; - break; - case DRAFT: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; - break; - case RETIRED: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; - break; - } - } - Set providesMembershipInCompartments = Collections.emptySet(); - Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); - - if (isBlank(name) || isBlank(path)) { - if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { - return null; - } - } - - IIdType id = theNextSp.getIdElement(); - String uri = ""; - boolean unique = false; - - List uniqueExts = theNextSp.getUndeclaredExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE); - if (uniqueExts.size() > 0) { - IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); - if (uniqueExtsValuePrimitive != null) { - if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { - unique = true; - } - } - } - - List components = Collections.emptyList(); - Collection> base = Collections.singletonList(theNextSp.getBaseElement()); - JpaRuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, base); - extractExtensions(theNextSp, retVal); - return retVal; - } - - private RuntimeSearchParam canonicalizeSearchParameterDstu3(org.hl7.fhir.dstu3.model.SearchParameter theNextSp) { - String name = theNextSp.getCode(); - String description = theNextSp.getDescription(); - String path = theNextSp.getExpression(); - RestSearchParameterTypeEnum paramType = null; - RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; - switch (theNextSp.getType()) { - case COMPOSITE: - paramType = RestSearchParameterTypeEnum.COMPOSITE; - break; - case DATE: - paramType = RestSearchParameterTypeEnum.DATE; - break; - case NUMBER: - paramType = RestSearchParameterTypeEnum.NUMBER; - break; - case QUANTITY: - paramType = RestSearchParameterTypeEnum.QUANTITY; - break; - case REFERENCE: - paramType = RestSearchParameterTypeEnum.REFERENCE; - break; - case STRING: - paramType = RestSearchParameterTypeEnum.STRING; - break; - case TOKEN: - paramType = RestSearchParameterTypeEnum.TOKEN; - break; - case URI: - paramType = RestSearchParameterTypeEnum.URI; - break; - case NULL: - break; - } - if (theNextSp.getStatus() != null) { - switch (theNextSp.getStatus()) { - case ACTIVE: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; - break; - case DRAFT: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; - break; - case RETIRED: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; - break; - case UNKNOWN: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN; - break; - case NULL: - break; - } - } - Set providesMembershipInCompartments = Collections.emptySet(); - Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); - - if (isBlank(name) || isBlank(path) || paramType == null) { - if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { - return null; - } - } - - IIdType id = theNextSp.getIdElement(); - String uri = ""; - boolean unique = false; - - List uniqueExts = theNextSp.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE); - if (uniqueExts.size() > 0) { - IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); - if (uniqueExtsValuePrimitive != null) { - if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { - unique = true; - } - } - } - - List components = new ArrayList<>(); - for (SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { - components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), next.getDefinition())); - } - - JpaRuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); - extractExtensions(theNextSp, retVal); - return retVal; - } - - private RuntimeSearchParam canonicalizeSearchParameterR4(org.hl7.fhir.r4.model.SearchParameter theNextSp) { - String name = theNextSp.getCode(); - String description = theNextSp.getDescription(); - String path = theNextSp.getExpression(); - RestSearchParameterTypeEnum paramType = null; - RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; - switch (theNextSp.getType()) { - case COMPOSITE: - paramType = RestSearchParameterTypeEnum.COMPOSITE; - break; - case DATE: - paramType = RestSearchParameterTypeEnum.DATE; - break; - case NUMBER: - paramType = RestSearchParameterTypeEnum.NUMBER; - break; - case QUANTITY: - paramType = RestSearchParameterTypeEnum.QUANTITY; - break; - case REFERENCE: - paramType = RestSearchParameterTypeEnum.REFERENCE; - break; - case STRING: - paramType = RestSearchParameterTypeEnum.STRING; - break; - case TOKEN: - paramType = RestSearchParameterTypeEnum.TOKEN; - break; - case URI: - paramType = RestSearchParameterTypeEnum.URI; - break; - case SPECIAL: - paramType = RestSearchParameterTypeEnum.SPECIAL; - break; - case NULL: - break; - } - if (theNextSp.getStatus() != null) { - switch (theNextSp.getStatus()) { - case ACTIVE: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; - break; - case DRAFT: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; - break; - case RETIRED: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; - break; - case UNKNOWN: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN; - break; - case NULL: - break; - } - } - Set providesMembershipInCompartments = Collections.emptySet(); - Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); - - if (isBlank(name) || isBlank(path) || paramType == null) { - if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { - return null; - } - } - - IIdType id = theNextSp.getIdElement(); - String uri = ""; - boolean unique = false; - - List uniqueExts = theNextSp.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE); - if (uniqueExts.size() > 0) { - IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); - if (uniqueExtsValuePrimitive != null) { - if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { - unique = true; - } - } - } - - List components = new ArrayList<>(); - for (org.hl7.fhir.r4.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { - components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new Reference(next.getDefinition()))); - } - - JpaRuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); - extractExtensions(theNextSp, retVal); - return retVal; - - } - - - private RuntimeSearchParam canonicalizeSearchParameterR5(org.hl7.fhir.r5.model.SearchParameter theNextSp) { - String name = theNextSp.getCode(); - String description = theNextSp.getDescription(); - String path = theNextSp.getExpression(); - RestSearchParameterTypeEnum paramType = null; - RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; - switch (theNextSp.getType()) { - case COMPOSITE: - paramType = RestSearchParameterTypeEnum.COMPOSITE; - break; - case DATE: - paramType = RestSearchParameterTypeEnum.DATE; - break; - case NUMBER: - paramType = RestSearchParameterTypeEnum.NUMBER; - break; - case QUANTITY: - paramType = RestSearchParameterTypeEnum.QUANTITY; - break; - case REFERENCE: - paramType = RestSearchParameterTypeEnum.REFERENCE; - break; - case STRING: - paramType = RestSearchParameterTypeEnum.STRING; - break; - case TOKEN: - paramType = RestSearchParameterTypeEnum.TOKEN; - break; - case URI: - paramType = RestSearchParameterTypeEnum.URI; - break; - case SPECIAL: - paramType = RestSearchParameterTypeEnum.SPECIAL; - break; - case NULL: - break; - } - if (theNextSp.getStatus() != null) { - switch (theNextSp.getStatus()) { - case ACTIVE: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; - break; - case DRAFT: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; - break; - case RETIRED: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; - break; - case UNKNOWN: - status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN; - break; - case NULL: - break; - } - } - Set providesMembershipInCompartments = Collections.emptySet(); - Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); - - if (isBlank(name) || isBlank(path) || paramType == null) { - if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { - return null; - } - } - - IIdType id = theNextSp.getIdElement(); - String uri = ""; - boolean unique = false; - - List uniqueExts = theNextSp.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE); - if (uniqueExts.size() > 0) { - IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); - if (uniqueExtsValuePrimitive != null) { - if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { - unique = true; - } - } - } - - List components = new ArrayList<>(); - for (org.hl7.fhir.r5.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { - components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new org.hl7.fhir.r5.model.Reference(next.getDefinition()))); - } - - JpaRuntimeSearchParam retVal = new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); - extractExtensions(theNextSp, retVal); - return retVal; - } - @Override public RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName) { @@ -760,26 +402,6 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry { return Collections.unmodifiableMap(myActiveSearchParams); } - @VisibleForTesting - void setSchedulerServiceForUnitTest(ISchedulerService theSchedulerService) { - mySchedulerService = theSchedulerService; - } - - /** - * Extracts any extensions from the resource and populates an extension field in the - */ - protected void extractExtensions(IBaseResource theSearchParamResource, JpaRuntimeSearchParam theRuntimeSearchParam) { - if (theSearchParamResource instanceof IBaseHasExtensions) { - List> extensions = ((IBaseHasExtensions) theSearchParamResource).getExtension(); - for (IBaseExtension next : extensions) { - String nextUrl = next.getUrl(); - if (isNotBlank(nextUrl)) { - theRuntimeSearchParam.addExtension(nextUrl, next); - } - } - } - } - public static Map> createBuiltInSearchParamMap(FhirContext theFhirContext) { Map> resourceNameToSearchParams = new HashMap<>(); @@ -797,4 +419,31 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry { } return Collections.unmodifiableMap(resourceNameToSearchParams); } + + /** + * All SearchParameters with the name "phonetic" encode the normalized index value using this phonetic encoder. + * + * @since 5.1.0 + */ + + public void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) { + myPhoneticEncoder = thePhoneticEncoder; + + if (myActiveSearchParams == null) { + return; + } + for (Map activeUniqueSearchParams : myActiveSearchParams.values()) { + for (RuntimeSearchParam searchParam : activeUniqueSearchParams.values()) { + setPhoneticEncoder(searchParam); + } + } + } + + private void setPhoneticEncoder(RuntimeSearchParam searchParam) { + if ("phonetic".equals(searchParam.getName())) { + ourLog.debug("Setting search param {} on {} phonetic encoder to {}", + searchParam.getName(), searchParam.getPath(), myPhoneticEncoder == null ? "null" : myPhoneticEncoder.name()); + searchParam.setPhoneticEncoder(myPhoneticEncoder); + } + } } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParameterCanonicalizer.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParameterCanonicalizer.java new file mode 100644 index 00000000000..625ca75d1fd --- /dev/null +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParameterCanonicalizer.java @@ -0,0 +1,431 @@ +package ca.uhn.fhir.jpa.searchparam.registry; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum; +import ca.uhn.fhir.jpa.model.util.JpaConstants; +import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; +import ca.uhn.fhir.model.api.ExtensionDt; +import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.util.DatatypeUtil; +import ca.uhn.fhir.util.HapiExtensions; +import org.apache.commons.lang3.EnumUtils; +import org.hl7.fhir.dstu3.model.Extension; +import org.hl7.fhir.dstu3.model.SearchParameter; +import org.hl7.fhir.instance.model.api.IBaseDatatype; +import org.hl7.fhir.instance.model.api.IBaseExtension; +import org.hl7.fhir.instance.model.api.IBaseHasExtensions; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.hl7.fhir.instance.model.api.IIdType; +import org.hl7.fhir.instance.model.api.IPrimitiveType; +import org.hl7.fhir.r4.model.Reference; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Set; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +@Service +public class SearchParameterCanonicalizer { + private static final Logger ourLog = LoggerFactory.getLogger(SearchParameterCanonicalizer.class); + + private final FhirContext myFhirContext; + + @Autowired + public SearchParameterCanonicalizer(FhirContext theFhirContext) { + myFhirContext = theFhirContext; + } + + protected RuntimeSearchParam canonicalizeSearchParameter(IBaseResource theSearchParameter) { + JpaRuntimeSearchParam retVal; + switch (myFhirContext.getVersion().getVersion()) { + case DSTU2: + retVal = canonicalizeSearchParameterDstu2((ca.uhn.fhir.model.dstu2.resource.SearchParameter) theSearchParameter); + break; + case DSTU3: + retVal = canonicalizeSearchParameterDstu3((org.hl7.fhir.dstu3.model.SearchParameter) theSearchParameter); + break; + case R4: + retVal = canonicalizeSearchParameterR4((org.hl7.fhir.r4.model.SearchParameter) theSearchParameter); + break; + case R5: + retVal = canonicalizeSearchParameterR5((org.hl7.fhir.r5.model.SearchParameter) theSearchParameter); + break; + case DSTU2_HL7ORG: + case DSTU2_1: + // Non-supported - these won't happen so just fall through + default: + throw new InternalErrorException("SearchParameter canonicalization not supported for FHIR version" + myFhirContext.getVersion().getVersion()); + } + extractExtensions(theSearchParameter, retVal); + return retVal; + } + + private JpaRuntimeSearchParam canonicalizeSearchParameterDstu2(ca.uhn.fhir.model.dstu2.resource.SearchParameter theNextSp) { + String name = theNextSp.getCode(); + String description = theNextSp.getDescription(); + String path = theNextSp.getXpath(); + RestSearchParameterTypeEnum paramType = null; + RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; + switch (theNextSp.getTypeElement().getValueAsEnum()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE_DATETIME: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + } + if (theNextSp.getStatus() != null) { + switch (theNextSp.getStatusElement().getValueAsEnum()) { + case ACTIVE: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; + break; + case DRAFT: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; + break; + case RETIRED: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; + break; + } + } + Set providesMembershipInCompartments = Collections.emptySet(); + Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); + + if (isBlank(name) || isBlank(path)) { + if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { + return null; + } + } + + IIdType id = theNextSp.getIdElement(); + String uri = ""; + boolean unique = false; + + List uniqueExts = theNextSp.getUndeclaredExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE); + if (uniqueExts.size() > 0) { + IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); + if (uniqueExtsValuePrimitive != null) { + if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { + unique = true; + } + } + } + + List components = Collections.emptyList(); + Collection> base = Collections.singletonList(theNextSp.getBaseElement()); + return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, base); + } + + private JpaRuntimeSearchParam canonicalizeSearchParameterDstu3(org.hl7.fhir.dstu3.model.SearchParameter theNextSp) { + String name = theNextSp.getCode(); + String description = theNextSp.getDescription(); + String path = theNextSp.getExpression(); + RestSearchParameterTypeEnum paramType = null; + RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; + switch (theNextSp.getType()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + case NULL: + break; + } + if (theNextSp.getStatus() != null) { + switch (theNextSp.getStatus()) { + case ACTIVE: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; + break; + case DRAFT: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; + break; + case RETIRED: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; + break; + case UNKNOWN: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN; + break; + case NULL: + break; + } + } + Set providesMembershipInCompartments = Collections.emptySet(); + Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); + + if (isBlank(name) || isBlank(path) || paramType == null) { + if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { + return null; + } + } + + IIdType id = theNextSp.getIdElement(); + String uri = ""; + boolean unique = false; + + List uniqueExts = theNextSp.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE); + if (uniqueExts.size() > 0) { + IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); + if (uniqueExtsValuePrimitive != null) { + if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { + unique = true; + } + } + } + + List components = new ArrayList<>(); + for (SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { + components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), next.getDefinition())); + } + + return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); + } + + private JpaRuntimeSearchParam canonicalizeSearchParameterR4(org.hl7.fhir.r4.model.SearchParameter theNextSp) { + String name = theNextSp.getCode(); + String description = theNextSp.getDescription(); + String path = theNextSp.getExpression(); + RestSearchParameterTypeEnum paramType = null; + RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; + switch (theNextSp.getType()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + case SPECIAL: + paramType = RestSearchParameterTypeEnum.SPECIAL; + break; + case NULL: + break; + } + if (theNextSp.getStatus() != null) { + switch (theNextSp.getStatus()) { + case ACTIVE: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; + break; + case DRAFT: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; + break; + case RETIRED: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; + break; + case UNKNOWN: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN; + break; + case NULL: + break; + } + } + Set providesMembershipInCompartments = Collections.emptySet(); + Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); + + if (isBlank(name) || isBlank(path) || paramType == null) { + if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { + return null; + } + } + + IIdType id = theNextSp.getIdElement(); + String uri = ""; + boolean unique = false; + + List uniqueExts = theNextSp.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE); + if (uniqueExts.size() > 0) { + IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); + if (uniqueExtsValuePrimitive != null) { + if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { + unique = true; + } + } + } + + List components = new ArrayList<>(); + for (org.hl7.fhir.r4.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { + components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new Reference(next.getDefinition()))); + } + + return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); + } + + private JpaRuntimeSearchParam canonicalizeSearchParameterR5(org.hl7.fhir.r5.model.SearchParameter theNextSp) { + String name = theNextSp.getCode(); + String description = theNextSp.getDescription(); + String path = theNextSp.getExpression(); + RestSearchParameterTypeEnum paramType = null; + RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null; + switch (theNextSp.getType()) { + case COMPOSITE: + paramType = RestSearchParameterTypeEnum.COMPOSITE; + break; + case DATE: + paramType = RestSearchParameterTypeEnum.DATE; + break; + case NUMBER: + paramType = RestSearchParameterTypeEnum.NUMBER; + break; + case QUANTITY: + paramType = RestSearchParameterTypeEnum.QUANTITY; + break; + case REFERENCE: + paramType = RestSearchParameterTypeEnum.REFERENCE; + break; + case STRING: + paramType = RestSearchParameterTypeEnum.STRING; + break; + case TOKEN: + paramType = RestSearchParameterTypeEnum.TOKEN; + break; + case URI: + paramType = RestSearchParameterTypeEnum.URI; + break; + case SPECIAL: + paramType = RestSearchParameterTypeEnum.SPECIAL; + break; + case NULL: + break; + } + if (theNextSp.getStatus() != null) { + switch (theNextSp.getStatus()) { + case ACTIVE: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE; + break; + case DRAFT: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.DRAFT; + break; + case RETIRED: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.RETIRED; + break; + case UNKNOWN: + status = RuntimeSearchParam.RuntimeSearchParamStatusEnum.UNKNOWN; + break; + case NULL: + break; + } + } + Set providesMembershipInCompartments = Collections.emptySet(); + Set targets = DatatypeUtil.toStringSet(theNextSp.getTarget()); + + if (isBlank(name) || isBlank(path) || paramType == null) { + if (paramType != RestSearchParameterTypeEnum.COMPOSITE) { + return null; + } + } + + IIdType id = theNextSp.getIdElement(); + String uri = ""; + boolean unique = false; + + List uniqueExts = theNextSp.getExtensionsByUrl(HapiExtensions.EXT_SP_UNIQUE); + if (uniqueExts.size() > 0) { + IPrimitiveType uniqueExtsValuePrimitive = uniqueExts.get(0).getValueAsPrimitive(); + if (uniqueExtsValuePrimitive != null) { + if ("true".equalsIgnoreCase(uniqueExtsValuePrimitive.getValueAsString())) { + unique = true; + } + } + } + + List components = new ArrayList<>(); + for (org.hl7.fhir.r5.model.SearchParameter.SearchParameterComponentComponent next : theNextSp.getComponent()) { + components.add(new JpaRuntimeSearchParam.Component(next.getExpression(), new org.hl7.fhir.r5.model.Reference(next.getDefinition()))); + } + + return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase()); + } + + + /** + * Extracts any extensions from the resource and populates an extension field in the + */ + protected void extractExtensions(IBaseResource theSearchParamResource, JpaRuntimeSearchParam theRuntimeSearchParam) { + if (theSearchParamResource instanceof IBaseHasExtensions) { + List> extensions = ((IBaseHasExtensions) theSearchParamResource).getExtension(); + for (IBaseExtension next : extensions) { + String nextUrl = next.getUrl(); + if (isNotBlank(nextUrl)) { + theRuntimeSearchParam.addExtension(nextUrl, next); + if (JpaConstants.EXT_SEARCHPARAM_PHONETIC_ENCODER.equals(nextUrl)) { + setEncoder(theRuntimeSearchParam, next.getValue()); + } + } + } + } + } + + private void setEncoder(JpaRuntimeSearchParam theRuntimeSearchParam, IBaseDatatype theValue) { + if (theValue instanceof IPrimitiveType) { + String stringValue = ((IPrimitiveType) theValue).getValueAsString(); + PhoneticEncoderEnum encoderEnum = EnumUtils.getEnum(PhoneticEncoderEnum.class, stringValue); + if (encoderEnum != null) { + theRuntimeSearchParam.setPhoneticEncoder(encoderEnum.getPhoneticEncoder()); + } else { + ourLog.error("Invalid PhoneticEncoderEnum value '" + stringValue + "'"); + } + } + } +} diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java index 4a62691398b..13a28f162d2 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorDstu3Test.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.searchparam.extractor; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.phonetic.IPhoneticEncoder; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.context.support.IValidationSupport; import ca.uhn.fhir.jpa.model.config.PartitionSettings; @@ -291,6 +292,11 @@ public class SearchParamExtractorDstu3Test { public Collection getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) { return null; } + + @Override + public void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) { + // nothing + } } @AfterClass diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java index 7d9ae2e0fdf..ac2e2347a5b 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/extractor/SearchParamExtractorMegaTest.java @@ -16,6 +16,7 @@ import ca.uhn.fhir.context.RuntimePrimitiveDatatypeNarrativeDefinition; import ca.uhn.fhir.context.RuntimePrimitiveDatatypeXhtmlHl7OrgDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.context.phonetic.IPhoneticEncoder; import ca.uhn.fhir.context.support.DefaultProfileValidationSupport; import ca.uhn.fhir.jpa.model.config.PartitionSettings; import ca.uhn.fhir.jpa.model.entity.ModelConfig; @@ -300,6 +301,11 @@ public class SearchParamExtractorMegaTest { public Collection getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) { return null; } + + @Override + public void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) { + // nothing + } } } diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImplTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImplTest.java index c9b8f817b50..a7b15951a45 100644 --- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImplTest.java +++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/SearchParamRegistryImplTest.java @@ -2,6 +2,8 @@ package ca.uhn.fhir.jpa.searchparam.registry; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeSearchParam; +import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.sched.ISchedulerService; import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -13,25 +15,50 @@ import org.hl7.fhir.r4.model.StringType; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.Mock; -import org.mockito.junit.MockitoJUnitRunner; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.test.context.junit4.SpringRunner; import java.util.Map; -import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.Mockito.when; -@RunWith(MockitoJUnitRunner.class) +@RunWith(SpringRunner.class) public class SearchParamRegistryImplTest { + @Autowired + SearchParamRegistryImpl mySearchParamRegistry; - @Mock + @MockBean private ISchedulerService mySchedulerService; - @Mock + @MockBean private ISearchParamProvider mySearchParamProvider; - private int myAnswerCount = 0; + @MockBean + private ModelConfig myModelConfig; + @MockBean + private IInterceptorBroadcaster myInterceptorBroadcaster; + @Configuration + static class SpringConfig { + @Bean + FhirContext fhirContext() { return FhirContext.forR4(); } + @Bean + ISearchParamRegistry searchParamRegistry() { return new SearchParamRegistryImpl(); } + @Bean + SearchParameterCanonicalizer searchParameterCanonicalizer(FhirContext theFhirContext) { + return new SearchParameterCanonicalizer(theFhirContext); + } + } + + private int myAnswerCount = 0; @Before public void before() { @@ -42,56 +69,40 @@ public class SearchParamRegistryImplTest { public void testRefreshAfterExpiry() { when(mySearchParamProvider.search(any())).thenReturn(new SimpleBundleProvider()); - SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); - registry.setSchedulerServiceForUnitTest(mySchedulerService); - registry.setFhirContextForUnitTest(FhirContext.forR4()); - registry.setSearchParamProviderForUnitTest(mySearchParamProvider); - registry.postConstruct(); - - registry.requestRefresh(); - assertEquals(146, registry.doRefresh(100000)); + mySearchParamRegistry.requestRefresh(); + assertEquals(146, mySearchParamRegistry.doRefresh(100000)); // Second time we don't need to run because we ran recently - assertEquals(0, registry.doRefresh(100000)); + assertEquals(0, mySearchParamRegistry.doRefresh(100000)); - assertEquals(146, registry.getActiveSearchParams().size()); + assertEquals(146, mySearchParamRegistry.getActiveSearchParams().size()); } @Test public void testRefreshCacheIfNecessary() { - SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); when(mySearchParamProvider.search(any())).thenReturn(new SimpleBundleProvider()); when(mySearchParamProvider.refreshCache(any(), anyLong())).thenAnswer(t -> { - registry.doRefresh(t.getArgument(1, Long.class)); + mySearchParamRegistry.doRefresh(t.getArgument(1, Long.class)); return 0; }); - registry.setSchedulerServiceForUnitTest(mySchedulerService); - registry.setFhirContextForUnitTest(FhirContext.forR4()); - registry.setSearchParamProviderForUnitTest(mySearchParamProvider); - registry.postConstruct(); - registry.requestRefresh(); + mySearchParamRegistry.requestRefresh(); - assertTrue(registry.refreshCacheIfNecessary()); - assertFalse(registry.refreshCacheIfNecessary()); + assertTrue(mySearchParamRegistry.refreshCacheIfNecessary()); + assertFalse(mySearchParamRegistry.refreshCacheIfNecessary()); - registry.requestRefresh(); - assertTrue(registry.refreshCacheIfNecessary()); + mySearchParamRegistry.requestRefresh(); + assertTrue(mySearchParamRegistry.refreshCacheIfNecessary()); } @Test public void testGetActiveUniqueSearchParams_Empty() { - SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); - assertThat(registry.getActiveUniqueSearchParams("Patient"), Matchers.empty()); + assertThat(mySearchParamRegistry.getActiveUniqueSearchParams("Patient"), Matchers.empty()); } @Test public void testGetActiveSearchParams() { - SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); - registry.setFhirContextForUnitTest(FhirContext.forR4()); - registry.postConstruct(); - when(mySearchParamProvider.search(any())).thenReturn(new SimpleBundleProvider()); when(mySearchParamProvider.refreshCache(any(), anyLong())).thenAnswer(t -> { if (myAnswerCount == 0) { @@ -99,21 +110,16 @@ public class SearchParamRegistryImplTest { throw new InternalErrorException("this is an error!"); } - registry.doRefresh(0); + mySearchParamRegistry.doRefresh(0); return 0; }); - registry.setSearchParamProviderForUnitTest(mySearchParamProvider); - Map outcome = registry.getActiveSearchParams("Patient"); + Map outcome = mySearchParamRegistry.getActiveSearchParams("Patient"); } @Test public void testExtractExtensions() { - SearchParamRegistryImpl registry = new SearchParamRegistryImpl(); - registry.setFhirContextForUnitTest(FhirContext.forR4()); - registry.postConstruct(); - SearchParameter searchParameter = new SearchParameter(); searchParameter.setCode("foo"); searchParameter.setStatus(Enumerations.PublicationStatus.ACTIVE); @@ -129,12 +135,12 @@ public class SearchParamRegistryImplTest { when(mySearchParamProvider.search(any())).thenReturn(new SimpleBundleProvider(searchParameter)); when(mySearchParamProvider.refreshCache(any(), anyLong())).thenAnswer(t -> { - registry.doRefresh(0); + mySearchParamRegistry.doRefresh(0); return 0; }); - registry.setSearchParamProviderForUnitTest(mySearchParamProvider); - Map outcome = registry.getActiveSearchParams("Patient"); + mySearchParamRegistry.forceRefresh(); + Map outcome = mySearchParamRegistry.getActiveSearchParams("Patient"); RuntimeSearchParam converted = outcome.get("foo"); assertNotNull(converted); diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/EmpiMetricEnum.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/EmpiMetricEnum.java index b50aa97047a..92d9780c323 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/EmpiMetricEnum.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/EmpiMetricEnum.java @@ -21,14 +21,13 @@ package ca.uhn.fhir.empi.rules.metric; */ import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.empi.rules.metric.matcher.DoubleMetaphoneStringMatcher; +import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum; import ca.uhn.fhir.empi.rules.metric.matcher.EmpiPersonNameMatchModeEnum; import ca.uhn.fhir.empi.rules.metric.matcher.HapiDateMatcher; import ca.uhn.fhir.empi.rules.metric.matcher.HapiStringMatcher; import ca.uhn.fhir.empi.rules.metric.matcher.IEmpiFieldMatcher; -import ca.uhn.fhir.empi.rules.metric.matcher.MetaphoneStringMatcher; import ca.uhn.fhir.empi.rules.metric.matcher.NameMatcher; -import ca.uhn.fhir.empi.rules.metric.matcher.StringEncoderMatcher; +import ca.uhn.fhir.empi.rules.metric.matcher.PhoneticEncoderMatcher; import ca.uhn.fhir.empi.rules.metric.matcher.SubstringStringMatcher; import ca.uhn.fhir.empi.rules.metric.similarity.HapiStringSimilarity; import ca.uhn.fhir.empi.rules.metric.similarity.IEmpiFieldSimilarity; @@ -37,9 +36,6 @@ import info.debatty.java.stringsimilarity.Jaccard; import info.debatty.java.stringsimilarity.JaroWinkler; import info.debatty.java.stringsimilarity.NormalizedLevenshtein; import info.debatty.java.stringsimilarity.SorensenDice; -import org.apache.commons.codec.language.Caverphone1; -import org.apache.commons.codec.language.Caverphone2; -import org.apache.commons.codec.language.Soundex; import org.hl7.fhir.instance.model.api.IBase; import javax.annotation.Nullable; @@ -49,13 +45,19 @@ import javax.annotation.Nullable; * calculating differences between strings (https://en.wikipedia.org/wiki/String_metric) */ public enum EmpiMetricEnum { + CAVERPHONE1(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.CAVERPHONE1))), + CAVERPHONE2(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.CAVERPHONE2))), + COLOGNE(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.COLOGNE))), + DOUBLE_METAPHONE(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.DOUBLE_METAPHONE))), + MATCH_RATING_APPROACH(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.MATCH_RATING_APPROACH))), + METAPHONE(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.METAPHONE))), + NYSIIS(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.NYSIIS))), + REFINED_SOUNDEX(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.REFINED_SOUNDEX))), + SOUNDEX(new HapiStringMatcher(new PhoneticEncoderMatcher(PhoneticEncoderEnum.SOUNDEX))), + STRING(new HapiStringMatcher()), SUBSTRING(new HapiStringMatcher(new SubstringStringMatcher())), - METAPHONE(new HapiStringMatcher(new MetaphoneStringMatcher())), - DOUBLE_METAPHONE(new HapiStringMatcher(new DoubleMetaphoneStringMatcher())), - SOUNDEX(new HapiStringMatcher(new StringEncoderMatcher(new Soundex()))), - CAVERPHONE1(new HapiStringMatcher(new StringEncoderMatcher(new Caverphone1()))), - CAVERPHONE2(new HapiStringMatcher(new StringEncoderMatcher(new Caverphone2()))), + DATE(new HapiDateMatcher()), JARO_WINKLER(new HapiStringSimilarity(new JaroWinkler())), COSINE(new HapiStringSimilarity(new Cosine())), diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/DoubleMetaphoneStringMatcher.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/DoubleMetaphoneStringMatcher.java deleted file mode 100644 index 7bc6b2127a6..00000000000 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/DoubleMetaphoneStringMatcher.java +++ /dev/null @@ -1,30 +0,0 @@ -package ca.uhn.fhir.empi.rules.metric.matcher; - -/*- - * #%L - * HAPI FHIR - Enterprise Master Patient Index - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * 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 org.apache.commons.codec.language.DoubleMetaphone; - -public class DoubleMetaphoneStringMatcher implements IEmpiStringMatcher { - @Override - public boolean matches(String theLeftString, String theRightString) { - return new DoubleMetaphone().isDoubleMetaphoneEqual(theLeftString, theRightString); - } -} diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/MetaphoneStringMatcher.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/PhoneticEncoderMatcher.java similarity index 58% rename from hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/MetaphoneStringMatcher.java rename to hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/PhoneticEncoderMatcher.java index 0ba8ab3f902..65926cc2a02 100644 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/MetaphoneStringMatcher.java +++ b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/PhoneticEncoderMatcher.java @@ -20,11 +20,22 @@ package ca.uhn.fhir.empi.rules.metric.matcher; * #L% */ -import org.apache.commons.codec.language.Metaphone; +import ca.uhn.fhir.context.phonetic.IPhoneticEncoder; +import ca.uhn.fhir.context.phonetic.PhoneticEncoderEnum; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class PhoneticEncoderMatcher implements IEmpiStringMatcher { + private static final Logger ourLog = LoggerFactory.getLogger(PhoneticEncoderMatcher.class); + + private final IPhoneticEncoder myStringEncoder; + + public PhoneticEncoderMatcher(PhoneticEncoderEnum thePhoneticEnum) { + myStringEncoder = thePhoneticEnum.getPhoneticEncoder(); + } -public class MetaphoneStringMatcher implements IEmpiStringMatcher { @Override public boolean matches(String theLeftString, String theRightString) { - return new Metaphone().isMetaphoneEqual(theLeftString, theRightString); + return myStringEncoder.encode(theLeftString).equals(myStringEncoder.encode(theRightString)); } } diff --git a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/StringEncoderMatcher.java b/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/StringEncoderMatcher.java deleted file mode 100644 index 727550f0221..00000000000 --- a/hapi-fhir-server-empi/src/main/java/ca/uhn/fhir/empi/rules/metric/matcher/StringEncoderMatcher.java +++ /dev/null @@ -1,46 +0,0 @@ -package ca.uhn.fhir.empi.rules.metric.matcher; - -/*- - * #%L - * HAPI FHIR - Enterprise Master Patient Index - * %% - * Copyright (C) 2014 - 2020 University Health Network - * %% - * 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 org.apache.commons.codec.EncoderException; -import org.apache.commons.codec.StringEncoder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -public class StringEncoderMatcher implements IEmpiStringMatcher { - private static final Logger ourLog = LoggerFactory.getLogger(StringEncoderMatcher.class); - - private final StringEncoder myStringEncoder; - - public StringEncoderMatcher(StringEncoder theStringEncoder) { - myStringEncoder = theStringEncoder; - } - - @Override - public boolean matches(String theLeftString, String theRightString) { - try { - return myStringEncoder.encode(theLeftString).equals(myStringEncoder.encode(theRightString)); - } catch (EncoderException e) { - ourLog.error("Failed to match strings '{}' and '{}' using encoder {}", theLeftString, theRightString, myStringEncoder.getClass().getName(), e); - } - return false; - } -}