diff --git a/examples/src/main/java/example/GenericClientExample.java b/examples/src/main/java/example/GenericClientExample.java index 1a18b9321f9..388830a4c16 100644 --- a/examples/src/main/java/example/GenericClientExample.java +++ b/examples/src/main/java/example/GenericClientExample.java @@ -269,6 +269,7 @@ public class GenericClientExample { .encodedJson() .where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22")) .and(Patient.BIRTHDATE.after().day("2011-01-01")) + .withTag("http://acme.org/codes", "needs-review") .include(Patient.INCLUDE_ORGANIZATION) .revInclude(Provenance.INCLUDE_TARGET) .lastUpdated(new DateRangeParam("2011-01-01", null)) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index 5b78c08978c..d0fbd22cfaa 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -129,6 +129,7 @@ import ca.uhn.fhir.rest.method.ValidateMethodBindingDstu1; import ca.uhn.fhir.rest.method.ValidateMethodBindingDstu2; import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; +import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; @@ -1556,6 +1557,10 @@ public class GenericClient extends BaseClient implements IGenericClient { myCriterion.populateParamList(params); + for (TokenParam next : myTags) { + addParam(params, Constants.PARAM_TAG, next.getValueAsQueryToken()); + } + for (Include next : myInclude) { addParam(params, Constants.PARAM_INCLUDE, next.getValue()); } @@ -1689,6 +1694,15 @@ public class GenericClient extends BaseClient implements IGenericClient { return this; } + private List myTags = new ArrayList(); + + @Override + public IQuery withTag(String theSystem, String theCode) { + Validate.notBlank(theCode, "theCode must not be null or empty"); + myTags.add(new TokenParam(theSystem, theCode)); + return this; + } + } @SuppressWarnings("rawtypes") diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java index 697eae5a0aa..05d3d1e3184 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java @@ -251,6 +251,12 @@ public interface IGenericClient extends IRestfulClient { @Override void registerInterceptor(IClientInterceptor theInterceptor); + /** + * Search for resources matching a given set of criteria. Searching is a very powerful + * feature in FHIR with many features for specifying exactly what should be seaerched for + * and how it should be returned. See the specification on search + * for more information. + */ IUntypedQuery search(); /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java index ae26266ca3b..5e7060cce75 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java @@ -37,6 +37,14 @@ public interface IQuery extends IClientExecutable, T>, IBaseQuery limitTo(int theLimitTo); + /** + * Match only resources where the resource has the given tag. This parameter corresponds to + * the _tag URL parameter. + * @param theSystem The tag code system, or null to match any code system (this may not be supported on all servers) + * @param theCode The tag code. Must not be null or empty. + */ + IQuery withTag(String theSystem, String theCode); + /** * Forces the query to perform the search using the given method (allowable methods are described in the * FHIR Specification Section 2.1.11) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java index c8b20e75b37..d3bfafde8de 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java @@ -110,6 +110,7 @@ public class Constants { public static final String PARAM_SORT_ASC = "_sort:asc"; public static final String PARAM_SORT_DESC = "_sort:desc"; public static final String PARAM_TAGS = "_tags"; + public static final String PARAM_TAG = "_tag"; public static final String PARAM_VALIDATE = "_validate"; public static final String PARAMQUALIFIER_MISSING = ":missing"; public static final String PARAMQUALIFIER_MISSING_FALSE = "false"; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index 1912d830166..74ec6ff27c9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -59,7 +59,6 @@ import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; -import org.omg.PortableInterceptor.InterceptorOperations; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Required; import org.springframework.transaction.PlatformTransactionManager; @@ -86,6 +85,7 @@ import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamToken; import ca.uhn.fhir.jpa.entity.ResourceLink; import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.entity.ResourceTag; import ca.uhn.fhir.jpa.entity.TagDefinition; import ca.uhn.fhir.jpa.entity.TagTypeEnum; import ca.uhn.fhir.jpa.util.StopWatch; @@ -293,6 +293,71 @@ public abstract class BaseHapiFhirResourceDao extends BaseH return new HashSet(q.getResultList()); } + private Set addPredicateTag(Set thePids, List> theList) { + Set pids = thePids; + if (theList == null || theList.isEmpty()) { + return pids; + } + + for (List nextAndParams : theList) { + boolean haveTags = false; + for (IQueryParameterType nextParamUncasted : nextAndParams) { + TokenParam nextParam = (TokenParam) nextParamUncasted; + if (isNotBlank(nextParam.getValue())) { + haveTags = true; + } else if (isNotBlank(nextParam.getSystem())) { + throw new InvalidRequestException("Invalid _tag parameter (must supply a value/code and not just a system): " + nextParam.getValueAsQueryToken()); + } + } + if (!haveTags) { + continue; + } + + CriteriaBuilder builder = myEntityManager.getCriteriaBuilder(); + CriteriaQuery cq = builder.createQuery(Long.class); + Root from = cq.from(ResourceTag.class); + cq.select(from.get("myResourceId").as(Long.class)); + + List andPredicates = new ArrayList(); + andPredicates.add(builder.equal(from.get("myResourceType"), myResourceName)); + + List orPredicates = new ArrayList(); + for (IQueryParameterType nextOrParams : nextAndParams) { + TokenParam nextParam = (TokenParam) nextOrParams; + + From defJoin = from.join("myTag"); + Predicate codePrediate = builder.equal(defJoin.get("myCode"), nextParam.getValue()); + if (isBlank(nextParam.getValue())) { + continue; + } + if (isNotBlank(nextParam.getSystem())) { + Predicate systemPrediate = builder.equal(defJoin.get("mySystem"), nextParam.getSystem()); + orPredicates.add(builder.and(systemPrediate, codePrediate)); + } else { + orPredicates.add(codePrediate); + } + + } + if (orPredicates.isEmpty() == false) { + andPredicates.add(builder.or(orPredicates.toArray(new Predicate[0]))); + } + + Predicate masterCodePredicate = builder.and(andPredicates.toArray(new Predicate[0])); + + if (pids.size() > 0) { + Predicate inPids = (from.get("myResourceId").in(pids)); + cq.where(builder.and(masterCodePredicate, inPids)); + } else { + cq.where(masterCodePredicate); + } + + TypedQuery q = myEntityManager.createQuery(cq); + pids = new HashSet(q.getResultList()); + } + + return pids; + } + private boolean addPredicateMissingFalseIfPresent(CriteriaBuilder theBuilder, String theParamName, Root from, List codePredicates, IQueryParameterType nextOr) { boolean missingFalse = false; @@ -635,11 +700,11 @@ public abstract class BaseHapiFhirResourceDao extends BaseH RuntimeResourceDefinition resDef = getContext().getResourceDefinition(ref.getResourceType()); resourceTypes.add(resDef.getImplementingClass()); } - + boolean foundChainMatch = false; for (Class nextType : resourceTypes) { RuntimeResourceDefinition typeDef = getContext().getResourceDefinition(nextType); - + String chain = ref.getChain(); String remainingChain = null; int chainDotIndex = chain.indexOf('.'); @@ -658,23 +723,23 @@ public abstract class BaseHapiFhirResourceDao extends BaseH ourLog.debug("Don't have a DAO for type {}", nextType.getSimpleName(), param); continue; } - + IQueryParameterType chainValue; if (remainingChain != null) { if (param.getParamType() != RestSearchParameterTypeEnum.REFERENCE) { ourLog.debug("Type {} parameter {} is not a reference, can not chain {}", new Object[] { nextType.getSimpleName(), chain, remainingChain }); continue; } - + chainValue = new ReferenceParam(); chainValue.setValueAsQueryToken(null, resourceId); - ((ReferenceParam)chainValue).setChain(remainingChain); + ((ReferenceParam) chainValue).setChain(remainingChain); } else { chainValue = toParameterType(param, resourceId); } - + foundChainMatch = true; - + Set pids = dao.searchForIds(chain, chainValue); if (pids.isEmpty()) { continue; @@ -684,7 +749,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH codePredicates.add(eq); } - + if (!foundChainMatch) { throw new InvalidRequestException(getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "invalidParameterChain", theParamName + '.' + ref.getChain())); } @@ -894,7 +959,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH protected IBaseOperationOutcome createErrorOperationOutcome(String theMessage) { return createOperationOutcome(IssueSeverityEnum.ERROR.getCode(), theMessage); } - + protected IBaseOperationOutcome createInfoOperationOutcome(String theMessage) { return createOperationOutcome(IssueSeverityEnum.INFORMATION.getCode(), theMessage); } @@ -1017,12 +1082,12 @@ public abstract class BaseHapiFhirResourceDao extends BaseH } 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); + throw new InvalidRequestException( + "Parameter[" + theParamName + "] 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); + throw new InvalidRequestException( + "Parameter[" + theParamName + "] has code (" + code.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamToken.MAX_LENGTH + "): " + code); } ArrayList singleCodePredicates = (new ArrayList()); @@ -1096,13 +1161,13 @@ public abstract class BaseHapiFhirResourceDao extends BaseH } From stringJoin = theFrom.join(joinAttrName, JoinType.INNER); - + if (param.getParamType() == RestSearchParameterTypeEnum.REFERENCE) { thePredicates.add(stringJoin.get("mySourcePath").as(String.class).in(param.getPathsSplit())); } else { thePredicates.add(theBuilder.equal(stringJoin.get("myParamName"), theSort.getParamName())); } - + // Predicate p = theBuilder.equal(stringJoin.get("myParamName"), theSort.getParamName()); // Predicate pn = theBuilder.isNull(stringJoin.get("myParamName")); // thePredicates.add(theBuilder.or(p, pn)); @@ -1197,7 +1262,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH DaoMethodOutcome outcome = toMethodOutcome(entity, theResource).setCreated(true); notifyWriteCompleted(); - + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulCreate", outcome.getId(), w.getMillisAndRestart()); outcome.setOperationOutcome(createInfoOperationOutcome(msg)); @@ -1594,8 +1659,8 @@ public abstract class BaseHapiFhirResourceDao extends BaseH if (entity == null) { if (theId.hasVersionIdPart()) { - TypedQuery q = myEntityManager.createQuery( - "SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class); + TypedQuery q = myEntityManager + .createQuery("SELECT t from ResourceHistoryTable t WHERE t.myResourceId = :RID AND t.myResourceType = :RTYP AND t.myResourceVersion = :RVER", ResourceHistoryTable.class); q.setParameter("RID", pid); q.setParameter("RTYP", myResourceName); q.setParameter("RVER", Long.parseLong(theId.getVersionIdPart())); @@ -1930,6 +1995,10 @@ public abstract class BaseHapiFhirResourceDao extends BaseH pids = addPredicateLanguage(pids, nextParamEntry.getValue()); + } else if (nextParamName.equals("_tag")) { + + pids = addPredicateTag(pids, nextParamEntry.getValue()); + } else { RuntimeSearchParam nextParamDef = resourceDef.getSearchParam(nextParamName); @@ -2136,7 +2205,7 @@ public abstract class BaseHapiFhirResourceDao extends BaseH ResourceTable savedEntity = updateEntity(theResource, entity, true, null, thePerformIndexing, true); notifyWriteCompleted(); - + DaoMethodOutcome outcome = toMethodOutcome(savedEntity, theResource).setCreated(false); String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "successfulCreate", outcome.getId(), w.getMillisAndRestart()); @@ -2160,8 +2229,8 @@ public abstract class BaseHapiFhirResourceDao extends BaseH private void validateResourceType(BaseHasResource entity) { if (!myResourceName.equals(entity.getResourceType())) { - throw new ResourceNotFoundException("Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " - + entity.getResourceType()); + throw new ResourceNotFoundException( + "Resource with ID " + entity.getIdDt().getIdPart() + " exists but it is not of type " + myResourceName + ", found resource of type " + entity.getResourceType()); } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java index 38047264b7d..f0b8d830ab3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/ResourceTag.java @@ -44,12 +44,12 @@ public class ResourceTag extends BaseTag { @JoinColumn(name = "RES_ID", referencedColumnName = "RES_ID") private ResourceTable myResource; - @Column(name = "RES_TYPE", length = ResourceTable.RESTYPE_LEN,nullable=false) + @Column(name = "RES_TYPE", length = ResourceTable.RESTYPE_LEN, nullable = false) private String myResourceType; - @Column(name="RES_ID", insertable=false,updatable=false) + @Column(name = "RES_ID", insertable = false, updatable = false) private Long myResourceId; - + public Long getResourceId() { return myResourceId; } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java index bef0f0f6254..81eb6b36630 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TagDefinition.java @@ -39,9 +39,7 @@ import ca.uhn.fhir.model.api.Tag; //@formatter:on @Entity -@Table(name = "HFJ_TAG_DEF", uniqueConstraints = { - @UniqueConstraint(columnNames = { "TAG_TYPE", "TAG_SYSTEM", "TAG_CODE" }) -}) +@Table(name = "HFJ_TAG_DEF", uniqueConstraints = { @UniqueConstraint(columnNames = { "TAG_TYPE", "TAG_SYSTEM", "TAG_CODE" }) }) //@formatter:off public class TagDefinition implements Serializable { @@ -89,6 +87,10 @@ public class TagDefinition implements Serializable { return myDisplay; } + public Long getId() { + return myId; + } + public String getSystem() { return mySystem; } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java index 5f30d358e30..f726b2a793f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/FhirResourceDaoDstu2Test.java @@ -87,6 +87,7 @@ import ca.uhn.fhir.rest.param.NumberParam; import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.ReferenceParam; import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.param.TokenAndListParam; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; import ca.uhn.fhir.rest.server.IBundleProvider; @@ -1873,6 +1874,80 @@ public class FhirResourceDaoDstu2Test extends BaseJpaTest { } + @Test + public void testSearchWithTagParameter() { + String methodName = "testSearchWithTagParameter"; + + IIdType tag1id; + { + Organization org = new Organization(); + org.getNameElement().setValue("FOO"); + TagList tagList = new TagList(); + tagList.addTag("urn:taglist", methodName + "1a"); + tagList.addTag("urn:taglist", methodName + "1b"); + ResourceMetadataKeyEnum.TAG_LIST.put(org, tagList); + tag1id = ourOrganizationDao.create(org).getId().toUnqualifiedVersionless(); + } + IIdType tag2id; + { + Organization org = new Organization(); + org.getNameElement().setValue("FOO"); + TagList tagList = new TagList(); + tagList.addTag("urn:taglist", methodName + "2a"); + tagList.addTag("urn:taglist", methodName + "2b"); + ResourceMetadataKeyEnum.TAG_LIST.put(org, tagList); + tag2id = ourOrganizationDao.create(org).getId().toUnqualifiedVersionless(); + } + + { + // One tag + SearchParameterMap params = new SearchParameterMap(); + params.add("_tag", new TokenParam("urn:taglist", methodName + "1a")); + List patients = toUnqualifiedVersionlessIds(ourOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id)); + } + { + // Code only + SearchParameterMap params = new SearchParameterMap(); + params.add("_tag", new TokenParam(null, methodName + "1a")); + List patients = toUnqualifiedVersionlessIds(ourOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id)); + } + { + // Or tags + SearchParameterMap params = new SearchParameterMap(); + TokenOrListParam orListParam = new TokenOrListParam(); + orListParam.add(new TokenParam("urn:taglist", methodName + "1a")); + orListParam.add(new TokenParam("urn:taglist", methodName + "2a")); + params.add("_tag", orListParam); + List patients = toUnqualifiedVersionlessIds(ourOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id, tag2id)); + } + // TODO: get multiple/AND working + { + // And tags + SearchParameterMap params = new SearchParameterMap(); + TokenAndListParam andListParam = new TokenAndListParam(); + andListParam.addValue(new TokenOrListParam("urn:taglist", methodName + "1a")); + andListParam.addValue(new TokenOrListParam("urn:taglist", methodName + "2a")); + params.add("_tag", andListParam); + List patients = toUnqualifiedVersionlessIds(ourOrganizationDao.search(params)); + assertEquals(0, patients.size()); + } + + { + // And tags + SearchParameterMap params = new SearchParameterMap(); + TokenAndListParam andListParam = new TokenAndListParam(); + andListParam.addValue(new TokenOrListParam("urn:taglist", methodName + "1a")); + andListParam.addValue(new TokenOrListParam("urn:taglist", methodName + "1b")); + params.add("_tag", andListParam); + List patients = toUnqualifiedVersionlessIds(ourOrganizationDao.search(params)); + assertThat(patients, containsInAnyOrder(tag1id)); + } + + } + @Test public void testSearchWithIncludes() { IIdType parentOrgId; diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java index b0727661871..6516c653c63 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -693,6 +693,34 @@ public class GenericClientTest { } + @SuppressWarnings("unused") + @Test + public void testSearchByTag() throws Exception { + + String msg = getPatientFeedWithOneResult(); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"))); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle response = client.search() + .forResource(Patient.class) + .withTag("urn:foo", "123") + .withTag("urn:bar", "456") + .execute(); + //@formatter:on + + assertEquals( + "http://example.com/fhir/Patient?_tag=urn%3Afoo%7C123&_tag=urn%3Abar%7C456", + capt.getValue().getURI().toString()); + + } + @SuppressWarnings("unused") @Test public void testSearchWithReverseInclude() throws Exception { diff --git a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm index c5ae48d90cb..6119883454a 100644 --- a/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm +++ b/hapi-tinder-plugin/src/main/resources/vm/jpa_resource_provider.vm @@ -49,6 +49,11 @@ public class ${className}ResourceProvider extends @Description(shortDefinition="The resource language") @OptionalParam(name="_language") StringParam theResourceLanguage, + + @Description(shortDefinition="Search for resources which have the given tag") + @OptionalParam(name="_tag") + TokenAndListParam theSearchForTag, + #foreach ( $param in $searchParams ) #{if}(true) #{end} @Description(shortDefinition="${param.description}") @@ -113,10 +118,11 @@ public class ${className}ResourceProvider extends startRequest(theServletRequest); try { SearchParameterMap paramMap = new SearchParameterMap(); - paramMap.add("_id", theId); - paramMap.add("_language", theResourceLanguage); + paramMap.add("_id", theId); + paramMap.add("_language", theResourceLanguage); + paramMap.add("_tag", theSearchForTag); #foreach ( $param in $searchParams ) - paramMap.add("${param.name}", the${param.nameCapitalized}); + paramMap.add("${param.name}", the${param.nameCapitalized}); #end #if ( $version != 'dstu' ) paramMap.setRevIncludes(theRevIncludes); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0683c91a138..d59e05eca4b 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -64,6 +64,9 @@ package lists are up to date. Thanks to GitHub user Brian S. Corbin (@corbinbs) for thr contribution! + + JPA server and generic client now both support the _tag search parameter + diff --git a/src/site/xdoc/doc_rest_client.xml b/src/site/xdoc/doc_rest_client.xml index 5650698cc68..d164031d706 100644 --- a/src/site/xdoc/doc_rest_client.xml +++ b/src/site/xdoc/doc_rest_client.xml @@ -178,7 +178,7 @@

Search - Other Query Options

The fluent search also has methods for sorting, limiting, specifying - JSON encoding, _include, _revinclude, _lastUpdated, etc. + JSON encoding, _include, _revinclude, _lastUpdated, _tag, etc.