From d710682fed9f013cd3344879f0b605588f2a8e03 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Tue, 5 Dec 2017 07:25:34 -0500 Subject: [PATCH] Fix searching in JPA with _id and _content params --- .../fhir/jpa/dao/FulltextSearchSvcImpl.java | 191 +++++++-------- .../ca/uhn/fhir/jpa/dao/SearchBuilder.java | 14 +- .../search/PersistedJpaBundleProvider.java | 2 +- ...istedJpaSearchFirstPageBundleProvider.java | 21 +- .../jpa/search/SearchCoordinatorSvcImpl.java | 5 + .../ca/uhn/fhir/jpa/config/TestR4Config.java | 25 +- .../provider/r4/ResourceProviderR4Test.java | 217 +++++++++++------- .../BaseResourceReturningMethodBinding.java | 2 +- .../server/method/SearchMethodBinding.java | 6 - src/changes/changes.xml | 4 + 10 files changed, 294 insertions(+), 193 deletions(-) diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java index 8e5a14861ae..ca50a4a8e1c 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FulltextSearchSvcImpl.java @@ -20,18 +20,21 @@ package ca.uhn.fhir.jpa.dao; * #L% */ -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.util.*; - -import javax.persistence.*; - +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.model.api.IQueryParameterType; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenParam; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import com.google.common.collect.Lists; +import com.google.common.collect.Sets; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.apache.lucene.analysis.Analyzer; import org.apache.lucene.search.Query; -import org.apache.lucene.search.highlight.*; import org.apache.lucene.search.highlight.Formatter; +import org.apache.lucene.search.highlight.*; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.FullTextQuery; import org.hibernate.search.query.dsl.BooleanJunction; @@ -40,15 +43,12 @@ import org.hl7.fhir.dstu3.model.BaseResource; import org.hl7.fhir.instance.model.api.IBaseResource; import org.springframework.transaction.annotation.Transactional; -import com.google.common.collect.Lists; -import com.google.common.collect.Sets; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.PersistenceContextType; +import java.util.*; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.model.api.IQueryParameterType; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import static org.apache.commons.lang3.StringUtils.isNotBlank; public class FulltextSearchSvcImpl extends BaseHapiFhirDao implements IFulltextSearchSvc { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FulltextSearchSvcImpl.class); @@ -62,7 +62,7 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem public FulltextSearchSvcImpl() { super(); } - + private void addTextSearch(QueryBuilder theQueryBuilder, BooleanJunction theBoolean, List> theTerms, String theFieldName, String theFieldNameEdgeNGram, String theFieldNameNGram) { if (theTerms == null) { return; @@ -87,11 +87,12 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem // .andField(theFieldNameNGram).boostedTo(1.0f) .sentence(terms.iterator().next().toLowerCase()).createQuery(); //@formatter:on - + theBoolean.must(textQuery); } else { String joinedTerms = StringUtils.join(terms, ' '); - theBoolean.must(theQueryBuilder.keyword().onField(theFieldName).matching(joinedTerms).createQuery()); } + theBoolean.must(theQueryBuilder.keyword().onField(theFieldName).matching(joinedTerms).createQuery()); + } } } } @@ -145,7 +146,7 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem return pids; } */ - + QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get(); BooleanJunction bool = qb.bool(); @@ -183,7 +184,7 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem List result = jpaQuery.getResultList(); HashSet pidsSet = pids != null ? new HashSet(pids) : null; - + ArrayList retVal = new ArrayList(); for (Object object : result) { Object[] nextArray = (Object[]) object; @@ -201,8 +202,16 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem Long pid = null; if (theParams.get(BaseResource.SP_RES_ID) != null) { - StringParam idParm = (StringParam) theParams.get(BaseResource.SP_RES_ID).get(0).get(0); - pid = BaseHapiFhirDao.translateForcedIdToPid(theResourceName, idParm.getValue(), myForcedIdDao); + String idParamValue; + IQueryParameterType idParam = theParams.get(BaseResource.SP_RES_ID).get(0).get(0); + if (idParam instanceof TokenParam) { + TokenParam idParm = (TokenParam) idParam; + idParamValue = idParm.getValue(); + } else { + StringParam idParm = (StringParam) idParam; + idParamValue = idParm.getValue(); + } + pid = BaseHapiFhirDao.translateForcedIdToPid(theResourceName, idParamValue, myForcedIdDao); } Long referencingPid = pid; @@ -213,6 +222,19 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem return retVal; } + @Override + public boolean isDisabled() { + try { + FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); + em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get(); + } catch (Exception e) { + ourLog.trace("FullText test failed", e); + ourLog.debug("Hibernate Search (Lucene) appears to be disabled on this server, fulltext will be disabled"); + return true; + } + return false; + } + @Transactional() @Override public List search(String theResourceName, SearchParameterMap theParams) { @@ -238,18 +260,18 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get(); Query textQuery = qb - .phrase() - .withSlop(2) - .onField("myContentText").boostedTo(4.0f) - .andField("myContentTextEdgeNGram").boostedTo(2.0f) - .andField("myContentTextNGram").boostedTo(1.0f) - .andField("myContentTextPhonetic").boostedTo(0.5f) - .sentence(theText.toLowerCase()).createQuery(); - + .phrase() + .withSlop(2) + .onField("myContentText").boostedTo(4.0f) + .andField("myContentTextEdgeNGram").boostedTo(2.0f) + .andField("myContentTextNGram").boostedTo(1.0f) + .andField("myContentTextPhonetic").boostedTo(0.5f) + .sentence(theText.toLowerCase()).createQuery(); + Query query = qb.bool() - .must(qb.keyword().onField("myResourceLinks.myTargetResourcePid").matching(pid).createQuery()) - .must(textQuery) - .createQuery(); + .must(qb.keyword().onField("myResourceLinks.myTargetResourcePid").matching(pid).createQuery()) + .must(textQuery) + .createQuery(); FullTextQuery ftq = em.createFullTextQuery(query, ResourceTable.class); ftq.setProjection("myContentText"); @@ -286,7 +308,7 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem Collections.sort(suggestions); Set terms = Sets.newHashSet(); - for (Iterator iter = suggestions.iterator(); iter.hasNext();) { + for (Iterator iter = suggestions.iterator(); iter.hasNext(); ) { String nextTerm = iter.next().getTerm().toLowerCase(); if (!terms.add(nextTerm)) { iter.remove(); @@ -294,39 +316,11 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem } long delay = System.currentTimeMillis() - start; - ourLog.info("Provided {} suggestions for term {} in {} ms", new Object[] { terms.size(), theText, delay }); + ourLog.info("Provided {} suggestions for term {} in {} ms", new Object[]{terms.size(), theText, delay}); return suggestions; } - public static class Suggestion implements Comparable { - public Suggestion(String theTerm, float theScore) { - myTerm = theTerm; - myScore = theScore; - } - - public String getTerm() { - return myTerm; - } - - public float getScore() { - return myScore; - } - - private String myTerm; - private float myScore; - - @Override - public int compareTo(Suggestion theO) { - return Float.compare(theO.myScore, myScore); - } - - @Override - public String toString() { - return "Suggestion[myTerm=" + myTerm + ", myScore=" + myScore + "]"; - } - } - public class MySuggestionFormatter implements Formatter { private List mySuggestions; @@ -340,26 +334,9 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem mySuggestions = theSuggestions; } - public void setFindPhrasesWith() { - myPartialMatchPhrases = new ArrayList(); - myPartialMatchScores = new ArrayList(); - - for (Suggestion next : mySuggestions) { - myPartialMatchPhrases.add(' ' + next.myTerm); - myPartialMatchScores.add(next.myScore); - } - - myPartialMatchPhrases.add(myOriginalSearch); - myPartialMatchScores.add(1.0f); - } - - public void setAnalyzer(String theString) { - myAnalyzer = theString; - } - @Override public String highlightTerm(String theOriginalText, TokenGroup theTokenGroup) { - ourLog.debug("{} Found {} with score {}", new Object[] { myAnalyzer, theOriginalText, theTokenGroup.getTotalScore() }); + ourLog.debug("{} Found {} with score {}", new Object[]{myAnalyzer, theOriginalText, theTokenGroup.getTotalScore()}); if (theTokenGroup.getTotalScore() > 0) { float score = theTokenGroup.getTotalScore(); if (theOriginalText.equalsIgnoreCase(myOriginalSearch)) { @@ -379,19 +356,51 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao implem return null; } + public void setAnalyzer(String theString) { + myAnalyzer = theString; + } + + public void setFindPhrasesWith() { + myPartialMatchPhrases = new ArrayList(); + myPartialMatchScores = new ArrayList(); + + for (Suggestion next : mySuggestions) { + myPartialMatchPhrases.add(' ' + next.myTerm); + myPartialMatchScores.add(next.myScore); + } + + myPartialMatchPhrases.add(myOriginalSearch); + myPartialMatchScores.add(1.0f); + } + } - @Override - public boolean isDisabled() { - try { - FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager); - em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get(); - } catch (Exception e) { - ourLog.trace("FullText test failed", e); - ourLog.debug("Hibernate Search (Lucene) appears to be disabled on this server, fulltext will be disabled"); - return true; + public static class Suggestion implements Comparable { + private String myTerm; + private float myScore; + + public Suggestion(String theTerm, float theScore) { + myTerm = theTerm; + myScore = theScore; + } + + @Override + public int compareTo(Suggestion theO) { + return Float.compare(theO.myScore, myScore); + } + + public float getScore() { + return myScore; + } + + public String getTerm() { + return myTerm; + } + + @Override + public String toString() { + return "Suggestion[myTerm=" + myTerm + ", myScore=" + myScore + "]"; } - return false; } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java index c4568fa629e..9ce3159803e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/SearchBuilder.java @@ -1378,7 +1378,13 @@ public class SearchBuilder implements ISearchBuilder { throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_CONTENT); } } - List pids = myFulltextSearchSvc.everything(myResourceName, myParams); + + List pids; + if (myParams.getEverythingMode() != null) { + pids = myFulltextSearchSvc.everything(myResourceName, myParams); + } else { + pids = myFulltextSearchSvc.search(myResourceName, myParams); + } if (pids.isEmpty()) { // Will never match pids = Collections.singletonList(-1L); @@ -1576,7 +1582,9 @@ public class SearchBuilder implements ISearchBuilder { cq.where(from.get("myId").in(pids)); TypedQuery q = entityManager.createQuery(cq); - for (ResourceTable next : q.getResultList()) { + List resultList = q.getResultList(); + + for (ResourceTable next : resultList) { Class resourceType = context.getResourceDefinition(next.getResourceType()).getImplementingClass(); IBaseResource resource = theDao.toResource(resourceType, next, theForHistoryOperation); Integer index = position.get(next.getId()); @@ -1615,7 +1623,7 @@ public class SearchBuilder implements ISearchBuilder { // when running asserts assert new HashSet<>(theIncludePids).size() == theIncludePids.size() : "PID list contains duplicates: " + theIncludePids; - Map position = new HashMap(); + Map position = new HashMap<>(); for (Long next : theIncludePids) { position.put(next, theResourceListToPopulate.size()); theResourceListToPopulate.add(null); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java index d0cece19699..8a55d7fe5be 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaBundleProvider.java @@ -263,7 +263,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider { } // Execute the query and make sure we return distinct results - List resources = new ArrayList(); + List resources = new ArrayList<>(); sb.loadResourcesByPid(pidsSubList, resources, includedPids, false, myEntityManager, myContext, myDao); return resources; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java index f1b63e8716c..82c7860a414 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/PersistedJpaSearchFirstPageBundleProvider.java @@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.search; import java.util.List; import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.support.TransactionCallback; @@ -36,7 +38,7 @@ import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl.SearchTask; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundleProvider { - + private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaSearchFirstPageBundleProvider.class); private SearchTask mySearchTask; private ISearchBuilder mySearchBuilder; private Search mySearch; @@ -54,20 +56,31 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl @Override public List getResources(int theFromIndex, int theToIndex) { SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch); + + ourLog.trace("Fetching search resource PIDs"); final List pids = mySearchTask.getResourcePids(theFromIndex, theToIndex); - + ourLog.trace("Done fetching search resource PIDs"); + TransactionTemplate txTemplate = new TransactionTemplate(myTxManager); txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED); - return txTemplate.execute(new TransactionCallback>() { + List retVal = txTemplate.execute(new TransactionCallback>() { @Override public List doInTransaction(TransactionStatus theStatus) { return toResourceList(mySearchBuilder, pids); - }}); + } + }); + + ourLog.trace("Loaded resources to return"); + + return retVal; } @Override public Integer size() { + ourLog.trace("Waiting for initial sync"); Integer size = mySearchTask.awaitInitialSync(); + ourLog.trace("Finished waiting for local sync"); + SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch); if (size != null) { return size; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java index 340e258f617..8a693203f1e 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/SearchCoordinatorSvcImpl.java @@ -117,8 +117,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc { if (myNeverUseLocalSearchForUnitTests == false) { SearchTask task = myIdToSearchTask.get(theUuid); if (task != null) { + ourLog.trace("Local search found"); return task.getResourcePids(theFrom, theTo); + } else { + ourLog.trace("No local search found"); } + } else { + ourLog.trace("Forced not using local search"); } TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java index c84e1f4748a..53f28c46ff9 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestR4Config.java @@ -1,11 +1,9 @@ package ca.uhn.fhir.jpa.config; import ca.uhn.fhir.jpa.dao.DaoConfig; -import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory; import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.validation.ResultSeverityEnum; import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder; -import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel; import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder; import org.apache.commons.dbcp2.BasicDataSource; import org.hibernate.jpa.HibernatePersistenceProvider; @@ -30,6 +28,17 @@ import static org.junit.Assert.*; public class TestR4Config extends BaseJavaConfigR4 { static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestR4Config.class); + private static int ourMaxThreads; + + static { + /* + * We use a randomized number of maximum threads in order to try + * and catch any potential deadlocks caused by database connection + * starvation + */ + ourMaxThreads = (int) (Math.random() * 6.0) + 1; + } + private Exception myLastStackTrace; @Bean() @@ -90,13 +99,7 @@ public class TestR4Config extends BaseJavaConfigR4 { retVal.setUsername(""); retVal.setPassword(""); - /* - * We use a randomized number of maximum threads in order to try - * and catch any potential deadlocks caused by database connection - * starvation - */ - int maxThreads = (int) (Math.random() * 6.0) + 1; - retVal.setMaxTotal(maxThreads); + retVal.setMaxTotal(ourMaxThreads); DataSource dataSource = ProxyDataSourceBuilder .create(retVal) @@ -155,4 +158,8 @@ public class TestR4Config extends BaseJavaConfigR4 { return retVal; } + public static int getMaxThreads() { + return ourMaxThreads; + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index 1ee7135ecfc..2caebf995c7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.provider.r4; +import ca.uhn.fhir.jpa.config.TestR4Config; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl; @@ -176,7 +177,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { return ids; } - // Y @Test public void testBundleCreate() throws Exception { IGenericClient client = myClient; @@ -1569,24 +1569,56 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } @Test - public void testGetResourceCountsOperation() throws Exception { - String methodName = "testMetaOperations"; + public void testFulltextEverythingWithIdAndContent() throws IOException { + Patient p = new Patient(); + p.setId("FOO"); + p.addName().setFamily("FAMILY"); + myClient.update().resource(p).execute(); - Patient pt = new Patient(); - pt.addName().setFamily(methodName); - myClient.create().resource(pt).execute().getId().toUnqualifiedVersionless(); + p = new Patient(); + p.setId("BAR"); + p.addName().setFamily("HELLO"); + myClient.update().resource(p).execute(); - HttpGet get = new HttpGet(ourServerBase + "/$get-resource-counts"); - CloseableHttpResponse response = ourHttpClient.execute(get); - try { - assertEquals(200, response.getStatusLine().getStatusCode()); - String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - IOUtils.closeQuietly(response.getEntity().getContent()); - ourLog.info(output); - assertThat(output, containsString(" ids = searchAndReturnUnqualifiedVersionlessIdValues(ourServerBase + "/Patient/FOO/$everything?_content=White"); + assertThat(ids, contains("Patient/FOO")); + + ids = searchAndReturnUnqualifiedVersionlessIdValues(ourServerBase + "/Patient/FOO/$everything?_content=HELLO"); + assertThat(ids, contains("Patient/FOO")); + + ids = searchAndReturnUnqualifiedVersionlessIdValues(ourServerBase + "/Patient/FOO/$everything?_content=GOODBYE"); + assertThat(ids, containsInAnyOrder("Patient/FOO", "Observation/BAZ")); + } + + @Test + public void testFulltextSearchWithIdAndContent() throws IOException { + Patient p = new Patient(); + p.setId("FOO"); + p.addName().setFamily("FAMILY"); + myClient.update().resource(p).execute(); + + p = new Patient(); + p.setId("BAR"); + p.addName().setFamily("HELLO"); + myClient.update().resource(p).execute(); + + Observation o = new Observation(); + o.setId("BAZ"); + o.getSubject().setReference("Patient/FOO"); + o.getCode().setText("GOODBYE"); + myClient.update().resource(o).execute(); + + List ids = searchAndReturnUnqualifiedVersionlessIdValues(ourServerBase + "/Patient?_id=FOO&_content=family"); + assertThat(ids, contains("Patient/FOO")); + + ids = searchAndReturnUnqualifiedVersionlessIdValues(ourServerBase + "/Patient?_id=FOO&_content=HELLO"); + assertThat(ids, empty()); } // private void delete(String theResourceType, String theParamName, String theParamValue) { @@ -1614,6 +1646,27 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { // } // } + @Test + public void testGetResourceCountsOperation() throws Exception { + String methodName = "testMetaOperations"; + + Patient pt = new Patient(); + pt.addName().setFamily(methodName); + myClient.create().resource(pt).execute().getId().toUnqualifiedVersionless(); + + HttpGet get = new HttpGet(ourServerBase + "/$get-resource-counts"); + CloseableHttpResponse response = ourHttpClient.execute(get); + try { + assertEquals(200, response.getStatusLine().getStatusCode()); + String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + IOUtils.closeQuietly(response.getEntity().getContent()); + ourLog.info(output); + assertThat(output, containsString("\n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + " \n" + + ""; + + HttpPost post = new HttpPost(ourServerBase + "/Patient"); + post.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + CloseableHttpResponse response = ourHttpClient.execute(post); + IdType id; + try { + assertEquals(201, response.getStatusLine().getStatusCode()); + String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); + assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); + id = new IdType(newIdString); + } finally { + response.close(); + } + + HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart() + "?_pretty=true"); + response = ourHttpClient.execute(get); + try { + String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(resp); + assertEquals(200, response.getStatusLine().getStatusCode()); + assertThat(resp, containsString("Underweight")); + } finally { + IOUtils.closeQuietly(response.getEntity().getContent()); + response.close(); + } + + } + @Test public void testPatchUsingJsonPatch() throws Exception { String methodName = "testPatchUsingJsonPatch"; @@ -3205,14 +3306,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourLog.info(myCapturingInterceptor.getLastResponse().getAllHeaders().toString()); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.empty()); - assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.empty()); + assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()), Matchers.empty()); String msg = "Total is " + found.getTotalElement().getValue() + " and took " + sw.getMillis() + " millis"; - // If this fails under load, try increasing the throttle above - assertEquals(msg,null, found.getTotalElement().getValue()); - assertEquals(msg, 1, found.getEntry().size()); - assertThat(msg, sw.getMillis(), lessThan(1000L)); + // When we've only got one DB connection available, we are forced to wait for the + // search to finish before returning + if (TestR4Config.getMaxThreads() > 1) { + assertEquals(null, found.getTotalElement().getValue()); + assertEquals(1, found.getEntry().size()); + assertThat(sw.getMillis(), lessThan(1000L)); + } else { + assertThat(sw.getMillis(), greaterThan(1000L)); + } } @@ -3239,9 +3345,9 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { assertThat(sw.getMillis(), not(lessThan(1000L))); - // If this fails under load, try increasing the throttle above assertEquals(10, found.getTotalElement().getValue().intValue()); assertEquals(1, found.getEntry().size()); + } @Test @@ -3266,12 +3372,17 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { .execute(); assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.empty()); - assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.empty()); + assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()), Matchers.empty()); - // If this fails under load, try increasing the throttle above - assertEquals(null, found.getTotalElement().getValue()); - assertEquals(1, found.getEntry().size()); - assertThat(sw.getMillis(), lessThan(1500L)); + // WHen we've only got one DB connection available, we are forced to wait for the + // search to finish before returning + if (TestR4Config.getMaxThreads() > 1) { + assertEquals(null, found.getTotalElement().getValue()); + assertEquals(1, found.getEntry().size()); + assertThat(sw.getMillis(), lessThan(1500L)); + } else { + assertThat(sw.getMillis(), greaterThan(1500L)); + } } @Test @@ -4353,56 +4464,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } } - @Test - public void testParseAndEncodeExtensionWithValueWithExtension() throws IOException { - String input = "\n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - ""; - - HttpPost post = new HttpPost(ourServerBase + "/Patient"); - post.setEntity(new StringEntity(input, ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); - CloseableHttpResponse response = ourHttpClient.execute(post); - IdType id; - try { - assertEquals(201, response.getStatusLine().getStatusCode()); - String newIdString = response.getFirstHeader(Constants.HEADER_LOCATION_LC).getValue(); - assertThat(newIdString, startsWith(ourServerBase + "/Patient/")); - id = new IdType(newIdString); - } finally { - response.close(); - } - - HttpGet get = new HttpGet(ourServerBase + "/Patient/" + id.getIdPart() + "?_pretty=true"); - response = ourHttpClient.execute(get); - try { - String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8); - ourLog.info(resp); - assertEquals(200, response.getStatusLine().getStatusCode()); - assertThat(resp, containsString("Underweight")); - } finally { - IOUtils.closeQuietly(response.getEntity().getContent()); - response.close(); - } - - } - - - @Test public void testValueSetExpandOperation() throws IOException { CodeSystem cs = myFhirCtx.newXmlParser().parseResource(CodeSystem.class, new InputStreamReader(ResourceProviderR4Test.class.getResourceAsStream("/extensional-case-3-cs.xml"))); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java index 88b41a0eab1..1c98343705c 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseResourceReturningMethodBinding.java @@ -178,7 +178,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi if (theRequest.getRequestType() == RequestTypeEnum.GET) { boolean first = true; Map parameters = theRequest.getParameters(); - for (String nextParamName : new TreeSet(parameters.keySet())) { + for (String nextParamName : new TreeSet<>(parameters.keySet())) { for (String nextParamValue : parameters.get(nextParamName)) { if (first) { b.append('?'); diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java index 5eafe6bfd9e..89c034d7352 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/SearchMethodBinding.java @@ -77,7 +77,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { * Check for parameter combinations and names that are invalid */ List parameters = getParameters(); - // List searchParameters = new ArrayList(); for (int i = 0; i < parameters.size(); i++) { IParameter next = parameters.get(i); if (!(next instanceof SearchParameter)) { @@ -93,12 +92,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { } } - // searchParameters.add(sp); } - // for (int i = 0; i < searchParameters.size(); i++) { - // SearchParameter next = searchParameters.get(i); - // // next. - // } /* * Only compartment searching methods may have an ID parameter diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 26188e4c9ab..60aacc017d4 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -17,6 +17,10 @@ RFC 3986. This affects client calls, as well as URLs generated by the server (e.g. REST HOOK calls). Thanks to James Daily for reporting! + + Searching in JPA server using a combination of _content and _id parameters + failed. Thanks to Jeff Weyer for reporting! +