diff --git a/hapi-fhir-base/src/changes/changes.xml b/hapi-fhir-base/src/changes/changes.xml index 4ff34116fab..651160a3b35 100644 --- a/hapi-fhir-base/src/changes/changes.xml +++ b/hapi-fhir-base/src/changes/changes.xml @@ -8,9 +8,6 @@ -<<<<<<< HEAD -======= - RESTful search method parameters have been overhauled to reduce confusing duplicate names and having multiple ways of accomplishing the same thing. This means that a number of existing classes have been deprocated in favour of new naming schemes.
]]> @@ -25,7 +22,6 @@ new parameters when possible.
->>>>>>> af3c35cbc07df69c760e200b4a80f4bcc3d183e9 Allow server methods to return wildcard generic types (e.g. List<? extends IResource>) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/IncludeParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/IncludeParam.java index 63f853e2e7a..857322bc672 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/IncludeParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/IncludeParam.java @@ -45,7 +45,13 @@ public @interface IncludeParam { /** * Optional parameter, if provided the server will only allow the values * within the given set. If an _include parameter is passed to the server - * which does not match any allowed values the server will return an error. + * which does not match any allowed values the server will return an error. + *

+ * You may also pass in a value of "*" which indicates to the server + * that any value is allowable and will be passed to this parameter. This is + * helpful if you want to explicitly declare support for some includes, but also + * allow others implicitly (e.g. imports from other resources) + *

*/ String[] allow() default {}; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java index 563ee1a77e8..0b83da97d11 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java @@ -61,7 +61,6 @@ import ca.uhn.fhir.rest.annotation.Validate; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; -import ca.uhn.fhir.rest.param.IncludeParameter; import ca.uhn.fhir.rest.server.BundleProviders; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IncludeParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IncludeParameter.java similarity index 93% rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IncludeParameter.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IncludeParameter.java index 3f61fa27451..8c8a43cfec5 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/IncludeParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IncludeParameter.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.rest.param; +package ca.uhn.fhir.rest.method; /* * #%L @@ -34,11 +34,11 @@ import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.api.PathSpecification; import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.rest.annotation.IncludeParam; -import ca.uhn.fhir.rest.method.QualifiedParamList; +import ca.uhn.fhir.rest.param.BaseQueryParameter; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -public class IncludeParameter extends BaseQueryParameter { +class IncludeParameter extends BaseQueryParameter { private Set myAllow; private Class> myInstantiableCollectionType; @@ -133,7 +133,9 @@ public class IncludeParameter extends BaseQueryParameter { String value = nextParamList.get(0); if (myAllow != null) { if (!myAllow.contains(value)) { - throw new InvalidRequestException("Invalid _include parameter value: '" + value + "'. Valid values are: " + new TreeSet(myAllow)); + if (!myAllow.contains("*")) { + throw new InvalidRequestException("Invalid _include parameter value: '" + value + "'. Valid values are: " + new TreeSet(myAllow)); + } } } if (retValCollection == null) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java index d735b6cb5f3..2651028a495 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java @@ -52,7 +52,6 @@ import ca.uhn.fhir.rest.annotation.VersionIdParam; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.param.CollectionBinder; -import ca.uhn.fhir.rest.param.IncludeParameter; import ca.uhn.fhir.rest.param.ResourceParameter; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index bc72e673ba1..1448ecbb53d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -1167,4 +1167,6 @@ public class RestfulServer extends HttpServlet { } } + + } diff --git a/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java b/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java index 316358675d2..ee094662f82 100644 --- a/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java +++ b/hapi-fhir-base/src/site/example/java/example/GenericClientExample.java @@ -1,7 +1,12 @@ package example; +import java.util.ArrayList; +import java.util.List; + import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.rest.client.IGenericClient; @@ -27,21 +32,64 @@ Bundle versions = client.history(Patient.class, "1",null,null); // END SNIPPET: simple } +@SuppressWarnings("unused") public static void fluentSearch() { FhirContext ctx = new FhirContext(); IGenericClient client = ctx.newRestfulGenericClient("http://fhir.healthintersections.com.au/open"); +{ +//START SNIPPET: create +Patient patient = new Patient(); +// ..populate the patient object.. +patient.addIdentifier("urn:system", "12345"); +patient.addName().addFamily("Smith").addGiven("John"); -//START SNIPPET: fluentExample +// Invoke the server create method (and send pretty-printed JSON encoding to the server +// instead of the default which is non-pretty printed XML) +client.create() + .resource(patient) + .prettyPrint() + .encodedJson() + .execute(); +//END SNIPPET: create +} +{ +//START SNIPPET: conformance +// Retrieve the server's conformance statement and print its description +Conformance conf = client.conformance(); +System.out.println(conf.getDescription().getValue()); +//END SNIPPET: conformance +} +{ +//START SNIPPET: search Bundle response = client.search() .forResource(Patient.class) .where(Patient.BIRTHDATE.beforeOrEquals().day("2011-01-01")) .and(Patient.PROVIDER.hasChainedProperty(Organization.NAME.matches().value("Health"))) .andLogRequestAndResponse(true) .execute(); -//END SNIPPET: fluentExample +//END SNIPPET: search +//START SNIPPET: searchPaging +if (response.getLinkNext().isEmpty() == false) { + + // load next page + Bundle nextPage = client.loadPage() + .next(response) + .execute(); +} +//END SNIPPET: searchPaging +} +{ +//START SNIPPET: transaction +List resources = new ArrayList(); +// .. populate this list - note that you can also pass in a populated Bundle if you want to create one manually .. + +List response = client.transaction() + .withResources(resources) + .execute(); +//END SNIPPET: transaction +} + -System.out.println(ctx.newXmlParser().setPrettyPrint(true).encodeBundleToString(response)); - } public static void main(String[] args) { diff --git a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java index 183023b14a8..8d9f52a7458 100644 --- a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java +++ b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java @@ -64,6 +64,8 @@ import ca.uhn.fhir.rest.param.DateParam; import ca.uhn.fhir.rest.param.DateRangeParam; import ca.uhn.fhir.rest.param.QuantityParam; import ca.uhn.fhir.rest.param.ReferenceParam; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringOrListParam; import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.param.TokenOrListParam; import ca.uhn.fhir.rest.param.TokenParam; @@ -180,6 +182,10 @@ public Patient getResourceById(@IdParam IdDt theId) { retVal.addName().addFamily("Smith").addGiven("Tester").addGiven("Q"); // ...etc... + // if you know the version ID of the resource, you should set it and HAPI will + // include it in a Content-Location header + retVal.setId(new IdDt("Patient", "123", "2")); + return retVal; } //END SNIPPET: read @@ -251,10 +257,8 @@ public List searchByLastName(@RequiredParam(name=Patient.SP_FAMILY) Str patient.addName().addFamily("Smith").addGiven("Tester").addGiven("Q"); // ...etc... - /* - * Every returned resource must have its logical ID set. If the server - * supports versioning, that should be set too - */ + // Every returned resource must have its logical ID set. If the server + // supports versioning, that should be set too String logicalId = "4325"; String versionId = "2"; // optional patient.setId(new IdDt("Patient", logicalId, versionId)); @@ -325,7 +329,9 @@ public List searchWithDocs( //START SNIPPET: searchMultiple @Search() -public List searchByObservationNames( @RequiredParam(name=Observation.SP_NAME) TokenOrListParam theCodings ) { +public List searchByObservationNames( + @RequiredParam(name=Observation.SP_NAME) TokenOrListParam theCodings ) { + // The list here will contain 0..* codings, and any observations which match any of the // given codings should be returned List wantedCodings = theCodings.getListAsCodings(); @@ -336,6 +342,32 @@ public List searchByObservationNames( @RequiredParam(name=Observati } //END SNIPPET: searchMultiple + +//START SNIPPET: searchMultipleAnd +@Search() +public List searchByPatientAddress( + @RequiredParam(name=Patient.SP_ADDRESS) StringAndListParam theAddressParts ) { + + // StringAndListParam is a container for 0..* StringOrListParam, which is in turn a + // container for 0..* strings. It is a little bit weird to understand at first, but think of the + // StringAndListParam to be an AND list with multiple OR lists inside it. So you will need + // to return results which match at least one string within every OR list. + List wantedCodings = theAddressParts.getValuesAsQueryTokens(); + for (StringOrListParam nextOrList : wantedCodings) { + List queryTokens = nextOrList.getValuesAsQueryTokens(); + // Only return results that match at least one of the tokens in the list below + for (StringParam nextString : queryTokens) { + // ....check for match... + } + } + + List retVal = new ArrayList(); + // ...populate... + return retVal; +} +//END SNIPPET: searchMultipleAnd + + //START SNIPPET: dates @Search() public List searchByObservationNames( @RequiredParam(name=Patient.SP_BIRTHDATE) DateParam theDate ) { diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml index 456ad19fdeb..cd78e362e9e 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_client.xml @@ -57,9 +57,8 @@ are very inexpensive to create so you can create a new one for each request if needed (although there is no requirement to do so, clients are reusable and thread-safe as well).

- - - + +

The generic client supports queries using a fluent interface which is inspired by the fantastic @@ -69,23 +68,77 @@ you to take advantage of intellisense/code completion in your favourite IDE.

-

- The following example shows how to query using this interface: + Note that most fluent operations end with an execute() + statement which actually performs the invocation. You may also invoke + several configuration operations just prior to the execute() statement, + such as encodedJson() or encodedXml().

+
+ +

+ The following example shows how to perform a create + operation using the generic client: +

- + + + +
+ + +

+ The following example shows how to query using the generic client: +

+ + + + +

+ If the server supports paging results, the client has a page method + which can be used to load subsequent pages. +

+ + + + +
+ + +

+ To retrieve the server's conformance statement, simply call the conformance() + method as shown below. +

+ + + + +
+ + +

+ The following example shows how to execute a transaction using the generic client: +

+ + -
+

+ HAPI also provides a second style of client caled the annotation-driven client. +

+

The design of the annotation-driven client is intended to be similar to that of @@ -95,10 +148,24 @@ annotated methods which HAPI binds to calls against a server.

+

+ The annotation-driven client is particularly useful if you have a server that + exposes a set of specific operations (search parameter combinations, named queries, etc.) + and you want to let developers have a stongly/statically typed interface to that + server. +

+

+ There is no difference in terms of capability between the two styles of + client. There is simply a difference in programming style and complexity. It + is probably safe to say that the generic client is easier to use and leads to + more readable code, at the expense of not giving any visibility into the + specific capabilities of the server you are interacting with. +

+

- The first step in creating a FHIR RESTful Client is to define a + The first step in creating an annotation-driven client is to define a restful client interface.

@@ -134,7 +201,7 @@ + value="src/site/example/java/example/IRestfulClient.java" />

diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml index cc80fc6004e..95398292051 100644 --- a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml +++ b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml @@ -466,14 +466,14 @@

- +

To allow a search using given search parameters, add one or more parameters to your search method and tag these parameters as either - @RequiredParam + @RequiredParam or - @OptionalParam + @OptionalParam .

@@ -734,13 +734,13 @@ may (or may not) be optional. To add a second required parameter, annotate the parameter with - @RequiredParam + @RequiredParam . To add an optional parameter (which will be passed in as null if no value is supplied), annotate the method with - @OptionalParam + @OptionalParam .

@@ -821,13 +821,31 @@ +

+ It is worth noting that according to the FHIR specification, you can have an + AND relationship combining multiple OR relationships, but not vice-versa. In + other words, it's possible to support a search like + ("name" = ("joe" or "john")) AND ("age" = (11 or 12)) but not + a search like + ("language" = ("en" AND "fr") OR ("address" = ("Canada" AND "Quebec")) +

+

OR Relationship Query Parameters

To accept a composite parameter, use a parameter type which implements the IQueryParameterOr - interface. For example, to accept searches for - Observations matching any of a collection of names: + interface. +

+

+ Each parameter type (StringParam, TokenParam, etc.) has a corresponding parameter + which accepts an OR list of parameters. These types are called "[type]OrListParam", for example: + StringOrListParam and TokenOrListParam. +

+

+ The following example shows a search for Observation by name, where a list of + names may be passed in (and the expectation is that the server will return Observations + that match any of these names):

@@ -848,12 +866,32 @@ IQueryParameterAnd interface.

-

- See the section below on - date ranges - for - one example of an AND parameter. + An example follows which shows a search for Patients by address, where multiple string + lists may be supplied by the client. For example, the client might request that the + address match ("Montreal" OR "Sherbrooke") AND ("Quebec" OR "QC") using + the following query: +
+ http://fhir.example.com/Patient?address=Montreal,Sherbrooke&address=Quebec,QC +

+

+ The following code shows how to receive this parameter using a + StringAndListParameter, + which can handle an AND list of multiple OR lists of strings. +

+ + + + + + +

AND Relationship Query Parameters for Dates

+ +

+ Dates are a bit of a special case, since it is a common scenario to want to match + a date range (which is really just an AND query on two qualified date parameters). + See the section below on date ranges + for an example of a DateRangeParameter.

diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java index dab34e45bc2..997c21e1c64 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseFhirDao.java @@ -910,7 +910,7 @@ public abstract class BaseFhirDao implements IDao { }; } - protected void loadResourcesById(Set theIncludePids, List theResourceListToPopulate) { + protected List loadResourcesById(Set theIncludePids) { Set pids = new HashSet(); for (IdDt next : theIncludePids) { pids.add(next.getIdPartAsLong()); @@ -926,10 +926,13 @@ public abstract class BaseFhirDao implements IDao { // } TypedQuery q = myEntityManager.createQuery(cq); + ArrayList retVal = new ArrayList(); for (ResourceTable next : q.getResultList()) { IResource resource = toResource(next); - theResourceListToPopulate.add(resource); + retVal.add(resource); } + + return retVal; } protected String normalizeString(String theString) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java index 7b685f4944a..b7b46ca25e3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java @@ -7,7 +7,21 @@ public class DaoConfig { private int myHardSearchLimit = 1000; private int myHardTagListLimit = 1000; private ResourceEncodingEnum myResourceEncoding=ResourceEncodingEnum.JSONC; + private int myIncludeLimit = 2000; + /** + * This is the maximum number of resources that will be added to a single page of + * returned resources. Because of includes with wildcards and other possibilities it is possible for a client to make + * requests that include very large amounts of data, so this hard limit can be imposed to prevent runaway + * requests. + */ + public void setIncludeLimit(int theIncludeLimit) { + myIncludeLimit = theIncludeLimit; + } + + /** + * See {@link #setIncludeLimit(int)} + */ public int getHardSearchLimit() { return myHardSearchLimit; } @@ -32,4 +46,8 @@ public class DaoConfig { myResourceEncoding = theResourceEncoding; } + public int getIncludeLimit() { + return myIncludeLimit; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java index 377a554f64f..9c9ec1296d9 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/FhirResourceDao.java @@ -63,6 +63,8 @@ import ca.uhn.fhir.model.dstu.composite.CodingDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.composite.QuantityDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.dstu.resource.OperationOutcome; +import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum; import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum; import ca.uhn.fhir.model.primitive.IdDt; @@ -83,6 +85,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.FhirTerser; +import example.QuickUsage.MyClientInterface; @Transactional(propagation = Propagation.REQUIRED) public class FhirResourceDao extends BaseFhirDao implements IFhirResourceDao { @@ -153,10 +156,10 @@ public class FhirResourceDao extends BaseFhirDao implements Predicate lt = builder.greaterThanOrEqualTo(from. get("myValueHigh"), lowerBound); lb = builder.or(gt, lt); -// Predicate gin = builder.isNull(from.get("myValueLow")); -// Predicate lbo = builder.or(gt, gin); -// Predicate lin = builder.isNull(from.get("myValueHigh")); -// Predicate hbo = builder.or(lt, lin); + // Predicate gin = builder.isNull(from.get("myValueLow")); + // Predicate lbo = builder.or(gt, gin); + // Predicate lin = builder.isNull(from.get("myValueHigh")); + // Predicate hbo = builder.or(lt, lin); // lb = builder.and(lbo, hbo); } @@ -165,11 +168,11 @@ public class FhirResourceDao extends BaseFhirDao implements Predicate gt = builder.lessThanOrEqualTo(from. get("myValueLow"), upperBound); Predicate lt = builder.lessThanOrEqualTo(from. get("myValueHigh"), upperBound); ub = builder.or(gt, lt); - -// Predicate gin = builder.isNull(from.get("myValueLow")); -// Predicate lbo = builder.or(gt, gin); -// Predicate lin = builder.isNull(from.get("myValueHigh")); -// Predicate ubo = builder.or(lt, lin); + + // Predicate gin = builder.isNull(from.get("myValueLow")); + // Predicate lbo = builder.or(gt, gin); + // Predicate lin = builder.isNull(from.get("myValueHigh")); + // Predicate ubo = builder.or(lt, lin); // ub = builder.and(ubo, lbo); } @@ -207,9 +210,9 @@ public class FhirResourceDao extends BaseFhirDao implements return found; } -// private Set addPredicateComposite(String theParamName, Set thePids, List theList) { -// } - + // private Set addPredicateComposite(String theParamName, Set thePids, List theList) { + // } + private Set addPredicateQuantity(String theParamName, Set thePids, List theList) { if (theList == null || theList.isEmpty()) { return thePids; @@ -228,7 +231,7 @@ public class FhirResourceDao extends BaseFhirDao implements String unitsValue; QuantityCompararatorEnum cmpValue; BigDecimal valueValue; - boolean approx=false; + boolean approx = false; if (params instanceof QuantityDt) { QuantityDt param = (QuantityDt) params; @@ -441,7 +444,8 @@ public class FhirResourceDao extends BaseFhirDao implements } if (rawSearchTerm.length() > ResourceIndexedSearchParamString.MAX_LENGTH) { - throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); + throw new InvalidRequestException("Parameter[" + theParamName + "] has length (" + rawSearchTerm.length() + ") that is longer than maximum allowed (" + + ResourceIndexedSearchParamString.MAX_LENGTH + "): " + rawSearchTerm); } String likeExpression = normalizeString(rawSearchTerm); @@ -573,10 +577,12 @@ public class FhirResourceDao extends BaseFhirDao implements } 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()); @@ -645,7 +651,8 @@ public class FhirResourceDao extends BaseFhirDao implements if (theResource.getId().isEmpty() == false) { if (isValidPid(theResource.getId())) { - throw new UnprocessableEntityException("This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID"); + throw new UnprocessableEntityException( + "This server cannot create an entity with a user-specified numeric ID - Client should not specify an ID when creating a new resource, or should include at least one letter in the ID to force a client-defined ID"); } createForcedIdIfNeeded(entity, theResource.getId()); @@ -729,7 +736,8 @@ public class FhirResourceDao extends BaseFhirDao implements final T current = currentTmp; - String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" + (theSince != null ? " AND h.myUpdated >= :SINCE" : ""); + String querySring = "SELECT count(h) FROM ResourceHistoryTable h " + "WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE" + " AND h.myUpdated < :END" + + (theSince != null ? " AND h.myUpdated >= :SINCE" : ""); TypedQuery countQuery = myEntityManager.createQuery(querySring, Long.class); countQuery.setParameter("PID", theId.getIdPartAsLong()); countQuery.setParameter("RESTYPE", resourceType); @@ -767,8 +775,9 @@ public class FhirResourceDao extends BaseFhirDao implements retVal.add(current); } - TypedQuery q = myEntityManager.createQuery("SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " + (theSince != null ? " AND h.myUpdated >= :SINCE" : "") - + " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class); + TypedQuery q = myEntityManager.createQuery( + "SELECT h FROM ResourceHistoryTable h WHERE h.myResourceId = :PID AND h.myResourceType = :RESTYPE AND h.myUpdated < :END " + + (theSince != null ? " AND h.myUpdated >= :SINCE" : "") + " ORDER BY h.myUpdated ASC", ResourceHistoryTable.class); q.setParameter("PID", theId.getIdPartAsLong()); q.setParameter("RESTYPE", resourceType); q.setParameter("END", end.getValue(), TemporalType.TIMESTAMP); @@ -837,7 +846,8 @@ public class FhirResourceDao extends BaseFhirDao implements throw new ConfigurationException("Unknown search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "]"); } if (sp.getParamType() != SearchParamTypeEnum.TOKEN) { - throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + "] is not a token type, only token is supported"); + throw new ConfigurationException("Search param on resource[" + myResourceName + "] for secondary key[" + mySecondaryPrimaryKeyParamName + + "] is not a token type, only token is supported"); } } @@ -864,7 +874,8 @@ public class FhirResourceDao extends BaseFhirDao implements 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()); } } @@ -888,7 +899,8 @@ public class FhirResourceDao extends BaseFhirDao implements 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", theId.getIdPartAsLong()); q.setParameter("RTYP", myResourceName); q.setParameter("RVER", theId.getVersionIdPartAsLong()); @@ -993,38 +1005,61 @@ public class FhirResourceDao extends BaseFhirDao implements // Load _include resources if (theParams.getIncludes() != null && theParams.getIncludes().isEmpty() == false) { - Set includePids = new HashSet(); - FhirTerser t = getContext().newTerser(); - for (Include next : theParams.getIncludes()) { - for (IResource nextResource : retVal) { - assert myResourceType.isAssignableFrom(nextResource.getClass()); + Set previouslyLoadedPids = new HashSet(); - List values = t.getValues(nextResource, next.getValue()); - for (Object object : values) { - if (object == null) { + Set includePids = new HashSet(); + List resources = retVal; + do { + includePids.clear(); + + FhirTerser t = getContext().newTerser(); + for (Include next : theParams.getIncludes()) { + for (IResource nextResource : resources) { + RuntimeResourceDefinition def = getContext().getResourceDefinition(nextResource); + if (!next.getValue().startsWith(def.getName() + ".")) { continue; } - if (!(object instanceof ResourceReferenceDt)) { - throw new InvalidRequestException("Path '" + next.getValue() + "' produced non ResourceReferenceDt value: " + object.getClass()); + + List values = t.getValues(nextResource, next.getValue()); + for (Object object : values) { + if (object == null) { + continue; + } + if (!(object instanceof ResourceReferenceDt)) { + throw new InvalidRequestException("Path '" + next.getValue() + "' produced non ResourceReferenceDt value: " + object.getClass()); + } + ResourceReferenceDt rr = (ResourceReferenceDt) object; + if (rr.getReference().isEmpty()) { + continue; + } + if (rr.getReference().isLocal()) { + continue; + } + + IdDt nextId = rr.getReference().toUnqualified(); + if (!previouslyLoadedPids.contains(nextId)) { + includePids.add(nextId); + previouslyLoadedPids.add(nextId); + } } - ResourceReferenceDt rr = (ResourceReferenceDt) object; - if (rr.getReference().isEmpty()) { - continue; - } - if (rr.getReference().isLocal()) { - continue; - } - includePids.add(rr.getReference().toUnqualified()); } } - } - if (!includePids.isEmpty()) { - ourLog.info("Loading {} included resources", includePids.size()); - loadResourcesById(includePids, retVal); + if (!includePids.isEmpty()) { + ourLog.info("Loading {} included resources", includePids.size()); + resources = loadResourcesById(includePids); + retVal.addAll(resources); + } + } while (includePids.size() > 0 && previouslyLoadedPids.size() < getConfig().getIncludeLimit()); + + if (previouslyLoadedPids.size() >= getConfig().getIncludeLimit()) { + OperationOutcome oo = new OperationOutcome(); + oo.addIssue().setSeverity(IssueSeverityEnum.WARNING).setDetails("Not all _include resources were actually included as the request surpassed the limit of " + getConfig().getIncludeLimit() + " resources"); + retVal.add(0, oo); } } + return retVal; } }); @@ -1181,8 +1216,7 @@ public class FhirResourceDao extends BaseFhirDao implements } /** - * If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to - * share the same value. + * If set, the given param will be treated as a secondary primary key, and multiple resources will not be able to share the same value. */ public void setSecondaryPrimaryKeyParamName(String theSecondaryPrimaryKeyParamName) { mySecondaryPrimaryKeyParamName = theSecondaryPrimaryKeyParamName; diff --git a/hapi-fhir-jpaserver-test/pom.xml b/hapi-fhir-jpaserver-test/pom.xml index bc2a416f256..90bff90b438 100644 --- a/hapi-fhir-jpaserver-test/pom.xml +++ b/hapi-fhir-jpaserver-test/pom.xml @@ -138,6 +138,7 @@ ca.uhn.test.jpasrv device + encounter diagnosticreport location observation diff --git a/hapi-fhir-jpaserver-test/src/main/resources/fhir-spring-test-config.xml b/hapi-fhir-jpaserver-test/src/main/resources/fhir-spring-test-config.xml index b2e8b536150..8f201345e00 100644 --- a/hapi-fhir-jpaserver-test/src/main/resources/fhir-spring-test-config.xml +++ b/hapi-fhir-jpaserver-test/src/main/resources/fhir-spring-test-config.xml @@ -24,6 +24,9 @@ + + + @@ -33,12 +36,12 @@ - - - + + + diff --git a/hapi-fhir-jpaserver-test/src/test/java/ca/uhn/fhir/jpa/test/CompleteResourceProviderTest.java b/hapi-fhir-jpaserver-test/src/test/java/ca/uhn/fhir/jpa/test/CompleteResourceProviderTest.java index bf159c58282..959dfc15a0e 100644 --- a/hapi-fhir-jpaserver-test/src/test/java/ca/uhn/fhir/jpa/test/CompleteResourceProviderTest.java +++ b/hapi-fhir-jpaserver-test/src/test/java/ca/uhn/fhir/jpa/test/CompleteResourceProviderTest.java @@ -1,7 +1,12 @@ package ca.uhn.fhir.jpa.test; -import static org.hamcrest.Matchers.*; -import static org.junit.Assert.*; +import static org.hamcrest.Matchers.containsString; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +import java.util.Date; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; @@ -17,31 +22,39 @@ import ca.uhn.fhir.jpa.dao.IFhirSystemDao; import ca.uhn.fhir.jpa.provider.JpaSystemProvider; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.dstu.composite.PeriodDt; import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt; +import ca.uhn.fhir.model.dstu.resource.Encounter; +import ca.uhn.fhir.model.dstu.resource.Location; import ca.uhn.fhir.model.dstu.resource.Observation; import ca.uhn.fhir.model.dstu.resource.Organization; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.resource.Questionnaire; +import ca.uhn.fhir.model.dstu.valueset.EncounterClassEnum; +import ca.uhn.fhir.model.dstu.valueset.EncounterStateEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator; import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; +import ca.uhn.test.jpasrv.EncounterResourceProvider; +import ca.uhn.test.jpasrv.LocationResourceProvider; import ca.uhn.test.jpasrv.ObservationResourceProvider; import ca.uhn.test.jpasrv.OrganizationResourceProvider; import ca.uhn.test.jpasrv.PatientResourceProvider; public class CompleteResourceProviderTest { + private static IFhirResourceDao observationDao; private static ClassPathXmlApplicationContext ourAppCtx; - private static FhirContext ourCtx; + private static IGenericClient ourClient; + private static FhirContext ourCtx; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CompleteResourceProviderTest.class); private static Server ourServer; private static IFhirResourceDao patientDao; - private static IFhirResourceDao questionnaireDao; - private static IGenericClient ourClient; - private static IFhirResourceDao observationDao; // private static JpaConformanceProvider ourConfProvider; @@ -66,23 +79,7 @@ public class CompleteResourceProviderTest { // // } - @Test - public void testSearchByIdentifier() { - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier01"); - p1.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven01"); - IdDt p1Id = ourClient.create(p1).getId(); - - Patient p2 = new Patient(); - p2.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier02"); - p2.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven02"); - ourClient.create(p2).getId(); - - Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testSearchByIdentifier01")).encodedJson().prettyPrint().execute(); - assertEquals(1, actual.size()); - assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart()); - - } + private static IFhirResourceDao questionnaireDao; @Test public void testCreateWithId() { @@ -112,52 +109,21 @@ public class CompleteResourceProviderTest { } @Test - public void testSearchByIdentifierWithoutSystem() { - Patient p1 = new Patient(); - p1.addIdentifier().setValue("testSearchByIdentifierWithoutSystem01"); - IdDt p1Id = ourClient.create(p1).getId(); - - Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint() - .execute(); - assertEquals(1, actual.size()); - assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart()); - - } - - @Test - public void testSearchByResourceChain() { - Organization o1 = new Organization(); - o1.setName("testSearchByResourceChainName01"); - IdDt o1id = ourClient.create(o1).getId(); - + public void testInsertBadReference() { Patient p1 = new Patient(); p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01"); p1.addName().addFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01"); - p1.setManagingOrganization(new ResourceReferenceDt(o1id)); - IdDt p1Id = ourClient.create(p1).getId(); + p1.setManagingOrganization(new ResourceReferenceDt("Organization/132312323")); - //@formatter:off - Bundle actual = ourClient.search() - .forResource(Patient.class) - .where(Patient.PROVIDER.hasId(o1id.getIdPart())) - .encodedJson().andLogRequestAndResponse(true).prettyPrint().execute(); - //@formatter:on - assertEquals(1, actual.size()); - assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart()); - - //@formatter:off - actual = ourClient.search() - .forResource(Patient.class) - .where(Patient.PROVIDER.hasId(o1id.getValue())) - .encodedJson().andLogRequestAndResponse(true).prettyPrint().execute(); - //@formatter:on - assertEquals(1, actual.size()); - assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart()); + try { + ourClient.create(p1).getId(); + fail(); + } catch (InvalidRequestException e) { + assertThat(e.getMessage(), containsString("Organization/132312323")); + } } - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CompleteResourceProviderTest.class); - @Test public void testInsertUpdatesConformance() { // Conformance conf = ourConfProvider.getServerConformance(); @@ -197,22 +163,6 @@ public class CompleteResourceProviderTest { // assertEquals(initial+1, number.getValueAsInteger()); } - @Test - public void testInsertBadReference() { - Patient p1 = new Patient(); - p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01"); - p1.addName().addFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01"); - p1.setManagingOrganization(new ResourceReferenceDt("Organization/132312323")); - - try { - ourClient.create(p1).getId(); - fail(); - } catch (InvalidRequestException e) { - assertThat(e.getMessage(), containsString("Organization/132312323")); - } - - } - @Test public void testSaveAndRetrieveExistingNarrative() { Patient p1 = new Patient(); @@ -236,6 +186,108 @@ public class CompleteResourceProviderTest { assertThat(actual.getText().getDiv().getValueAsString(), containsString("IdentifiertestSearchByResourceChain01")); } + @Test + public void testSearchByIdentifier() { + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier01"); + p1.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven01"); + IdDt p1Id = ourClient.create(p1).getId(); + + Patient p2 = new Patient(); + p2.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier02"); + p2.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven02"); + ourClient.create(p2).getId(); + + Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode("urn:system", "testSearchByIdentifier01")).encodedJson().prettyPrint().execute(); + assertEquals(1, actual.size()); + assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart()); + } + + @Test + public void testSearchByIdentifierWithoutSystem() { + Patient p1 = new Patient(); + p1.addIdentifier().setValue("testSearchByIdentifierWithoutSystem01"); + IdDt p1Id = ourClient.create(p1).getId(); + + Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint() + .execute(); + assertEquals(1, actual.size()); + assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart()); + + } + + @Test + public void testDeepChaining() { +// ourClient = ourCtx.newRestfulGenericClient("http://fhir.healthintersections.com.au/open"); +// ourClient = ourCtx.newRestfulGenericClient("https://fhir.orionhealth.com/blaze/fhir"); +// ourClient = ourCtx.newRestfulGenericClient("http://spark.furore.com/fhir"); +// ourClient.registerInterceptor(new LoggingInterceptor(true)); + + Location l1 = new Location(); + l1.getName().setValue("testDeepChainingL1"); + IdDt l1id = ourClient.create().resource(l1).execute().getId(); + + Location l2 = new Location(); + l2.getName().setValue("testDeepChainingL2"); + l2.getPartOf().setReference(l1id.toVersionless().toUnqualified()); + IdDt l2id = ourClient.create().resource(l2).execute().getId(); + + Encounter e1 = new Encounter(); + e1.addIdentifier().setSystem("urn:foo").setValue("testDeepChainingE1"); + e1.getStatus().setValueAsEnum(EncounterStateEnum.IN_PROGRESS); + e1.getClassElement().setValueAsEnum(EncounterClassEnum.HOME); + ca.uhn.fhir.model.dstu.resource.Encounter.Location location = e1.addLocation(); + location.getLocation().setReference(l2id.toUnqualifiedVersionless()); + location.setPeriod(new PeriodDt().setStartWithSecondsPrecision(new Date()).setEndWithSecondsPrecision(new Date())); + IdDt e1id = ourClient.create().resource(e1).execute().getId(); + + //@formatter:off + Bundle res = ourClient.search() + .forResource(Encounter.class) + .where(Encounter.IDENTIFIER.exactly().systemAndCode("urn:foo", "testDeepChainingE1")) + .include(Encounter.INCLUDE_LOCATION_LOCATION) + .include(Location.INCLUDE_PARTOF) + .execute(); + //@formatter:on + + assertEquals(3, res.size()); + assertEquals(1, res.getResources(Encounter.class).size()); + assertEquals(e1id.toUnqualifiedVersionless(), res.getResources(Encounter.class).get(0).getId().toUnqualifiedVersionless()); + + } + + @Test + public void testSearchByResourceChain() { + Organization o1 = new Organization(); + o1.setName("testSearchByResourceChainName01"); + IdDt o1id = ourClient.create(o1).getId(); + + Patient p1 = new Patient(); + p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01"); + p1.addName().addFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01"); + p1.setManagingOrganization(new ResourceReferenceDt(o1id)); + IdDt p1Id = ourClient.create(p1).getId(); + + //@formatter:off + Bundle actual = ourClient.search() + .forResource(Patient.class) + .where(Patient.PROVIDER.hasId(o1id.getIdPart())) + .encodedJson().andLogRequestAndResponse(true).prettyPrint().execute(); + //@formatter:on + assertEquals(1, actual.size()); + assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart()); + + //@formatter:off + actual = ourClient.search() + .forResource(Patient.class) + .where(Patient.PROVIDER.hasId(o1id.getValue())) + .encodedJson().andLogRequestAndResponse(true).prettyPrint().execute(); + //@formatter:on + assertEquals(1, actual.size()); + assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart()); + + } + @AfterClass public static void afterClass() throws Exception { ourServer.stop(); @@ -259,12 +311,21 @@ public class CompleteResourceProviderTest { ObservationResourceProvider observationRp = new ObservationResourceProvider(); observationRp.setDao(observationDao); + IFhirResourceDao locationDao = (IFhirResourceDao) ourAppCtx.getBean("myLocationDao", IFhirResourceDao.class); + LocationResourceProvider locationRp = new LocationResourceProvider(); + locationRp.setDao(locationDao); + + IFhirResourceDao encounterDao = (IFhirResourceDao) ourAppCtx.getBean("myEncounterDao", IFhirResourceDao.class); + EncounterResourceProvider encounterRp = new EncounterResourceProvider(); + encounterRp.setDao(encounterDao); + IFhirResourceDao organizationDao = (IFhirResourceDao) ourAppCtx.getBean("myOrganizationDao", IFhirResourceDao.class); OrganizationResourceProvider organizationRp = new OrganizationResourceProvider(); organizationRp.setDao(organizationDao); RestfulServer restServer = new RestfulServer(); - restServer.setResourceProviders(patientRp, questionnaireRp, observationRp, organizationRp); + + restServer.setResourceProviders(encounterRp, locationRp, patientRp, questionnaireRp, observationRp, organizationRp); restServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator()); IFhirSystemDao systemDao = (IFhirSystemDao) ourAppCtx.getBean("mySystemDao", IFhirSystemDao.class); @@ -292,7 +353,7 @@ public class CompleteResourceProviderTest { ourCtx = restServer.getFhirContext(); ourClient = ourCtx.newRestfulGenericClient(serverBase); - ourClient.setLogRequestAndResponse(true); + ourClient.registerInterceptor(new LoggingInterceptor(true)); } } 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 81caecbef78..df5a19acee5 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 @@ -63,6 +63,7 @@ public class ${className}ResourceProvider extends JpaResourceProvider<${classNam "${include.path}" #{if}($foreach.hasNext || $haveMore), #{end} #end #end + #{if}($searchParamsReference.empty == false), #{end}"*" }) Set theIncludes ) {