Re-enable contains searches on the public HAPI FHIR server and improve

message formatting in HapiLocalizer
This commit is contained in:
James Agnew 2019-03-14 13:19:26 -04:00
parent ed4da7c414
commit a8c76450e5
12 changed files with 129 additions and 5 deletions

View File

@ -101,13 +101,36 @@ public class HapiLocalizer {
String formatString = getFormatString(theQualifiedKey); String formatString = getFormatString(theQualifiedKey);
format = new MessageFormat(formatString.trim()); format = newMessageFormat(formatString);
myKeyToMessageFormat.put(theQualifiedKey, format); myKeyToMessageFormat.put(theQualifiedKey, format);
return format.format(theParameters); return format.format(theParameters);
} }
return getFormatString(theQualifiedKey); return getFormatString(theQualifiedKey);
} }
MessageFormat newMessageFormat(String theFormatString) {
StringBuilder pattern = new StringBuilder(theFormatString.trim());
for (int i = 0; i < (pattern.length()-1); i++) {
if (pattern.charAt(i) == '{') {
char nextChar = pattern.charAt(i+1);
if (nextChar >= '0' && nextChar <= '9') {
continue;
}
pattern.replace(i, i+1, "'{'");
int closeBraceIndex = pattern.indexOf("}", i);
if (closeBraceIndex > 0) {
i = closeBraceIndex;
pattern.replace(i, i+1, "'}'");
}
}
}
return new MessageFormat(pattern.toString());
}
protected void init() { protected void init() {
for (String nextName : myBundleNames) { for (String nextName : myBundleNames) {
myBundle.add(ResourceBundle.getBundle(nextName)); myBundle.add(ResourceBundle.getBundle(nextName));

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.i18n;
import static org.hamcrest.Matchers.empty; import static org.hamcrest.Matchers.empty;
import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.not;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
import java.util.Set; import java.util.Set;
@ -10,6 +11,15 @@ import org.junit.Test;
public class HapiLocalizerTest { public class HapiLocalizerTest {
@Test
public void testEscapePatterns() {
HapiLocalizer loc = new HapiLocalizer();
assertEquals("some message", loc.newMessageFormat("some message").format(new Object[]{}));
assertEquals("var1 {var2} var3 {var4}", loc.newMessageFormat("var1 {var2} var3 {var4}").format(new Object[]{}));
assertEquals("var1 A var3 B", loc.newMessageFormat("var1 {0} var3 {1}").format(new Object[]{ "A", "B"}));
}
@Test @Test
public void testAllKeys() { public void testAllKeys() {

View File

@ -76,7 +76,7 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
toDelete.add(nextEntry.getValue()); toDelete.add(nextEntry.getValue());
} }
} }
mySearchParamPresentDao.deleteInBatch(toDelete); mySearchParamPresentDao.deleteAll(toDelete);
// Add any that should be added // Add any that should be added
List<SearchParamPresent> toAdd = new ArrayList<>(); List<SearchParamPresent> toAdd = new ArrayList<>();

View File

@ -398,7 +398,7 @@ public abstract class BaseHapiTerminologySvcImpl implements IHapiTerminologySvc,
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager); TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); txTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
txTemplate.execute(t -> { txTemplate.execute(t -> {
theDao.deleteInBatch(link); theDao.deleteAll(link);
return null; return null;
}); });

View File

@ -2935,6 +2935,52 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(ids, containsInAnyOrder(pt2id)); assertThat(ids, containsInAnyOrder(pt2id));
} }
@Test
public void testSearchWithContainsLowerCase() {
myDaoConfig.setAllowContainsSearches(true);
Patient pt1 = new Patient();
pt1.addName().setFamily("abcdefghijk");
String pt1id = myPatientDao.create(pt1).getId().toUnqualifiedVersionless().getValue();
Patient pt2 = new Patient();
pt2.addName().setFamily("fghijk");
String pt2id = myPatientDao.create(pt2).getId().toUnqualifiedVersionless().getValue();
Patient pt3 = new Patient();
pt3.addName().setFamily("zzzzz");
myPatientDao.create(pt3).getId().toUnqualifiedVersionless().getValue();
List<String> ids;
SearchParameterMap map;
IBundleProvider results;
// Contains = true
map = new SearchParameterMap();
map.add(Patient.SP_NAME, new StringParam("FGHIJK").setContains(true));
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, containsInAnyOrder(pt1id, pt2id));
// Contains = false
map = new SearchParameterMap();
map.add(Patient.SP_NAME, new StringParam("FGHIJK").setContains(false));
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, containsInAnyOrder(pt2id));
// No contains
map = new SearchParameterMap();
map.add(Patient.SP_NAME, new StringParam("FGHIJK"));
map.setLoadSynchronous(true);
results = myPatientDao.search(map);
ids = toUnqualifiedVersionlessIdValues(results);
assertThat(ids, containsInAnyOrder(pt2id));
}
@Test @Test
public void testSearchWithContainsDisabled() { public void testSearchWithContainsDisabled() {
myDaoConfig.setAllowContainsSearches(false); myDaoConfig.setAllowContainsSearches(false);

View File

@ -37,8 +37,10 @@ import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.TestUtil; import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import org.apache.commons.io.IOUtils; import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
@ -137,6 +139,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis()); myDaoConfig.setReuseCachedSearchResultsForMillis(new DaoConfig().getReuseCachedSearchResultsForMillis());
myDaoConfig.setCountSearchResultsUpTo(new DaoConfig().getCountSearchResultsUpTo()); myDaoConfig.setCountSearchResultsUpTo(new DaoConfig().getCountSearchResultsUpTo());
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds()); myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
myDaoConfig.setAllowContainsSearches(new DaoConfig().isAllowContainsSearches());
mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(null); mySearchCoordinatorSvcRaw.setLoadingThrottleForUnitTests(null);
mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE); mySearchCoordinatorSvcRaw.setSyncSizeForUnitTests(SearchCoordinatorSvcImpl.DEFAULT_SYNC_SIZE);
@ -156,7 +159,42 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds()); myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
} }
@Test
public void testSearchWithContainsLowerCase() {
myDaoConfig.setAllowContainsSearches(true);
Patient pt1 = new Patient();
pt1.addName().setFamily("Elizabeth");
String pt1id = myPatientDao.create(pt1).getId().toUnqualifiedVersionless().getValue();
Patient pt2 = new Patient();
pt2.addName().setFamily("fghijk");
String pt2id = myPatientDao.create(pt2).getId().toUnqualifiedVersionless().getValue();
Patient pt3 = new Patient();
pt3.addName().setFamily("zzzzz");
myPatientDao.create(pt3).getId().toUnqualifiedVersionless().getValue();
Bundle output = ourClient
.search()
.forResource("Patient")
.where(Patient.NAME.contains().value("ZAB"))
.returnBundle(Bundle.class)
.execute();
List<String> ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(ids, containsInAnyOrder(pt1id));
output = ourClient
.search()
.forResource("Patient")
.where(Patient.NAME.contains().value("zab"))
.returnBundle(Bundle.class)
.execute();
ids = output.getEntry().stream().map(t -> t.getResource().getIdElement().toUnqualifiedVersionless().getValue()).collect(Collectors.toList());
assertThat(ids, containsInAnyOrder(pt1id));
}
@Test @Test
public void testManualPagingLinkOffsetDoesntReturnBeyondEnd() { public void testManualPagingLinkOffsetDoesntReturnBeyondEnd() {

View File

@ -93,7 +93,7 @@ public class WebsocketWithCriteriaDstu3Test extends BaseResourceProviderDstu3Tes
} }
@Test @Test
public void createObservation() throws Exception { public void createObservation() {
Observation observation = new Observation(); Observation observation = new Observation();
CodeableConcept codeableConcept = new CodeableConcept(); CodeableConcept codeableConcept = new CodeableConcept();
observation.setCode(codeableConcept); observation.setCode(codeableConcept);

View File

@ -46,7 +46,7 @@ import static org.apache.commons.lang3.StringUtils.left;
* IDX_SP_STRING * IDX_SP_STRING
*/ */
// This one us used only for sorting // This is used for sorting, and for :contains queries currently
@Index(name = "IDX_SP_STRING_HASH_IDENT", columnList = "HASH_IDENTITY"), @Index(name = "IDX_SP_STRING_HASH_IDENT", columnList = "HASH_IDENTITY"),
@Index(name = "IDX_SP_STRING_HASH_NRM", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED"), @Index(name = "IDX_SP_STRING_HASH_NRM", columnList = "HASH_NORM_PREFIX,SP_VALUE_NORMALIZED"),

View File

@ -61,6 +61,7 @@ public class TestDstu2Config extends BaseJavaConfigDstu2 {
retVal.setSubscriptionEnabled(true); retVal.setSubscriptionEnabled(true);
retVal.setSubscriptionPollDelay(5000); retVal.setSubscriptionPollDelay(5000);
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR); retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
retVal.setAllowContainsSearches(true);
retVal.setAllowMultipleDelete(true); retVal.setAllowMultipleDelete(true);
retVal.setAllowInlineMatchUrlReferences(true); retVal.setAllowInlineMatchUrlReferences(true);
retVal.setAllowExternalReferences(true); retVal.setAllowExternalReferences(true);

View File

@ -52,6 +52,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
retVal.setSubscriptionEnabled(true); retVal.setSubscriptionEnabled(true);
retVal.setSubscriptionPollDelay(5000); retVal.setSubscriptionPollDelay(5000);
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR); retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
retVal.setAllowContainsSearches(true);
retVal.setAllowMultipleDelete(true); retVal.setAllowMultipleDelete(true);
retVal.setAllowInlineMatchUrlReferences(true); retVal.setAllowInlineMatchUrlReferences(true);
retVal.setAllowExternalReferences(true); retVal.setAllowExternalReferences(true);

View File

@ -52,6 +52,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
retVal.setSubscriptionEnabled(true); retVal.setSubscriptionEnabled(true);
retVal.setSubscriptionPollDelay(5000); retVal.setSubscriptionPollDelay(5000);
retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR); retVal.setSubscriptionPurgeInactiveAfterMillis(DateUtils.MILLIS_PER_HOUR);
retVal.setAllowContainsSearches(true);
retVal.setAllowMultipleDelete(true); retVal.setAllowMultipleDelete(true);
retVal.setAllowInlineMatchUrlReferences(true); retVal.setAllowInlineMatchUrlReferences(true);
retVal.setAllowExternalReferences(true); retVal.setAllowExternalReferences(true);

View File

@ -77,6 +77,10 @@
A new config setting has been added to the JPA DaoConfig that disables validation A new config setting has been added to the JPA DaoConfig that disables validation
of the resource type for target resources in references. of the resource type for target resources in references.
</action> </action>
<action type="add">
HapiLocalizer can now handle message patterns with braces that aren't a part of a
message format expression. E.g. "Here is an {example}".
</action>
</release> </release>
<release version="3.7.0" date="2019-02-06" description="Gale"> <release version="3.7.0" date="2019-02-06" description="Gale">
<action type="add"> <action type="add">