From 8039d6860878878e279f0edf6992a30669ad4977 Mon Sep 17 00:00:00 2001
From: Jafer Khan
Date: Thu, 26 Dec 2019 01:36:18 +0500
Subject: [PATCH 01/32] Fixed various typos and other minor issues in docs
---
.../ca/uhn/hapi/fhir/docs/appendix/logging.md | 2 +-
.../hapi/fhir/docs/client/annotation_client.md | 4 ++--
.../fhir/docs/client/client_configuration.md | 6 +++---
.../ca/uhn/hapi/fhir/docs/client/examples.md | 5 +++--
.../hapi/fhir/docs/client/generic_client.md | 8 ++++----
.../uhn/hapi/fhir/docs/client/get_started.md | 2 +-
.../ca/uhn/hapi/fhir/docs/files.properties | 2 +-
.../built_in_server_interceptors.md | 4 ++--
.../fhir/docs/interceptors/interceptors.md | 2 +-
.../introduction/downloading_and_importing.md | 6 ++----
.../ca/uhn/hapi/fhir/docs/model/parsers.md | 6 +++---
.../ca/uhn/hapi/fhir/docs/model/references.md | 4 ++--
.../ca/uhn/hapi/fhir/docs/security/cors.md | 2 +-
.../uhn/hapi/fhir/docs/server_plain/jax_rs.md | 6 +++---
.../{multitenency.md => multitenancy.md} | 2 +-
.../docs/server_plain/resource_providers.md | 2 +-
.../docs/server_plain/web_testpage_overlay.md | 4 ++--
.../fhir/docs/validation/profile_validator.md | 18 +++++++++---------
.../fhir/docs/validation/schema_validator.md | 4 ++--
19 files changed, 44 insertions(+), 45 deletions(-)
rename hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/{multitenency.md => multitenancy.md} (98%)
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/logging.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/logging.md
index 1af92981bd4..a9bf2d8b5cf 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/logging.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/appendix/logging.md
@@ -62,5 +62,5 @@ To enable detailed logging of client requests and responses (what URL is being r
# Server Request Logging
-To enable detailed logging of server requests and responses, an interceptor may be added to the server which logs each transaction. See [Logging Interceptr](/docs/interceptors/built_in_server_interceptors.html#logging_interceptor) for more information.
+To enable detailed logging of server requests and responses, an interceptor may be added to the server which logs each transaction. See [Logging Interceptor](/docs/interceptors/built_in_server_interceptors.html#logging_interceptor) for more information.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/annotation_client.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/annotation_client.md
index d28aea66dd6..45838177186 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/annotation_client.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/annotation_client.md
@@ -5,7 +5,7 @@ HAPI also provides a second style of client, called the *annotation-driven* clie
The design of the annotation-driven client is intended to be similar to that of JAX-WS, so users of that specification should be comfortable with this one. It uses a user-defined interface containing special 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.
+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 strongly/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.
@@ -40,7 +40,7 @@ Once your client interface is created, all that is left is to create a FhirConte
Restful client interfaces that you create will also extend the interface [IRestfulClient](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IRestfulClient.html), which comes with some helpful methods for configuring the way that the client will interact with the server.
-The following snippet shows how to configure the cliet to explicitly request JSON or XML responses, and how to request "pretty printed" responses on servers that support this (HAPI based servers currently).
+The following snippet shows how to configure the client to explicitly request JSON or XML responses, and how to request "pretty printed" responses on servers that support this (HAPI based servers currently).
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ClientExamples.java|clientConfig}}
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/client_configuration.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/client_configuration.md
index f674a1d8cd7..f73edba269a 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/client_configuration.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/client_configuration.md
@@ -10,7 +10,7 @@ This page outlines ways that the client can be configured for specific behaviour
By default, the client will query the server before the very first operation to download the server's conformance/metadata statement and verify that the server is appropriate for the given client. This check is only done once per server endpoint for a given FhirContext.
-This check is useful to prevent bugs or unexpected behaviour when talking to servers. It may introduce unneccesary overhead however in circumstances where the client and server are known to be compatible. The following example shows how to disable this check.
+This check is useful to prevent bugs or unexpected behaviour when talking to servers. It may introduce unnecessary overhead however in circumstances where the client and server are known to be compatible. The following example shows how to disable this check.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|dontValidate}}
@@ -36,7 +36,7 @@ REST clients (both Generic and Annotation-Driven) use [Apache HTTP Client](http:
The Apache HTTP Client is very powerful and extremely flexible, but can be confusing at first to configure, because of the low-level approach that the library uses.
-In many cases, the default configuration should suffice. HAPI FHIR also encapsulates some of the more common configuration settings you might want to use (socket timesouts, proxy settings, etc.) so that these can be configured through HAPI's API without needing to understand the underlying HTTP Client library.
+In many cases, the default configuration should suffice. HAPI FHIR also encapsulates some of the more common configuration settings you might want to use (socket timeouts, proxy settings, etc.) so that these can be configured through HAPI's API without needing to understand the underlying HTTP Client library.
This configuration is provided by accessing the [IRestfulClientFactory](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IRestfulClientFactory.html) class from the FhirContext.
@@ -62,7 +62,7 @@ The following example shows how to configure the use of an HTTP proxy in the cli
As of HAPI FHIR 2.0, an alternate client implementation is available. This client replaces the low-level Apache HttpClient implementation with the Square [OkHttp](http://square.github.io/okhttp/) library.
-Changing HTTP implementations should be mostly ransparent (it has no effect on the actual FHIR semantics which are transmitted over the wire) but might be useful if you have an application that uses OkHttp in other parts of the application and has specific configuration for that library.
+Changing HTTP implementations should be mostly transparent (it has no effect on the actual FHIR semantics which are transmitted over the wire) but might be useful if you have an application that uses OkHttp in other parts of the application and has specific configuration for that library.
Note that as of HAPI FHIR 2.1, OkHttp is the default provider on Android, and will be used without any configuration being required. This is done because HttpClient is deprecated on Android and has caused problems in the past.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md
index 0203bf6c684..f79d97cbfff 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/examples.md
@@ -161,11 +161,12 @@ The server responds with the following response. Note that the ID of the already
-```
+
+```
# Fetch all Pages of a Bundle
-This following example shows how to load all pages of a bundle by fetching each page one-after-the-other and then joining the resuts.
+This following example shows how to load all pages of a bundle by fetching each page one-after-the-other and then joining the results.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/BundleFetcher.java|loadAll}}
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md
index 055f2aaf636..59e7badb69c 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/generic_client.md
@@ -22,7 +22,7 @@ Note that most fluent operations end with an `execute()` statement which actuall
# Search
-Searching is a very powerful part of the FHIR API specification itself, and HAPI FHIR aims to proide a complete implementation of the FHIR API search specification via the generic client API.
+Searching is a very powerful part of the FHIR API specification itself, and HAPI FHIR aims to provide a complete implementation of the FHIR API search specification via the generic client API.
## Search - By Type
@@ -66,7 +66,7 @@ If the server supports paging results, the client has a page method which can be
## Search - Composite Parameters
-If a composite parameter is being searched on, the parameter takes a "left" and "right" operand, each of which is a parameter from the resource being seached. The following example shows the syntax.
+If a composite parameter is being searched on, the parameter takes a "left" and "right" operand, each of which is a parameter from the resource being searched. The following example shows the syntax.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|searchComposite}}
@@ -193,7 +193,7 @@ The following example shows how to perform an update operation using the generic
## Conditional Updates
-FHIR also specifies a type of update called "conditional updates", where insetad of using the logical ID of a resource to update, a set of search parameters is provided. If a single resource matches that set of parameters, that resource is updated. See the FHIR specification for information on how conditional updates work.
+FHIR also specifies a type of update called "conditional updates", where instead of using the logical ID of a resource to update, a set of search parameters is provided. If a single resource matches that set of parameters, that resource is updated. See the FHIR specification for information on how conditional updates work.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/GenericClientExample.java|updateConditional}}
@@ -233,7 +233,7 @@ To retrieve the server's capability statement, simply call the [`capabilities()`
# Extended Operations
-FHIR also supports a set of *extended operatioons*, which are operatons beyond the basic CRUD operations defined in the specificiation. These operations are an RPC style of invocation, with a set of named input parameters passed to the server and a set of named output parameters returned back.
+FHIR also supports a set of *extended operations*, which are operations beyond the basic CRUD operations defined in the specification. These operations are an RPC style of invocation, with a set of named input parameters passed to the server and a set of named output parameters returned back.
To invoke an operation using the client, you simply need to create the input [Parameters](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/model/Parameters.html) resource, then pass that to the [`operation()`](/hapi-fhir/apidocs/hapi-fhir-base/ca/uhn/fhir/rest/client/api/IGenericClient.html#operation()) fluent method.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/get_started.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/get_started.md
index 801c261e428..51a1a352e94 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/get_started.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/client/get_started.md
@@ -1,5 +1,5 @@
# Getting Started with the Client
-A starter project containing all nedded dependencies and some starter code for the HAPI FHIR client is available here:
+A starter project containing all needed dependencies and some starter code for the HAPI FHIR client is available here:
* [hapi-fhirstarters-client-skeleton](https://github.com/FirelyTeam/fhirstarters/tree/master/java/hapi-fhirstarters-client-skeleton/)
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
index 1d07de1084b..4d18cc6cac4 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/files.properties
@@ -34,7 +34,7 @@ page.server_plain.rest_operations_search=REST Operations: Search
page.server_plain.rest_operations_operations=REST Operations: Extended Operations
page.server_plain.paging=Paging Search Results
page.server_plain.web_testpage_overlay=Web Testpage Overlay
-page.server_plain.multitenency=Multitenency
+page.server_plain.multitenancy=Multitenancy
page.server_plain.jax_rs=JAX-RS Support
section.server_jpa.title=JPA Server
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
index 525a8ccdaa3..a482abbc947 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
@@ -26,12 +26,12 @@ This interceptor will then produce output similar to the following:
# Response Customizing: Syntax Highlighting
-The ResponseHighlighterInterceptor detects when a request is coming from a browser and returns HTML with syntax highlighted XML/JSON instead of just the raw text. In other words, if a user uses a browser to request `http://foo/Patient/1` by typing this address into their URL bar, they will get nice formatted HTML back with a human readable version of the content. This is particularly helpful for testers and public/development APIs where users are likely to invoke the API directly to see how it works.
+The ResponseHighlighterInterceptor detects when a request is coming from a browser and returns HTML with syntax highlighted XML/JSON instead of just the raw text. In other words, if a user uses a browser to request `http://foo/Patient/1` by typing this address into their URL bar, they will get a nicely formatted HTML back with a human readable version of the content. This is particularly helpful for testers and public/development APIs where users are likely to invoke the API directly to see how it works.
* [ResponseHighlighterInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.html)
* [ResponseHighlighterInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java)
-To see an example of how the output of this interceptor looks, see our demo server using the following example query: [http://hapi.fhir.org/baseR4/Patient](http://hapi.fhir.org/baseR4/Patient). The HTML view you see no that page with colour and indenting is provided by ResponseHighlighterInterceptor. Without this interceptor the respnose will simply by raw JSON/XML (as it will also be with this interceptor if the request is not coming from a browser, or is invoked by JavaScript).
+To see an example of how the output of this interceptor looks, see our demo server using the following example query: [http://hapi.fhir.org/baseR4/Patient](http://hapi.fhir.org/baseR4/Patient). The HTML view you see in that page with colour and indenting is provided by ResponseHighlighterInterceptor. Without this interceptor the response will simply be raw JSON/XML (as it will also be with this interceptor if the request is not coming from a browser, or is invoked by JavaScript).
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|responseHighlighterInterceptor}}
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/interceptors.md
index 56d11da723c..20b86b004ba 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/interceptors.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/interceptors.md
@@ -2,7 +2,7 @@
HAPI FHIR 3.8.0 introduced a new interceptor framework that is used across the entire library. In previous versions of HAPI FHIR, a "Server Interceptor" framework existed and a separate "Client Interceptor" framework existed. These have now been combined into a single unified (and much more powerful) framework.
-Interceptor classes may "hook into" various points in the processing chain in both the client and the server. The interceptor framework has been designed do be flexible enough to hook into almost every part of the library. When trying to figure out "how would I make HAPI FHIR do X", the answer is very often to create an interceptor.
+Interceptor classes may "hook into" various points in the processing chain in both the client and the server. The interceptor framework has been designed to be flexible enough to hook into almost every part of the library. When trying to figure out "how would I make HAPI FHIR do X", the answer is very often to create an interceptor.
The following example shows a very simple interceptor example.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/downloading_and_importing.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/downloading_and_importing.md
index 58165c42ce9..93c436b42ca 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/downloading_and_importing.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/downloading_and_importing.md
@@ -59,7 +59,7 @@ compile 'ca.uhn.hapi.fhir:hapi-fhir-structures-r4:${project.version}'
The HAPI FHIR project generally releases a new full software release 4 times per year, or approximately every 3 months.
-Generally speaking it is a good idea to use a stable build. However, FHIR is a fast moving specification, and there is a lot of ongoing work in HAPI as well. There may be times when you want to try out the latest unreleased version, either to test a new feature or to get a bugfix. You can usually look at the [Changelog](/docs/introduction/changelog.html) to get a sense of what has changed in the next unreleased version.
+Generally speaking, it is a good idea to use a stable build. However, FHIR is a fast moving specification, and there is a lot of ongoing work in HAPI as well. There may be times when you want to try out the latest unreleased version, either to test a new feature or to get a bugfix. You can usually look at the [Changelog](/docs/introduction/changelog.html) to get a sense of what has changed in the next unreleased version.
## Using Snapshot Builds
@@ -69,8 +69,6 @@ Using a snapshot build generally involves appending *-SNAPSHOT* to the version n
### Using Maven:
-To use a snapshot build, you
-
```xml
@@ -109,7 +107,7 @@ XML processing (for resource marshalling and unmarshalling) uses the Java StAX A
Upon starting up, HAPI will emit a log line indicating which StAX implementation is being used, e.g:
```
-08:01:32.044 [main] INFO ca.uhn.fhir.util.XmlUtil - FHIR XML processing will use StAX implementation 'Woodstox XML-phrocessor' version '4.4.0'
+08:01:32.044 [main] INFO ca.uhn.fhir.util.XmlUtil - FHIR XML processing will use StAX implementation 'Woodstox XML-processor' version '4.4.0'
```
Although most testing is done using the Woodstox implementation of StAX, it is not required and HAPI should work correctly with any compliant implementation of StAX.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/parsers.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/parsers.md
index c25e2e5488d..f1b6699eb01 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/parsers.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/parsers.md
@@ -6,7 +6,7 @@ A built in parser can be used to convert HAPI FHIR Java objects into a serialize
# Parsing (aka Deserializing)
-As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encodng style that is then used to parse.
+As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encoding style that is then used to parse.
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|parsing}}
@@ -14,7 +14,7 @@ As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/ap
# Encoding (aka Serializing)
-As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encodng style that is then used to serialize.
+As with many parts of the HAPI FHIR API, parsing begins with a [FhirContext](/apidocs/hapi-fhir-base/ca/uhn/fhir/context/FhirContext.html) object. The FhirContext can be used to request an [IParser](/apidocs/hapi-fhir-base/ca/uhn/fhir/parser/IParser.html) for your chosen encoding style that is then used to serialize.
The following example shows a JSON Parser being used to serialize a FHIR resource.
@@ -24,7 +24,7 @@ The following example shows a JSON Parser being used to serialize a FHIR resourc
## Pretty Printing
-By default, the parser will output in condensed form, with no newlines or indenting. This is good for machine-to-machine communication since it reduces the amount of data to be transferred but it is harder to read. To enable pretty printed outout:
+By default, the parser will output in condensed form, with no newlines or indenting. This is good for machine-to-machine communication since it reduces the amount of data to be transferred but it is harder to read. To enable pretty printed output:
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/Parser.java|encodingPretty}}
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/references.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/references.md
index bebd8e5f40c..ef060cf9c6d 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/references.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/model/references.md
@@ -135,7 +135,7 @@ This will give the following output:
```
-Note that you may also "contain" resources manually in your own code if you prefer. The following example show how to do this:
+Note that you may also "contain" resources manually in your own code if you prefer. The following example shows how to do this:
```java
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ResourceRefs.java|manualContained}}
@@ -147,7 +147,7 @@ By default, HAPI will strip resource versions from references between resources.
This is because in most circumstances, references between resources should be versionless (e.g. the reference just points to the latest version, whatever version that might be).
-There are valid circumstances however for wanting versioned references. If you need HAPI to emit versionned references, you have a few options:
+There are valid circumstances however for wanting versioned references. If you need HAPI to emit versioned references, you have a few options:
You can force the parser to never strip versions:
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/security/cors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/security/cors.md
index b07e767a353..225baded0ce 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/security/cors.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/security/cors.md
@@ -48,7 +48,7 @@ Note that in previous revisions of HAPI FHIR documentation we recommended using
The following examples show how to use the Apache Tomcat CorsFilter to enable CORS support. The filter being used (`org.apache.catalina.filters.CorsFilter`) is bundled with Apache Tomcat so if you are deploying to that server you can use the filter.
-Other containers have similar filters you can use, so consult the documentation for the given container you are using for more information. (If you have an example for how to configure a different CORS filter, please send it our way! Examples are always useful!)
+Other containers have similar filters you can use, so consult the documentation for the given container you are using for more information. (If you have an example for configuring a different CORS filter, please send it our way! Examples are always useful!)
In your web.xml file (within the WEB-INF directory in your WAR file), the following filter definition adds the CORS filter, including support for the X-FHIR-Starter header defined by SMART Platforms.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/jax_rs.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/jax_rs.md
index a905b38c4fe..eabd07c4aa5 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/jax_rs.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/jax_rs.md
@@ -2,10 +2,10 @@
The HAPI FHIR Plain Server ([RestfulServer](/hapi-fhir/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/RestfulServer.html)) is implemented as a standard JEE Servlet, meaning that it can be deployed in any compliant JEE web container.
-For users who already have an existing JAX-RS infrastructure, and who would like to use that technology foor their FHIR stack as well, a module exists which implements the server using [JAX-RS technology](https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html).
+For users who already have an existing JAX-RS infrastructure, and who would like to use that technology for their FHIR stack as well, a module exists which implements the server using [JAX-RS technology](https://jax-rs-spec.java.net/nonav/2.0/apidocs/index.html).
- The JAX-RS module is a community-supported module that was not developed by the core HAPI FHIR team. Before decid the HAPI FHIR JAX-RS module, please be aware that it does not have as complete of support for the full FHIR REST specification as the Plain Server. If you need a feature that is missing, please consiider adding it and making a pull request!
+ The JAX-RS module is a community-supported module that was not developed by the core HAPI FHIR team. Before deciding to use the HAPI FHIR JAX-RS module, please be aware that it does not have as complete of support for the full FHIR REST specification as the Plain Server. If you need a feature that is missing, please consider adding it and making a pull request!
## Features
@@ -27,7 +27,7 @@ An example server can be found in the Git repo [here](https://github.com/jamesag
The set-up of a JAX-RS server goes beyond the scope of this documentation. The implementation of the server follows the same pattern as the standard server. It is required to put the correct [annotation](./rest_operations.html) on the methods in the [Resource Providers](./resource_providers.html) in order to be able to call them.
-Implementing a JAX-RS Resource Provider requires some JAX-RS annotations. The [@Path](https://docs.oracle.com/javaee/6/api/javax/ws/rs/Path.html) annotation needs to define the resource path. The @Produces
annotation needs to declare the produced formats. The constructor needs to pass the class of the object explicitely in order to avoid problems with proxy classes in a Java EE environment.
+Implementing a JAX-RS Resource Provider requires some JAX-RS annotations. The [@Path](https://docs.oracle.com/javaee/6/api/javax/ws/rs/Path.html) annotation needs to define the resource path. The @Produces
annotation needs to declare the produced formats. The constructor needs to pass the class of the object explicitly in order to avoid problems with proxy classes in a Java EE environment.
It is necessary to extend the abstract class [AbstractJaxRsResourceProvide](/hapi-fhir/apidocs/hapi-fhir-jaxrsserver-base/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.html).
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenency.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenancy.md
similarity index 98%
rename from hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenency.md
rename to hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenancy.md
index 58b03dbd896..93c9efde97f 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenency.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/multitenancy.md
@@ -1,4 +1,4 @@
-# Multitenency
+# Multitenancy
If you wish to allow a single endpoint to support multiple tenants, you may supply the server with a multitenancy provider.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/resource_providers.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/resource_providers.md
index f72a13ca7f8..22151dc2175 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/resource_providers.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/resource_providers.md
@@ -76,7 +76,7 @@ In some cases, it may be useful to have access to the underlying HttpServletRequ
# REST Exception/Error Handling
-Within your RESTful operations, you will generally be returning resources or bundles of resources under normal operation. During execution you may also need to propagate errors back to the client for a variety of reasons.
+Within your RESTful operations, you will generally be returning resources or bundles of resources under normal operation. During execution, you may also need to propagate errors back to the client for a variety of reasons.
## Automatic Exception Handling
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/web_testpage_overlay.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/web_testpage_overlay.md
index b66e3fac35d..240a8c484d1 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/web_testpage_overlay.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/server_plain/web_testpage_overlay.md
@@ -8,7 +8,7 @@ to create your server) and the overlay drops a number of files into your project
# Adding the Overlay
-These instructions assume that you have an exsiting web project which uses Maven to build. The POM.xml should have a "packaging" type of "war".
+These instructions assume that you have an existing web project which uses Maven to build. The POM.xml should have a "packaging" type of "war".
Adding the overlay to your project is relatively simple. First, add the "hapi-fhir-testpage-overlay" dependency to the dependencies section of your POM.xml file.
@@ -54,7 +54,7 @@ Then, add the following WAR plugin to the plugins section of your POM.xml
```
-Then, create a Java source file called `FhirTesterConfig.java` and copy in the following contents:
+Then, create a Java source file called `FhirTesterConfig.java` and copy the following contents:
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/profile_validator.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/profile_validator.md
index 5e71c9064eb..1eb782861e9 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/profile_validator.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/profile_validator.md
@@ -13,7 +13,7 @@ The validator can be manually invoked at any time by creating a validator and co
```
- Note that in earlier releases of HAPI FHIR it was common to register different kinds of validator modules (such as [Schema/Schematron](./schema_validator.html)) because the FHIR Instance Validator module described below was not mature. This is no longer the case, and it is generally recommended to use the FHIR Instance Validator.
+ Note that in earlier releases of HAPI FHIR it was common to register different kinds of validator modules (such as
Schema/Schematron) because the FHIR Instance Validator module described below was not mature. This is no longer the case, and it is generally recommended to use the FHIR Instance Validator.
# FHIR Conformance Packages
@@ -22,9 +22,9 @@ There are a few key concepts worth explaining before getting into how validation
Conformance Resources:
-* [StructureDefinition](http://hl7.org/fhir/structuredefinition.html) – Contains definitions of the valid fields in a given resource, including details about their datatypes, min/max cardinalities, valid values, and other rules about what content is valid and what is not. StructureDefinition resources are also used to express derivative profiles (e.g. a description of a constraint on a FHIR resource for a specfic purpose) as well as to describe extensions.
+* [StructureDefinition](http://hl7.org/fhir/structuredefinition.html) – Contains definitions of the valid fields in a given resource, including details about their datatypes, min/max cardinalities, valid values, and other rules about what content is valid and what is not. StructureDefinition resources are also used to express derivative profiles (e.g. a description of a constraint on a FHIR resource for a specific purpose) as well as to describe extensions.
-* [CodeSystem](http://hl7.org/fhir/codesystem.html) – Contains definiitions of codes and vocabularies that can be used in FHIR resources, or even outside of FHIR resources.
+* [CodeSystem](http://hl7.org/fhir/codesystem.html) – Contains definitions of codes and vocabularies that can be used in FHIR resources, or even outside of FHIR resources.
* [ValueSet](http://hl7.org/fhir/valueset.html) – Contains lists of codes drawn from one or more CodeSystems that are suitable for use in a specific field in a FHIR resource.
@@ -33,7 +33,7 @@ Conformance Resources:
HAPI has very complete support for validation against FHIR conformance resources.
-This functionality is proviided by the HAPI FHIR "reference validator", which is able
+This functionality is provided by the HAPI FHIR "reference validator", which is able
to check a resource for conformance to FHIR profiles.
The FHIR instance validator is very powerful. It will use terminology services to validate codes, StructureDefinitions to validate semantics, and uses a customized XML/JSON parser in order to provide descriptive error messages.
@@ -59,7 +59,7 @@ definitions provided either by HL7 or by the user.
To execute the validator, you simply create an instance of [FhirInstanceValidator](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/FhirInstanceValidator.html) and register it to new validator, as shown in the example below.
-Note that the example below uses the official FHIR StructureDefintions and ValueSets
+Note that the example below uses the official FHIR StructureDefinitions and ValueSets
to validate the resource. It will not work unless you include the
**hapi-fhir-validation-resources-[version].jar** module/JAR on your classpath.
@@ -69,7 +69,7 @@ to validate the resource. It will not work unless you include the
# Supplying Your Own Definitions
-The FhirInstanceValidator relies on an implementation of an interface called [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.html) interface to load StructureDefinitions, validate codes, etx.
+The FhirInstanceValidator relies on an implementation of an interface called [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.html) interface to load StructureDefinitions, validate codes, etc.
By default, an implementation of this interface called [DefaultProfileValidationSupport](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.html) is used. This implementation simply uses the built-in official FHIR definitions to validate against (and in many cases, this is good enough).
@@ -79,9 +79,9 @@ However, if you have needs beyond simply validating against the core FHIR specif
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ValidatorExamples.java|validateSupplyProfiles}}
```
-# Buiilt-In Validation Support Classes
+# Built-In Validation Support Classes
-There are a several implementations of the [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.html) interface built into HAPI FHIR that can be used, typically in a chain.
+There are several implementations of the [IValidationSupport](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.html) interface built into HAPI FHIR that can be used, typically in a chain.
* [**DefaultProfileValidationSupport**](/hapi-fhir/apidocs/hapi-fhir-structures-r4/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.html) - Supplies the built-in FHIR core structure definitions, including both structures and vocabulary.
@@ -93,7 +93,7 @@ There are a several implementations of the [IValidationSupport](/hapi-fhir/apido
* [**CachingValidationSupport**](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/CachingValidationSupport.html) - Caches results of calls to a wrapped service implementation for a period of time. This class can be a significant help in terms of performance if you are loading conformance resources or performing terminology operations from a database or disk.
-* [**SnapshotGeneratingValidationSupport**](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/SnapshotGeneratingValidationSupport.html) - Generates StructureDefinition snapshots as needed. This should be added to your chain if you are working wiith differential StructueDefinitions that do not include the snapshot view.
+* [**SnapshotGeneratingValidationSupport**](/hapi-fhir/apidocs/hapi-fhir-validation/org/hl7/fhir/r4/hapi/validation/SnapshotGeneratingValidationSupport.html) - Generates StructureDefinition snapshots as needed. This should be added to your chain if you are working with differential StructureDefinitions that do not include the snapshot view.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/schema_validator.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/schema_validator.md
index ad4cc53a62e..d93f0c138b9 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/schema_validator.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/validation/schema_validator.md
@@ -1,12 +1,12 @@
# Schema / Schematron Validator
-FHIR resource definitions are distributed with a set of XML schema files (XSD) as well as a set of XML Schematron (SCH) files. These two sets of files are complimentary to each other, meaning that in order to claim compliance to the FHIR specification, your resources must validate against both sets.
+FHIR resource definitions are distributed with a set of XML schema files (XSD) as well as a set of XML Schematron (SCH) files. These two sets of files are complementary to each other, meaning that in order to claim compliance to the FHIR specification, your resources must validate against both sets.
The two sets of files are included with HAPI, and it uses them to perform validation.
# Preparation
-In order to use HAPI's Schematron support, a libaray called [Ph-Schematron](https://github.com/phax/ph-schematron) is used, so this library must be added to your classpath (or Maven POM file, Gradle file, etc.)
+In order to use HAPI's Schematron support, a library called [Ph-Schematron](https://github.com/phax/ph-schematron) is used, so this library must be added to your classpath (or Maven POM file, Gradle file, etc.)
Note that this library is specified as an optional dependency by HAPI FHIR so you need to explicitly include it if you want to use this functionality.
From 27959614d84a18e5d3eafacf95993e8d1f3738a0 Mon Sep 17 00:00:00 2001
From: Tadgh
Date: Thu, 19 Mar 2020 14:21:22 -0700
Subject: [PATCH 02/32] Add basic stale.yml (#1764)
---
.github/stale.yml | 17 +++++++++++++++++
1 file changed, 17 insertions(+)
create mode 100644 .github/stale.yml
diff --git a/.github/stale.yml b/.github/stale.yml
new file mode 100644
index 00000000000..b21223c3726
--- /dev/null
+++ b/.github/stale.yml
@@ -0,0 +1,17 @@
+# Two years until issues go stale
+daysUntilStale: 730
+# Number of days of inactivity before a stale issue is closed
+daysUntilClose: 7
+# Issues with these labels will never be considered stale
+exemptLabels:
+ - pinned
+ - security
+# Label to use when marking an issue as stale
+staleLabel: wontfix
+# Comment to post when marking an issue as stale. Set to `false` to disable
+markComment: >
+ This issue has been automatically marked as stale because it has not had
+ recent activity. It will be closed if no further activity occurs. Thank you
+ for your contributions.
+# Comment to post when closing a stale issue. Set to `false` to disable
+closeComment: false
From a5257f0fa8e0c792bde7c3a1a9d3a1e32c60aacf Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Thu, 19 Mar 2020 20:19:40 -0400
Subject: [PATCH 03/32] Add new indexing extractor method
---
.../dao/r4/SearchParamExtractorR4Test.java | 17 ++
.../extractor/BaseSearchParamExtractor.java | 213 ++++++++++--------
.../extractor/ISearchParamExtractor.java | 3 +
3 files changed, 140 insertions(+), 93 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
index 38997c5fb5b..07f53c62737 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java
@@ -117,6 +117,23 @@ public class SearchParamExtractorR4Test {
}
+ @Test
+ public void testExtractSearchParamTokenTest() {
+ Patient p = new Patient();
+ p.addIdentifier().setSystem("sys").setValue("val");
+
+ SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry);
+ RuntimeSearchParam param = mySearchParamRegistry.getActiveSearchParam("Patient", Patient.SP_IDENTIFIER);
+ assertNotNull(param);
+ ISearchParamExtractor.SearchParamSet params = extractor.extractSearchParamTokens(p, param);
+ assertEquals(1, params.size());
+ ResourceIndexedSearchParamToken paramValue = (ResourceIndexedSearchParamToken) params.iterator().next();
+ assertEquals("identifier", paramValue.getParamName());
+ assertEquals("sys", paramValue.getSystem());
+ assertEquals("val", paramValue.getValue());
+ }
+
+
@Test
public void testExtensionContainingReference() {
String path = "Patient.extension('http://patext').value.as(Reference)";
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
index 10583e7095d..9ccff73e0a9 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/BaseSearchParamExtractor.java
@@ -186,7 +186,19 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
@Override
public SearchParamSet extractSearchParamTokens(IBaseResource theResource) {
+ IExtractor extractor = createTokenExtractor(theResource);
+ return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN);
+ }
+ @Override
+ public SearchParamSet extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam) {
+ IExtractor extractor = createTokenExtractor(theResource);
+ SearchParamSet setToPopulate = new SearchParamSet<>();
+ extractSearchParam(theSearchParam, theResource, extractor, setToPopulate);
+ return setToPopulate;
+ }
+
+ private IExtractor createTokenExtractor(IBaseResource theResource) {
String resourceTypeName = toRootTypeName(theResource);
String useSystem;
if (getContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU2)) {
@@ -204,85 +216,7 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
}
- IExtractor extractor = (params, searchParam, value, path) -> {
-
- // DSTU3+
- if (value instanceof IBaseEnumeration>) {
- IBaseEnumeration> obj = (IBaseEnumeration>) value;
- String system = extractSystem(obj);
- String code = obj.getValueAsString();
- createTokenIndexIfNotBlank(resourceTypeName, params, searchParam, system, code);
- return;
- }
-
- // DSTU2 only
- if (value instanceof BoundCodeDt) {
- BoundCodeDt boundCode = (BoundCodeDt) value;
- Enum valueAsEnum = boundCode.getValueAsEnum();
- String system = null;
- if (valueAsEnum != null) {
- //noinspection unchecked
- system = boundCode.getBinder().toSystemString(valueAsEnum);
- }
- String code = boundCode.getValueAsString();
- createTokenIndexIfNotBlank(resourceTypeName, params, searchParam, system, code);
- return;
- }
-
- if (value instanceof IPrimitiveType) {
- IPrimitiveType> nextValue = (IPrimitiveType>) value;
- String systemAsString = null;
- String valueAsString = nextValue.getValueAsString();
- if ("CodeSystem.concept.code".equals(path)) {
- systemAsString = useSystem;
- } else if ("ValueSet.codeSystem.concept.code".equals(path)) {
- systemAsString = useSystem;
- }
-
- createTokenIndexIfNotBlank(resourceTypeName, params, searchParam, systemAsString, valueAsString);
- return;
- }
-
- switch (path) {
- case "Patient.communication":
- addToken_PatientCommunication(resourceTypeName, params, searchParam, value);
- return;
- case "Consent.source":
- // Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
- return;
- case "Location.position":
- addCoords_Position(resourceTypeName, params, searchParam, value);
- return;
- case "StructureDefinition.context":
- // TODO: implement this
- ourLog.warn("StructureDefinition context indexing not currently supported");
- return;
- case "CapabilityStatement.rest.security":
- addToken_CapabilityStatementRestSecurity(resourceTypeName, params, searchParam, value);
- return;
- }
-
- String nextType = toRootTypeName(value);
- switch (nextType) {
- case "Identifier":
- addToken_Identifier(resourceTypeName, params, searchParam, value);
- break;
- case "CodeableConcept":
- addToken_CodeableConcept(resourceTypeName, params, searchParam, value);
- break;
- case "Coding":
- addToken_Coding(resourceTypeName, params, searchParam, value);
- break;
- case "ContactPoint":
- addToken_ContactPoint(resourceTypeName, params, searchParam, value);
- break;
- default:
- addUnexpectedDatatypeWarning(params, searchParam, value);
- break;
- }
- };
-
- return extractSearchParams(theResource, extractor, RestSearchParameterTypeEnum.TOKEN);
+ return new TokenExtractor(resourceTypeName, useSystem);
}
@Override
@@ -798,25 +732,29 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
continue;
}
- String nextPathUnsplit = nextSpDef.getPath();
- if (isBlank(nextPathUnsplit)) {
- continue;
- }
+ extractSearchParam(nextSpDef, theResource, theExtractor, retVal);
+ }
+ return retVal;
+ }
- String[] splitPaths = split(nextPathUnsplit);
- for (String nextPath : splitPaths) {
- nextPath = trim(nextPath);
- for (IBase nextObject : extractValues(nextPath, theResource)) {
- if (nextObject != null) {
- String typeName = toRootTypeName(nextObject);
- if (!myIgnoredForSearchDatatypes.contains(typeName)) {
- theExtractor.extract(retVal, nextSpDef, nextObject, nextPath);
- }
+ private void extractSearchParam(RuntimeSearchParam theSearchParameterDef, IBaseResource theResource, IExtractor theExtractor, SearchParamSet theSetToPopulate) {
+ String nextPathUnsplit = theSearchParameterDef.getPath();
+ if (isBlank(nextPathUnsplit)) {
+ return;
+ }
+
+ String[] splitPaths = split(nextPathUnsplit);
+ for (String nextPath : splitPaths) {
+ nextPath = trim(nextPath);
+ for (IBase nextObject : extractValues(nextPath, theResource)) {
+ if (nextObject != null) {
+ String typeName = toRootTypeName(nextObject);
+ if (!myIgnoredForSearchDatatypes.contains(typeName)) {
+ theExtractor.extract(theSetToPopulate, theSearchParameterDef, nextObject, nextPath);
}
}
}
}
- return retVal;
}
private String toRootTypeName(IBase nextObject) {
@@ -1032,6 +970,95 @@ public abstract class BaseSearchParamExtractor implements ISearchParamExtractor
}
+ private class TokenExtractor implements IExtractor {
+ private final String myResourceTypeName;
+ private final String myUseSystem;
+
+ public TokenExtractor(String theResourceTypeName, String theUseSystem) {
+ myResourceTypeName = theResourceTypeName;
+ myUseSystem = theUseSystem;
+ }
+
+ @Override
+ public void extract(SearchParamSet params, RuntimeSearchParam searchParam, IBase value, String path) {
+
+ // DSTU3+
+ if (value instanceof IBaseEnumeration>) {
+ IBaseEnumeration> obj = (IBaseEnumeration>) value;
+ String system = extractSystem(obj);
+ String code = obj.getValueAsString();
+ BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, system, code);
+ return;
+ }
+
+ // DSTU2 only
+ if (value instanceof BoundCodeDt) {
+ BoundCodeDt boundCode = (BoundCodeDt) value;
+ Enum valueAsEnum = boundCode.getValueAsEnum();
+ String system = null;
+ if (valueAsEnum != null) {
+ //noinspection unchecked
+ system = boundCode.getBinder().toSystemString(valueAsEnum);
+ }
+ String code = boundCode.getValueAsString();
+ BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, system, code);
+ return;
+ }
+
+ if (value instanceof IPrimitiveType) {
+ IPrimitiveType> nextValue = (IPrimitiveType>) value;
+ String systemAsString = null;
+ String valueAsString = nextValue.getValueAsString();
+ if ("CodeSystem.concept.code".equals(path)) {
+ systemAsString = myUseSystem;
+ } else if ("ValueSet.codeSystem.concept.code".equals(path)) {
+ systemAsString = myUseSystem;
+ }
+
+ BaseSearchParamExtractor.this.createTokenIndexIfNotBlank(myResourceTypeName, params, searchParam, systemAsString, valueAsString);
+ return;
+ }
+
+ switch (path) {
+ case "Patient.communication":
+ BaseSearchParamExtractor.this.addToken_PatientCommunication(myResourceTypeName, params, searchParam, value);
+ return;
+ case "Consent.source":
+ // Consent#source-identifier has a path that isn't typed - This is a one-off to deal with that
+ return;
+ case "Location.position":
+ BaseSearchParamExtractor.this.addCoords_Position(myResourceTypeName, params, searchParam, value);
+ return;
+ case "StructureDefinition.context":
+ // TODO: implement this
+ ourLog.warn("StructureDefinition context indexing not currently supported");
+ return;
+ case "CapabilityStatement.rest.security":
+ BaseSearchParamExtractor.this.addToken_CapabilityStatementRestSecurity(myResourceTypeName, params, searchParam, value);
+ return;
+ }
+
+ String nextType = BaseSearchParamExtractor.this.toRootTypeName(value);
+ switch (nextType) {
+ case "Identifier":
+ BaseSearchParamExtractor.this.addToken_Identifier(myResourceTypeName, params, searchParam, value);
+ break;
+ case "CodeableConcept":
+ BaseSearchParamExtractor.this.addToken_CodeableConcept(myResourceTypeName, params, searchParam, value);
+ break;
+ case "Coding":
+ BaseSearchParamExtractor.this.addToken_Coding(myResourceTypeName, params, searchParam, value);
+ break;
+ case "ContactPoint":
+ BaseSearchParamExtractor.this.addToken_ContactPoint(myResourceTypeName, params, searchParam, value);
+ break;
+ default:
+ BaseSearchParamExtractor.this.addUnexpectedDatatypeWarning(params, searchParam, value);
+ break;
+ }
+ }
+ }
+
private static void addIgnoredType(FhirContext theCtx, String theType, Set theIgnoredTypes) {
BaseRuntimeElementDefinition> elementDefinition = theCtx.getElementDefinition(theType);
if (elementDefinition != null) {
diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java
index 1f9ef8bb3ea..15e4479ab8c 100644
--- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java
+++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/extractor/ISearchParamExtractor.java
@@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.searchparam.extractor;
+import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.model.entity.*;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -42,6 +43,8 @@ public interface ISearchParamExtractor {
SearchParamSet extractSearchParamTokens(IBaseResource theResource);
+ SearchParamSet extractSearchParamTokens(IBaseResource theResource, RuntimeSearchParam theSearchParam);
+
SearchParamSet extractSearchParamSpecial(IBaseResource theResource);
SearchParamSet extractSearchParamUri(IBaseResource theResource);
From b3cf323e07fae33b16902cd72088e42b295b221c Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Fri, 20 Mar 2020 18:30:14 -0400
Subject: [PATCH 04/32] Add docs
---
.../main/java/ca/uhn/fhir/rest/param/ReferenceParam.java | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java
index 6f9e3babc32..7e59d6d08eb 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ReferenceParam.java
@@ -200,6 +200,13 @@ public class ReferenceParam extends BaseParam /*implements IQueryParameterType*/
return myValue;
}
+ /**
+ * Note that the parameter to this method must be a resource reference, e.g
+ * 123
or Patient/123
or http://example.com/fhir/Patient/123
+ * or something like this. This is not appropriate for cases where a chain is being used and
+ * the value is for a different type of parameter (e.g. a token). In that case, use one of the
+ * setter constructors.
+ */
public ReferenceParam setValue(String theValue) {
IdDt id = new IdDt(theValue);
String qualifier= null;
From c2ae5a832690a1a06e677ff4fe12e3c63f226c32 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Sun, 22 Mar 2020 12:57:24 -0400
Subject: [PATCH 05/32] Add FHIRPath evaluation interceptor (#1769)
* Add FHIRPath evaluation interceptor
* Add changelog
---
.../java/ca/uhn/fhir/context/FhirContext.java | 19 +-
.../FhirPathExecutionException.java} | 10 +-
.../IFhirPath.java} | 4 +-
.../ca/uhn/fhir/model/api/IFhirVersion.java | 4 +-
.../narrative2/BaseNarrativeGenerator.java | 4 +-
.../java/ca/uhn/fhir/rest/api/Constants.java | 1 +
.../java/ca/uhn/fhir/util/ParametersUtil.java | 17 +-
.../uhn/hapi/fhir/docs/ServletExamples.java | 22 ++-
.../4_3_0/1769-add-fhirpath-interceptor.yaml | 6 +
.../hapi/fhir/changelog/4_3_0/changes.yaml | 8 +
.../built_in_server_interceptors.md | 46 +++++
.../ca/uhn/fhirtest/TestRestfulServer.java | 6 +
.../FhirPathFilterInterceptor.java | 70 ++++++++
.../ca/uhn/fhir/model/dstu/FhirDstu1.java | 2 +-
.../dstu2016may/hapi/ctx/FhirDstu2_1.java | 4 +-
.../ca/uhn/fhir/model/dstu2/FhirDstu2.java | 4 +-
.../hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java | 8 +-
...luentPathDstu3.java => FhirPathDstu3.java} | 12 +-
.../fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java | 4 +-
.../java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java | 8 +-
.../{FluentPathR4.java => FhirPathR4.java} | 12 +-
.../FhirPathFilterInterceptorTest.java | 170 ++++++++++++++++++
.../java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java | 4 +-
.../hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java | 10 +-
.../fhir/test/utilities/HttpClientRule.java | 58 ++++++
.../FluentPathTest.java | 14 +-
.../ca/uhn/fhir/model/dstu2/FhirDstu2.java | 4 +-
27 files changed, 469 insertions(+), 62 deletions(-)
rename hapi-fhir-base/src/main/java/ca/uhn/fhir/{fluentpath/FluentPathExecutionException.java => fhirpath/FhirPathExecutionException.java} (73%)
rename hapi-fhir-base/src/main/java/ca/uhn/fhir/{fluentpath/IFluentPath.java => fhirpath/IFhirPath.java} (96%)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1769-add-fhirpath-interceptor.yaml
create mode 100644 hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java
rename hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/{FluentPathDstu3.java => FhirPathDstu3.java} (74%)
rename hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/{FluentPathR4.java => FhirPathR4.java} (75%)
create mode 100644 hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptorTest.java
create mode 100644 hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HttpClientRule.java
rename hapi-fhir-validation/src/test/java/ca/uhn/fhir/{fluentpath => fhirpath}/FluentPathTest.java (88%)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
index b0e7d69c9ac..f9bf329011f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
@@ -3,7 +3,7 @@ package ca.uhn.fhir.context;
import ca.uhn.fhir.context.api.AddProfileTagEnum;
import ca.uhn.fhir.context.support.DefaultProfileValidationSupport;
import ca.uhn.fhir.context.support.IValidationSupport;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.i18n.HapiLocalizer;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IFhirVersion;
@@ -625,12 +625,21 @@ public class FhirContext {
}
/**
- * Creates a new FluentPath engine which can be used to exvaluate
+ * @since 2.2
+ * @deprecated Deprecated in HAPI FHIR 5.0.0. Use {@link #newFhirPath()} instead.
+ */
+ @Deprecated
+ public IFhirPath newFluentPath() {
+ return newFhirPath();
+ }
+
+ /**
+ * Creates a new FhirPath engine which can be used to evaluate
* path expressions over FHIR resources. Note that this engine will use the
* {@link IValidationSupport context validation support} module which is
* configured on the context at the time this method is called.
*
- * In other words, call {@link #setValidationSupport(IValidationSupport)} before
+ * In other words, you may wish to call {@link #setValidationSupport(IValidationSupport)} before
* calling {@link #newFluentPath()}
*
*
@@ -640,9 +649,9 @@ public class FhirContext {
* {@link UnsupportedOperationException}
*
*
- * @since 2.2
+ * @since 5.0.0
*/
- public IFluentPath newFluentPath() {
+ public IFhirPath newFhirPath() {
return myVersion.createFluentPathExecutor(this);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/FluentPathExecutionException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java
similarity index 73%
rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/FluentPathExecutionException.java
rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java
index 8cd3e96f0a6..04413995318 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/FluentPathExecutionException.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.fluentpath;
+package ca.uhn.fhir.fhirpath;
/*
* #%L
@@ -23,18 +23,18 @@ package ca.uhn.fhir.fluentpath;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
/**
- * This exception is thrown if a FluentPath expression can not be executed successfully
+ * This exception is thrown if a FHIRPath expression can not be executed successfully
* for any reason
*/
-public class FluentPathExecutionException extends InternalErrorException {
+public class FhirPathExecutionException extends InternalErrorException {
private static final long serialVersionUID = 1L;
- public FluentPathExecutionException(Throwable theCause) {
+ public FhirPathExecutionException(Throwable theCause) {
super(theCause);
}
- public FluentPathExecutionException(String theMessage) {
+ public FhirPathExecutionException(String theMessage) {
super(theMessage);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java
similarity index 96%
rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java
rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java
index 8d225b20c27..a15f716c37f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fluentpath/IFluentPath.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.fluentpath;
+package ca.uhn.fhir.fhirpath;
/*
* #%L
@@ -25,7 +25,7 @@ import java.util.Optional;
import org.hl7.fhir.instance.model.api.IBase;
-public interface IFluentPath {
+public interface IFhirPath {
/**
* Apply the given FluentPath expression against the given input and return
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java
index f3e8c02542d..164f5969b9b 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java
@@ -23,10 +23,10 @@ package ca.uhn.fhir.model.api;
import java.io.InputStream;
import java.util.Date;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
-import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
/**
@@ -38,7 +38,7 @@ import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
*/
public interface IFhirVersion {
- IFluentPath createFluentPathExecutor(FhirContext theFhirContext);
+ IFhirPath createFluentPathExecutor(FhirContext theFhirContext);
IBaseResource generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition, String theServerBase);
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java
index f8a4193cf16..c73c545c407 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/narrative2/BaseNarrativeGenerator.java
@@ -24,7 +24,7 @@ import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.narrative.INarrativeGenerator;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import org.hl7.fhir.instance.model.api.IBase;
@@ -120,7 +120,7 @@ public abstract class BaseNarrativeGenerator implements INarrativeGenerator {
if (theFhirContext.getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) {
return Collections.singletonList(theResource);
}
- IFluentPath fhirPath = theFhirContext.newFluentPath();
+ IFhirPath fhirPath = theFhirContext.newFluentPath();
return fhirPath.evaluate(theResource, theContextPath, IBase.class);
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
index 50aec5b8393..8678d76cae8 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
@@ -259,6 +259,7 @@ public class Constants {
*
*/
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
+ public static final String PARAM_FHIRPATH = "_fhirpath";
static {
CHARSET_UTF8 = StandardCharsets.UTF_8;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java
index f4db4b682c2..42292361c26 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/ParametersUtil.java
@@ -238,7 +238,7 @@ public class ParametersUtil {
addPart(theContext, theParameter, theName, coding);
}
- private static void addPart(FhirContext theContext, IBase theParameter, String theName, IBase theValue) {
+ public static void addPart(FhirContext theContext, IBase theParameter, String theName, IBase theValue) {
BaseRuntimeElementCompositeDefinition> def = (BaseRuntimeElementCompositeDefinition>) theContext.getElementDefinition(theParameter.getClass());
BaseRuntimeChildDefinition partChild = def.getChildByName("part");
@@ -252,4 +252,19 @@ public class ParametersUtil {
partChildElem.getChildByName("value[x]").getMutator().addValue(part, theValue);
}
+
+ public static void addPartResource(FhirContext theContext, IBase theParameter, String theName, IBaseResource theValue) {
+ BaseRuntimeElementCompositeDefinition> def = (BaseRuntimeElementCompositeDefinition>) theContext.getElementDefinition(theParameter.getClass());
+ BaseRuntimeChildDefinition partChild = def.getChildByName("part");
+
+ BaseRuntimeElementCompositeDefinition> partChildElem = (BaseRuntimeElementCompositeDefinition>) partChild.getChildByName("part");
+ IBase part = partChildElem.newInstance();
+ partChild.getMutator().addValue(theParameter, part);
+
+ IPrimitiveType name = (IPrimitiveType) theContext.getElementDefinition("string").newInstance();
+ name.setValue(theName);
+ partChildElem.getChildByName("name").getMutator().addValue(part, name);
+
+ partChildElem.getChildByName("resource").getMutator().addValue(part, theValue);
+ }
}
diff --git a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java
index 96803d8bda2..eaf84d6ce1f 100644
--- a/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java
+++ b/hapi-fhir-docs/src/main/java/ca/uhn/hapi/fhir/docs/ServletExamples.java
@@ -32,11 +32,11 @@ import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import java.util.Arrays;
-@SuppressWarnings("serial")
+@SuppressWarnings({"serial", "RedundantThrows", "InnerClassMayBeStatic"})
public class ServletExamples {
// START SNIPPET: loggingInterceptor
- @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
+ @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
public class RestfulServerWithLogging extends RestfulServer {
@Override
@@ -121,6 +121,24 @@ public class ServletExamples {
}
// END SNIPPET: exceptionInterceptor
+ // START SNIPPET: fhirPathInterceptor
+ @WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
+ public class RestfulServerWithFhirPath extends RestfulServer {
+
+ @Override
+ protected void initialize() throws ServletException {
+
+ // ... define your resource providers here ...
+
+ // Now register the interceptor
+ FhirPathFilterInterceptor interceptor = new FhirPathFilterInterceptor();
+ registerInterceptor(interceptor);
+
+ }
+
+ }
+ // END SNIPPET: fhirPathInterceptor
+
// START SNIPPET: responseHighlighterInterceptor
@WebServlet(urlPatterns = { "/fhir/*" }, displayName = "FHIR Server")
public class RestfulServerWithResponseHighlighter extends RestfulServer {
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1769-add-fhirpath-interceptor.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1769-add-fhirpath-interceptor.yaml
new file mode 100644
index 00000000000..c6e9a32c0d5
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1769-add-fhirpath-interceptor.yaml
@@ -0,0 +1,6 @@
+---
+type: add
+issue: 1769
+title: A new built-in server interceptor called FhirPathFilterInterceptor has been added. This interceptor
+ evaluates an arbitrary FHIRPath expression against the resource being returned and replaces the response
+ with a Parameters resource containing the results of the evaluation.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
index aa375e46680..08691d1826a 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/changes.yaml
@@ -17,3 +17,11 @@
[Migrating to HAPI FHIR 5.x](/hapi-fhir/docs/validation/instance_validator.html#migrating-to-hapi-fhir-5x)
for details on how to account for this change in your code.
"
+- item:
+ issue: "1769"
+ type: "change"
+ title: "**Breaking Change**:
+ The `IFluentPath` interface has been renamed to `IFhirPath`, and the `FhirContext#newFluentPath()` method
+ has been replaced with an equivalent `FhirContext.newFhirPath()`. The FhirPath expression language was initially
+ called FluentPath before being renamed, so this change brings HAPI FHIR inline with the correct naming.
+ "
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
index 6c166e8cce9..35f20dc14ca 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
@@ -50,6 +50,52 @@ The following example shows how to register the ExceptionHandlingInterceptor.
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|exceptionInterceptor}}
```
+# Response Customizing: Evaluate FHIRPath
+
+The FhirPathFilterInterceptor looks for a request URL parameter in the form `_fhirpath=(expression)` in all REST requests. If this parameter is found, the value is treated as a [FHIRPath](http://hl7.org/fhirpath/) expression. The response resource will be replaced with a [Parameters](hl7.org/fhir/parameters.html) resource containing the results of the given expression applied against the response resource.
+
+* [FhirPathFilterInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.html)
+* [FhirPathFilterInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java)
+
+The following example shows how to register the ExceptionHandlingInterceptor.
+
+```java
+{{snippet:classpath:/ca/uhn/hapi/fhir/docs/ServletExamples.java|exceptionInterceptor}}
+```
+
+An example URL to invoke this function is shown below:
+
+```url
+https://hapi.fhir.org/baseR4/Patient?_fhirpath=Bundle.entry.resource.as(Patient).name&_pretty=true
+```
+
+A sample response to this query is shown below:
+
+```json
+{
+ "resourceType": "Parameters",
+ "parameter": [ {
+ "name": "result",
+ "part": [ {
+ "name": "expression",
+ "valueString": "Bundle.entry.resource.as(Patient).name"
+ }, {
+ "name": "result",
+ "valueHumanName": {
+ "family": "Simpson",
+ "given": [ "Homer", "Jay" ]
+ }
+ }, {
+ "name": "result",
+ "valueHumanName": {
+ "family": "Simpson",
+ "given": [ "Grandpa" ]
+ }
+ } ]
+ } ]
+}
+```
+
# Validation: Request and Response Validation
HAPI FHIR provides a pair of interceptors that can be used to validate incoming requests received by the server, as well as outgoing responses generated by the server.
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
index e2a164287b9..4a767f35d5f 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/TestRestfulServer.java
@@ -28,6 +28,7 @@ import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.BanUnsupportedHttpMethodsInterceptor;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
+import ca.uhn.fhir.rest.server.interceptor.FhirPathFilterInterceptor;
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
import ca.uhn.fhirtest.config.TestDstu2Config;
import ca.uhn.fhirtest.config.TestDstu3Config;
@@ -198,6 +199,11 @@ public class TestRestfulServer extends RestfulServer {
CorsInterceptor corsInterceptor = new CorsInterceptor();
registerInterceptor(corsInterceptor);
+ /*
+ * Enable FHIRPath evaluation
+ */
+ registerInterceptor(new FhirPathFilterInterceptor());
+
/*
* Enable version conversion
*/
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java
new file mode 100644
index 00000000000..c254be28f19
--- /dev/null
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java
@@ -0,0 +1,70 @@
+package ca.uhn.fhir.rest.server.interceptor;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
+import ca.uhn.fhir.fhirpath.IFhirPath;
+import ca.uhn.fhir.interceptor.api.Hook;
+import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.api.server.ResponseDetails;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.util.ParametersUtil;
+import org.hl7.fhir.instance.model.api.IBase;
+import org.hl7.fhir.instance.model.api.IBaseParameters;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
+import java.util.List;
+
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+
+/**
+ * This interceptor looks for a URL parameter on requests called _fhirpath
and
+ * replaces the resource being returned with a Parameters resource containing the results of
+ * the given FHIRPath expression evaluated against the resource that would otherwise
+ * have been returned.
+ *
+ * @see Interceptors - Response Customization: Evaluate FHIRPath
+ * @since 5.0.0
+ */
+public class FhirPathFilterInterceptor {
+
+ @Hook(Pointcut.SERVER_OUTGOING_RESPONSE)
+ public void preProcessOutgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails) {
+ IBaseResource responseResource = theResponseDetails.getResponseResource();
+ if (responseResource != null) {
+ String[] fhirPathParams = theRequestDetails.getParameters().get(Constants.PARAM_FHIRPATH);
+ if (fhirPathParams != null) {
+
+ FhirContext ctx = theRequestDetails.getFhirContext();
+ IBaseParameters responseParameters = ParametersUtil.newInstance(ctx);
+
+ for (String expression : fhirPathParams) {
+ if (isNotBlank(expression)) {
+ IBase resultPart = ParametersUtil.addParameterToParameters(ctx, responseParameters, "result");
+ ParametersUtil.addPartString(ctx, resultPart, "expression", expression);
+
+ IFhirPath fhirPath = ctx.newFhirPath();
+ List outputs;
+ try {
+ outputs = fhirPath.evaluate(responseResource, expression, IBase.class);
+ } catch (FhirPathExecutionException e) {
+ throw new InvalidRequestException("Error parsing FHIRPath expression: " + e.getMessage());
+ }
+
+ for (IBase nextOutput : outputs) {
+ if (nextOutput instanceof IBaseResource) {
+ ParametersUtil.addPartResource(ctx, resultPart, "result", (IBaseResource) nextOutput);
+ } else {
+ ParametersUtil.addPart(ctx, resultPart, "result", nextOutput);
+ }
+ }
+ }
+ }
+
+ theResponseDetails.setResponseResource(responseParameters);
+ }
+ }
+ }
+
+}
diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java
index 6ed05ffa58a..731db1e8a57 100644
--- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java
+++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/model/dstu/FhirDstu1.java
@@ -60,7 +60,7 @@ import ca.uhn.fhir.context.RuntimePrimitiveDatatypeDefinition;
import ca.uhn.fhir.context.RuntimeResourceBlockDefinition;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.support.IContextValidationSupport;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.IFluentPath;
import ca.uhn.fhir.model.api.ICompositeDatatype;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.api.IPrimitiveDatatype;
diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/ctx/FhirDstu2_1.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/ctx/FhirDstu2_1.java
index 250d7170d10..dd9a1c39a1c 100644
--- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/ctx/FhirDstu2_1.java
+++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/ctx/FhirDstu2_1.java
@@ -30,7 +30,7 @@ import org.hl7.fhir.dstu2016may.model.*;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
@@ -41,7 +41,7 @@ public class FhirDstu2_1 implements IFhirVersion {
private String myId;
@Override
- public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
index ea7ad545b30..53b45ab795a 100644
--- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
+++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
@@ -23,11 +23,11 @@ package ca.uhn.fhir.model.dstu2;
import java.io.InputStream;
import java.util.Date;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
-import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.base.composite.*;
import ca.uhn.fhir.model.dstu2.composite.*;
@@ -42,7 +42,7 @@ public class FhirDstu2 implements IFhirVersion {
private String myId;
@Override
- public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java
index a2037e1d788..1980ce93b25 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java
@@ -24,13 +24,13 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
import ca.uhn.fhir.util.ReflectionUtil;
import org.apache.commons.lang3.StringUtils;
-import org.hl7.fhir.dstu3.hapi.fluentpath.FluentPathDstu3;
+import org.hl7.fhir.dstu3.hapi.fluentpath.FhirPathDstu3;
import org.hl7.fhir.dstu3.hapi.rest.server.Dstu3BundleFactory;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.*;
@@ -44,8 +44,8 @@ public class FhirDstu3 implements IFhirVersion {
private String myId;
@Override
- public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
- return new FluentPathDstu3(theFhirContext);
+ public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
+ return new FhirPathDstu3(theFhirContext);
}
@Override
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FluentPathDstu3.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java
similarity index 74%
rename from hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FluentPathDstu3.java
rename to hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java
index 9eb25f1d369..84bbca753dc 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FluentPathDstu3.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java
@@ -2,8 +2,8 @@ package org.hl7.fhir.dstu3.hapi.fluentpath;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
-import ca.uhn.fhir.fluentpath.FluentPathExecutionException;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.model.Base;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
@@ -13,11 +13,11 @@ import org.hl7.fhir.instance.model.api.IBase;
import java.util.List;
import java.util.Optional;
-public class FluentPathDstu3 implements IFluentPath {
+public class FhirPathDstu3 implements IFhirPath {
private FHIRPathEngine myEngine;
- public FluentPathDstu3(FhirContext theCtx) {
+ public FhirPathDstu3(FhirContext theCtx) {
IValidationSupport validationSupport = theCtx.getValidationSupport();
myEngine = new FHIRPathEngine(new HapiWorkerContext(theCtx, validationSupport));
}
@@ -29,12 +29,12 @@ public class FluentPathDstu3 implements IFluentPath {
try {
result = myEngine.evaluate((Base)theInput, thePath);
} catch (FHIRException e) {
- throw new FluentPathExecutionException(e);
+ throw new FhirPathExecutionException(e);
}
for (Base next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
- throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
+ throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}
diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java
index 6048745858f..dfc192139b1 100644
--- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java
+++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java
@@ -24,12 +24,12 @@ import java.io.InputStream;
import java.util.ArrayList;
import java.util.Date;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.dstu2.model.*;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
-import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
@@ -41,7 +41,7 @@ public class FhirDstu2Hl7Org implements IFhirVersion {
private String myId;
@Override
- public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java
index cb1eeeb2358..1014f31e2b4 100644
--- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java
+++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java
@@ -26,12 +26,12 @@ import java.util.List;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.*;
-import org.hl7.fhir.r4.hapi.fluentpath.FluentPathR4;
+import org.hl7.fhir.r4.hapi.fluentpath.FhirPathR4;
import org.hl7.fhir.r4.hapi.rest.server.R4BundleFactory;
import org.hl7.fhir.r4.model.*;
import ca.uhn.fhir.context.*;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
@@ -42,8 +42,8 @@ public class FhirR4 implements IFhirVersion {
private String myId;
@Override
- public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
- return new FluentPathR4(theFhirContext);
+ public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
+ return new FhirPathR4(theFhirContext);
}
@Override
diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FluentPathR4.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java
similarity index 75%
rename from hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FluentPathR4.java
rename to hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java
index 5a34d3d8ff3..eb1ca366b43 100644
--- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FluentPathR4.java
+++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java
@@ -2,8 +2,8 @@ package org.hl7.fhir.r4.hapi.fluentpath;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
-import ca.uhn.fhir.fluentpath.FluentPathExecutionException;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
@@ -13,11 +13,11 @@ import org.hl7.fhir.r4.utils.FHIRPathEngine;
import java.util.List;
import java.util.Optional;
-public class FluentPathR4 implements IFluentPath {
+public class FhirPathR4 implements IFhirPath {
private FHIRPathEngine myEngine;
- public FluentPathR4(FhirContext theCtx) {
+ public FhirPathR4(FhirContext theCtx) {
IValidationSupport validationSupport = theCtx.getValidationSupport();
myEngine = new FHIRPathEngine(new HapiWorkerContext(theCtx, validationSupport));
}
@@ -29,12 +29,12 @@ public class FluentPathR4 implements IFluentPath {
try {
result = myEngine.evaluate((Base) theInput, thePath);
} catch (FHIRException e) {
- throw new FluentPathExecutionException(e);
+ throw new FhirPathExecutionException(e);
}
for (Base next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
- throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
+ throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptorTest.java
new file mode 100644
index 00000000000..68bc9ad406d
--- /dev/null
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptorTest.java
@@ -0,0 +1,170 @@
+package ca.uhn.fhir.rest.server.interceptor;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.client.api.IGenericClient;
+import ca.uhn.fhir.test.utilities.HttpClientRule;
+import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderRule;
+import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
+import ca.uhn.fhir.util.UrlUtil;
+import com.google.common.base.Charsets;
+import org.apache.commons.io.IOUtils;
+import org.apache.http.client.methods.CloseableHttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.Patient;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Test;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+
+import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.not;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertThat;
+
+public class FhirPathFilterInterceptorTest {
+
+ private static final Logger ourLog = LoggerFactory.getLogger(FhirPathFilterInterceptorTest.class);
+ @ClassRule
+ public static HttpClientRule ourClientRule = new HttpClientRule();
+ private static FhirContext ourCtx = FhirContext.forR4();
+ @ClassRule
+ public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx);
+ @ClassRule
+ public static HashMapResourceProviderRule ourProviderRule = new HashMapResourceProviderRule<>(ourServerRule, Patient.class);
+ private IGenericClient myClient;
+ private String myBaseUrl;
+ private CloseableHttpClient myHttpClient;
+ private IIdType myPatientId;
+
+ @Before
+ public void before() {
+ ourProviderRule.clear();
+ ourServerRule.getRestfulServer().getInterceptorService().unregisterAllInterceptors();
+ ourServerRule.getRestfulServer().getInterceptorService().registerInterceptor(new FhirPathFilterInterceptor());
+
+ myClient = ourServerRule.getFhirClient();
+ myBaseUrl = "http://localhost:" + ourServerRule.getPort();
+ myHttpClient = ourClientRule.getClient();
+ }
+
+ @Test
+ public void testUnfilteredResponse() throws IOException {
+ createPatient();
+
+ HttpGet request = new HttpGet(myPatientId.getValue());
+ try (CloseableHttpResponse response = myHttpClient.execute(request)) {
+ String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
+ ourLog.info("Response:\n{}", responseText);
+ assertThat(responseText, containsString("\"system\": \"http://identifiers/1\""));
+ assertThat(responseText, containsString("\"given\": [ \"Homer\", \"Jay\" ]"));
+ }
+ }
+
+
+ @Test
+ public void testUnfilteredResponse_WithResponseHighlightingInterceptor() throws IOException {
+ ourServerRule.getRestfulServer().registerInterceptor(new ResponseHighlighterInterceptor());
+ createPatient();
+
+ HttpGet request = new HttpGet(myPatientId.getValue() + "?_format=" + Constants.FORMATS_HTML_JSON);
+ try (CloseableHttpResponse response = myHttpClient.execute(request)) {
+ String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
+ ourLog.info("Response:\n{}", responseText);
+ assertThat(responseText, containsString(""system": "http://identifiers/1""));
+ assertThat(responseText, containsString(""given": [ "Homer", "Jay" ]"));
+ }
+ }
+
+ @Test
+ public void testFilteredResponse() throws IOException {
+ createPatient();
+
+ HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.identifier&_pretty=true");
+ try (CloseableHttpResponse response = myHttpClient.execute(request)) {
+ String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
+ ourLog.info("Response:\n{}", responseText);
+ assertThat(responseText, containsString("\"system\": \"http://identifiers/1\""));
+ assertThat(responseText, not(containsString("\"given\": [ \"Homer\", \"Jay\" ]")));
+ }
+
+ }
+
+ @Test
+ public void testFilteredResponse_ExpressionReturnsResource() throws IOException {
+ createPatient();
+
+ HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient&_pretty=true");
+ try (CloseableHttpResponse response = myHttpClient.execute(request)) {
+ String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
+ ourLog.info("Response:\n{}", responseText);
+ assertThat(responseText, containsString("\"resource\": {"));
+ assertThat(responseText, containsString("\"system\": \"http://identifiers/1\""));
+ assertThat(responseText, containsString("\"given\": [ \"Homer\", \"Jay\" ]"));
+ }
+
+ }
+
+ @Test
+ public void testFilteredResponse_ExpressionIsInvalid() throws IOException {
+ createPatient();
+
+ HttpGet request = new HttpGet(myPatientId + "?_fhirpath=" + UrlUtil.escapeUrlParam("***"));
+ try (CloseableHttpResponse response = myHttpClient.execute(request)) {
+ String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
+ ourLog.info("Response:\n{}", responseText);
+ assertEquals(400, response.getStatusLine().getStatusCode());
+ assertThat(responseText, containsString("Error parsing FHIRPath expression: Error performing *: left operand has more than one value"));
+ }
+
+ }
+
+ @Test
+ public void testFilteredResponseBundle() throws IOException {
+ createPatient();
+
+ HttpGet request = new HttpGet(myBaseUrl + "/Patient?_fhirpath=Bundle.entry.resource.as(Patient).name&_pretty=true");
+ try (CloseableHttpResponse response = myHttpClient.execute(request)) {
+ String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
+ ourLog.info("Response:\n{}", responseText);
+ assertThat(responseText, containsString(
+ " \"valueHumanName\": {\n" +
+ " \"family\": \"Simpson\",\n" +
+ " \"given\": [ \"Homer\", \"Jay\" ]\n" +
+ " }"
+ ));
+ }
+
+ }
+
+ @Test
+ public void testFilteredResponse_WithResponseHighlightingInterceptor() throws IOException {
+ ourServerRule.getRestfulServer().registerInterceptor(new ResponseHighlighterInterceptor());
+ createPatient();
+
+ HttpGet request = new HttpGet(myPatientId + "?_fhirpath=Patient.identifier&_format=" + Constants.FORMATS_HTML_JSON);
+ try (CloseableHttpResponse response = myHttpClient.execute(request)) {
+ String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
+ ourLog.info("Response:\n{}", responseText);
+ assertThat(responseText, containsString(""system": "http://identifiers/1""));
+ assertThat(responseText, not(containsString(""given": [ "Homer", "Jay" ]")));
+ }
+
+ }
+
+ private void createPatient() {
+ Patient p = new Patient();
+ p.setActive(true);
+ p.addIdentifier().setSystem("http://identifiers/1").setValue("value-1");
+ p.addIdentifier().setSystem("http://identifiers/2").setValue("value-2");
+ p.addName().setFamily("Simpson").addGiven("Homer").addGiven("Jay");
+ p.addName().setFamily("Simpson").addGiven("Grandpa");
+ myPatientId = myClient.create().resource(p).execute().getId().withServerBase(myBaseUrl, "Patient");
+ }
+
+}
diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java
index c4c2e210235..c303fd18107 100644
--- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java
+++ b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java
@@ -24,7 +24,7 @@ import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import ca.uhn.fhir.model.api.IFhirVersion;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
@@ -52,7 +52,7 @@ public class FhirR5 implements IFhirVersion {
private String myId;
@Override
- public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
return new FhirPathR5(theFhirContext);
}
diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java
index 1aab76cf9ed..d82be431bc1 100644
--- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java
+++ b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/fhirpath/FhirPathR5.java
@@ -2,8 +2,8 @@ package org.hl7.fhir.r5.hapi.fhirpath;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.support.IValidationSupport;
-import ca.uhn.fhir.fluentpath.FluentPathExecutionException;
-import ca.uhn.fhir.fluentpath.IFluentPath;
+import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
@@ -13,7 +13,7 @@ import org.hl7.fhir.r5.utils.FHIRPathEngine;
import java.util.List;
import java.util.Optional;
-public class FhirPathR5 implements IFluentPath {
+public class FhirPathR5 implements IFhirPath {
private FHIRPathEngine myEngine;
@@ -29,12 +29,12 @@ public class FhirPathR5 implements IFluentPath {
try {
result = myEngine.evaluate((Base) theInput, thePath);
} catch (FHIRException e) {
- throw new FluentPathExecutionException(e);
+ throw new FhirPathExecutionException(e);
}
for (Base next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
- throw new FluentPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
+ throw new FhirPathExecutionException("FluentPath expression \"" + thePath + "\" returned unexpected type " + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}
diff --git a/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HttpClientRule.java b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HttpClientRule.java
new file mode 100644
index 00000000000..4c834c1eff4
--- /dev/null
+++ b/hapi-fhir-test-utilities/src/main/java/ca/uhn/fhir/test/utilities/HttpClientRule.java
@@ -0,0 +1,58 @@
+package ca.uhn.fhir.test.utilities;
+
+/*-
+ * #%L
+ * HAPI FHIR Test Utilities
+ * %%
+ * Copyright (C) 2014 - 2020 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClientBuilder;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runners.model.Statement;
+
+public class HttpClientRule implements TestRule {
+ private CloseableHttpClient myClient;
+
+ @Override
+ public Statement apply(Statement theBase, Description theDescription) {
+ return new Statement() {
+ @Override
+ public void evaluate() throws Throwable {
+ startClient();
+ theBase.evaluate();
+ stopClient();
+ }
+ };
+ }
+
+
+ private void stopClient() throws Exception {
+ myClient.close();
+ }
+
+ private void startClient() {
+ myClient = HttpClientBuilder
+ .create()
+ .build();
+ }
+
+ public CloseableHttpClient getClient() {
+ return myClient;
+ }
+}
diff --git a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/fluentpath/FluentPathTest.java b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/fhirpath/FluentPathTest.java
similarity index 88%
rename from hapi-fhir-validation/src/test/java/ca/uhn/fhir/fluentpath/FluentPathTest.java
rename to hapi-fhir-validation/src/test/java/ca/uhn/fhir/fhirpath/FluentPathTest.java
index 4acf3a2d406..6bc7da3d654 100644
--- a/hapi-fhir-validation/src/test/java/ca/uhn/fhir/fluentpath/FluentPathTest.java
+++ b/hapi-fhir-validation/src/test/java/ca/uhn/fhir/fhirpath/FluentPathTest.java
@@ -1,4 +1,4 @@
-package ca.uhn.fhir.fluentpath;
+package ca.uhn.fhir.fhirpath;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.util.TestUtil;
@@ -21,7 +21,7 @@ public class FluentPathTest {
p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
- IFluentPath fp = ourCtx.newFluentPath();
+ IFhirPath fp = ourCtx.newFluentPath();
List names = fp.evaluate(p, "Patient.name", HumanName.class);
assertEquals(2, names.size());
assertEquals("N1F1", names.get(0).getFamily());
@@ -36,7 +36,7 @@ public class FluentPathTest {
p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
- IFluentPath fp = ourCtx.newFluentPath();
+ IFhirPath fp = ourCtx.newFluentPath();
List names = fp.evaluate(p, "Patient.nameFOO", HumanName.class);
assertEquals(0, names.size());
}
@@ -47,10 +47,10 @@ public class FluentPathTest {
p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
- IFluentPath fp = ourCtx.newFluentPath();
+ IFhirPath fp = ourCtx.newFluentPath();
try {
fp.evaluate(p, "Patient....nameFOO", HumanName.class);
- } catch (FluentPathExecutionException e) {
+ } catch (FhirPathExecutionException e) {
assertThat(e.getMessage(), containsString("termination at unexpected token"));
}
}
@@ -61,10 +61,10 @@ public class FluentPathTest {
p.addName().setFamily("N1F1").addGiven("N1G1").addGiven("N1G2");
p.addName().setFamily("N2F1").addGiven("N2G1").addGiven("N2G2");
- IFluentPath fp = ourCtx.newFluentPath();
+ IFhirPath fp = ourCtx.newFluentPath();
try {
fp.evaluate(p, "Patient.name", StringType.class);
- } catch (FluentPathExecutionException e) {
+ } catch (FhirPathExecutionException e) {
assertEquals("FluentPath expression \"Patient.name\" returned unexpected type HumanName - Expected org.hl7.fhir.dstu3.model.StringType", e.getMessage());
}
}
diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
index 06a10a7948b..a95a1109967 100644
--- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
+++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
@@ -23,11 +23,11 @@ package ca.uhn.fhir.model.dstu2;
import java.io.InputStream;
import java.util.Date;
+import ca.uhn.fhir.fhirpath.IFhirPath;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.context.*;
-import ca.uhn.fhir.fluentpath.IFluentPath;
import ca.uhn.fhir.model.api.*;
import ca.uhn.fhir.model.base.composite.*;
import ca.uhn.fhir.model.dstu2.composite.*;
@@ -41,7 +41,7 @@ public class FhirDstu2 implements IFhirVersion {
private String myId;
@Override
- public IFluentPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
From f89661526c5a4eed00621f32ea8f4fcb71191222 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Sun, 22 Mar 2020 13:27:04 -0400
Subject: [PATCH 06/32] Missed noe rename in #1769
---
.../src/main/java/ca/uhn/fhir/context/FhirContext.java | 2 +-
.../src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java | 2 +-
.../java/org/hl7/fhir/dstu2016may/hapi/ctx/FhirDstu2_1.java | 2 +-
.../src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java | 2 +-
.../src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java | 2 +-
.../main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java | 2 +-
.../src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java | 2 +-
.../src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java | 2 +-
.../src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java | 2 +-
9 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
index f9bf329011f..45273c488d8 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java
@@ -652,7 +652,7 @@ public class FhirContext {
* @since 5.0.0
*/
public IFhirPath newFhirPath() {
- return myVersion.createFluentPathExecutor(this);
+ return myVersion.createFhirPathExecutor(this);
}
/**
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java
index 164f5969b9b..d1c5b270631 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IFhirVersion.java
@@ -38,7 +38,7 @@ import ca.uhn.fhir.rest.api.IVersionSpecificBundleFactory;
*/
public interface IFhirVersion {
- IFhirPath createFluentPathExecutor(FhirContext theFhirContext);
+ IFhirPath createFhirPathExecutor(FhirContext theFhirContext);
IBaseResource generateProfile(RuntimeResourceDefinition theRuntimeResourceDefinition, String theServerBase);
diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/ctx/FhirDstu2_1.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/ctx/FhirDstu2_1.java
index dd9a1c39a1c..796c06e3f8d 100644
--- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/ctx/FhirDstu2_1.java
+++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/ctx/FhirDstu2_1.java
@@ -41,7 +41,7 @@ public class FhirDstu2_1 implements IFhirVersion {
private String myId;
@Override
- public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
index 53b45ab795a..7bbbb6d054f 100644
--- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
+++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
@@ -42,7 +42,7 @@ public class FhirDstu2 implements IFhirVersion {
private String myId;
@Override
- public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java
index 1980ce93b25..300f3410a86 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/FhirDstu3.java
@@ -44,7 +44,7 @@ public class FhirDstu3 implements IFhirVersion {
private String myId;
@Override
- public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
return new FhirPathDstu3(theFhirContext);
}
diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java
index dfc192139b1..6ee07334272 100644
--- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java
+++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/dstu2/hapi/ctx/FhirDstu2Hl7Org.java
@@ -41,7 +41,7 @@ public class FhirDstu2Hl7Org implements IFhirVersion {
private String myId;
@Override
- public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java
index 1014f31e2b4..ec2ef5edd07 100644
--- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java
+++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/FhirR4.java
@@ -42,7 +42,7 @@ public class FhirR4 implements IFhirVersion {
private String myId;
@Override
- public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
return new FhirPathR4(theFhirContext);
}
diff --git a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java
index c303fd18107..c9e8747dd13 100644
--- a/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java
+++ b/hapi-fhir-structures-r5/src/main/java/org/hl7/fhir/r5/hapi/ctx/FhirR5.java
@@ -52,7 +52,7 @@ public class FhirR5 implements IFhirVersion {
private String myId;
@Override
- public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
return new FhirPathR5(theFhirContext);
}
diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
index a95a1109967..b0ace6cc11f 100644
--- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
+++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/model/dstu2/FhirDstu2.java
@@ -41,7 +41,7 @@ public class FhirDstu2 implements IFhirVersion {
private String myId;
@Override
- public IFhirPath createFluentPathExecutor(FhirContext theFhirContext) {
+ public IFhirPath createFhirPathExecutor(FhirContext theFhirContext) {
throw new UnsupportedOperationException("FluentPath is not supported in DSTU2 contexts");
}
From 3f20f7b67f911767e32423bc94ac3b6251bfbd59 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Sun, 22 Mar 2020 21:01:19 -0400
Subject: [PATCH 07/32] Make SubscriptionDeliveryHandlerFactory not abstract
---
.../java/ca/uhn/fhir/jpa/config/BaseConfig.java | 7 ++++++-
.../channel/SubscriptionDeliveryHandlerFactory.java | 13 ++++++++++---
2 files changed, 16 insertions(+), 4 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
index 1d6007615f2..11163796b33 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java
@@ -40,7 +40,12 @@ import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.context.annotation.*;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.FilterType;
+import org.springframework.context.annotation.Lazy;
+import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor;
diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryHandlerFactory.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryHandlerFactory.java
index 693d1ec994f..8d62a7fd0be 100644
--- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryHandlerFactory.java
+++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/channel/SubscriptionDeliveryHandlerFactory.java
@@ -31,13 +31,20 @@ import org.springframework.stereotype.Component;
import java.util.Optional;
@Component
-public abstract class SubscriptionDeliveryHandlerFactory {
+public class SubscriptionDeliveryHandlerFactory {
private IEmailSender myEmailSender;
@Lookup
- protected abstract SubscriptionDeliveringEmailSubscriber getSubscriptionDeliveringEmailSubscriber(IEmailSender myEmailSender);
+ protected SubscriptionDeliveringEmailSubscriber getSubscriptionDeliveringEmailSubscriber(IEmailSender myEmailSender) {
+ // stub method since this is a @Lookup
+ throw new IllegalStateException();
+ }
+
@Lookup
- protected abstract SubscriptionDeliveringRestHookSubscriber getSubscriptionDeliveringRestHookSubscriber();
+ protected SubscriptionDeliveringRestHookSubscriber getSubscriptionDeliveringRestHookSubscriber() {
+ // stub method since this is a @Lookup
+ throw new IllegalStateException();
+ }
public Optional createDeliveryHandler(CanonicalSubscriptionChannelType theChannelType) {
if (theChannelType == CanonicalSubscriptionChannelType.EMAIL) {
From 14316147c5653efb66bbd2883c403ac24b3171d3 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 23 Mar 2020 10:34:12 -0400
Subject: [PATCH 08/32] Resolve some docs issues
---
.../parser/json/jackson/JacksonStructure.java | 20 +++++++++++++++++++
.../parser/json/jackson/JacksonWriter.java | 20 +++++++++++++++++++
hapi-fhir-converter/pom.xml | 10 ++++++++++
.../built_in_server_interceptors.md | 2 +-
.../fhir/jpa/entity/TermConceptProperty.java | 2 +-
.../FhirPathFilterInterceptor.java | 20 +++++++++++++++++++
hapi-fhir-structures-dstu2.1/pom.xml | 10 ++++++++++
hapi-fhir-structures-dstu3/pom.xml | 10 ++++++++++
hapi-fhir-structures-hl7org-dstu2/pom.xml | 10 ++++++++++
hapi-fhir-structures-r4/pom.xml | 10 +++++-----
hapi-fhir-structures-r5/pom.xml | 5 +++++
pom.xml | 2 +-
12 files changed, 113 insertions(+), 8 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java
index d22ac026796..6b1c70006a6 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonStructure.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.parser.json.jackson;
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2020 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.json.JsonLikeArray;
import ca.uhn.fhir.parser.json.JsonLikeObject;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java
index 54c40e36e9c..5e8a23786f5 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/json/jackson/JacksonWriter.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.parser.json.jackson;
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2020 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
import ca.uhn.fhir.parser.json.JsonLikeWriter;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonGenerator;
diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml
index 7b00bf29578..09e0e7df7c6 100644
--- a/hapi-fhir-converter/pom.xml
+++ b/hapi-fhir-converter/pom.xml
@@ -85,6 +85,16 @@
Saxon-HE
+
+
+ com.google.code.gson
+ gson
+ true
+
+
ch.qos.logback
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
index 35f20dc14ca..d9261468d13 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
@@ -52,7 +52,7 @@ The following example shows how to register the ExceptionHandlingInterceptor.
# Response Customizing: Evaluate FHIRPath
-The FhirPathFilterInterceptor looks for a request URL parameter in the form `_fhirpath=(expression)` in all REST requests. If this parameter is found, the value is treated as a [FHIRPath](http://hl7.org/fhirpath/) expression. The response resource will be replaced with a [Parameters](hl7.org/fhir/parameters.html) resource containing the results of the given expression applied against the response resource.
+The FhirPathFilterInterceptor looks for a request URL parameter in the form `_fhirpath=(expression)` in all REST requests. If this parameter is found, the value is treated as a [FHIRPath](http://hl7.org/fhirpath/) expression. The response resource will be replaced with a [Parameters](http://hl7.org/fhir/parameters.html) resource containing the results of the given expression applied against the response resource.
* [FhirPathFilterInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.html)
* [FhirPathFilterInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java
index 8bdbd909b24..13bb6b24a07 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/entity/TermConceptProperty.java
@@ -42,7 +42,7 @@ public class TermConceptProperty implements Serializable {
private static final long serialVersionUID = 1L;
private static final int MAX_LENGTH = 500;
- static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
+ public static final int MAX_PROPTYPE_ENUM_LENGTH = 6;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "CONCEPT_PID", referencedColumnName = "PID", foreignKey = @ForeignKey(name = "FK_CONCEPTPROP_CONCEPT"))
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java
index c254be28f19..5273dc5b2cc 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/FhirPathFilterInterceptor.java
@@ -1,5 +1,25 @@
package ca.uhn.fhir.rest.server.interceptor;
+/*-
+ * #%L
+ * HAPI FHIR - Server Framework
+ * %%
+ * Copyright (C) 2014 - 2020 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
import ca.uhn.fhir.fhirpath.IFhirPath;
diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml
index f7c4258f74e..383348d5c43 100644
--- a/hapi-fhir-structures-dstu2.1/pom.xml
+++ b/hapi-fhir-structures-dstu2.1/pom.xml
@@ -74,6 +74,16 @@
commons-codec
+
+
+ com.google.code.gson
+ gson
+ true
+
+
+
+ com.google.code.gson
+ gson
+ true
+
+
xpp3
xpp3
diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml
index 1b2953fcdb7..a7719f3e0dc 100644
--- a/hapi-fhir-structures-hl7org-dstu2/pom.xml
+++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml
@@ -85,6 +85,16 @@
true
+
+
+ com.google.code.gson
+ gson
+ true
+
+
org.xmlunit
diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml
index 4f16839bcf0..6bcdc740f1f 100644
--- a/hapi-fhir-structures-r4/pom.xml
+++ b/hapi-fhir-structures-r4/pom.xml
@@ -116,6 +116,11 @@
1.4
true
+
+ com.google.code.gson
+ gson
+ true
+
com.google.code.findbugs
@@ -219,11 +224,6 @@
-
- com.google.code.gson
- gson
- test
-
net.sf.json-lib
json-lib
diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml
index 0fabed502e0..f5d1094edaa 100644
--- a/hapi-fhir-structures-r5/pom.xml
+++ b/hapi-fhir-structures-r5/pom.xml
@@ -116,6 +116,11 @@
1.4
true
+
+ com.google.code.gson
+ gson
+ true
+
diff --git a/pom.xml b/pom.xml
index bf88321d7d5..18b28372908 100644
--- a/pom.xml
+++ b/pom.xml
@@ -606,7 +606,7 @@
- 4.2.9-SNAPSHOT
+ 4.2.10-SNAPSHOT
1.0.2
-Dfile.encoding=UTF-8 -Xmx2048m
From 0ac434ea5da3c65a8f45be29ced2281e1d91001c Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 23 Mar 2020 17:53:30 -0400
Subject: [PATCH 09/32] Add chain on _type parameter
---
.../java/ca/uhn/fhir/rest/api/Constants.java | 1 +
.../ca/uhn/fhir/i18n/hapi-messages.properties | 3 +
.../predicate/PredicateBuilderReference.java | 78 ++++++++++++++++---
.../r4/FhirResourceDaoR4SearchNoFtTest.java | 61 +++++++++++++++
4 files changed, 134 insertions(+), 9 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
index 8678d76cae8..09dfbae3d4f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/Constants.java
@@ -260,6 +260,7 @@ public class Constants {
*/
public static final String EXT_META_SOURCE = "http://hapifhir.io/fhir/StructureDefinition/resource-meta-source";
public static final String PARAM_FHIRPATH = "_fhirpath";
+ public static final String PARAM_TYPE = "_type";
static {
CHARSET_UTF8 = StandardCharsets.UTF_8;
diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
index c5b3696974e..bc15d09dc64 100644
--- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
+++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties
@@ -135,3 +135,6 @@ ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.cannotCreateDuplicateValueSetUrl=Can no
ca.uhn.fhir.jpa.term.BaseTermReadSvcImpl.expansionTooLarge=Expansion of ValueSet produced too many codes (maximum {0}) - Operation aborted!
ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils.failedToApplyPatch=Failed to apply JSON patch to {0}: {1}
+
+ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidTargetTypeForChain=Resource type "{0}" is not a valid target type for reference search parameter: {1}
+ca.uhn.fhir.jpa.dao.predicate.PredicateBuilderReference.invalidResourceType=Invalid/unsupported resource type: "{0}"
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
index 6306487cabf..12f377485cb 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
@@ -20,14 +20,31 @@ package ca.uhn.fhir.jpa.dao.predicate;
* #L%
*/
-import ca.uhn.fhir.context.*;
+import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
+import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
+import ca.uhn.fhir.context.ConfigurationException;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.RuntimeChildChoiceDefinition;
+import ca.uhn.fhir.context.RuntimeChildResourceDefinition;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.interceptor.api.HookParams;
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
import ca.uhn.fhir.interceptor.api.Pointcut;
-import ca.uhn.fhir.jpa.dao.*;
+import ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao;
+import ca.uhn.fhir.jpa.dao.DaoConfig;
+import ca.uhn.fhir.jpa.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.dao.IDao;
+import ca.uhn.fhir.jpa.dao.SearchBuilder;
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
import ca.uhn.fhir.jpa.model.cross.ResourcePersistentId;
-import ca.uhn.fhir.jpa.model.entity.*;
+import ca.uhn.fhir.jpa.model.entity.ResourceHistoryProvenanceEntity;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamDate;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamQuantity;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
+import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
+import ca.uhn.fhir.jpa.model.entity.ResourceLink;
+import ca.uhn.fhir.jpa.model.entity.ResourceTable;
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
@@ -47,22 +64,36 @@ import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.Lists;
-import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
+import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Scope;
import org.springframework.stereotype.Component;
-import javax.persistence.criteria.*;
-import java.util.*;
+import javax.persistence.criteria.From;
+import javax.persistence.criteria.Join;
+import javax.persistence.criteria.JoinType;
+import javax.persistence.criteria.Predicate;
+import javax.persistence.criteria.Root;
+import javax.persistence.criteria.Subquery;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.ListIterator;
+import java.util.Set;
import java.util.stream.Collectors;
-import static org.apache.commons.lang3.StringUtils.*;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.commons.lang3.StringUtils.trim;
@Component
@Scope("prototype")
@@ -273,20 +304,43 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
} else {
+
try {
RuntimeResourceDefinition resDef = myContext.getResourceDefinition(theReferenceParam.getResourceType());
resourceTypes = new ArrayList<>(1);
resourceTypes.add(resDef.getImplementingClass());
} catch (DataFormatException e) {
- throw new InvalidRequestException("Invalid resource type: " + theReferenceParam.getResourceType());
+ throw newInvalidResourceTypeException(theReferenceParam.getResourceType());
}
+
+ }
+
+ // Handle chain on _type
+ String chain = theReferenceParam.getChain();
+ if (Constants.PARAM_TYPE.equals(chain)) {
+ String typeValue = theReferenceParam.getValue();
+
+ Class extends IBaseResource> wantedType;
+ try {
+ wantedType = myContext.getResourceDefinition(typeValue).getImplementingClass();
+ } catch (DataFormatException e) {
+ throw newInvalidResourceTypeException(typeValue);
+ }
+ if (!resourceTypes.contains(wantedType)) {
+ String searchParamName = theResourceName + ":" + theParamName;
+ String msg = myContext.getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", typeValue, searchParamName);
+ throw new InvalidRequestException(msg);
+ }
+
+ Predicate targetTypeParameter = myCriteriaBuilder.equal(theJoin.get("myTargetResourceType"), typeValue);
+ myQueryRoot.addPredicate(targetTypeParameter);
+ return targetTypeParameter;
}
boolean foundChainMatch = false;
List> candidateTargetTypes = new ArrayList<>();
for (Class extends IBaseResource> nextType : resourceTypes) {
- String chain = theReferenceParam.getChain();
String remainingChain = null;
int chainDotIndex = chain.indexOf('.');
if (chainDotIndex != -1) {
@@ -355,6 +409,12 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return predicate;
}
+ @NotNull
+ private InvalidRequestException newInvalidResourceTypeException(String theResourceType) {
+ String msg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType);
+ throw new InvalidRequestException(msg);
+ }
+
private void warnAboutPerformanceOnUnqualifiedResources(String theParamName, RequestDetails theRequest, List> theCandidateTargetTypes) {
String message = new StringBuilder()
.append("This search uses an unqualified resource(a parameter in a chain without a resource type). ")
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java
index e465c6d03a6..9d7d26bfada 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchNoFtTest.java
@@ -316,6 +316,67 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
assertThat(ids, empty());
}
+ @Test
+ public void testChainOnType() {
+
+ Patient sub1 = new Patient();
+ sub1.setActive(true);
+ sub1.addIdentifier().setSystem("foo").setValue("bar");
+ String sub1Id = myPatientDao.create(sub1).getId().toUnqualifiedVersionless().getValue();
+
+ Group sub2 = new Group();
+ sub2.setActive(true);
+ sub2.addIdentifier().setSystem("foo").setValue("bar");
+ String sub2Id = myGroupDao.create(sub2).getId().toUnqualifiedVersionless().getValue();
+
+ Encounter enc1 = new Encounter();
+ enc1.getSubject().setReference(sub1Id);
+ String enc1Id = myEncounterDao.create(enc1).getId().toUnqualifiedVersionless().getValue();
+
+ Encounter enc2 = new Encounter();
+ enc2.getSubject().setReference(sub2Id);
+ String enc2Id = myEncounterDao.create(enc2).getId().toUnqualifiedVersionless().getValue();
+
+ List ids;
+ SearchParameterMap map;
+ IBundleProvider results;
+
+ map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Patient").setChain("_type"));
+ results = myEncounterDao.search(map);
+ ids = toUnqualifiedVersionlessIdValues(results);
+ assertThat(ids, hasItems(enc1Id));
+
+ map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Group").setChain("_type"));
+ results = myEncounterDao.search(map);
+ ids = toUnqualifiedVersionlessIdValues(results);
+ assertThat(ids, hasItems(enc2Id));
+
+ map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "Organization").setChain("_type"));
+ try {
+ myEncounterDao.search(map);
+ fail();
+ } catch (InvalidRequestException e) {
+ assertEquals("Resource type \"Organization\" is not a valid target type for reference search parameter: Encounter:subject", e.getMessage());
+ }
+
+ map = new SearchParameterMap();
+ map.setLoadSynchronous(true);
+ map.add(Encounter.SP_SUBJECT, new ReferenceParam("subject", "HelpImABug").setChain("_type"));
+ try {
+ myEncounterDao.search(map);
+ fail();
+ } catch (InvalidRequestException e) {
+ assertEquals("Invalid/unsupported resource type: \"HelpImABug\"", e.getMessage());
+ }
+
+ }
+
/**
* See #441
*/
From 7706600a8c4958134a3c94c206757d2f1714728d Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 23 Mar 2020 18:05:48 -0400
Subject: [PATCH 10/32] Add changelog
---
.../hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml | 5 +++++
1 file changed, 5 insertions(+)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml
new file mode 100644
index 00000000000..e62150f7c46
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 1772
+title: "The JPA server now allows chained searches on the `_type` parameter. For example, the following
+ could be used to find all Encounters with a context of type Group: `Encounter?context._type=Group`."
From a067a2001d3f45afd9db87a199a428a5804eacde Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 23 Mar 2020 18:53:16 -0400
Subject: [PATCH 11/32] Test fix
---
.../org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java
index a8357f361a8..df5b855c627 100644
--- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java
@@ -679,7 +679,7 @@ public class FhirInstanceValidatorR5Test {
ValidationResult output = myVal.validateWithResult(input);
assertEquals(output.toString(), 1, output.getMessages().size());
- assertEquals("This \"Patient2 cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage());
+ assertEquals("This \"Patient\" cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage());
ourLog.info(output.getMessages().get(0).getLocationString());
}
From 300c4e66bab68fff7789140c316aa2f5c341fbb4 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 23 Mar 2020 19:09:03 -0400
Subject: [PATCH 12/32] Fix broken test
---
.../org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java
index a8357f361a8..df5b855c627 100644
--- a/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java
+++ b/hapi-fhir-validation/src/test/java/org/hl7/fhir/r5/validation/FhirInstanceValidatorR5Test.java
@@ -679,7 +679,7 @@ public class FhirInstanceValidatorR5Test {
ValidationResult output = myVal.validateWithResult(input);
assertEquals(output.toString(), 1, output.getMessages().size());
- assertEquals("This \"Patient2 cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage());
+ assertEquals("This \"Patient\" cannot be parsed as a FHIR object (no namespace)", output.getMessages().get(0).getMessage());
ourLog.info(output.getMessages().get(0).getLocationString());
}
From 2b23a6f2f0b116525df1ca9142b3a8c5aa3995cc Mon Sep 17 00:00:00 2001
From: August Langhout
Date: Mon, 23 Mar 2020 19:21:22 +0100
Subject: [PATCH 13/32] Fix Xml parse issue: do not override the references
with global contained resources if they have already been resolved
---
.../java/ca/uhn/fhir/parser/ParserState.java | 9 +-
.../ca/uhn/fhir/parser/XmlParserR4Test.java | 22 ++
.../bundle-with-two-patient-resources.xml | 197 ++++++++++++++++++
3 files changed, 225 insertions(+), 3 deletions(-)
create mode 100644 hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
index b5eb3ec6137..7f66e0f40ab 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
@@ -1054,6 +1054,8 @@ class ParserState {
private void stitchBundleCrossReferences() {
final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName());
if (bundle) {
+ //if (myGlobalReferences.stream().map(x -> x.))
+ //throw new DataFormatException("resourceType does not appear to be 'TagList', found: " + theLocalPart);
FhirTerser t = myContext.newTerser();
@@ -1074,7 +1076,7 @@ class ParserState {
*/
for (IBaseResource next : myGlobalResources) {
IIdType id = next.getIdElement();
- if (id != null && id.isEmpty() == false) {
+ if (id != null && !id.isEmpty()) {
String resName = myContext.getResourceDefinition(next).getName();
IIdType idType = id.withResourceType(resName).toUnqualifiedVersionless();
idToResource.put(idType.getValueAsString(), next);
@@ -1082,10 +1084,11 @@ class ParserState {
}
for (IBaseReference nextRef : myGlobalReferences) {
- if (nextRef.isEmpty() == false && nextRef.getReferenceElement() != null) {
+ if (!nextRef.isEmpty() && nextRef.getReferenceElement() != null) {
IIdType unqualifiedVersionless = nextRef.getReferenceElement().toUnqualifiedVersionless();
IBaseResource target = idToResource.get(unqualifiedVersionless.getValueAsString());
- if (target != null) {
+ // resource can already be filled with local contained resource by populateTarget()
+ if (target != null && nextRef.getResource() == null) {
nextRef.setResource(target);
}
}
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java
index c16ac5c8950..7332ca7c730 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java
@@ -7,18 +7,25 @@ import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertThat;
import ca.uhn.fhir.test.BaseTest;
+
+import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Composition;
+import org.hl7.fhir.r4.model.DocumentReference;
import org.hl7.fhir.r4.model.MessageHeader;
import org.hl7.fhir.r4.model.Narrative;
import org.hl7.fhir.r4.model.Observation;
+import org.hl7.fhir.r4.model.Patient;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import ca.uhn.fhir.context.FhirContext;
+import com.google.common.base.Charsets;
+import com.google.common.io.Resources;
import java.io.IOException;
+import java.net.URL;
public class XmlParserR4Test extends BaseTest {
private static final Logger ourLog = LoggerFactory.getLogger(XmlParserR4Test.class);
@@ -79,6 +86,21 @@ public class XmlParserR4Test extends BaseTest {
}
+ @Test
+ public void testParseBundleWithMultipleNestedContainedResources() throws Exception {
+ URL url = Resources.getResource("bundle-with-two-patient-resources.xml");
+ String text = Resources.toString(url, Charsets.UTF_8);
+ Bundle input = ourCtx.newXmlParser().parseResource(Bundle.class, text);
+ assertEquals("12346", getPatientIdValue(input, 0));
+ assertEquals("12345", getPatientIdValue(input, 1));
+ }
+
+ private String getPatientIdValue(Bundle input, int entry) {
+ final DocumentReference documentReference = (DocumentReference)input.getEntry().get(entry).getResource();
+ final Patient patient = (Patient) documentReference.getSubject().getResource();
+ return patient.getIdentifier().get(0).getValue();
+ }
+
/**
* See #1658
*/
diff --git a/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml
new file mode 100644
index 00000000000..90bcf9d60ef
--- /dev/null
+++ b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml
@@ -0,0 +1,197 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
From a75bf948c112e22ec987a3c79ce446dc41ba41a4 Mon Sep 17 00:00:00 2001
From: August Langhout
Date: Mon, 23 Mar 2020 19:39:47 +0100
Subject: [PATCH 14/32] Remove personal data
---
.../test/resources/bundle-with-two-patient-resources.xml | 8 ++++----
1 file changed, 4 insertions(+), 4 deletions(-)
diff --git a/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml
index 90bcf9d60ef..b10ad761ced 100644
--- a/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml
+++ b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.xml
@@ -20,7 +20,7 @@
-
+
@@ -115,11 +115,11 @@
-
-
+
+
-
+
From a9b6864988a66cc55e0c455414d3cca96842b971 Mon Sep 17 00:00:00 2001
From: August Langhout
Date: Mon, 23 Mar 2020 19:43:00 +0100
Subject: [PATCH 15/32] remove first attempt to fix
---
.../src/main/java/ca/uhn/fhir/parser/ParserState.java | 2 --
1 file changed, 2 deletions(-)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
index 7f66e0f40ab..d3f6f7193f6 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/ParserState.java
@@ -1054,8 +1054,6 @@ class ParserState {
private void stitchBundleCrossReferences() {
final boolean bundle = "Bundle".equals(myContext.getResourceDefinition(myInstance).getName());
if (bundle) {
- //if (myGlobalReferences.stream().map(x -> x.))
- //throw new DataFormatException("resourceType does not appear to be 'TagList', found: " + theLocalPart);
FhirTerser t = myContext.newTerser();
From 2c1ec8fb0d6bfa8a346666ae41c04700cd481955 Mon Sep 17 00:00:00 2001
From: August Langhout
Date: Mon, 23 Mar 2020 22:15:46 +0100
Subject: [PATCH 16/32] add test to check that json parsing also fails with
identically named contained resources
---
.../ca/uhn/fhir/parser/JsonParserR4Test.java | 21 ++
.../ca/uhn/fhir/parser/XmlParserR4Test.java | 8 +-
.../bundle-with-two-patient-resources.json | 260 ++++++++++++++++++
3 files changed, 286 insertions(+), 3 deletions(-)
create mode 100644 hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.json
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
index e7783c95f7d..3b30dfb70b9 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
@@ -5,7 +5,10 @@ import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.test.BaseTest;
import ca.uhn.fhir.util.StopWatch;
import ca.uhn.fhir.util.TestUtil;
+import com.google.common.base.Charsets;
import com.google.common.collect.Sets;
+import com.google.common.io.Resources;
+
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.output.NullWriter;
import org.apache.commons.lang.StringUtils;
@@ -18,6 +21,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
+import java.net.URL;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
@@ -150,6 +154,23 @@ public class JsonParserR4Test extends BaseTest {
}
+ @Test
+ public void testParseBundleWithMultipleNestedContainedResources() throws Exception {
+ URL url = Resources.getResource("bundle-with-two-patient-resources.json");
+ String text = Resources.toString(url, Charsets.UTF_8);
+
+ Bundle bundle = ourCtx.newJsonParser().parseResource(Bundle.class, text);
+
+ assertEquals("12346", getPatientIdValue(bundle, 0));
+ assertEquals("12345", getPatientIdValue(bundle, 1));
+ }
+
+ private String getPatientIdValue(Bundle input, int entry) {
+ final DocumentReference documentReference = (DocumentReference)input.getEntry().get(entry).getResource();
+ final Patient patient = (Patient) documentReference.getSubject().getResource();
+ return patient.getIdentifier().get(0).getValue();
+ }
+
/**
* See #814
*/
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java
index 7332ca7c730..e109aa84490 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java
@@ -90,9 +90,11 @@ public class XmlParserR4Test extends BaseTest {
public void testParseBundleWithMultipleNestedContainedResources() throws Exception {
URL url = Resources.getResource("bundle-with-two-patient-resources.xml");
String text = Resources.toString(url, Charsets.UTF_8);
- Bundle input = ourCtx.newXmlParser().parseResource(Bundle.class, text);
- assertEquals("12346", getPatientIdValue(input, 0));
- assertEquals("12345", getPatientIdValue(input, 1));
+
+ Bundle bundle = ourCtx.newXmlParser().parseResource(Bundle.class, text);
+
+ assertEquals("12346", getPatientIdValue(bundle, 0));
+ assertEquals("12345", getPatientIdValue(bundle, 1));
}
private String getPatientIdValue(Bundle input, int entry) {
diff --git a/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.json b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.json
new file mode 100644
index 00000000000..e0a15fc763a
--- /dev/null
+++ b/hapi-fhir-structures-r4/src/test/resources/bundle-with-two-patient-resources.json
@@ -0,0 +1,260 @@
+{
+ "resourceType": "Bundle",
+ "meta": {
+ "profile": [
+ "http://forcare.com/fhir/createCda"
+ ]
+ },
+ "type": "transaction",
+ "entry": [
+ {
+ "resource": {
+ "resourceType": "DocumentReference",
+ "id": "doc1",
+ "contained": [
+ {
+ "resourceType": "Patient",
+ "id": "patient",
+ "identifier": [
+ {
+ "system": "urn:oid:1.3.6.1.4.1.21367.2005.3.7",
+ "value": "12346"
+ }
+ ],
+ "name": [
+ {
+ "use": "official",
+ "family": "Beugels",
+ "given": [
+ "Kees"
+ ]
+ }
+ ],
+ "gender": "male",
+ "birthDate": "1970-01-01"
+ }
+ ],
+ "status": "current",
+ "type": {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.6.1",
+ "code": "57016-8",
+ "display": "Privacy Policy Acknowledgment"
+ }
+ ]
+ },
+ "category": [
+ {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.6.1",
+ "code": "57016-8",
+ "display": "Privacy Policy Acknowledgment"
+ }
+ ]
+ }
+ ],
+ "subject": {
+ "reference": "#patient"
+ },
+ "date": "2016-05-04T08:18:03.203Z",
+ "author": [
+ {
+ "reference": "#patient"
+ }
+ ],
+ "description": "Hospital Privacy Consent",
+ "securityLabel": [
+ {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.5.25",
+ "code": "N",
+ "display": "Normal"
+ }
+ ]
+ }
+ ],
+ "content": [
+ {
+ "attachment": {
+ "contentType": "text/xml",
+ "language": "en-US",
+ "url": "urn:uuid:d7d8ffca-e364-484b-bbf9-6a730854aea5"
+ },
+ "format": {
+ "system": "1.3.6.1.4.1.19376.1.2.3",
+ "code": "urn:ihe:iti:bppc-sd:2007",
+ "display": "Basic Patient Privacy Consent (scanned part)"
+ }
+ }
+ ],
+ "context": {
+ "event": [
+ {
+ "coding": [
+ {
+ "system": "1.2.826.0.1.3680043.2.1611.2.10",
+ "code": "allowMedicalDoctorsFromHospitalAToSeeDocuments",
+ "display": "I allow Medical Doctors in Hospital A to access my medical record"
+ }
+ ]
+ },
+ {
+ "coding": [
+ {
+ "system": "1.2.826.0.1.3680043.2.1611.2.10",
+ "code": "denyMedicalDoctorsFromHospitalBToSeeDocuments",
+ "display": "I deny Medical Doctors in Hospital B to access my medical record"
+ }
+ ]
+ }
+ ],
+ "period": {
+ "start": "2016-05-04T08:18:03.203Z",
+ "end": "2016-09-04T08:18:03.203Z"
+ },
+ "facilityType": {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.5.10588",
+ "code": "HOSP",
+ "display": "Hospital"
+ }
+ ]
+ },
+ "practiceSetting": {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.2.1.6.8",
+ "code": "300",
+ "display": "General Medicine"
+ }
+ ]
+ }
+ }
+ }
+ },
+ {
+ "resource": {
+ "resourceType": "DocumentReference",
+ "id": "doc2",
+ "contained": [
+ {
+ "resourceType": "Patient",
+ "id": "patient",
+ "identifier": [
+ {
+ "system": "urn:oid:1.3.6.1.4.1.21367.2005.3.7",
+ "value": "12345"
+ }
+ ],
+ "name": [
+ {
+ "use": "official",
+ "family": "Baker",
+ "given": [
+ "Rob"
+ ]
+ }
+ ],
+ "gender": "male",
+ "birthDate": "1970-01-01"
+ }
+ ],
+ "status": "current",
+ "type": {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.6.1",
+ "code": "57016-8",
+ "display": "Privacy Policy Acknowledgment"
+ }
+ ]
+ },
+ "category": [
+ {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.6.1",
+ "code": "57016-8",
+ "display": "Privacy Policy Acknowledgment"
+ }
+ ]
+ }
+ ],
+ "subject": {
+ "reference": "#patient"
+ },
+ "date": "2016-05-04T08:18:03.203Z",
+ "author": [
+ {
+ "reference": "#patient"
+ }
+ ],
+ "description": "GPs Privacy Consent",
+ "securityLabel": [
+ {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.5.25",
+ "code": "N",
+ "display": "Normal"
+ }
+ ]
+ }
+ ],
+ "content": [
+ {
+ "attachment": {
+ "contentType": "text/xml",
+ "language": "en-US",
+ "url": "urn:uuid:d7d8ffca-e364-484b-bbf9-6a730854aea5"
+ },
+ "format": {
+ "system": "1.3.6.1.4.1.19376.1.2.3",
+ "code": "urn:ihe:iti:bppc-sd:2007",
+ "display": "Basic Patient Privacy Consent (scanned part)"
+ }
+ }
+ ],
+ "context": {
+ "event": [
+ {
+ "coding": [
+ {
+ "system": "1.2.826.0.1.3680043.2.1611.2.10",
+ "code": "denyGeneralPractitionersFromHestiaToSeeDocuments",
+ "display": "I deny Medical Doctors in Hestia General Practitioners to access my medical record"
+ }
+ ]
+ }
+ ],
+ "period": {
+ "start": "2016-05-04T08:18:03.203Z",
+ "end": "2016-09-04T08:18:03.203Z"
+ },
+ "facilityType": {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.5.10588",
+ "code": "HOSP",
+ "display": "Hospital"
+ }
+ ]
+ },
+ "practiceSetting": {
+ "coding": [
+ {
+ "system": "2.16.840.1.113883.2.1.6.8",
+ "code": "300",
+ "display": "General Medicine"
+ }
+ ]
+ }
+ }
+ }
+ }
+ ]
+}
From fa29f3e8ab76cd4a48b0f59f801f8cd8deb53886 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 23 Mar 2020 19:12:05 -0400
Subject: [PATCH 17/32] Credit for #1770
---
.../changelog/4_3_0/1770-fix-contained_resource_mixup.yaml | 6 ++++++
pom.xml | 4 ++++
2 files changed, 10 insertions(+)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1770-fix-contained_resource_mixup.yaml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1770-fix-contained_resource_mixup.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1770-fix-contained_resource_mixup.yaml
new file mode 100644
index 00000000000..6ad1da006db
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1770-fix-contained_resource_mixup.yaml
@@ -0,0 +1,6 @@
+---
+type: fix
+issue: 1770
+title: When parsing Bundles, contained resoures from other entries in the Bundle could incorrecly be
+ stitched into a target resource if they had the same local ID. Thanks to August Langhout for
+ the pull request!
diff --git a/pom.xml b/pom.xml
index 18b28372908..f3c0bdfb4e7 100644
--- a/pom.xml
+++ b/pom.xml
@@ -595,6 +595,10 @@
Trifork
Tue Toft Nørgård
+
+ augla
+ August Langhout
+
From 13f0e5384b0a3a91d9c4ab7a53a8010b7cb228b8 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 24 Mar 2020 06:02:55 -0400
Subject: [PATCH 18/32] Address review comments
---
azure-pipelines.yml | 2 +-
.../4_3_0/1772-allow-chain-on-type.yaml | 2 +-
.../predicate/PredicateBuilderReference.java | 26 ++++++++++++-------
3 files changed, 18 insertions(+), 12 deletions(-)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index fb818edbbe8..d3222478c07 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -29,7 +29,7 @@ jobs:
env:
JAVA_HOME_11_X64: /usr/local/openjdk-11
inputs:
- goals: 'clean install'
+ goals: 'dependency:resolve clean install'
# These are Maven CLI options (and show up in the build logs) - "-nsu"=Don't update snapshots. We can remove this when Maven OSS is more healthy
options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -nsu -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
# These are JVM options (and don't show up in the build logs)
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml
index e62150f7c46..61454172e40 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1772-allow-chain-on-type.yaml
@@ -2,4 +2,4 @@
type: add
issue: 1772
title: "The JPA server now allows chained searches on the `_type` parameter. For example, the following
- could be used to find all Encounters with a context of type Group: `Encounter?context._type=Group`."
+ could be used to find all Encounters with a context of type Group: `Encounter?subject._type=Group`."
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
index 12f377485cb..b891b12879f 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
@@ -64,7 +64,6 @@ import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
-import ca.uhn.fhir.util.UrlUtil;
import com.google.common.collect.Lists;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseResource;
@@ -327,9 +326,8 @@ class PredicateBuilderReference extends BasePredicateBuilder {
throw newInvalidResourceTypeException(typeValue);
}
if (!resourceTypes.contains(wantedType)) {
- String searchParamName = theResourceName + ":" + theParamName;
- String msg = myContext.getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", typeValue, searchParamName);
- throw new InvalidRequestException(msg);
+ InvalidRequestException invalidRequestException = newInvalidTargetTypeForChainException(theResourceName, theParamName, typeValue);
+ throw invalidRequestException;
}
Predicate targetTypeParameter = myCriteriaBuilder.equal(theJoin.get("myTargetResourceType"), typeValue);
@@ -409,12 +407,6 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return predicate;
}
- @NotNull
- private InvalidRequestException newInvalidResourceTypeException(String theResourceType) {
- String msg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType);
- throw new InvalidRequestException(msg);
- }
-
private void warnAboutPerformanceOnUnqualifiedResources(String theParamName, RequestDetails theRequest, List> theCandidateTargetTypes) {
String message = new StringBuilder()
.append("This search uses an unqualified resource(a parameter in a chain without a resource type). ")
@@ -996,4 +988,18 @@ class PredicateBuilderReference extends BasePredicateBuilder {
return retVal;
}
+
+ @NotNull
+ private InvalidRequestException newInvalidTargetTypeForChainException(String theResourceName, String theParamName, String theTypeValue) {
+ String searchParamName = theResourceName + ":" + theParamName;
+ String msg = myContext.getLocalizer().getMessage(PredicateBuilderReference.class, "invalidTargetTypeForChain", theTypeValue, searchParamName);
+ return new InvalidRequestException(msg);
+ }
+
+ @NotNull
+ private InvalidRequestException newInvalidResourceTypeException(String theResourceType) {
+ String msg = myContext.getLocalizer().getMessageSanitized(PredicateBuilderReference.class, "invalidResourceType", theResourceType);
+ throw new InvalidRequestException(msg);
+ }
+
}
From 31893be836648b56fd34756d611d2e2148c17276 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 24 Mar 2020 06:04:21 -0400
Subject: [PATCH 19/32] Fix azure yaml
---
azure-pipelines.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index d3222478c07..64356a39b75 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -29,7 +29,7 @@ jobs:
env:
JAVA_HOME_11_X64: /usr/local/openjdk-11
inputs:
- goals: 'dependency:resolve clean install'
+ goals: 'dependency:resolve clean install'
# These are Maven CLI options (and show up in the build logs) - "-nsu"=Don't update snapshots. We can remove this when Maven OSS is more healthy
options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -nsu -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
# These are JVM options (and don't show up in the build logs)
From 22e0c7a596dd65dd2ddceff6bd8a7032950c0b31 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 24 Mar 2020 06:09:03 -0400
Subject: [PATCH 20/32] Correct HTTPs in pom
---
pom.xml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/pom.xml b/pom.xml
index 18b28372908..744e85c8a9d 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1524,7 +1524,7 @@
maven2
Maven2
- http://central.maven.org/maven2/
+ https://central.maven.org/maven2/
true
From bb4cbb6af5145eea449c7b05523c29148935fd69 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 24 Mar 2020 06:10:29 -0400
Subject: [PATCH 21/32] One more pom tweak
---
pom.xml | 2 ++
1 file changed, 2 insertions(+)
diff --git a/pom.xml b/pom.xml
index 744e85c8a9d..2ced92eb308 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1512,6 +1512,7 @@
+
From 718c248c694cff446ce37f2ee5280ad151a1f81f Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 24 Mar 2020 07:55:25 -0400
Subject: [PATCH 22/32] Test fix
---
.../fhir/jpa/dao/predicate/PredicateBuilderReference.java | 7 +++----
.../fhir/jpa/provider/dstu3/ResourceProviderDstu3Test.java | 2 +-
.../uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java | 4 ++--
3 files changed, 6 insertions(+), 7 deletions(-)
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
index b891b12879f..1a71242f249 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/predicate/PredicateBuilderReference.java
@@ -315,8 +315,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
}
// Handle chain on _type
- String chain = theReferenceParam.getChain();
- if (Constants.PARAM_TYPE.equals(chain)) {
+ if (Constants.PARAM_TYPE.equals(theReferenceParam.getChain())) {
String typeValue = theReferenceParam.getValue();
Class extends IBaseResource> wantedType;
@@ -326,8 +325,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
throw newInvalidResourceTypeException(typeValue);
}
if (!resourceTypes.contains(wantedType)) {
- InvalidRequestException invalidRequestException = newInvalidTargetTypeForChainException(theResourceName, theParamName, typeValue);
- throw invalidRequestException;
+ throw newInvalidTargetTypeForChainException(theResourceName, theParamName, typeValue);
}
Predicate targetTypeParameter = myCriteriaBuilder.equal(theJoin.get("myTargetResourceType"), typeValue);
@@ -338,6 +336,7 @@ class PredicateBuilderReference extends BasePredicateBuilder {
boolean foundChainMatch = false;
List> candidateTargetTypes = new ArrayList<>();
for (Class extends IBaseResource> nextType : resourceTypes) {
+ String chain = theReferenceParam.getChain();
String remainingChain = null;
int chainDotIndex = chain.indexOf('.');
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 f03f54eefee..acaeccee165 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
@@ -346,7 +346,7 @@ public class ResourceProviderDstu3Test extends BaseResourceProviderDstu3Test {
.returnBundle(Bundle.class)
.execute();
} catch (InvalidRequestException e) {
- assertEquals("HTTP 400 Bad Request: Invalid resource type: FOO", e.getMessage());
+ assertEquals("HTTP 400 Bad Request: Invalid/unsupported resource type: \"FOO\"", e.getMessage());
}
}
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
index 56c98b94cfd..9fe504d4752 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java
@@ -405,15 +405,15 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
assertThat(idValues, contains(pid));
// Search param on extension
- myCaptureQueriesListener.clear();
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg=" + orgId.getValue());
- myCaptureQueriesListener.logSelectQueries();
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.name=ORGANIZATION");
assertThat(idValues, contains(pid));
+ myCaptureQueriesListener.clear();
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.name=PARENT");
+ myCaptureQueriesListener.logSelectQueries();
assertThat(idValues, contains(pid));
idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.extorgorg.name=GRANDPARENT");
From 274a6cb7a10a8c4dde65ad47b29ba0b799c46024 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 24 Mar 2020 08:52:32 -0400
Subject: [PATCH 23/32] Try azure again
---
azure-pipelines.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 64356a39b75..5b99ec7dace 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -29,7 +29,7 @@ jobs:
env:
JAVA_HOME_11_X64: /usr/local/openjdk-11
inputs:
- goals: 'dependency:resolve clean install'
+ goals: 'dependency:go-offline clean install'
# These are Maven CLI options (and show up in the build logs) - "-nsu"=Don't update snapshots. We can remove this when Maven OSS is more healthy
options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -nsu -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
# These are JVM options (and don't show up in the build logs)
From 1271d5cf4df63476a2e537e56ae55705b5a6b2a5 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 24 Mar 2020 09:44:33 -0400
Subject: [PATCH 24/32] One more azure tweak
---
azure-pipelines.yml | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 5b99ec7dace..fb818edbbe8 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -29,7 +29,7 @@ jobs:
env:
JAVA_HOME_11_X64: /usr/local/openjdk-11
inputs:
- goals: 'dependency:go-offline clean install'
+ goals: 'clean install'
# These are Maven CLI options (and show up in the build logs) - "-nsu"=Don't update snapshots. We can remove this when Maven OSS is more healthy
options: '-P ALLMODULES,JACOCO,CI,ERRORPRONE -nsu -e -B -Dmaven.repo.local=$(MAVEN_CACHE_FOLDER)'
# These are JVM options (and don't show up in the build logs)
From eaaddaf7ca644dfc35f3479c6559f7caf5ffc218 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Tue, 24 Mar 2020 16:24:41 -0400
Subject: [PATCH 25/32] Fix #1763 - Include bundle type in summary=count
responses (#1773)
* Fix #1763 - Include bundle type in summary=count responses
* Test fix
---
.../1763-include_bundletype-in-count.yaml | 5 ++
.../fhir/rest/server/RestfulServerUtils.java | 3 +-
.../ca/uhn/fhir/rest/server/SearchR4Test.java | 79 ++++++++++++-------
3 files changed, 57 insertions(+), 30 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1763-include_bundletype-in-count.yaml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1763-include_bundletype-in-count.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1763-include_bundletype-in-count.yaml
new file mode 100644
index 00000000000..cc246038dcf
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1763-include_bundletype-in-count.yaml
@@ -0,0 +1,5 @@
+---
+type: fix
+issue: 1763
+title: In servers, when requesting _summary=count, the response Bundle.type value was filtered, leading
+ to an invalid response bundle. This has been corrected. Thanks to GitHub user @Legi429 for reporting!
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
index 0ef51233a04..38f1e624994 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
@@ -45,6 +45,7 @@ import ca.uhn.fhir.rest.server.method.SummaryEnumParameter;
import ca.uhn.fhir.util.BinaryUtil;
import ca.uhn.fhir.util.DateUtils;
import ca.uhn.fhir.util.UrlUtil;
+import com.google.common.collect.Sets;
import org.hl7.fhir.instance.model.api.IAnyResource;
import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseReference;
@@ -163,7 +164,7 @@ public class RestfulServerUtils {
}
if (summaryModeCount) {
- parser.setEncodeElements(Collections.singleton("Bundle.total"));
+ parser.setEncodeElements(Sets.newHashSet("Bundle.total", "Bundle.type"));
} else if (summaryMode.contains(SummaryEnum.TEXT) && summaryMode.size() == 1) {
parser.setEncodeElements(TEXT_ENCODE_ELEMENTS);
parser.setEncodeElementsAppliesToChildResourcesOnly(true);
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java
index 6fd38e3a591..1d77a7d16d6 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/SearchR4Test.java
@@ -10,6 +10,7 @@ import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.api.SearchStyleEnum;
+import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.gclient.StringClientParam;
@@ -21,7 +22,6 @@ import ca.uhn.fhir.validation.FhirValidator;
import ca.uhn.fhir.validation.ValidationResult;
import com.google.common.collect.Lists;
import org.apache.commons.io.IOUtils;
-import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
@@ -66,7 +66,16 @@ public class SearchR4Test {
ourIdentifiers = null;
}
- private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException, ClientProtocolException {
+ private Bundle executeSearchAndValidateHasLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException {
+ Bundle bundle = executeSearch(httpGet, theExpectEncoding);
+ String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
+ assertNotNull(linkNext);
+
+ assertEquals(10, bundle.getEntry().size());
+ return bundle;
+ }
+
+ private Bundle executeSearch(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException {
Bundle bundle;
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
@@ -76,9 +85,6 @@ public class SearchR4Test {
assertEquals(theExpectEncoding, ct);
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
validate(bundle);
- assertEquals(10, bundle.getEntry().size());
- String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
- assertNotNull(linkNext);
}
return bundle;
}
@@ -98,6 +104,21 @@ public class SearchR4Test {
}
+ /**
+ * See #1763
+ */
+ @Test
+ public void testSummaryCount() throws Exception {
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&"+Constants.PARAM_SUMMARY + "=" + SummaryEnum.COUNT.getCode());
+ Bundle bundle = executeSearch(httpGet, EncodingEnum.JSON);
+ ourLog.info(toJson(bundle));
+ assertEquals(200, bundle.getTotal());
+ assertEquals("searchset", bundle.getType().toCode());
+ assertEquals(0, bundle.getEntry().size());
+ }
+
+
+
@Test
public void testPagingPreservesElements() throws Exception {
HttpGet httpGet;
@@ -107,7 +128,7 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_elements=name&_elements:exclude=birthDate,active");
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkSelf = bundle.getLink(Constants.LINK_SELF).getUrl();
assertThat(linkSelf, containsString("_elements=name"));
@@ -118,7 +139,7 @@ public class SearchR4Test {
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name"));
@@ -126,7 +147,7 @@ public class SearchR4Test {
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name"));
@@ -134,7 +155,7 @@ public class SearchR4Test {
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), not(containsString("\"active\"")));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_elements=name"));
@@ -203,25 +224,25 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
@@ -235,26 +256,26 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json");
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
assertThat(toJson(bundle), containsString("active"));
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=json"));
@@ -268,25 +289,25 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.JSON);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
@@ -301,28 +322,28 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
// Fetch the next page
httpGet = new HttpGet(linkNext);
httpGet.addHeader(Constants.HEADER_ACCEPT, "text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8");
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, not(containsString("_format")));
@@ -336,25 +357,25 @@ public class SearchR4Test {
// Initial search
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=xml");
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
// Fetch the next page
httpGet = new HttpGet(linkNext);
- bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.XML);
+ bundle = executeSearchAndValidateHasLinkNext(httpGet, EncodingEnum.XML);
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
assertThat(linkNext, containsString("_format=xml"));
From 4b5165b5e2ac8a888553289f35c23a190f4fbce3 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 24 Mar 2020 20:10:03 -0400
Subject: [PATCH 26/32] Docs fix
---
.../resources/ca/uhn/hapi/fhir/docs/introduction/versions.md | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/versions.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/versions.md
index 27e2f13f6a8..1f063820f7b 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/versions.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/introduction/versions.md
@@ -26,7 +26,7 @@ Note also that after the release of the FHIR DSTU2 specification, the FHIR
- HAPI FHIR 4.2.0-SNAPSHOT |
+ HAPI FHIR 4.2.0 |
JDK8 |
|
1.0.2 |
From b794618e9d244db23eb862f8d20f56bd989a10de Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Wed, 25 Mar 2020 21:20:10 -0400
Subject: [PATCH 27/32] Avoid a crash encoding contained bundle (#1778)
* Avoid a crash encoding contained bundle
* Add changelog
* Apply same fix to XML
* Fix typo
---
.../java/ca/uhn/fhir/parser/JsonParser.java | 11 ++----
.../java/ca/uhn/fhir/parser/XmlParser.java | 5 ++-
...avoid-crash-encoding-contained-bundle.yaml | 5 +++
.../ca/uhn/fhir/parser/JsonParserR4Test.java | 34 ++++++++++++++++
.../ca/uhn/fhir/parser/XmlParserR4Test.java | 39 +++++++++++++++++++
5 files changed, 84 insertions(+), 10 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1778-avoid-crash-encoding-contained-bundle.yaml
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
index 10b8609356a..4dea1260aca 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/JsonParser.java
@@ -317,19 +317,14 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
}
case CONTAINED_RESOURCE_LIST:
case CONTAINED_RESOURCES: {
- /*
- * Disabled per #103 ContainedDt value = (ContainedDt) theNextValue; for (IResource next :
- * value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; }
- * encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true,
- * fixContainedResourceId(next.getId().getValue())); }
- */
List containedResources = getContainedResources().getContainedResources();
if (containedResources.size() > 0) {
beginArray(theEventWriter, theChildName);
for (IBaseResource next : containedResources) {
IIdType resourceId = getContainedResources().getResourceId(next);
- encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext);
+ String value = resourceId.getValue();
+ encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, fixContainedResourceId(value), theEncodeContext);
}
theEventWriter.endArray();
@@ -359,7 +354,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
RuntimeResourceDefinition def = myContext.getResourceDefinition(resource);
theEncodeContext.pushPath(def.getName(), true);
- encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, false, theEncodeContext);
+ encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, theContainedResource, theEncodeContext);
theEncodeContext.popPath();
break;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java
index 2ed7221cc16..a09ad1208be 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/parser/XmlParser.java
@@ -287,7 +287,8 @@ public class XmlParser extends BaseParser {
for (IBaseResource next : getContainedResources().getContainedResources()) {
IIdType resourceId = getContainedResources().getResourceId(next);
theEventWriter.writeStartElement("contained");
- encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(resourceId.getValue()), theEncodeContext);
+ String value = resourceId.getValue();
+ encodeResourceToXmlStreamWriter(next, theEventWriter, true, fixContainedResourceId(value), theEncodeContext);
theEventWriter.writeEndElement();
}
break;
@@ -300,7 +301,7 @@ public class XmlParser extends BaseParser {
}
theEventWriter.writeStartElement(theChildName);
theEncodeContext.pushPath(resourceName, true);
- encodeResourceToXmlStreamWriter(resource, theEventWriter, false, theEncodeContext);
+ encodeResourceToXmlStreamWriter(resource, theEventWriter, theIncludedResource, theEncodeContext);
theEncodeContext.popPath();
theEventWriter.writeEndElement();
break;
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1778-avoid-crash-encoding-contained-bundle.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1778-avoid-crash-encoding-contained-bundle.yaml
new file mode 100644
index 00000000000..d7c698890e9
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1778-avoid-crash-encoding-contained-bundle.yaml
@@ -0,0 +1,5 @@
+---
+type: fix
+issue: 1778
+title: "When encoding a resource, a crash could occur if the resource had a contained Bundle resource. This
+ is not commonly done, but there are valid scenarios for doing so."
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
index 3b30dfb70b9..350746625aa 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/JsonParserR4Test.java
@@ -789,6 +789,40 @@ public class JsonParserR4Test extends BaseTest {
return b;
}
+ /**
+ * Ensure that a contained bundle doesn't cause a crash
+ */
+ @Test
+ public void testEncodeContainedBundle() {
+ String auditEvent = "{\n" +
+ " \"resourceType\": \"AuditEvent\",\n" +
+ " \"contained\": [ {\n" +
+ " \"resourceType\": \"Bundle\",\n" +
+ " \"id\": \"REASONS\",\n" +
+ " \"entry\": [ {\n" +
+ " \"resource\": {\n" +
+ " \"resourceType\": \"Condition\",\n" +
+ " \"id\": \"123\"\n" +
+ " }\n" +
+ " } ]\n" +
+ " }, {\n" +
+ " \"resourceType\": \"MeasureReport\",\n" +
+ " \"id\": \"MRPT5000602611RD\",\n" +
+ " \"evaluatedResource\": [ {\n" +
+ " \"reference\": \"#REASONS\"\n" +
+ " } ]\n" +
+ " } ],\n" +
+ " \"entity\": [ {\n" +
+ " \"what\": {\n" +
+ " \"reference\": \"#MRPT5000602611RD\"\n" +
+ " }\n" +
+ " } ]\n" +
+ "}";
+ AuditEvent ae = ourCtx.newJsonParser().parseResource(AuditEvent.class, auditEvent);
+ String auditEventAsString = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(ae);
+ assertEquals(auditEvent, auditEventAsString);
+ }
+
@AfterClass
public static void afterClassClearContext() {
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java
index e109aa84490..536dce99b7e 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/parser/XmlParserR4Test.java
@@ -9,6 +9,7 @@ import static org.junit.Assert.assertThat;
import ca.uhn.fhir.test.BaseTest;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.AuditEvent;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.Composition;
import org.hl7.fhir.r4.model.DocumentReference;
@@ -115,5 +116,43 @@ public class XmlParserR4Test extends BaseTest {
ourLog.info(encoded);
}
+ /**
+ * Ensure that a contained bundle doesn't cause a crash
+ */
+ @Test
+ public void testEncodeContainedBundle() {
+ String auditEvent = "\n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ " \n" +
+ "";
+ AuditEvent ae = ourCtx.newXmlParser().parseResource(AuditEvent.class, auditEvent);
+ String auditEventAsString = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(ae);
+ assertEquals(auditEvent, auditEventAsString);
+ }
+
+
}
From 97ac551c87859b4f95fb11ba205214c202876c28 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Thu, 26 Mar 2020 19:53:17 -0400
Subject: [PATCH 28/32] Dont fail on shrink only task
---
.../fhir/jpa/migrate/taskdef/BaseTask.java | 5 ++++
.../jpa/migrate/taskdef/ModifyColumnTask.java | 16 ++++++++++--
.../jpa/migrate/taskdef/ModifyColumnTest.java | 25 +++++++++++++++++++
3 files changed, 44 insertions(+), 2 deletions(-)
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java
index 6e66298a827..2b8daf16972 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/BaseTask.java
@@ -173,6 +173,11 @@ public abstract class BaseTask {
myFailureAllowed = theFailureAllowed;
}
+ protected boolean isFailureAllowed() {
+ return myFailureAllowed;
+ }
+
+
public String getFlywayVersion() {
String releasePart = myProductVersion;
if (releasePart.startsWith("V")) {
diff --git a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java
index 2b6033a0be9..e21d008bccf 100644
--- a/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java
+++ b/hapi-fhir-jpaserver-migrate/src/main/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTask.java
@@ -62,10 +62,17 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask {
}
Long taskColumnLength = getColumnLength();
- if (taskColumnLength != null && isNoColumnShrink()) {
+ boolean isShrinkOnly = false;
+ if (taskColumnLength != null) {
long existingLength = existingType.getLength() != null ? existingType.getLength() : 0;
if (existingLength > taskColumnLength) {
- taskColumnLength = existingLength;
+ if (isNoColumnShrink()) {
+ taskColumnLength = existingLength;
+ } else {
+ if (existingType.getColumnTypeEnum() == getColumnType()) {
+ isShrinkOnly = true;
+ }
+ }
}
}
@@ -129,6 +136,10 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask {
throw new IllegalStateException("Dont know how to handle " + getDriverType());
}
+ if (!isFailureAllowed() && isShrinkOnly) {
+ setFailureAllowed(true);
+ }
+
logInfo(ourLog, "Updating column {} on table {} to type {}", getColumnName(), getTableName(), type);
if (sql != null) {
executeSql(getTableName(), sql);
@@ -139,4 +150,5 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask {
executeSql(getTableName(), sqlNotNull);
}
}
+
}
diff --git a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java
index d34d4bb942e..a7763ee927d 100644
--- a/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java
+++ b/hapi-fhir-jpaserver-migrate/src/test/java/ca/uhn/fhir/jpa/migrate/taskdef/ModifyColumnTest.java
@@ -269,4 +269,29 @@ public class ModifyColumnTest extends BaseTest {
assertTrue(existingColumnType.equals(task.getColumnType(), task.getColumnLength()));
}
+
+ @Test
+ public void testShrinkDoesntFailIfShrinkCannotProceed() throws SQLException {
+ executeSql("create table SOMETABLE (PID bigint not null, TEXTCOL varchar(10))");
+ executeSql("insert into SOMETABLE (PID, TEXTCOL) values (1, '0123456789')");
+
+ ModifyColumnTask task = new ModifyColumnTask("1", "123456.7");
+ task.setTableName("SOMETABLE");
+ task.setColumnName("TEXTCOL");
+ task.setColumnType(AddColumnTask.ColumnTypeEnum.STRING);
+ task.setNullable(true);
+ task.setColumnLength(5);
+
+ getMigrator().addTask(task);
+ getMigrator().migrate();
+
+ assertEquals(1, task.getExecutedStatements().size());
+ assertEquals(new JdbcUtils.ColumnType(BaseTableColumnTypeTask.ColumnTypeEnum.STRING, 10), JdbcUtils.getColumnType(getConnectionProperties(), "SOMETABLE", "TEXTCOL"));
+
+ // Make sure additional migrations don't crash
+ getMigrator().migrate();
+ getMigrator().migrate();
+
+ }
+
}
From fe48313100e5ee36aa5ecee2e81e8b414b460586 Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Mon, 30 Mar 2020 11:24:10 -0400
Subject: [PATCH 29/32] Add response size capturing interceptor (#1776)
* Work on capturing repsonse sizes
* Interceptor complete
* Add changelog
* Test fix
---
.../ca/uhn/fhir/interceptor/api/Pointcut.java | 57 ++++++--
...6-response-size-capturing-interceptor.yaml | 6 +
.../built_in_server_interceptors.md | 7 +
.../fhir/rest/api/server/RequestDetails.java | 9 ++
.../fhir/rest/server/RestfulServerUtils.java | 18 ++-
.../server/interceptor/InterceptorOrders.java | 6 +-
.../ResponseSizeCapturingInterceptor.java | 123 ++++++++++++++++++
.../ResponseSizeCapturingInterceptorTest.java | 70 ++++++++++
8 files changed, 285 insertions(+), 11 deletions(-)
create mode 100644 hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1776-response-size-capturing-interceptor.yaml
create mode 100644 hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java
create mode 100644 hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptorTest.java
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
index 634ccade76e..dbcd4fea92f 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/interceptor/api/Pointcut.java
@@ -27,7 +27,12 @@ import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import javax.annotation.Nonnull;
-import java.util.*;
+import java.io.Writer;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
/**
* Value for {@link Hook#value()}
@@ -374,6 +379,44 @@ public enum Pointcut {
),
+ /**
+ * Server Hook:
+ * This method is called when a stream writer is generated that will be used to stream a non-binary response to
+ * a client. Hooks may return a wrapped writer which adds additional functionality as needed.
+ *
+ *
+ * Hooks may accept the following parameters:
+ *
+ * -
+ * java.io.Writer - The response writing Writer. Typically a hook will wrap this writer and layer additional functionality
+ * into the wrapping writer.
+ *
+ * -
+ * ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
+ * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
+ * pulled out of the servlet request.
+ *
+ * -
+ * ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
+ * resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
+ * pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
+ * only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
+ *
+ *
+ *
+ *
+ * Hook methods should return a {@link Writer} instance that will be used to stream the response. Hook methods
+ * should not throw any exception.
+ *
+ *
+ * @since 5.0.0
+ */
+ SERVER_OUTGOING_WRITER_CREATED(Writer.class,
+ "java.io.Writer",
+ "ca.uhn.fhir.rest.api.server.RequestDetails",
+ "ca.uhn.fhir.rest.server.servlet.ServletRequestDetails"
+ ),
+
/**
* Server Hook:
@@ -1643,12 +1686,12 @@ public enum Pointcut {
*
*
* THIS IS AN EXPERIMENTAL HOOK AND MAY BE REMOVED OR CHANGED WITHOUT WARNING.
- *
- *
+ *
+ *
* Note that this is a performance tracing hook. Use with caution in production
* systems, since calling it may (or may not) carry a cost.
- *
- *
+ *
+ *
* Hooks may accept the following parameters:
*
*
@@ -1722,9 +1765,7 @@ public enum Pointcut {
* This pointcut is used only for unit tests. Do not use in production code as it may be changed or
* removed at any time.
*/
- TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName())
-
- ;
+ TEST_RO(BaseServerResponseException.class, String.class.getName(), String.class.getName());
private final List myParameterTypes;
private final Class> myReturnType;
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1776-response-size-capturing-interceptor.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1776-response-size-capturing-interceptor.yaml
new file mode 100644
index 00000000000..c67ab01baa0
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/4_3_0/1776-response-size-capturing-interceptor.yaml
@@ -0,0 +1,6 @@
+---
+type: add
+issue: 1776
+title: A new server interceptor called `ResponseSizeCapturingInterceptor` has been added. This interceptor captures and makes
+ available the number of characters written (pre-compression if Gzip compression is being used) to the HTTP response
+ stream for FHIR responses.
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
index d9261468d13..67deec6bf9c 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/interceptors/built_in_server_interceptors.md
@@ -154,3 +154,10 @@ If you wish to override the value of `Resource.meta.source` using the value supp
* [CaptureResourceSourceFromHeaderInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.html)
* [CaptureResourceSourceFromHeaderInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/CaptureResourceSourceFromHeaderInterceptor.java)
+# Utility: ResponseSizeCapturingInterceptor
+
+The ResponseSizeCapturingInterceptor can be used to capture the number of characters written in each HTTP FHIR response.
+
+* [ResponseSizeCapturingInterceptor JavaDoc](/apidocs/hapi-fhir-server/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.html)
+* [ResponseSizeCapturingInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java)
+
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java
index 10c982a0439..85f5cca6989 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/api/server/RequestDetails.java
@@ -462,6 +462,15 @@ public abstract class RequestDetails {
if (myRequestContents == null) {
myRequestContents = getByteStreamRequestContents();
}
+ return getRequestContentsIfLoaded();
+ }
+
+ /**
+ * Returns the request contents if they were loaded, returns null
otherwise
+ *
+ * @see #loadRequestContents()
+ */
+ public byte[] getRequestContentsIfLoaded() {
return myRequestContents;
}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
index 38f1e624994..0c1c4df0ef6 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
@@ -22,6 +22,8 @@ package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
+import ca.uhn.fhir.interceptor.api.HookParams;
+import ca.uhn.fhir.interceptor.api.Pointcut;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
@@ -42,6 +44,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.method.ElementsParameter;
import ca.uhn.fhir.rest.server.method.SummaryEnumParameter;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import ca.uhn.fhir.util.BinaryUtil;
import ca.uhn.fhir.util.DateUtils;
import ca.uhn.fhir.util.UrlUtil;
@@ -74,7 +77,7 @@ public class RestfulServerUtils {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class);
private static final HashSet TEXT_ENCODE_ELEMENTS = new HashSet<>(Arrays.asList("*.text", "*.id", "*.meta", "*.(mandatory)"));
- private static Map myFhirContextMap = Collections.synchronizedMap(new HashMap());
+ private static Map myFhirContextMap = Collections.synchronizedMap(new HashMap<>());
private enum NarrativeModeEnum {
NORMAL, ONLY, SUPPRESS;
@@ -893,6 +896,19 @@ public class RestfulServerUtils {
String charset = Constants.CHARSET_NAME_UTF8;
Writer writer = response.getResponseWriter(theStatusCode, theStatusMessage, contentType, charset, respondGzip);
+
+ // Interceptor call: SERVER_OUTGOING_WRITER_CREATED
+ if (theServer.getInterceptorService() != null && theServer.getInterceptorService().hasHooks(Pointcut.SERVER_OUTGOING_WRITER_CREATED)) {
+ HookParams params = new HookParams()
+ .add(Writer.class, writer)
+ .add(RequestDetails.class, theRequestDetails)
+ .addIfMatchesType(ServletRequestDetails.class, theRequestDetails);
+ Object newWriter = theServer.getInterceptorService().callHooksAndReturnObject(Pointcut.SERVER_OUTGOING_WRITER_CREATED, params);
+ if (newWriter != null) {
+ writer = (Writer) newWriter;
+ }
+ }
+
if (theResource == null) {
// No response is being returned
} else if (encodingDomainResourceAsText && theResource instanceof IResource) {
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorOrders.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorOrders.java
index a3995b7d5b6..3c413192152 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorOrders.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorOrders.java
@@ -23,10 +23,12 @@ package ca.uhn.fhir.rest.server.interceptor;
public class InterceptorOrders {
public static final int SERVE_MEDIA_RESOURCE_RAW_INTERCEPTOR = 1000;
-
public static final int RESPONSE_HIGHLIGHTER_INTERCEPTOR = 10000;
+ public static final int RESPONSE_SIZE_CAPTURING_INTERCEPTOR_COMPLETED = -1;
- /** Non instantiable */
+ /**
+ * Non instantiable
+ */
private InterceptorOrders() {
// nothing
}
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java
new file mode 100644
index 00000000000..34a906c07dd
--- /dev/null
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptor.java
@@ -0,0 +1,123 @@
+package ca.uhn.fhir.rest.server.interceptor;
+
+import ca.uhn.fhir.interceptor.api.Hook;
+import ca.uhn.fhir.interceptor.api.Pointcut;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import org.apache.commons.lang3.Validate;
+
+import javax.annotation.Nonnull;
+import java.io.IOException;
+import java.io.Writer;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+/**
+ * This interceptor captures and makes
+ * available the number of characters written (pre-compression if Gzip compression is being used) to the HTTP response
+ * stream for FHIR responses.
+ *
+ * Response details are made available in the request {@link RequestDetails#getUserData() RequestDetails UserData map}
+ * with {@link #RESPONSE_RESULT_KEY} as the key.
+ *
+ *
+ * @since 5.0.0
+ */
+public class ResponseSizeCapturingInterceptor {
+
+ /**
+ * If the response was a character stream, a character count will be placed in the
+ * {@link RequestDetails#getUserData() RequestDetails UserData map} with this key, containing
+ * an {@link Result} value.
+ *
+ * The value will be placed at the start of the {@link Pointcut#SERVER_PROCESSING_COMPLETED} pointcut, so it will not
+ * be available before that time.
+ *
+ */
+ public static final String RESPONSE_RESULT_KEY = ResponseSizeCapturingInterceptor.class.getName() + "_RESPONSE_RESULT_KEY";
+
+ private static final String COUNTING_WRITER_KEY = ResponseSizeCapturingInterceptor.class.getName() + "_COUNTING_WRITER_KEY";
+ private List> myConsumers = new ArrayList<>();
+
+ @Hook(Pointcut.SERVER_OUTGOING_WRITER_CREATED)
+ public Writer capture(RequestDetails theRequestDetails, Writer theWriter) {
+ CountingWriter retVal = new CountingWriter(theWriter);
+ theRequestDetails.getUserData().put(COUNTING_WRITER_KEY, retVal);
+ return retVal;
+ }
+
+
+ @Hook(value = Pointcut.SERVER_PROCESSING_COMPLETED, order = InterceptorOrders.RESPONSE_SIZE_CAPTURING_INTERCEPTOR_COMPLETED)
+ public void completed(RequestDetails theRequestDetails) {
+ CountingWriter countingWriter = (CountingWriter) theRequestDetails.getUserData().get(COUNTING_WRITER_KEY);
+ if (countingWriter != null) {
+ int charCount = countingWriter.getCount();
+ Result result = new Result(charCount);
+ notifyConsumers(result);
+
+ theRequestDetails.getUserData().put(RESPONSE_RESULT_KEY, result);
+ }
+ }
+
+ /**
+ * Registers a new consumer. All consumers will be notified each time a request is complete.
+ *
+ * @param theConsumer The consumer
+ */
+ public void registerConsumer(@Nonnull Consumer theConsumer) {
+ Validate.notNull(theConsumer);
+ myConsumers.add(theConsumer);
+ }
+
+ private void notifyConsumers(Result theResult) {
+ myConsumers.forEach(t -> t.accept(theResult));
+ }
+
+ /**
+ * Contains the results of the capture
+ */
+ public static class Result {
+ private final int myWrittenChars;
+
+ public Result(int theWrittenChars) {
+ myWrittenChars = theWrittenChars;
+ }
+
+ public int getWrittenChars() {
+ return myWrittenChars;
+ }
+
+ }
+
+
+ private static class CountingWriter extends Writer {
+
+ private final Writer myWrap;
+ private int myCount;
+
+ private CountingWriter(Writer theWrap) {
+ myWrap = theWrap;
+ }
+
+ @Override
+ public void write(char[] cbuf, int off, int len) throws IOException {
+ myCount += len;
+ myWrap.write(cbuf, off, len);
+ }
+
+ @Override
+ public void flush() throws IOException {
+ myWrap.flush();
+ }
+
+ @Override
+ public void close() throws IOException {
+ myWrap.close();
+ }
+
+ public int getCount() {
+ return myCount;
+ }
+ }
+
+}
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptorTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptorTest.java
new file mode 100644
index 00000000000..954e7f4b6ce
--- /dev/null
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseSizeCapturingInterceptorTest.java
@@ -0,0 +1,70 @@
+package ca.uhn.fhir.rest.server.interceptor;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.test.utilities.server.HashMapResourceProviderRule;
+import ca.uhn.fhir.test.utilities.server.RestfulServerRule;
+import org.hl7.fhir.instance.model.api.IIdType;
+import org.hl7.fhir.r4.model.Bundle;
+import org.hl7.fhir.r4.model.Patient;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import java.util.function.Consumer;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+
+@RunWith(MockitoJUnitRunner.class)
+public class ResponseSizeCapturingInterceptorTest {
+
+ private static FhirContext ourCtx = FhirContext.forR4();
+ @ClassRule
+ public static RestfulServerRule ourServerRule = new RestfulServerRule(ourCtx);
+ private ResponseSizeCapturingInterceptor myInterceptor;
+ @Rule
+ public HashMapResourceProviderRule myPatientProviderRule = new HashMapResourceProviderRule<>(ourServerRule, Patient.class);
+ @Mock
+ private Consumer myConsumer;
+ @Captor
+ private ArgumentCaptor myResultCaptor;
+
+ @Before
+ public void before() {
+ myInterceptor = new ResponseSizeCapturingInterceptor();
+ ourServerRule.getRestfulServer().registerInterceptor(myInterceptor);
+ }
+
+ @After
+ public void after() {
+ ourServerRule.getRestfulServer().unregisterInterceptor(myInterceptor);
+ }
+
+ @Test
+ public void testReadResource() {
+ Patient resource = new Patient();
+ resource.setActive(true);
+ IIdType id = ourServerRule.getFhirClient().create().resource(resource).execute().getId().toUnqualifiedVersionless();
+
+ myInterceptor.registerConsumer(myConsumer);
+
+ resource = ourServerRule.getFhirClient().read().resource(Patient.class).withId(id).execute();
+ assertEquals(true, resource.getActive());
+
+ verify(myConsumer, times(1)).accept(myResultCaptor.capture());
+ assertEquals(100, myResultCaptor.getValue().getWrittenChars());
+ }
+
+
+}
From c4bd1a97fd4d10026c58e237153ee97b97f16efb Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Mon, 30 Mar 2020 11:54:27 -0400
Subject: [PATCH 30/32] Add test
---
.../dao/r4/FhirResourceDaoR4ValidateTest.java | 39 ++++++++++++++++++-
1 file changed, 38 insertions(+), 1 deletion(-)
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java
index 8eb000a1a1a..b4d7d4112d3 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4ValidateTest.java
@@ -38,6 +38,7 @@ import java.util.Collections;
import static org.awaitility.Awaitility.await;
import static org.hamcrest.Matchers.containsString;
+import static org.hamcrest.Matchers.in;
import static org.hamcrest.Matchers.not;
import static org.junit.Assert.*;
@@ -382,7 +383,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
}
@Test
- public void testValidateResourceContainingProfileDeclarationInvalid() throws Exception {
+ public void testValidateResourceContainingProfileDeclarationInvalid() {
String methodName = "testValidateResourceContainingProfileDeclarationInvalid";
Observation input = new Observation();
@@ -408,6 +409,42 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
}
}
+ @Test
+ public void testValidateBundleContainingResourceContainingProfileDeclarationInvalid() {
+ String methodName = "testValidateResourceContainingProfileDeclarationInvalid";
+
+ Observation observation = new Observation();
+ String profileUri = "http://example.com/StructureDefinition/" + methodName;
+ observation.getMeta().getProfile().add(new CanonicalType(profileUri));
+ observation.addIdentifier().setSystem("http://acme").setValue("12345");
+ observation.getEncounter().setReference("http://foo.com/Encounter/9");
+ observation.setStatus(ObservationStatus.FINAL);
+ observation.getCode().addCoding().setSystem("http://loinc.org").setCode("12345");
+
+ Bundle input = new Bundle();
+ input.setType(Bundle.BundleType.TRANSACTION);
+ input.addEntry()
+ .setResource(observation)
+ .setFullUrl("http://example.com/Observation")
+ .getRequest()
+ .setUrl("http://example.com/Observation")
+ .setMethod(Bundle.HTTPVerb.POST);
+
+ ValidationModeEnum mode = ValidationModeEnum.CREATE;
+ String encoded = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(input);
+ ourLog.info(encoded);
+
+ try {
+ myBundleDao.validate(input, null, encoded, EncodingEnum.JSON, mode, null, mySrd);
+ fail();
+ } catch (PreconditionFailedException e) {
+ org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome();
+ String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
+ ourLog.info(outputString);
+ assertThat(outputString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked"));
+ }
+ }
+
@Test
public void testValidateWithCanonicalReference() {
FhirInstanceValidator val = AopTestUtils.getTargetObject(myValidatorModule);
From 9d09466c2118fd7cee51374f81532714426d55f9 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 31 Mar 2020 10:49:16 -0400
Subject: [PATCH 31/32] Add debug logging to CLI
---
.../main/java/ca/uhn/fhir/cli/BaseApp.java | 66 ++++++++++++++-----
.../main/resources/logback-cli-on-debug.xml | 38 +++++++++++
.../uhn/hapi/fhir/docs/tools/hapi_fhir_cli.md | 3 +
3 files changed, 90 insertions(+), 17 deletions(-)
create mode 100644 hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml
diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java
index 33c36cc8bdb..c1b98297f41 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java
+++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/BaseApp.java
@@ -25,7 +25,11 @@ import ch.qos.logback.classic.LoggerContext;
import ch.qos.logback.classic.joran.JoranConfigurator;
import ch.qos.logback.core.joran.spi.JoranException;
import com.helger.commons.io.file.FileHelper;
-import org.apache.commons.cli.*;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.text.WordUtils;
import org.fusesource.jansi.Ansi;
@@ -43,11 +47,12 @@ import static org.fusesource.jansi.Ansi.ansi;
@SuppressWarnings("WeakerAccess")
public abstract class BaseApp {
+ protected static final org.slf4j.Logger ourLog;
+ static final String LINESEP = System.getProperty("line.separator");
private static final String STACKFILTER_PATTERN = "%xEx{full, sun.reflect, org.junit, org.eclipse, java.lang.reflect.Method, org.springframework, org.hibernate, com.sun.proxy, org.attoparser, org.thymeleaf}";
private static final String STACKFILTER_PATTERN_PROP = "log.stackfilter.pattern";
- static final String LINESEP = System.getProperty("line.separator");
- protected static final org.slf4j.Logger ourLog;
private static List ourCommands;
+ private static boolean ourDebugMode;
static {
System.setProperty(STACKFILTER_PATTERN_PROP, STACKFILTER_PATTERN);
@@ -115,13 +120,19 @@ public abstract class BaseApp {
System.out.println("Options:");
HelpFormatter fmt = new HelpFormatter();
PrintWriter pw = new PrintWriter(System.out);
- fmt.printOptions(pw, columns, theCommand.getOptions(), 2, 2);
+ fmt.printOptions(pw, columns, getOptions(theCommand), 2, 2);
pw.flush();
// That's it!
System.out.println();
}
+ private Options getOptions(BaseCommand theCommand) {
+ Options options = theCommand.getOptions();
+ options.addOption(null, "debug", false, "Enable debug mode");
+ return options;
+ }
+
private void logUsage() {
logAppHeader();
System.out.println("Usage:");
@@ -232,7 +243,7 @@ public abstract class BaseApp {
myShutdownHook = new MyShutdownHook(command);
Runtime.getRuntime().addShutdownHook(myShutdownHook);
- Options options = command.getOptions();
+ Options options = getOptions(command);
DefaultParser parser = new DefaultParser();
CommandLine parsedOptions;
@@ -247,6 +258,11 @@ public abstract class BaseApp {
throw new ParseException("Unrecognized argument: " + parsedOptions.getArgList().get(0));
}
+ if (parsedOptions.hasOption("debug")) {
+ loggingConfigOnDebug();
+ ourDebugMode = true;
+ }
+
// Actually execute the command
command.run(parsedOptions);
@@ -290,7 +306,7 @@ public abstract class BaseApp {
private void exitDueToException(Throwable e) {
if ("true".equals(System.getProperty("test"))) {
if (e instanceof CommandFailureException) {
- throw (CommandFailureException)e;
+ throw (CommandFailureException) e;
}
throw new Error(e);
} else {
@@ -316,6 +332,24 @@ public abstract class BaseApp {
}
}
+ private class MyShutdownHook extends Thread {
+ private final BaseCommand myFinalCommand;
+
+ MyShutdownHook(BaseCommand theFinalCommand) {
+ myFinalCommand = theFinalCommand;
+ }
+
+ @Override
+ public void run() {
+ ourLog.info(provideProductName() + " is shutting down...");
+ myFinalCommand.cleanup();
+ }
+ }
+
+ public static boolean isDebugMode() {
+ return ourDebugMode;
+ }
+
private static void loggingConfigOff() {
try {
JoranConfigurator configurator = new JoranConfigurator();
@@ -337,18 +371,16 @@ public abstract class BaseApp {
}
}
-
- private class MyShutdownHook extends Thread {
- private final BaseCommand myFinalCommand;
-
- MyShutdownHook(BaseCommand theFinalCommand) {
- myFinalCommand = theFinalCommand;
+ private static void loggingConfigOnDebug() {
+ try {
+ JoranConfigurator configurator = new JoranConfigurator();
+ configurator.setContext((LoggerContext) LoggerFactory.getILoggerFactory());
+ ((LoggerContext) LoggerFactory.getILoggerFactory()).reset();
+ configurator.doConfigure(App.class.getResourceAsStream("/logback-cli-on-debug.xml"));
+ } catch (JoranException e) {
+ e.printStackTrace();
}
- @Override
- public void run() {
- ourLog.info(provideProductName() + " is shutting down...");
- myFinalCommand.cleanup();
- }
+ ourLog.info("Debug logging is enabled");
}
}
diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml
new file mode 100644
index 00000000000..632c7a79946
--- /dev/null
+++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml
@@ -0,0 +1,38 @@
+
+
+
+ true
+
+ %green(%d{yyyy-MM-dd}) %boldGreen(%d{HH:mm:ss.SS}) %white([%thread] [%file:%line]) %white(%-5level) %boldBlue(%logger{20}) %boldWhite(%msg%n)
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/tools/hapi_fhir_cli.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/tools/hapi_fhir_cli.md
index c13c723a81c..550e20a73ce 100644
--- a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/tools/hapi_fhir_cli.md
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/tools/hapi_fhir_cli.md
@@ -38,6 +38,9 @@ java version "1.8.0_60"
Java(TM) SE Runtime Environment (build 1.8.0_60-b27)
Java HotSpot(TM) 64-Bit Server VM (build 25.60-b23, mixed mode)
```
+
+Individual commands can be troubleshooted by adding the `--debug` command line argument.
+
If this does not help, please post a question on our [Google Group](https://groups.google.com/d/forum/hapi-fhir).
# Server (run-server)
From 082c20133341275e3e4c3f0bd521f5bb58a37c77 Mon Sep 17 00:00:00 2001
From: jamesagnew
Date: Tue, 31 Mar 2020 11:20:48 -0400
Subject: [PATCH 32/32] Improve debug logging
---
.../main/resources/logback-cli-on-debug.xml | 23 +++++++++++++++----
1 file changed, 19 insertions(+), 4 deletions(-)
diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml
index 632c7a79946..fa16cfb7451 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml
+++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/resources/logback-cli-on-debug.xml
@@ -1,38 +1,53 @@
- true
+ false
- %green(%d{yyyy-MM-dd}) %boldGreen(%d{HH:mm:ss.SS}) %white([%thread] [%file:%line]) %white(%-5level) %boldBlue(%logger{20}) %boldWhite(%msg%n)
-
+ %d{yyyy-MM-dd} %d{HH:mm:ss.SS} [%thread] [%file:%line] %-5level %logger{20} %msg%n
+
+ output.log
+ false
+
+ %d{yyyy-MM-dd} %d{HH:mm:ss.SS} [%thread] [%file:%line] %-5level %logger{20} %msg%n
+
+ utf-8
+
+
+
+
-
+
+
+
+
+
+