Fix searching in JPA with _id and _content params
This commit is contained in:
parent
e1c62ef03e
commit
d710682fed
|
@ -20,18 +20,21 @@ package ca.uhn.fhir.jpa.dao;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
||||||
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
import java.util.*;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.param.StringParam;
|
||||||
import javax.persistence.*;
|
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.StringUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.apache.lucene.analysis.Analyzer;
|
import org.apache.lucene.analysis.Analyzer;
|
||||||
import org.apache.lucene.search.Query;
|
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.Formatter;
|
||||||
|
import org.apache.lucene.search.highlight.*;
|
||||||
import org.hibernate.search.jpa.FullTextEntityManager;
|
import org.hibernate.search.jpa.FullTextEntityManager;
|
||||||
import org.hibernate.search.jpa.FullTextQuery;
|
import org.hibernate.search.jpa.FullTextQuery;
|
||||||
import org.hibernate.search.query.dsl.BooleanJunction;
|
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.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import com.google.common.collect.Lists;
|
import javax.persistence.EntityManager;
|
||||||
import com.google.common.collect.Sets;
|
import javax.persistence.PersistenceContext;
|
||||||
|
import javax.persistence.PersistenceContextType;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.entity.ResourceTable;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
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;
|
|
||||||
|
|
||||||
public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implements IFulltextSearchSvc {
|
public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implements IFulltextSearchSvc {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FulltextSearchSvcImpl.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FulltextSearchSvcImpl.class);
|
||||||
|
@ -62,7 +62,7 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
public FulltextSearchSvcImpl() {
|
public FulltextSearchSvcImpl() {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addTextSearch(QueryBuilder theQueryBuilder, BooleanJunction<?> theBoolean, List<List<? extends IQueryParameterType>> theTerms, String theFieldName, String theFieldNameEdgeNGram, String theFieldNameNGram) {
|
private void addTextSearch(QueryBuilder theQueryBuilder, BooleanJunction<?> theBoolean, List<List<? extends IQueryParameterType>> theTerms, String theFieldName, String theFieldNameEdgeNGram, String theFieldNameNGram) {
|
||||||
if (theTerms == null) {
|
if (theTerms == null) {
|
||||||
return;
|
return;
|
||||||
|
@ -87,11 +87,12 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
// .andField(theFieldNameNGram).boostedTo(1.0f)
|
// .andField(theFieldNameNGram).boostedTo(1.0f)
|
||||||
.sentence(terms.iterator().next().toLowerCase()).createQuery();
|
.sentence(terms.iterator().next().toLowerCase()).createQuery();
|
||||||
//@formatter:on
|
//@formatter:on
|
||||||
|
|
||||||
theBoolean.must(textQuery);
|
theBoolean.must(textQuery);
|
||||||
} else {
|
} else {
|
||||||
String joinedTerms = StringUtils.join(terms, ' ');
|
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<IBaseResource> implem
|
||||||
return pids;
|
return pids;
|
||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get();
|
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get();
|
||||||
BooleanJunction<?> bool = qb.bool();
|
BooleanJunction<?> bool = qb.bool();
|
||||||
|
|
||||||
|
@ -183,7 +184,7 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
List<?> result = jpaQuery.getResultList();
|
List<?> result = jpaQuery.getResultList();
|
||||||
|
|
||||||
HashSet<Long> pidsSet = pids != null ? new HashSet<Long>(pids) : null;
|
HashSet<Long> pidsSet = pids != null ? new HashSet<Long>(pids) : null;
|
||||||
|
|
||||||
ArrayList<Long> retVal = new ArrayList<Long>();
|
ArrayList<Long> retVal = new ArrayList<Long>();
|
||||||
for (Object object : result) {
|
for (Object object : result) {
|
||||||
Object[] nextArray = (Object[]) object;
|
Object[] nextArray = (Object[]) object;
|
||||||
|
@ -201,8 +202,16 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
|
|
||||||
Long pid = null;
|
Long pid = null;
|
||||||
if (theParams.get(BaseResource.SP_RES_ID) != null) {
|
if (theParams.get(BaseResource.SP_RES_ID) != null) {
|
||||||
StringParam idParm = (StringParam) theParams.get(BaseResource.SP_RES_ID).get(0).get(0);
|
String idParamValue;
|
||||||
pid = BaseHapiFhirDao.translateForcedIdToPid(theResourceName, idParm.getValue(), myForcedIdDao);
|
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;
|
Long referencingPid = pid;
|
||||||
|
@ -213,6 +222,19 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
return retVal;
|
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()
|
@Transactional()
|
||||||
@Override
|
@Override
|
||||||
public List<Long> search(String theResourceName, SearchParameterMap theParams) {
|
public List<Long> search(String theResourceName, SearchParameterMap theParams) {
|
||||||
|
@ -238,18 +260,18 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get();
|
QueryBuilder qb = em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get();
|
||||||
|
|
||||||
Query textQuery = qb
|
Query textQuery = qb
|
||||||
.phrase()
|
.phrase()
|
||||||
.withSlop(2)
|
.withSlop(2)
|
||||||
.onField("myContentText").boostedTo(4.0f)
|
.onField("myContentText").boostedTo(4.0f)
|
||||||
.andField("myContentTextEdgeNGram").boostedTo(2.0f)
|
.andField("myContentTextEdgeNGram").boostedTo(2.0f)
|
||||||
.andField("myContentTextNGram").boostedTo(1.0f)
|
.andField("myContentTextNGram").boostedTo(1.0f)
|
||||||
.andField("myContentTextPhonetic").boostedTo(0.5f)
|
.andField("myContentTextPhonetic").boostedTo(0.5f)
|
||||||
.sentence(theText.toLowerCase()).createQuery();
|
.sentence(theText.toLowerCase()).createQuery();
|
||||||
|
|
||||||
Query query = qb.bool()
|
Query query = qb.bool()
|
||||||
.must(qb.keyword().onField("myResourceLinks.myTargetResourcePid").matching(pid).createQuery())
|
.must(qb.keyword().onField("myResourceLinks.myTargetResourcePid").matching(pid).createQuery())
|
||||||
.must(textQuery)
|
.must(textQuery)
|
||||||
.createQuery();
|
.createQuery();
|
||||||
|
|
||||||
FullTextQuery ftq = em.createFullTextQuery(query, ResourceTable.class);
|
FullTextQuery ftq = em.createFullTextQuery(query, ResourceTable.class);
|
||||||
ftq.setProjection("myContentText");
|
ftq.setProjection("myContentText");
|
||||||
|
@ -286,7 +308,7 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
Collections.sort(suggestions);
|
Collections.sort(suggestions);
|
||||||
|
|
||||||
Set<String> terms = Sets.newHashSet();
|
Set<String> terms = Sets.newHashSet();
|
||||||
for (Iterator<Suggestion> iter = suggestions.iterator(); iter.hasNext();) {
|
for (Iterator<Suggestion> iter = suggestions.iterator(); iter.hasNext(); ) {
|
||||||
String nextTerm = iter.next().getTerm().toLowerCase();
|
String nextTerm = iter.next().getTerm().toLowerCase();
|
||||||
if (!terms.add(nextTerm)) {
|
if (!terms.add(nextTerm)) {
|
||||||
iter.remove();
|
iter.remove();
|
||||||
|
@ -294,39 +316,11 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
}
|
}
|
||||||
|
|
||||||
long delay = System.currentTimeMillis() - start;
|
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;
|
return suggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class Suggestion implements Comparable<Suggestion> {
|
|
||||||
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 {
|
public class MySuggestionFormatter implements Formatter {
|
||||||
|
|
||||||
private List<Suggestion> mySuggestions;
|
private List<Suggestion> mySuggestions;
|
||||||
|
@ -340,26 +334,9 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
mySuggestions = theSuggestions;
|
mySuggestions = theSuggestions;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFindPhrasesWith() {
|
|
||||||
myPartialMatchPhrases = new ArrayList<String>();
|
|
||||||
myPartialMatchScores = new ArrayList<Float>();
|
|
||||||
|
|
||||||
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
|
@Override
|
||||||
public String highlightTerm(String theOriginalText, TokenGroup theTokenGroup) {
|
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) {
|
if (theTokenGroup.getTotalScore() > 0) {
|
||||||
float score = theTokenGroup.getTotalScore();
|
float score = theTokenGroup.getTotalScore();
|
||||||
if (theOriginalText.equalsIgnoreCase(myOriginalSearch)) {
|
if (theOriginalText.equalsIgnoreCase(myOriginalSearch)) {
|
||||||
|
@ -379,19 +356,51 @@ public class FulltextSearchSvcImpl extends BaseHapiFhirDao<IBaseResource> implem
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setAnalyzer(String theString) {
|
||||||
|
myAnalyzer = theString;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setFindPhrasesWith() {
|
||||||
|
myPartialMatchPhrases = new ArrayList<String>();
|
||||||
|
myPartialMatchScores = new ArrayList<Float>();
|
||||||
|
|
||||||
|
for (Suggestion next : mySuggestions) {
|
||||||
|
myPartialMatchPhrases.add(' ' + next.myTerm);
|
||||||
|
myPartialMatchScores.add(next.myScore);
|
||||||
|
}
|
||||||
|
|
||||||
|
myPartialMatchPhrases.add(myOriginalSearch);
|
||||||
|
myPartialMatchScores.add(1.0f);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
public static class Suggestion implements Comparable<Suggestion> {
|
||||||
public boolean isDisabled() {
|
private String myTerm;
|
||||||
try {
|
private float myScore;
|
||||||
FullTextEntityManager em = org.hibernate.search.jpa.Search.getFullTextEntityManager(myEntityManager);
|
|
||||||
em.getSearchFactory().buildQueryBuilder().forEntity(ResourceTable.class).get();
|
public Suggestion(String theTerm, float theScore) {
|
||||||
} catch (Exception e) {
|
myTerm = theTerm;
|
||||||
ourLog.trace("FullText test failed", e);
|
myScore = theScore;
|
||||||
ourLog.debug("Hibernate Search (Lucene) appears to be disabled on this server, fulltext will be disabled");
|
}
|
||||||
return true;
|
|
||||||
|
@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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
throw new InvalidRequestException("Fulltext search is not enabled on this service, can not process parameter: " + Constants.PARAM_CONTENT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
List<Long> pids = myFulltextSearchSvc.everything(myResourceName, myParams);
|
|
||||||
|
List<Long> pids;
|
||||||
|
if (myParams.getEverythingMode() != null) {
|
||||||
|
pids = myFulltextSearchSvc.everything(myResourceName, myParams);
|
||||||
|
} else {
|
||||||
|
pids = myFulltextSearchSvc.search(myResourceName, myParams);
|
||||||
|
}
|
||||||
if (pids.isEmpty()) {
|
if (pids.isEmpty()) {
|
||||||
// Will never match
|
// Will never match
|
||||||
pids = Collections.singletonList(-1L);
|
pids = Collections.singletonList(-1L);
|
||||||
|
@ -1576,7 +1582,9 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
cq.where(from.get("myId").in(pids));
|
cq.where(from.get("myId").in(pids));
|
||||||
TypedQuery<ResourceTable> q = entityManager.createQuery(cq);
|
TypedQuery<ResourceTable> q = entityManager.createQuery(cq);
|
||||||
|
|
||||||
for (ResourceTable next : q.getResultList()) {
|
List<ResourceTable> resultList = q.getResultList();
|
||||||
|
|
||||||
|
for (ResourceTable next : resultList) {
|
||||||
Class<? extends IBaseResource> resourceType = context.getResourceDefinition(next.getResourceType()).getImplementingClass();
|
Class<? extends IBaseResource> resourceType = context.getResourceDefinition(next.getResourceType()).getImplementingClass();
|
||||||
IBaseResource resource = theDao.toResource(resourceType, next, theForHistoryOperation);
|
IBaseResource resource = theDao.toResource(resourceType, next, theForHistoryOperation);
|
||||||
Integer index = position.get(next.getId());
|
Integer index = position.get(next.getId());
|
||||||
|
@ -1615,7 +1623,7 @@ public class SearchBuilder implements ISearchBuilder {
|
||||||
// when running asserts
|
// when running asserts
|
||||||
assert new HashSet<>(theIncludePids).size() == theIncludePids.size() : "PID list contains duplicates: " + theIncludePids;
|
assert new HashSet<>(theIncludePids).size() == theIncludePids.size() : "PID list contains duplicates: " + theIncludePids;
|
||||||
|
|
||||||
Map<Long, Integer> position = new HashMap<Long, Integer>();
|
Map<Long, Integer> position = new HashMap<>();
|
||||||
for (Long next : theIncludePids) {
|
for (Long next : theIncludePids) {
|
||||||
position.put(next, theResourceListToPopulate.size());
|
position.put(next, theResourceListToPopulate.size());
|
||||||
theResourceListToPopulate.add(null);
|
theResourceListToPopulate.add(null);
|
||||||
|
|
|
@ -263,7 +263,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Execute the query and make sure we return distinct results
|
// Execute the query and make sure we return distinct results
|
||||||
List<IBaseResource> resources = new ArrayList<IBaseResource>();
|
List<IBaseResource> resources = new ArrayList<>();
|
||||||
sb.loadResourcesByPid(pidsSubList, resources, includedPids, false, myEntityManager, myContext, myDao);
|
sb.loadResourcesByPid(pidsSubList, resources, includedPids, false, myEntityManager, myContext, myDao);
|
||||||
|
|
||||||
return resources;
|
return resources;
|
||||||
|
|
|
@ -23,6 +23,8 @@ package ca.uhn.fhir.jpa.search;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
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.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.TransactionStatus;
|
import org.springframework.transaction.TransactionStatus;
|
||||||
import org.springframework.transaction.support.TransactionCallback;
|
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;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
|
||||||
public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundleProvider {
|
public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundleProvider {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(PersistedJpaSearchFirstPageBundleProvider.class);
|
||||||
private SearchTask mySearchTask;
|
private SearchTask mySearchTask;
|
||||||
private ISearchBuilder mySearchBuilder;
|
private ISearchBuilder mySearchBuilder;
|
||||||
private Search mySearch;
|
private Search mySearch;
|
||||||
|
@ -54,20 +56,31 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
public List<IBaseResource> getResources(int theFromIndex, int theToIndex) {
|
||||||
SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch);
|
SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch);
|
||||||
|
|
||||||
|
ourLog.trace("Fetching search resource PIDs");
|
||||||
final List<Long> pids = mySearchTask.getResourcePids(theFromIndex, theToIndex);
|
final List<Long> pids = mySearchTask.getResourcePids(theFromIndex, theToIndex);
|
||||||
|
ourLog.trace("Done fetching search resource PIDs");
|
||||||
|
|
||||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||||
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
|
txTemplate.setPropagationBehavior(TransactionTemplate.PROPAGATION_REQUIRED);
|
||||||
return txTemplate.execute(new TransactionCallback<List<IBaseResource>>() {
|
List<IBaseResource> retVal = txTemplate.execute(new TransactionCallback<List<IBaseResource>>() {
|
||||||
@Override
|
@Override
|
||||||
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
|
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
|
||||||
return toResourceList(mySearchBuilder, pids);
|
return toResourceList(mySearchBuilder, pids);
|
||||||
}});
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
ourLog.trace("Loaded resources to return");
|
||||||
|
|
||||||
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer size() {
|
public Integer size() {
|
||||||
|
ourLog.trace("Waiting for initial sync");
|
||||||
Integer size = mySearchTask.awaitInitialSync();
|
Integer size = mySearchTask.awaitInitialSync();
|
||||||
|
ourLog.trace("Finished waiting for local sync");
|
||||||
|
|
||||||
SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch);
|
SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch);
|
||||||
if (size != null) {
|
if (size != null) {
|
||||||
return size;
|
return size;
|
||||||
|
|
|
@ -117,8 +117,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
||||||
if (myNeverUseLocalSearchForUnitTests == false) {
|
if (myNeverUseLocalSearchForUnitTests == false) {
|
||||||
SearchTask task = myIdToSearchTask.get(theUuid);
|
SearchTask task = myIdToSearchTask.get(theUuid);
|
||||||
if (task != null) {
|
if (task != null) {
|
||||||
|
ourLog.trace("Local search found");
|
||||||
return task.getResourcePids(theFrom, theTo);
|
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);
|
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
|
||||||
|
|
|
@ -1,11 +1,9 @@
|
||||||
package ca.uhn.fhir.jpa.config;
|
package ca.uhn.fhir.jpa.config;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
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.rest.server.interceptor.RequestValidatingInterceptor;
|
||||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||||
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
|
import net.ttddyy.dsproxy.listener.ThreadQueryCountHolder;
|
||||||
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
|
|
||||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||||
import org.apache.commons.dbcp2.BasicDataSource;
|
import org.apache.commons.dbcp2.BasicDataSource;
|
||||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||||
|
@ -30,6 +28,17 @@ import static org.junit.Assert.*;
|
||||||
public class TestR4Config extends BaseJavaConfigR4 {
|
public class TestR4Config extends BaseJavaConfigR4 {
|
||||||
|
|
||||||
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestR4Config.class);
|
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;
|
private Exception myLastStackTrace;
|
||||||
|
|
||||||
@Bean()
|
@Bean()
|
||||||
|
@ -90,13 +99,7 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
||||||
retVal.setUsername("");
|
retVal.setUsername("");
|
||||||
retVal.setPassword("");
|
retVal.setPassword("");
|
||||||
|
|
||||||
/*
|
retVal.setMaxTotal(ourMaxThreads);
|
||||||
* 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);
|
|
||||||
|
|
||||||
DataSource dataSource = ProxyDataSourceBuilder
|
DataSource dataSource = ProxyDataSourceBuilder
|
||||||
.create(retVal)
|
.create(retVal)
|
||||||
|
@ -155,4 +158,8 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static int getMaxThreads() {
|
||||||
|
return ourMaxThreads;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.provider.r4;
|
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.dao.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
|
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
|
||||||
|
@ -176,7 +177,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
return ids;
|
return ids;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Y
|
|
||||||
@Test
|
@Test
|
||||||
public void testBundleCreate() throws Exception {
|
public void testBundleCreate() throws Exception {
|
||||||
IGenericClient client = myClient;
|
IGenericClient client = myClient;
|
||||||
|
@ -1569,24 +1569,56 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGetResourceCountsOperation() throws Exception {
|
public void testFulltextEverythingWithIdAndContent() throws IOException {
|
||||||
String methodName = "testMetaOperations";
|
Patient p = new Patient();
|
||||||
|
p.setId("FOO");
|
||||||
|
p.addName().setFamily("FAMILY");
|
||||||
|
myClient.update().resource(p).execute();
|
||||||
|
|
||||||
Patient pt = new Patient();
|
p = new Patient();
|
||||||
pt.addName().setFamily(methodName);
|
p.setId("BAR");
|
||||||
myClient.create().resource(pt).execute().getId().toUnqualifiedVersionless();
|
p.addName().setFamily("HELLO");
|
||||||
|
myClient.update().resource(p).execute();
|
||||||
|
|
||||||
HttpGet get = new HttpGet(ourServerBase + "/$get-resource-counts");
|
Observation o = new Observation();
|
||||||
CloseableHttpResponse response = ourHttpClient.execute(get);
|
o.setId("BAZ");
|
||||||
try {
|
o.getSubject().setReference("Patient/FOO");
|
||||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
o.getCode().setText("GOODBYE");
|
||||||
String output = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
|
myClient.update().resource(o).execute();
|
||||||
IOUtils.closeQuietly(response.getEntity().getContent());
|
|
||||||
ourLog.info(output);
|
List<String> ids = searchAndReturnUnqualifiedVersionlessIdValues(ourServerBase + "/Patient/FOO/$everything?_content=White");
|
||||||
assertThat(output, containsString("<parameter><name value=\"Patient\"/><valueInteger value=\""));
|
assertThat(ids, contains("Patient/FOO"));
|
||||||
} finally {
|
|
||||||
response.close();
|
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<String> 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) {
|
// 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("<parameter><name value=\"Patient\"/><valueInteger value=\""));
|
||||||
|
} finally {
|
||||||
|
response.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testHasParameter() throws Exception {
|
public void testHasParameter() throws Exception {
|
||||||
IIdType pid0;
|
IIdType pid0;
|
||||||
|
@ -2049,6 +2102,54 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseAndEncodeExtensionWithValueWithExtension() throws IOException {
|
||||||
|
String input = "<Patient xmlns=\"http://hl7.org/fhir\">\n" +
|
||||||
|
" <extension url=\"https://purl.org/elab/fhir/network/StructureDefinition/1/BirthWeight\">\n" +
|
||||||
|
" <valueDecimal>\n" +
|
||||||
|
" <extension url=\"http://www.hl7.org/fhir/extension-data-absent-reason.html\">\n" +
|
||||||
|
" <valueCoding>\n" +
|
||||||
|
" <system value=\"http://hl7.org/fhir/ValueSet/birthweight\"/>\n" +
|
||||||
|
" <code value=\"Underweight\"/>\n" +
|
||||||
|
" <userSelected value=\"false\"/>\n" +
|
||||||
|
" </valueCoding>\n" +
|
||||||
|
" </extension>\n" +
|
||||||
|
" </valueDecimal>\n" +
|
||||||
|
" </extension>\n" +
|
||||||
|
" <identifier>\n" +
|
||||||
|
" <system value=\"https://purl.org/elab/fhir/network/StructureDefinition/1/EuroPrevallStudySubjects\"/>\n" +
|
||||||
|
" <value value=\"1\"/>\n" +
|
||||||
|
" </identifier>\n" +
|
||||||
|
" <gender value=\"female\"/>\n" +
|
||||||
|
"</Patient>";
|
||||||
|
|
||||||
|
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
|
@Test
|
||||||
public void testPatchUsingJsonPatch() throws Exception {
|
public void testPatchUsingJsonPatch() throws Exception {
|
||||||
String methodName = "testPatchUsingJsonPatch";
|
String methodName = "testPatchUsingJsonPatch";
|
||||||
|
@ -3205,14 +3306,19 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
ourLog.info(myCapturingInterceptor.getLastResponse().getAllHeaders().toString());
|
ourLog.info(myCapturingInterceptor.getLastResponse().getAllHeaders().toString());
|
||||||
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.<String>empty());
|
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.<String>empty());
|
||||||
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.<String>empty());
|
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()), Matchers.<String>empty());
|
||||||
|
|
||||||
String msg = "Total is " + found.getTotalElement().getValue() + " and took " + sw.getMillis() + " millis";
|
String msg = "Total is " + found.getTotalElement().getValue() + " and took " + sw.getMillis() + " millis";
|
||||||
|
|
||||||
// If this fails under load, try increasing the throttle above
|
// When we've only got one DB connection available, we are forced to wait for the
|
||||||
assertEquals(msg,null, found.getTotalElement().getValue());
|
// search to finish before returning
|
||||||
assertEquals(msg, 1, found.getEntry().size());
|
if (TestR4Config.getMaxThreads() > 1) {
|
||||||
assertThat(msg, sw.getMillis(), lessThan(1000L));
|
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)));
|
assertThat(sw.getMillis(), not(lessThan(1000L)));
|
||||||
|
|
||||||
// If this fails under load, try increasing the throttle above
|
|
||||||
assertEquals(10, found.getTotalElement().getValue().intValue());
|
assertEquals(10, found.getTotalElement().getValue().intValue());
|
||||||
assertEquals(1, found.getEntry().size());
|
assertEquals(1, found.getEntry().size());
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -3266,12 +3372,17 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.<String>empty());
|
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE), Matchers.<String>empty());
|
||||||
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()),Matchers.<String>empty());
|
assertThat(myCapturingInterceptor.getLastResponse().getHeaders(Constants.HEADER_X_CACHE.toLowerCase()), Matchers.<String>empty());
|
||||||
|
|
||||||
// If this fails under load, try increasing the throttle above
|
// WHen we've only got one DB connection available, we are forced to wait for the
|
||||||
assertEquals(null, found.getTotalElement().getValue());
|
// search to finish before returning
|
||||||
assertEquals(1, found.getEntry().size());
|
if (TestR4Config.getMaxThreads() > 1) {
|
||||||
assertThat(sw.getMillis(), lessThan(1500L));
|
assertEquals(null, found.getTotalElement().getValue());
|
||||||
|
assertEquals(1, found.getEntry().size());
|
||||||
|
assertThat(sw.getMillis(), lessThan(1500L));
|
||||||
|
} else {
|
||||||
|
assertThat(sw.getMillis(), greaterThan(1500L));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -4353,56 +4464,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void testParseAndEncodeExtensionWithValueWithExtension() throws IOException {
|
|
||||||
String input = "<Patient xmlns=\"http://hl7.org/fhir\">\n" +
|
|
||||||
" <extension url=\"https://purl.org/elab/fhir/network/StructureDefinition/1/BirthWeight\">\n" +
|
|
||||||
" <valueDecimal>\n" +
|
|
||||||
" <extension url=\"http://www.hl7.org/fhir/extension-data-absent-reason.html\">\n" +
|
|
||||||
" <valueCoding>\n" +
|
|
||||||
" <system value=\"http://hl7.org/fhir/ValueSet/birthweight\"/>\n" +
|
|
||||||
" <code value=\"Underweight\"/>\n" +
|
|
||||||
" <userSelected value=\"false\"/>\n" +
|
|
||||||
" </valueCoding>\n" +
|
|
||||||
" </extension>\n" +
|
|
||||||
" </valueDecimal>\n" +
|
|
||||||
" </extension>\n" +
|
|
||||||
" <identifier>\n" +
|
|
||||||
" <system value=\"https://purl.org/elab/fhir/network/StructureDefinition/1/EuroPrevallStudySubjects\"/>\n" +
|
|
||||||
" <value value=\"1\"/>\n" +
|
|
||||||
" </identifier>\n" +
|
|
||||||
" <gender value=\"female\"/>\n" +
|
|
||||||
"</Patient>";
|
|
||||||
|
|
||||||
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
|
@Test
|
||||||
public void testValueSetExpandOperation() throws IOException {
|
public void testValueSetExpandOperation() throws IOException {
|
||||||
CodeSystem cs = myFhirCtx.newXmlParser().parseResource(CodeSystem.class, new InputStreamReader(ResourceProviderR4Test.class.getResourceAsStream("/extensional-case-3-cs.xml")));
|
CodeSystem cs = myFhirCtx.newXmlParser().parseResource(CodeSystem.class, new InputStreamReader(ResourceProviderR4Test.class.getResourceAsStream("/extensional-case-3-cs.xml")));
|
||||||
|
|
|
@ -178,7 +178,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
||||||
if (theRequest.getRequestType() == RequestTypeEnum.GET) {
|
if (theRequest.getRequestType() == RequestTypeEnum.GET) {
|
||||||
boolean first = true;
|
boolean first = true;
|
||||||
Map<String, String[]> parameters = theRequest.getParameters();
|
Map<String, String[]> parameters = theRequest.getParameters();
|
||||||
for (String nextParamName : new TreeSet<String>(parameters.keySet())) {
|
for (String nextParamName : new TreeSet<>(parameters.keySet())) {
|
||||||
for (String nextParamValue : parameters.get(nextParamName)) {
|
for (String nextParamValue : parameters.get(nextParamName)) {
|
||||||
if (first) {
|
if (first) {
|
||||||
b.append('?');
|
b.append('?');
|
||||||
|
|
|
@ -77,7 +77,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
* Check for parameter combinations and names that are invalid
|
* Check for parameter combinations and names that are invalid
|
||||||
*/
|
*/
|
||||||
List<IParameter> parameters = getParameters();
|
List<IParameter> parameters = getParameters();
|
||||||
// List<SearchParameter> searchParameters = new ArrayList<SearchParameter>();
|
|
||||||
for (int i = 0; i < parameters.size(); i++) {
|
for (int i = 0; i < parameters.size(); i++) {
|
||||||
IParameter next = parameters.get(i);
|
IParameter next = parameters.get(i);
|
||||||
if (!(next instanceof SearchParameter)) {
|
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
|
* Only compartment searching methods may have an ID parameter
|
||||||
|
|
|
@ -17,6 +17,10 @@
|
||||||
RFC 3986. This affects client calls, as well as URLs generated by
|
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!
|
the server (e.g. REST HOOK calls). Thanks to James Daily for reporting!
|
||||||
</action>
|
</action>
|
||||||
|
<action type="fix">
|
||||||
|
Searching in JPA server using a combination of _content and _id parameters
|
||||||
|
failed. Thanks to Jeff Weyer for reporting!
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.1.0" date="2017-11-23">
|
<release version="3.1.0" date="2017-11-23">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue