diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java index cdb4c99e142..290b22738d4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ModelScanner.java @@ -28,17 +28,8 @@ import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; -import java.util.ArrayList; -import java.util.Collection; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Properties; -import java.util.Set; import org.apache.commons.io.IOUtils; import org.hl7.fhir.instance.model.api.IBase; @@ -408,7 +399,23 @@ class ModelScanner { Map nameToParam = new HashMap(); Map compositeFields = new LinkedHashMap(); - for (Field nextField : theClass.getFields()) { + /* + * Make sure we pick up fields in interfaces too.. This ensures that we + * grab the _id field which generally gets picked up via interface + */ + Set fields = new HashSet(Arrays.asList(theClass.getFields())); + Class nextClass = theClass; + do { + for (Class nextInterface : nextClass.getInterfaces()) { + fields.addAll(Arrays.asList(nextInterface.getFields())); + } + nextClass = nextClass.getSuperclass(); + } while (nextClass.equals(Object.class) == false); + + /* + * Now scan the fields for search params + */ + for (Field nextField : fields) { SearchParamDefinition searchParam = pullAnnotation(nextField, SearchParamDefinition.class); if (searchParam != null) { RestSearchParameterTypeEnum paramType = RestSearchParameterTypeEnum.forCode(searchParam.type().toLowerCase()); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java index 2a6e4d9f9d0..40b6a1c5f98 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/TokenClientParam.java @@ -6,6 +6,8 @@ import java.util.Arrays; import java.util.Collection; import java.util.List; +import org.apache.commons.lang3.ObjectUtils; + import ca.uhn.fhir.model.base.composite.BaseIdentifierDt; /* @@ -34,6 +36,8 @@ import ca.uhn.fhir.model.base.composite.BaseIdentifierDt; */ public class TokenClientParam extends BaseClientParam implements IParam { + private static final String[] EMPTY_STRING_LIST = new String[0]; + private String myParamName; public TokenClientParam(String theParamName) { @@ -77,6 +81,16 @@ public class TokenClientParam extends BaseClientParam implements IParam { return new TokenCriterion(getParamName(), defaultString(theSystem), theCode); } + @Override + public ICriterion systemAndValues(String theSystem, String... theValues) { + return new TokenCriterion(getParamName(), defaultString(theSystem), convertToList(theValues)); + } + + private List convertToList(String[] theValues) { + String[] values = ObjectUtils.defaultIfNull(theValues, EMPTY_STRING_LIST); + return Arrays.asList(values); + } + @Override public ICriterion systemAndValues(String theSystem, Collection theValues) { return new TokenCriterion(getParamName(), defaultString(theSystem), theValues); @@ -111,6 +125,7 @@ public class TokenClientParam extends BaseClientParam implements IParam { */ ICriterion code(String theIdentifier); + /** * Creates a search criterion that matches against the given identifier (system and code if both are present, or whatever is present) * @@ -160,15 +175,6 @@ public class TokenClientParam extends BaseClientParam implements IParam { */ ICriterion systemAndCode(String theSystem, String theCode); - /** - * Creates a search criterion that matches a given system with a collection of possible - * values (this will be used to form a comma-separated OR query) - * - * @param theSystem The system, which will be used with each value - * @param theValues The values - */ - public ICriterion systemAndValues(String theSystem, Collection theValues); - /** * Creates a search criterion that matches against the given system and identifier * @@ -180,6 +186,24 @@ public class TokenClientParam extends BaseClientParam implements IParam { */ ICriterion systemAndIdentifier(String theSystem, String theIdentifier); + /** + * Creates a search criterion that matches a given system with a collection of possible + * values (this will be used to form a comma-separated OR query) + * + * @param theSystem The system, which will be used with each value + * @param theValues The values + */ + ICriterion systemAndValues(String theSystem, String... theValues); + + /** + * Creates a search criterion that matches a given system with a collection of possible + * values (this will be used to form a comma-separated OR query) + * + * @param theSystem The system, which will be used with each value + * @param theValues The values + */ + public ICriterion systemAndValues(String theSystem, Collection theValues); + } } diff --git a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java index 2932586db77..999c69e49b9 100644 --- a/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java +++ b/hapi-fhir-base/src/main/java/org/hl7/fhir/instance/model/api/IAnyResource.java @@ -1,7 +1,7 @@ package org.hl7.fhir.instance.model.api; import ca.uhn.fhir.model.api.annotation.SearchParamDefinition; -import ca.uhn.fhir.rest.gclient.StringClientParam; +import ca.uhn.fhir.rest.gclient.TokenClientParam; /* * #%L @@ -35,7 +35,7 @@ public interface IAnyResource extends IBaseResource { /** * Search parameter constant for _id */ - @SearchParamDefinition(name="_id", path="", description="The ID of the resource", type="string" ) + @SearchParamDefinition(name="_id", path="", description="The ID of the resource", type="token" ) public static final String SP_RES_ID = "_id"; /** @@ -46,7 +46,7 @@ public interface IAnyResource extends IBaseResource { * Path: Resource._id
*

*/ - public static final StringClientParam RES_ID = new StringClientParam(IAnyResource.SP_RES_ID); + public static final TokenClientParam RES_ID = new TokenClientParam(IAnyResource.SP_RES_ID); String getId(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java index 689390b2987..08ec2719c49 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java @@ -2283,17 +2283,25 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test { id2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless(); } - //@formatter:off Bundle found = ourClient .search() .forResource(Patient.class) - .where(BaseResource.RES_ID.matches().values(id1.getIdPart(), id2.getIdPart())) - .and(BaseResource.RES_ID.matches().value(id1.getIdPart())) + .where(BaseResource.RES_ID.exactly().systemAndValues(null, id1.getIdPart(), id2.getIdPart())) + .and(BaseResource.RES_ID.exactly().systemAndCode(null, id1.getIdPart())) .returnBundle(Bundle.class) .execute(); - //@formatter:on assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1)); + + found = ourClient + .search() + .forResource(Patient.class) + .where(BaseResource.RES_ID.exactly().systemAndValues(null, id1.getIdPart(), id2.getIdPart())) + .returnBundle(Bundle.class) + .execute(); + + assertThat(toUnqualifiedVersionlessIds(found), containsInAnyOrder(id1, id2)); + } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ServerDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ServerDstu3Test.java new file mode 100644 index 00000000000..92728e7358e --- /dev/null +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/dstu3/ServerDstu3Test.java @@ -0,0 +1,69 @@ +package ca.uhn.fhir.jpa.provider.dstu3; + +import static org.hamcrest.Matchers.not; +import static org.hamcrest.Matchers.stringContainsInOrder; +import static org.junit.Assert.*; + +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.util.HashSet; +import java.util.Set; + +import org.apache.commons.io.IOUtils; +import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpGet; +import org.hl7.fhir.dstu3.model.CapabilityStatement; +import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceComponent; +import org.hl7.fhir.dstu3.model.CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent; +import org.junit.AfterClass; +import org.junit.Test; + +import ca.uhn.fhir.util.TestUtil; + +public class ServerDstu3Test extends BaseResourceProviderDstu3Test { + + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerDstu3Test.class); + + + + /** + * See #519 + */ + @Test + public void saveIdParamOnlyAppearsOnce() throws IOException { + HttpGet get = new HttpGet(ourServerBase + "/metadata?_pretty=true&_format=xml"); + CloseableHttpResponse resp = ourHttpClient.execute(get); + try { + ourLog.info(resp.toString()); + assertEquals(200, resp.getStatusLine().getStatusCode()); + + String respString = IOUtils.toString(resp.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(respString); + + CapabilityStatement cs = myFhirCtx.newXmlParser().parseResource(CapabilityStatement.class, respString); + + for (CapabilityStatementRestResourceComponent nextResource : cs.getRest().get(0).getResource()) { + ourLog.info("Testing resource: " + nextResource.getType()); + Set sps = new HashSet(); + for (CapabilityStatementRestResourceSearchParamComponent nextSp : nextResource.getSearchParam()) { + if (sps.add(nextSp.getName()) == false) { + fail("Duplicate search parameter " + nextSp.getName() + " for resource " + nextResource.getType()); + } + } + + if (!sps.contains("_id")) { + fail("No search parameter _id for resource " + nextResource.getType()); + } + } + } finally { + IOUtils.closeQuietly(resp.getEntity().getContent()); + } + } + + + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); + } + +} diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Binary.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Binary.java index 7f797ccba59..bcf4e3d46d3 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Binary.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/model/Binary.java @@ -47,7 +47,7 @@ import org.hl7.fhir.exceptions.FHIRException; * A binary resource can contain any content, whether text, image, pdf, zip archive, etc. */ @ResourceDef(name="Binary", profile="http://hl7.org/fhir/Profile/Binary") -public class Binary extends BaseBinary implements IBaseBinary { +public class Binary extends BaseBinary implements IBaseBinary, IAnyResource { /** * MimeType of the binary content represented as a standard MimeType (BCP 13). diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ModelScannerDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ModelScannerDstu3Test.java index 52a7f2a86fc..6b9e9bec1f6 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ModelScannerDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/context/ModelScannerDstu3Test.java @@ -36,6 +36,15 @@ public class ModelScannerDstu3Test { TestUtil.clearAllStaticFieldsForUnitTest(); } + @Test + public void testScanBundle() { + FhirContext ctx = FhirContext.forDstu3(); + RuntimeResourceDefinition def = ctx.getResourceDefinition("Bundle"); + + assertNotNull(def.getSearchParam("composition")); + assertNotNull(def.getSearchParam("_id")); + } + @Test public void testBundleMustImplementIBaseBundle() throws DataFormatException { diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java index 3f472665ef9..b5afb29c180 100644 --- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java +++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java @@ -236,10 +236,14 @@ public class TinderJpaRestServerMojo extends AbstractMojo { TinderJpaRestServerMojo mojo = new TinderJpaRestServerMojo(); mojo.myProject = new MavenProject(); - mojo.version = "dstu3"; + mojo.version = "dstu2"; mojo.packageBase = "ca.uhn.test"; mojo.configPackageBase = "ca.uhn.test"; - mojo.baseResourceNames = new ArrayList(Arrays.asList("structuredefinition", "observation")); + mojo.baseResourceNames = new ArrayList(Arrays.asList( +// "observation" +// "communicationrequest" + "binary" + )); mojo.targetDirectory = new File("target/generated/valuesets"); mojo.targetResourceDirectory = new File("target/generated/valuesets"); mojo.targetResourceSpringBeansFile = "tmp_beans.xml"; 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 60929ca81b2..212dfb62ee2 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 @@ -45,14 +45,6 @@ public class ${className}ResourceProvider extends ca.uhn.fhir.rest.method.RequestDetails theRequestDetails, - @Description(shortDefinition="The resource identity") - @OptionalParam(name="_id") - StringAndListParam theId, - - @Description(shortDefinition="The resource language") - @OptionalParam(name="_language") - StringAndListParam theResourceLanguage, - @Description(shortDefinition="Search the contents of the resource's data using a fulltext search") @OptionalParam(name=ca.uhn.fhir.rest.server.Constants.PARAM_CONTENT) StringAndListParam theFtContent, @@ -141,8 +133,6 @@ public class ${className}ResourceProvider extends startRequest(theServletRequest); try { SearchParameterMap paramMap = new SearchParameterMap(); - paramMap.add("_id", theId); - paramMap.add("_language", theResourceLanguage); paramMap.add(ca.uhn.fhir.rest.server.Constants.PARAM_CONTENT, theFtContent); paramMap.add(ca.uhn.fhir.rest.server.Constants.PARAM_TEXT, theFtText); paramMap.add(ca.uhn.fhir.rest.server.Constants.PARAM_TAG, theSearchForTag); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 0936688e053..07e01307af4 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -192,6 +192,12 @@ before the interceptor could see it. Thanks to Eeva Turkka for reporting! + + JPA server exported CapabilityStatement includes + double entries for the _id parameter and uses the + wrong type (string instead of token). Thanks to + Robert Lichtenberger for reporting! +