diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 0d85e48bba4..5756e093c49 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -59,6 +59,8 @@ stages:
module: hapi-fhir-jpaserver-elastic-test-utilities
- name: hapi_fhir_jpaserver_ips
module: hapi-fhir-jpaserver-ips
+ - name: hapi_fhir_jpaserver_hfql
+ module: hapi-fhir-jpaserver-hfql
- name: hapi_fhir_jpaserver_mdm
module: hapi-fhir-jpaserver-mdm
- name: hapi_fhir_jpaserver_model
diff --git a/hapi-deployable-pom/pom.xml b/hapi-deployable-pom/pom.xml
index 3221bdf9e59..b8c8d696619 100644
--- a/hapi-deployable-pom/pom.xml
+++ b/hapi-deployable-pom/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
diff --git a/hapi-fhir-android/pom.xml b/hapi-fhir-android/pom.xml
index 1b66411c122..13c3ac675f9 100644
--- a/hapi-fhir-android/pom.xml
+++ b/hapi-fhir-android/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml
index af145ce4837..ffda99b7c56 100644
--- a/hapi-fhir-base/pom.xml
+++ b/hapi-fhir-base/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java
index 65d2a889333..9aa36f2eb70 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/FhirPathExecutionException.java
@@ -29,8 +29,8 @@ public class FhirPathExecutionException extends InternalErrorException {
private static final long serialVersionUID = 1L;
- public FhirPathExecutionException(Throwable theCause) {
- super(theCause);
+ public FhirPathExecutionException(String theMessage, Throwable theCause) {
+ super(theMessage, theCause);
}
public FhirPathExecutionException(String theMessage) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java
index 59d3920b1cc..67fc8ce5ff3 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/fhirpath/IFhirPath.java
@@ -37,20 +37,55 @@ public interface IFhirPath {
*/
List evaluate(IBase theInput, String thePath, Class theReturnType);
+ /**
+ * Apply the given FhirPath expression against the given input and return
+ * all results in a list. Unlike the {@link #evaluate(IBase, String, Class)} method which
+ * uses a String containing a FHIRPath expression, this method takes a parsed FHIRPath
+ * expression returned by the {@link #parse(String)} method. This has the advantage
+ * of avoiding re-parsing expressions if the same expression will be evaluated
+ * repeatedly.
+ *
+ * @param theInput The input object (generally a resource or datatype)
+ * @param theParsedExpression A parsed FHIRPath expression returned by {@link #parse(String)}
+ * @param theReturnType The type to return (in order to avoid casting)
+ * @since 6.8.0
+ */
+ List evaluate(IBase theInput, IParsedExpression theParsedExpression, Class theReturnType);
+
/**
* Apply the given FhirPath expression against the given input and return
* the first match (if any)
*
- * @param theInput The input object (generally a resource or datatype)
- * @param thePath The fluent path expression
+ * @param theInput The input object (generally a resource or datatype)
+ * @param thePath The fluent path expression
* @param theReturnType The type to return (in order to avoid casting)
*/
Optional evaluateFirst(IBase theInput, String thePath, Class theReturnType);
/**
- * Parses the expression and throws an exception if it can not parse correctly
+ * Apply the given FhirPath expression against the given input and return
+ * the first match (if any). Unlike the {@link #evaluateFirst(IBase, String, Class)} method which
+ * uses a String containing a FHIRPath expression, this method takes a parsed FHIRPath
+ * expression returned by the {@link #parse(String)} method. This has the advantage
+ * of avoiding re-parsing expressions if the same expression will be evaluated
+ * repeatedly.
+ *
+ * @param theInput The input object (generally a resource or datatype)
+ * @param theParsedExpression A parsed FHIRPath expression returned by {@link #parse(String)}
+ * @param theReturnType The type to return (in order to avoid casting)
+ * @since 6.8.0
*/
- void parse(String theExpression) throws Exception;
+ Optional evaluateFirst(
+ IBase theInput, IParsedExpression theParsedExpression, Class theReturnType);
+
+ /**
+ * Parses the expression and throws an exception if it can not parse correctly.
+ * Note that the return type from this method is intended to be a "black box". It can
+ * be passed back into the {@link #evaluate(IBase, IParsedExpression, Class)}
+ * method on any FHIRPath instance that comes from the same {@link ca.uhn.fhir.context.FhirContext}
+ * instance. Any other use will produce unspecified results.
+ */
+ IParsedExpression parse(String theExpression) throws Exception;
/**
* This method can be used optionally to supply an evaluation context for the
@@ -61,4 +96,23 @@ public interface IFhirPath {
* @since 6.4.0
*/
void setEvaluationContext(@Nonnull IFhirPathEvaluationContext theEvaluationContext);
+
+ /**
+ * This interface is a marker interface representing a parsed FHIRPath expression.
+ * Instances of this class will be returned by {@link #parse(String)} and can be
+ * passed to {@link #evaluate(IBase, IParsedExpression, Class)} and
+ * {@link #evaluateFirst(IBase, IParsedExpression, Class)}. Using a pre-parsed
+ * FHIRPath expression can perform much faster in some situations where an
+ * identical expression will be evaluated many times against different targets,
+ * since the parsing step doesn't need to be repeated.
+ *
+ * Instances of this interface should be treated as a "black box". There are no
+ * methods that can be used to manipulate parsed FHIRPath expressions.
+ *
+ *
+ * @since 6.8.0
+ */
+ interface IParsedExpression {
+ // no methods
+ }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationUntypedWithInput.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationUntypedWithInput.java
index d1293db973f..abec5ca2ef2 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationUntypedWithInput.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IOperationUntypedWithInput.java
@@ -40,6 +40,9 @@ public interface IOperationUntypedWithInput extends IClientExecutablePatient/NNN/$everything) which return a bundle instead of
* a Parameters resource.
+ *
+ * Passing in {@literal Binary.class} allows any arbitrary response to be returned. Any payload at
+ * all will be read as raw bytes into a Binary resource.
*/
IOperationUntypedWithInput returnResourceType(Class theReturnType);
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTypeUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTypeUtil.java
index b9ef2662d33..031d2c63c31 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTypeUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/FhirTypeUtil.java
@@ -1,3 +1,22 @@
+/*-
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2023 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
package ca.uhn.fhir.util;
public final class FhirTypeUtil {
diff --git a/hapi-fhir-bom/pom.xml b/hapi-fhir-bom/pom.xml
index f9a2db6aae1..90901b7a0fc 100644
--- a/hapi-fhir-bom/pom.xml
+++ b/hapi-fhir-bom/pom.xml
@@ -4,7 +4,7 @@
4.0.0ca.uhn.hapi.fhirhapi-fhir-bom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOTpomHAPI FHIR BOM
@@ -12,7 +12,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
@@ -84,6 +84,11 @@
hapi-fhir-jpaserver-ips${project.version}
+
+ ${project.groupId}
+ hapi-fhir-jpaserver-hfql
+ ${project.version}
+ ${project.groupId}hapi-fhir-jpaserver-mdm
diff --git a/hapi-fhir-checkstyle/pom.xml b/hapi-fhir-checkstyle/pom.xml
index 9ce73f15e78..a6c57313fff 100644
--- a/hapi-fhir-checkstyle/pom.xml
+++ b/hapi-fhir-checkstyle/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
index c74f435b37b..b6140c04f43 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
+++ b/hapi-fhir-cli/hapi-fhir-cli-api/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml
index a99a671b074..c303962d636 100644
--- a/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml
+++ b/hapi-fhir-cli/hapi-fhir-cli-app/pom.xml
@@ -6,7 +6,7 @@
ca.uhn.hapi.fhirhapi-fhir-cli
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
diff --git a/hapi-fhir-cli/pom.xml b/hapi-fhir-cli/pom.xml
index ae9231aa860..05af3145b01 100644
--- a/hapi-fhir-cli/pom.xml
+++ b/hapi-fhir-cli/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
diff --git a/hapi-fhir-client-okhttp/pom.xml b/hapi-fhir-client-okhttp/pom.xml
index fc7e460f746..734e86fed8e 100644
--- a/hapi-fhir-client-okhttp/pom.xml
+++ b/hapi-fhir-client-okhttp/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-client/pom.xml b/hapi-fhir-client/pom.xml
index 346417a3e88..c70aa8e228e 100644
--- a/hapi-fhir-client/pom.xml
+++ b/hapi-fhir-client/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
index 59aa5b066ec..97ab8f757f6 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/GenericClient.java
@@ -136,6 +136,7 @@ import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.instance.model.api.IBase;
+import org.hl7.fhir.instance.model.api.IBaseBinary;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import org.hl7.fhir.instance.model.api.IBaseDatatype;
@@ -1422,7 +1423,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
if (myReturnResourceType != null) {
ResourceResponseHandler handler;
- handler = new ResourceResponseHandler(myReturnResourceType);
+ if (IBaseBinary.class.isAssignableFrom(myReturnResourceType)) {
+ handler = new ResourceOrBinaryResponseHandler();
+ } else {
+ handler = new ResourceResponseHandler(myReturnResourceType);
+ }
Object retVal = invoke(null, handler, invocation);
return retVal;
}
diff --git a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/HttpBasicAuthInterceptor.java b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/HttpBasicAuthInterceptor.java
index 8b62c883546..3f5d07f83c3 100644
--- a/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/HttpBasicAuthInterceptor.java
+++ b/hapi-fhir-client/src/main/java/ca/uhn/fhir/rest/client/impl/HttpBasicAuthInterceptor.java
@@ -19,9 +19,6 @@
*/
package ca.uhn.fhir.rest.client.impl;
-import ca.uhn.fhir.rest.client.api.IBasicClient;
-import ca.uhn.fhir.rest.client.api.IClientInterceptor;
-import ca.uhn.fhir.rest.client.api.IGenericClient;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
@@ -35,9 +32,10 @@ import org.apache.http.protocol.HttpContext;
import java.io.IOException;
/**
- * @deprecated Use {@link ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor} instead. Note that BasicAuthInterceptor class is a HAPI client interceptor instead of being a commons-httpclient interceptor, so you register it to your client instance once it's created using {@link IGenericClient#registerInterceptor(IClientInterceptor)} or {@link IBasicClient#registerInterceptor(IClientInterceptor)} instead
+ * Apache HTTPClient interceptor which adds basic auth
+ *
+ * @see ca.uhn.fhir.rest.client.interceptor.BasicAuthInterceptor A HAPI FHIR interceptor that is generally easier to use
*/
-@Deprecated
public class HttpBasicAuthInterceptor implements HttpRequestInterceptor {
private String myUsername;
diff --git a/hapi-fhir-converter/pom.xml b/hapi-fhir-converter/pom.xml
index 9a1b7253f50..720f2f90d66 100644
--- a/hapi-fhir-converter/pom.xml
+++ b/hapi-fhir-converter/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-dist/pom.xml b/hapi-fhir-dist/pom.xml
index 2c0f93b3639..911c7eaea4e 100644
--- a/hapi-fhir-dist/pom.xml
+++ b/hapi-fhir-dist/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
@@ -233,6 +233,13 @@
provided
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-jpaserver-hfql
+ ${project.version}
+
+
ch.qos.logbacklogback-classic
@@ -277,6 +284,7 @@
${project.basedir}/src/assembly/hapi-fhir-standard-distribution.xml${project.basedir}/src/assembly/hapi-fhir-android-distribution.xml${project.basedir}/src/assembly/hapi-fhir-cli.xml
+ ${project.basedir}/src/assembly/hapi-fhir-hfql-jdbc-driver.xmlhapi-fhir-${project.version}
diff --git a/hapi-fhir-dist/src/assembly/hapi-fhir-android-distribution.xml b/hapi-fhir-dist/src/assembly/hapi-fhir-android-distribution.xml
index ba42adafb18..61579ce0152 100644
--- a/hapi-fhir-dist/src/assembly/hapi-fhir-android-distribution.xml
+++ b/hapi-fhir-dist/src/assembly/hapi-fhir-android-distribution.xml
@@ -6,7 +6,6 @@
zip
- tar.bz2false
diff --git a/hapi-fhir-dist/src/assembly/hapi-fhir-jpaserver-example.xml b/hapi-fhir-dist/src/assembly/hapi-fhir-hfql-jdbc-driver.xml
similarity index 77%
rename from hapi-fhir-dist/src/assembly/hapi-fhir-jpaserver-example.xml
rename to hapi-fhir-dist/src/assembly/hapi-fhir-hfql-jdbc-driver.xml
index facf0b4d47d..68d9f3f127f 100644
--- a/hapi-fhir-dist/src/assembly/hapi-fhir-jpaserver-example.xml
+++ b/hapi-fhir-dist/src/assembly/hapi-fhir-hfql-jdbc-driver.xml
@@ -1,7 +1,7 @@
- jpaserver-example
+ hfql-jdbc-driverzip
@@ -11,11 +11,10 @@
- ${project.basedir}/../hapi-fhir-jpaserver-example
+ ${project.basedir}/../hapi-fhir-jpaserver-hfql/target//
- pom.xml
- src/**
+ hapi-fhir-hfql-jdbc-*.jar
diff --git a/hapi-fhir-docs/pom.xml b/hapi-fhir-docs/pom.xml
index 1a0b971a168..ea61e279871 100644
--- a/hapi-fhir-docs/pom.xml
+++ b/hapi-fhir-docs/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5083-add-fhirpath-evaluate-parsed-method.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5083-add-fhirpath-evaluate-parsed-method.yaml
new file mode 100644
index 00000000000..6f31ac1268f
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5083-add-fhirpath-evaluate-parsed-method.yaml
@@ -0,0 +1,7 @@
+---
+type: add
+issue: 5083
+title: "The IFhirPath evaluator interface now has an additional overload of the
+ `evaluate` method which takes in a parsed expression returned by the
+ `parse` method. This can be used to improve performance in cases where the same
+ expression is being used repeatedly."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5083-add-hfql.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5083-add-hfql.yaml
new file mode 100644
index 00000000000..085d4e26127
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5083-add-hfql.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 5083
+title: "A new SQL-like evaluator called the HAPI FHIR Query Language (HFQL)
+ has been added."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5115-add-hfql.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5115-add-hfql.yaml
new file mode 100644
index 00000000000..9812c111463
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5115-add-hfql.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 5115
+title: "A new experimental SQL-like query syntax called HFQL (HAPI FHIR Query Language)
+ has been added."
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5115-allow-genericclient-arbitrary-binary-response.yaml b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5115-allow-genericclient-arbitrary-binary-response.yaml
new file mode 100644
index 00000000000..6922ff3f915
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/6_8_0/5115-allow-genericclient-arbitrary-binary-response.yaml
@@ -0,0 +1,5 @@
+---
+type: add
+issue: 5115
+title: "The Generic/Fluent client can now handle arbitrary (ie. non-FHIR) responses from $operation
+ invocation by specifying a response resource type of Binary."
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 4a073716ef5..41de46a1a1e 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
@@ -85,6 +85,9 @@ page.server_jpa_partitioning.enabling_in_hapi_fhir=Enabling Partitioning in HAPI
section.server_jpa_batch.title=JPA Server: Batch Processing
page.server_jpa_batch.introduction=Batch Introduction
+section.hfql.title=JPA Server: HFQL (SQL) Driver
+page.hfql.hfql=HFQL Module
+
section.clinical_reasoning.title=Clinical Reasoning
page.clinical_reasoning.overview=Clinical Reasoning Overview
page.clinical_reasoning.cql=CQL
diff --git a/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/hfql/hfql.md b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/hfql/hfql.md
new file mode 100644
index 00000000000..3a9e006d259
--- /dev/null
+++ b/hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/docs/hfql/hfql.md
@@ -0,0 +1,56 @@
+# FQL Driver: SQL For FHIR Repositories
+
+
+This is an experimental module. Use with caution. This API is likely to change.
+
+
+The HAPI FHIR JPA server can optionally be configured to support SQL-like queries against the FHIR repository. This module is intended for analytical queries. It is not optimized for performance, and may take a long time to produce results.
+
+# Syntax
+
+This module uses a proprietary flavour of SQL that is specific to HAPI FHIR. It is similar to the [Firely Query Language](https://simplifier.net/docs/fql), although it also has differences.
+
+A simple example query is shown below:
+
+```sql
+SELECT
+ name.family as family,
+ name.given as given,
+ birthDate,
+ identifier.where(system='http://hl7.org/fhir/sid/us-ssn').value as SSN
+FROM
+ Patient
+WHERE
+ active = true
+```
+
+See [SQL Syntax](https://smilecdr.com/docs/hfql/sql_syntax.html) for details on this syntax.
+
+# JDBC Driver
+
+When HFQL is enabled on the server, a JDBC-compatible driver is available. This can be used to query the FHIR server directly from a JDBC compliant database browser.
+
+This module has been tested with [DBeaver](https://dbeaver.io/), which is a free and excellent database browser. Other JDBC compatible database tools may also work. Note that not all JDBC API methods have been implemented in the driver, so other tools may use methods that have not yet been implemented. Please let us know in the [Google Group](https://groups.google.com/g/hapi-fhir) if you encounter issues or have suggestions.
+
+The JDBC driver can be downloaded from the [GitHub Releases site](https://github.com/hapifhir/hapi-fhir/releases). It can also be built from sources by executing the following command:
+
+```bash
+mvn -DskipTests -P DIST clean install -pl :hapi-fhir-jpaserver-hfql -am
+```
+
+To import this driver into your database tool, import the JDBC JAR and use the following settings:
+
+
+
Setting
Description
+
+
+
Class Name
ca.uhn.fhir.jpa.fql.jdbc.JdbcDriver
+
+
URL
jdbc:hapifhirql:[server_base_url]
+
+
Username
If provided, the username/password will be added as an HTTP Basic Authorization header on all requests to the server.
+
+
Password
+
+
+
diff --git a/hapi-fhir-jacoco/pom.xml b/hapi-fhir-jacoco/pom.xml
index f83f188f06c..755bcf1d5a5 100644
--- a/hapi-fhir-jacoco/pom.xml
+++ b/hapi-fhir-jacoco/pom.xml
@@ -11,7 +11,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
@@ -182,6 +182,11 @@
hapi-fhir-jpaserver-model${project.version}
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-jpaserver-hfql
+ ${project.version}
+ ca.uhn.hapi.fhirhapi-fhir-jpaserver-ips
diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml
index 9ede27f5875..88331d45254 100644
--- a/hapi-fhir-jaxrsserver-base/pom.xml
+++ b/hapi-fhir-jaxrsserver-base/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-jpa/pom.xml b/hapi-fhir-jpa/pom.xml
index c618237c461..2720f72bc97 100644
--- a/hapi-fhir-jpa/pom.xml
+++ b/hapi-fhir-jpa/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml
index 9411eb36d2d..213b215cc59 100644
--- a/hapi-fhir-jpaserver-base/pom.xml
+++ b/hapi-fhir-jpaserver-base/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java
index c701d1392c3..5697302d49d 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/QueryStack.java
@@ -2559,7 +2559,7 @@ public class QueryStack {
mySearchParamRegistry.getActiveSearchParam(theResourceName, fullName);
if (fullChainParam != null) {
List swappedParamTypes = nextAnd.stream()
- .map(t -> toParameterType(fullChainParam, null, t.getValueAsQueryToken(myFhirContext)))
+ .map(t -> newParameterInstance(fullChainParam, null, t.getValueAsQueryToken(myFhirContext)))
.collect(Collectors.toList());
List> params = List.of(swappedParamTypes);
Condition predicate = createPredicateSearchParameter(
@@ -2660,15 +2660,15 @@ public class QueryStack {
mySqlBuilder.addPredicate(predicate);
}
- public IQueryParameterType toParameterType(
+ public IQueryParameterType newParameterInstance(
RuntimeSearchParam theParam, String theQualifier, String theValueAsQueryToken) {
- IQueryParameterType qp = toParameterType(theParam);
+ IQueryParameterType qp = newParameterInstance(theParam);
qp.setValueAsQueryToken(myFhirContext, theParam.getName(), theQualifier, theValueAsQueryToken);
return qp;
}
- private IQueryParameterType toParameterType(RuntimeSearchParam theParam) {
+ private IQueryParameterType newParameterInstance(RuntimeSearchParam theParam) {
IQueryParameterType qp;
switch (theParam.getParamType()) {
@@ -2694,8 +2694,8 @@ public class QueryStack {
throw new InternalErrorException(Msg.code(1224) + "Parameter " + theParam.getName() + " has "
+ compositeOf.size() + " composite parts. Don't know how handlt this.");
}
- IQueryParameterType leftParam = toParameterType(compositeOf.get(0));
- IQueryParameterType rightParam = toParameterType(compositeOf.get(1));
+ IQueryParameterType leftParam = newParameterInstance(compositeOf.get(0));
+ IQueryParameterType rightParam = newParameterInstance(compositeOf.get(1));
qp = new CompositeParam<>(leftParam, rightParam);
break;
case URI:
@@ -2876,7 +2876,7 @@ public class QueryStack {
if (RestSearchParameterTypeEnum.REFERENCE.equals(nextSearchParam.getParamType())) {
orValues.add(new ReferenceParam(nextQualifier, "", theTargetValue));
} else {
- IQueryParameterType qp = toParameterType(nextSearchParam);
+ IQueryParameterType qp = newParameterInstance(nextSearchParam);
qp.setValueAsQueryToken(myFhirContext, nextSearchParam.getName(), null, theTargetValue);
orValues.add(qp);
}
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ResourceLinkPredicateBuilder.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ResourceLinkPredicateBuilder.java
index 10c17cc2541..5952f398c56 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ResourceLinkPredicateBuilder.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/search/builder/predicate/ResourceLinkPredicateBuilder.java
@@ -692,7 +692,7 @@ public class ResourceLinkPredicateBuilder extends BaseJoiningPredicateBuilder im
type.setValueAsQueryToken(getFhirContext(), theParamName, qualifier, resourceId);
chainValue = type;
} else {
- chainValue = myQueryStack.toParameterType(param, qualifier, resourceId);
+ chainValue = myQueryStack.newParameterInstance(param, qualifier, resourceId);
}
return chainValue;
diff --git a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml
index 7a993dd23a0..a00afb5256a 100644
--- a/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml
+++ b/hapi-fhir-jpaserver-elastic-test-utilities/pom.xml
@@ -6,7 +6,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-jpaserver-hfql/pom.xml b/hapi-fhir-jpaserver-hfql/pom.xml
new file mode 100644
index 00000000000..1446717ef32
--- /dev/null
+++ b/hapi-fhir-jpaserver-hfql/pom.xml
@@ -0,0 +1,68 @@
+
+ 4.0.0
+
+ ca.uhn.hapi.fhir
+ hapi-deployable-pom
+ 6.7.15-SNAPSHOT
+ ../hapi-deployable-pom/pom.xml
+
+
+ hapi-fhir-jpaserver-hfql
+ jar
+ HAPI FHIR JPA Server - HFQL Driver
+
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-jpaserver-base
+ ${project.version}
+
+
+
+
+ javax.servlet
+ javax.servlet-api
+ provided
+
+
+
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-test-utilities
+ ${project.version}
+ test
+
+
+
+
+
+ DIST
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+ jar-with-dependencies
+
+ false
+ hapi-fhir-hfql-jdbc-${project.version}
+ false
+
+
+
+
+
+
+
+
diff --git a/hapi-fhir-jpaserver-hfql/src/main/java/ca/uhn/fhir/jpa/fql/executor/HfqlDataTypeEnum.java b/hapi-fhir-jpaserver-hfql/src/main/java/ca/uhn/fhir/jpa/fql/executor/HfqlDataTypeEnum.java
new file mode 100644
index 00000000000..3bc711c6bf8
--- /dev/null
+++ b/hapi-fhir-jpaserver-hfql/src/main/java/ca/uhn/fhir/jpa/fql/executor/HfqlDataTypeEnum.java
@@ -0,0 +1,46 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - HFQL Driver
+ * %%
+ * Copyright (C) 2014 - 2023 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.jpa.fql.executor;
+
+import java.math.BigDecimal;
+import java.sql.Date;
+import java.sql.Types;
+
+public enum HfqlDataTypeEnum {
+ STRING(Types.VARCHAR, String.class),
+ JSON(Types.VARCHAR, String.class),
+ INTEGER(Types.INTEGER, Integer.class),
+ BOOLEAN(Types.BOOLEAN, Boolean.class),
+ DATE(Types.DATE, Date.class),
+ TIMESTAMP(Types.TIMESTAMP_WITH_TIMEZONE, Date.class),
+ LONGINT(Types.BIGINT, Long.class),
+ TIME(Types.TIME, String.class),
+ DECIMAL(Types.DECIMAL, BigDecimal.class);
+
+ private final int mySqlType;
+
+ HfqlDataTypeEnum(int theSqlType, Class> theJavaType) {
+ mySqlType = theSqlType;
+ }
+
+ public int getSqlType() {
+ return mySqlType;
+ }
+}
diff --git a/hapi-fhir-jpaserver-hfql/src/main/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutor.java b/hapi-fhir-jpaserver-hfql/src/main/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutor.java
new file mode 100644
index 00000000000..4edb5ec95f3
--- /dev/null
+++ b/hapi-fhir-jpaserver-hfql/src/main/java/ca/uhn/fhir/jpa/fql/executor/HfqlExecutor.java
@@ -0,0 +1,921 @@
+/*-
+ * #%L
+ * HAPI FHIR JPA Server - HFQL Driver
+ * %%
+ * Copyright (C) 2014 - 2023 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
+package ca.uhn.fhir.jpa.fql.executor;
+
+import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.context.RuntimeResourceDefinition;
+import ca.uhn.fhir.context.RuntimeSearchParam;
+import ca.uhn.fhir.fhirpath.FhirPathExecutionException;
+import ca.uhn.fhir.fhirpath.IFhirPath;
+import ca.uhn.fhir.i18n.Msg;
+import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
+import ca.uhn.fhir.jpa.api.dao.IFhirResourceDao;
+import ca.uhn.fhir.jpa.fql.parser.HfqlFhirPathParser;
+import ca.uhn.fhir.jpa.fql.parser.HfqlStatement;
+import ca.uhn.fhir.jpa.fql.parser.HfqlStatementParser;
+import ca.uhn.fhir.jpa.fql.util.HfqlConstants;
+import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
+import ca.uhn.fhir.jpa.searchparam.util.JpaParamUtil;
+import ca.uhn.fhir.model.api.IQueryParameterAnd;
+import ca.uhn.fhir.parser.DataFormatException;
+import ca.uhn.fhir.rest.api.Constants;
+import ca.uhn.fhir.rest.api.QualifiedParamList;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.param.DateOrListParam;
+import ca.uhn.fhir.rest.param.DateParam;
+import ca.uhn.fhir.rest.param.QualifierDetails;
+import ca.uhn.fhir.rest.param.TokenOrListParam;
+import ca.uhn.fhir.rest.server.IPagingProvider;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+import ca.uhn.fhir.rest.server.util.ISearchParamRegistry;
+import ca.uhn.fhir.util.UrlUtil;
+import com.google.common.collect.Lists;
+import org.apache.commons.collections4.ListUtils;
+import org.apache.commons.lang3.Validate;
+import org.apache.commons.lang3.builder.HashCodeBuilder;
+import org.hl7.fhir.instance.model.api.IBase;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.instance.model.api.IPrimitiveType;
+import org.hl7.fhir.r4.model.DateTimeType;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.springframework.beans.factory.annotation.Autowired;
+
+import java.math.BigDecimal;
+import java.sql.Types;
+import java.util.ArrayList;
+import java.util.Comparator;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.Set;
+import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Function;
+import java.util.function.Predicate;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
+import javax.annotation.Nullable;
+
+import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+
+/**
+ * This class could be considered the main entrypoint into the HFQL executor.
+ * It receives a raw HFQL query, parses it, executes it, and returns a result set.
+ * Conceptually the {@link #executeInitialSearch(String, Integer, RequestDetails)}
+ * method can be thought of like the JPA DAO search method, and the
+ * {@link #executeContinuation(HfqlStatement, String, int, Integer, RequestDetails)}
+ * can be thought of like loading a subsequent page of the search results.
+ *
+ * Both of these methods return an {@link IHfqlExecutionResult}, which is essentially
+ * a result row iterator.
+ */
+public class HfqlExecutor implements IHfqlExecutor {
+ public static final int BATCH_SIZE = 1000;
+ public static final String[] EMPTY_STRING_ARRAY = new String[0];
+ public static final Set NULL_GROUP_BY_KEY = Set.of(new GroupByKey(List.of()));
+ private static final Logger ourLog = LoggerFactory.getLogger(HfqlExecutor.class);
+
+ @Autowired
+ private DaoRegistry myDaoRegistry;
+
+ @Autowired
+ private FhirContext myFhirContext;
+
+ @Autowired
+ private IPagingProvider myPagingProvider;
+
+ @Autowired
+ private ISearchParamRegistry mySearchParamRegistry;
+
+ /**
+ * Constructor
+ */
+ public HfqlExecutor() {
+ super();
+ }
+
+ @Override
+ public IHfqlExecutionResult executeInitialSearch(
+ String theStatement, Integer theLimit, RequestDetails theRequestDetails) {
+ try {
+ return doExecuteInitialSearch(theStatement, theLimit, theRequestDetails);
+ } catch (Exception e) {
+ ourLog.warn("Failed to execute HFFQL statement", e);
+ return StaticHfqlExecutionResult.withError(defaultIfNull(e.getMessage(), "(no message)"));
+ }
+ }
+
+ @Nonnull
+ private IHfqlExecutionResult doExecuteInitialSearch(
+ String theStatement, Integer theLimit, RequestDetails theRequestDetails) {
+ HfqlStatementParser parser = new HfqlStatementParser(myFhirContext, theStatement);
+ HfqlStatement statement = parser.parse();
+ IFhirResourceDao dao = myDaoRegistry.getResourceDao(statement.getFromResourceName());
+ if (dao == null) {
+ throw new DataFormatException(
+ Msg.code(2406) + "Unknown or unsupported FROM type: " + statement.getFromResourceName());
+ }
+
+ massageSelectColumnNames(statement);
+ populateSelectColumnDataTypes(statement);
+
+ SearchParameterMap map = new SearchParameterMap();
+ addHfqlWhereClausesToSearchParameterMap(statement, map);
+
+ Integer limit = theLimit;
+ if (statement.hasOrderClause()) {
+ /*
+ * If we're ordering search results, we need to load all available data in order
+ * to sort it because we handle ordering in application code currently. A good
+ * future optimization would be to handle ordering in the database when possible,
+ * but we can't always do that because the query can specify an order on any
+ * arbitrary FHIRPath expression.
+ */
+ limit = null;
+ } else if (statement.getLimit() != null) {
+ limit = limit == null ? statement.getLimit() : Math.min(limit, statement.getLimit());
+ }
+
+ HfqlExecutionContext executionContext = new HfqlExecutionContext(myFhirContext.newFhirPath());
+ IBundleProvider outcome = dao.search(map, theRequestDetails);
+ Predicate whereClausePredicate = newWhereClausePredicate(executionContext, statement);
+
+ IHfqlExecutionResult executionResult;
+ if (statement.hasCountClauses()) {
+ executionResult = executeCountClause(statement, executionContext, outcome, whereClausePredicate);
+ } else {
+ executionResult = new LocalSearchHfqlExecutionResult(
+ statement, outcome, executionContext, limit, 0, whereClausePredicate, myFhirContext);
+ }
+
+ if (statement.hasOrderClause()) {
+ executionResult = createOrderedResult(statement, executionResult);
+ }
+
+ return executionResult;
+ }
+
+ private void addHfqlWhereClausesToSearchParameterMap(HfqlStatement statement, SearchParameterMap map) {
+ List searchClauses = statement.getWhereClauses();
+ for (HfqlStatement.WhereClause nextSearchClause : searchClauses) {
+ if (nextSearchClause.getOperator() != HfqlStatement.WhereClauseOperatorEnum.SEARCH_MATCH) {
+ continue;
+ }
+
+ if (!"id".equals(nextSearchClause.getLeft())) {
+ throw new InvalidRequestException(
+ Msg.code(2412) + "search_match function can only be applied to the id element");
+ }
+
+ if (nextSearchClause.getRight().size() != 2) {
+ throw new InvalidRequestException(Msg.code(2413) + "search_match function requires 2 arguments");
+ }
+
+ List argumentStrings = nextSearchClause.getRightAsStrings();
+ String paramName = argumentStrings.get(0);
+ String paramValueUnsplit = argumentStrings.get(1);
+ List paramValues = QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, paramValueUnsplit);
+
+ if (paramName.equals(Constants.PARAM_ID)) {
+ map.add(Constants.PARAM_ID, new TokenOrListParam(null, paramValues.toArray(EMPTY_STRING_ARRAY)));
+ } else if (paramName.equals(Constants.PARAM_LASTUPDATED)) {
+ DateOrListParam param = new DateOrListParam();
+ for (String nextValue : paramValues) {
+ param.addOr(new DateParam(nextValue));
+ }
+ map.add(Constants.PARAM_LASTUPDATED, param);
+ } else if (paramName.startsWith("_")) {
+ throw newInvalidRequestExceptionUnknownSearchParameter(paramName);
+ } else {
+ QualifierDetails qualifiedParamName = QualifierDetails.extractQualifiersFromParameterName(paramName);
+
+ RuntimeSearchParam searchParam = mySearchParamRegistry.getActiveSearchParam(
+ statement.getFromResourceName(), qualifiedParamName.getParamName());
+ if (searchParam == null) {
+ throw newInvalidRequestExceptionUnknownSearchParameter(paramName);
+ }
+
+ QualifiedParamList values = new QualifiedParamList();
+ values.setQualifier(qualifiedParamName.getWholeQualifier());
+ values.addAll(paramValues);
+ IQueryParameterAnd> andParam = JpaParamUtil.parseQueryParams(
+ myFhirContext, searchParam.getParamType(), paramName, List.of(values));
+ map.add(qualifiedParamName.getParamName(), andParam);
+ }
+ }
+ }
+
+ private IHfqlExecutionResult createOrderedResult(
+ HfqlStatement theStatement, IHfqlExecutionResult theExecutionResult) {
+ List rows = new ArrayList<>();
+ while (theExecutionResult.hasNext()) {
+ IHfqlExecutionResult.Row nextRow = theExecutionResult.getNextRow();
+ rows.add(nextRow);
+ Validate.isTrue(
+ rows.size() <= HfqlConstants.ORDER_AND_GROUP_LIMIT,
+ "Can not ORDER BY result sets over %d results",
+ HfqlConstants.ORDER_AND_GROUP_LIMIT);
+ }
+
+ List orderColumnIndexes = theStatement.getOrderByClauses().stream()
+ .map(t -> {
+ int index = theStatement.findSelectClauseIndex(t.getClause());
+ if (index == -1) {
+ throw new InvalidRequestException(
+ Msg.code(2407) + "Invalid/unknown ORDER BY clause: " + t.getClause());
+ }
+ return index;
+ })
+ .collect(Collectors.toList());
+ List orderAscending = theStatement.getOrderByClauses().stream()
+ .map(HfqlStatement.OrderByClause::isAscending)
+ .collect(Collectors.toList());
+
+ Comparator comparator = null;
+ for (int i = 0; i < orderColumnIndexes.size(); i++) {
+ int columnIndex = orderColumnIndexes.get(i);
+ HfqlDataTypeEnum dataType = theExecutionResult
+ .getStatement()
+ .getSelectClauses()
+ .get(columnIndex)
+ .getDataType();
+ Comparator nextComparator = newRowComparator(columnIndex, dataType);
+ if (!orderAscending.get(i)) {
+ nextComparator = nextComparator.reversed();
+ }
+ if (comparator == null) {
+ comparator = nextComparator;
+ } else {
+ comparator = comparator.thenComparing(nextComparator);
+ }
+ }
+
+ rows.sort(comparator);
+ for (int i = 0; i < rows.size(); i++) {
+ rows.set(i, rows.get(i).toRowOffset(i));
+ }
+
+ List> rowData =
+ rows.stream().map(IHfqlExecutionResult.Row::getRowValues).collect(Collectors.toList());
+ return new StaticHfqlExecutionResult(null, theStatement, rowData);
+ }
+
+ @Override
+ public IHfqlExecutionResult executeContinuation(
+ HfqlStatement theStatement,
+ String theSearchId,
+ int theStartingOffset,
+ Integer theLimit,
+ RequestDetails theRequestDetails) {
+ IBundleProvider resultList = myPagingProvider.retrieveResultList(theRequestDetails, theSearchId);
+ HfqlExecutionContext executionContext = new HfqlExecutionContext(myFhirContext.newFhirPath());
+ Predicate whereClausePredicate = newWhereClausePredicate(executionContext, theStatement);
+ return new LocalSearchHfqlExecutionResult(
+ theStatement,
+ resultList,
+ executionContext,
+ theLimit,
+ theStartingOffset,
+ whereClausePredicate,
+ myFhirContext);
+ }
+
+ private IHfqlExecutionResult executeCountClause(
+ HfqlStatement theStatement,
+ HfqlExecutionContext theExecutionContext,
+ IBundleProvider theOutcome,
+ Predicate theWhereClausePredicate) {
+
+ Set selectClauses = theStatement.getSelectClauses().stream()
+ .filter(t -> t.getOperator() == HfqlStatement.SelectClauseOperator.SELECT)
+ .map(HfqlStatement.SelectClause::getClause)
+ .collect(Collectors.toSet());
+ for (String next : selectClauses) {
+ if (!theStatement.getGroupByClauses().contains(next)) {
+ throw newInvalidRequestCountWithSelectOnNonGroupedClause(next);
+ }
+ }
+ Set countClauses = theStatement.getSelectClauses().stream()
+ .filter(t -> t.getOperator() == HfqlStatement.SelectClauseOperator.COUNT)
+ .map(HfqlStatement.SelectClause::getClause)
+ .collect(Collectors.toSet());
+
+ Map> keyCounter = new HashMap<>();
+
+ int offset = 0;
+ int batchSize = 1000;
+ while (theOutcome.size() == null || theOutcome.sizeOrThrowNpe() > offset) {
+ List resources = theOutcome.getResources(offset, offset + batchSize);
+
+ for (IBaseResource nextResource : resources) {
+
+ if (nextResource != null && theWhereClausePredicate.test(nextResource)) {
+
+ List> groupByClauseValues = new ArrayList<>();
+
+ for (String nextClause : theStatement.getGroupByClauses()) {
+ List nextClauseValues =
+ theExecutionContext.evaluate(nextResource, nextClause, IPrimitiveType.class).stream()
+ .map(IPrimitiveType::getValueAsString)
+ .collect(Collectors.toList());
+ if (nextClauseValues.isEmpty()) {
+ nextClauseValues.add(null);
+ }
+ groupByClauseValues.add(nextClauseValues);
+ }
+ Set allKeys = createCrossProduct(groupByClauseValues);
+
+ for (GroupByKey nextKey : allKeys) {
+
+ Map counts = keyCounter.computeIfAbsent(nextKey, t -> new HashMap<>());
+ if (keyCounter.size() >= HfqlConstants.ORDER_AND_GROUP_LIMIT) {
+ throw new InvalidRequestException(Msg.code(2402) + "Can not group on > "
+ + HfqlConstants.ORDER_AND_GROUP_LIMIT + " terms");
+ }
+ for (String nextCountClause : countClauses) {
+ if (!nextCountClause.equals("*")) {
+ if (theExecutionContext
+ .evaluateFirst(nextResource, nextCountClause, IBase.class)
+ .isEmpty()) {
+ continue;
+ }
+ }
+ counts.computeIfAbsent(nextCountClause, k -> new AtomicInteger())
+ .incrementAndGet();
+ }
+ }
+ }
+ }
+
+ offset += batchSize;
+ }
+
+ List> rows = new ArrayList<>();
+ for (Map.Entry> nextEntry : keyCounter.entrySet()) {
+ List
+ */
+public interface IHfqlExecutionResult {
+
+ int ROW_OFFSET_ERROR = -1;
+
+ boolean hasNext();
+
+ Row getNextRow();
+
+ boolean isClosed();
+
+ void close();
+
+ String getSearchId();
+
+ int getLimit();
+
+ HfqlStatement getStatement();
+
+ class Row {
+
+ private final List
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-jpaserver-hfql
+ ${project.version}
+ org.hl7.fhir.testcases
diff --git a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestR5Config.java b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestR5Config.java
index cb8d9ed8d74..f4d46f5d342 100644
--- a/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestR5Config.java
+++ b/hapi-fhir-jpaserver-test-utilities/src/main/java/ca/uhn/fhir/jpa/test/config/TestR5Config.java
@@ -27,6 +27,7 @@ import ca.uhn.fhir.jpa.binstore.MemoryBinaryStorageSvcImpl;
import ca.uhn.fhir.jpa.config.HapiJpaConfig;
import ca.uhn.fhir.jpa.config.r5.JpaR5Config;
import ca.uhn.fhir.jpa.config.util.HapiEntityManagerFactoryUtil;
+import ca.uhn.fhir.jpa.fql.provider.HfqlRestProviderCtxConfig;
import ca.uhn.fhir.jpa.model.dialect.HapiFhirH2Dialect;
import ca.uhn.fhir.jpa.topic.SubscriptionTopicConfig;
import ca.uhn.fhir.jpa.util.CircularQueueCaptureQueriesListener;
@@ -61,7 +62,8 @@ import static org.junit.jupiter.api.Assertions.fail;
SubscriptionTopicConfig.class,
JpaBatch2Config.class,
Batch2JobsConfig.class,
- TestHSearchAddInConfig.DefaultLuceneHeap.class
+ TestHSearchAddInConfig.DefaultLuceneHeap.class,
+ HfqlRestProviderCtxConfig.class
})
public class TestR5Config {
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml
index edd5c0a393b..69f9825d6cf 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/pom.xml
+++ b/hapi-fhir-jpaserver-uhnfhirtest/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
@@ -59,6 +59,11 @@
hapi-fhir-jpaserver-ips${project.version}
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-jpaserver-hfql
+ ${project.version}
+ com.helger
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 47ba8f8ec7f..84e393ce4fd 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
@@ -10,6 +10,7 @@ import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.delete.ThreadSafeResourceDeleterSvc;
+import ca.uhn.fhir.jpa.fql.provider.HfqlRestProvider;
import ca.uhn.fhir.jpa.graphql.GraphQLProvider;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.ips.provider.IpsOperationProvider;
@@ -250,6 +251,7 @@ public class TestRestfulServer extends RestfulServer {
providers.add(myAppCtx.getBean(JpaSystemProvider.class));
providers.add(myAppCtx.getBean(InstanceReindexProvider.class));
+ providers.add(myAppCtx.getBean(HfqlRestProvider.class));
/*
* On the DSTU2 endpoint, we want to enable ETag support
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java
index 0db46aa8638..b28a9367462 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/main/java/ca/uhn/fhirtest/config/CommonConfig.java
@@ -6,6 +6,7 @@ import ca.uhn.fhir.interceptor.api.IInterceptorService;
import ca.uhn.fhir.jpa.api.config.JpaStorageSettings;
import ca.uhn.fhir.jpa.api.config.ThreadPoolFactoryConfig;
import ca.uhn.fhir.jpa.batch2.JpaBatch2Config;
+import ca.uhn.fhir.jpa.fql.provider.HfqlRestProviderCtxConfig;
import ca.uhn.fhir.jpa.model.config.PartitionSettings;
import ca.uhn.fhir.jpa.subscription.channel.config.SubscriptionChannelConfig;
import ca.uhn.fhir.jpa.subscription.match.config.SubscriptionProcessorConfig;
@@ -34,14 +35,13 @@ import org.springframework.context.annotation.Import;
SubscriptionSubmitterConfig.class,
JpaBatch2Config.class,
Batch2JobsConfig.class,
- ThreadPoolFactoryConfig.class
+ ThreadPoolFactoryConfig.class,
+ HfqlRestProviderCtxConfig.class
})
public class CommonConfig {
/**
* Do some fancy logging to create a nice access log that has details about each incoming request.
- *
- * @return
*/
@Bean
public LoggingInterceptor accessLoggingInterceptor() {
diff --git a/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java b/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java
index d5888a9aa87..9b5dd2c68f5 100644
--- a/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java
+++ b/hapi-fhir-jpaserver-uhnfhirtest/src/test/java/ca/uhn/fhirtest/UhnFhirTestApp.java
@@ -12,6 +12,8 @@ import org.hl7.fhir.dstu3.model.Subscription.SubscriptionChannelType;
import org.hl7.fhir.dstu3.model.Subscription.SubscriptionStatus;
import org.hl7.fhir.instance.model.api.IIdType;
+import java.sql.Driver;
+
import static ca.uhn.fhirtest.config.TestDstu3Config.FHIR_LUCENE_LOCATION_DSTU3;
public class UhnFhirTestApp {
@@ -20,6 +22,8 @@ public class UhnFhirTestApp {
public static void main(String[] args) throws Exception {
+ org.h2.Driver.load();
+
int myPort = 8889;
String base = "http://localhost:" + myPort + "/baseR4";
diff --git a/hapi-fhir-server-cds-hooks/pom.xml b/hapi-fhir-server-cds-hooks/pom.xml
index 02b890a9402..385e40cba23 100644
--- a/hapi-fhir-server-cds-hooks/pom.xml
+++ b/hapi-fhir-server-cds-hooks/pom.xml
@@ -7,7 +7,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-server-mdm/pom.xml b/hapi-fhir-server-mdm/pom.xml
index 419c8cec6a3..591eefa6abd 100644
--- a/hapi-fhir-server-mdm/pom.xml
+++ b/hapi-fhir-server-mdm/pom.xml
@@ -7,7 +7,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockListJson.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockListJson.java
index 3354030f87f..06582912e81 100644
--- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockListJson.java
+++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockListJson.java
@@ -1,3 +1,22 @@
+/*-
+ * #%L
+ * HAPI FHIR - Master Data Management
+ * %%
+ * Copyright (C) 2014 - 2023 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
package ca.uhn.fhir.mdm.blocklist.json;
import ca.uhn.fhir.model.api.IModelJson;
diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockListRuleJson.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockListRuleJson.java
index 954b5cdbbb1..5acf8eab6ca 100644
--- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockListRuleJson.java
+++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockListRuleJson.java
@@ -1,3 +1,22 @@
+/*-
+ * #%L
+ * HAPI FHIR - Master Data Management
+ * %%
+ * Copyright (C) 2014 - 2023 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
package ca.uhn.fhir.mdm.blocklist.json;
import ca.uhn.fhir.model.api.IModelJson;
diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockedFieldJson.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockedFieldJson.java
index 89aa40832dc..181f2597749 100644
--- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockedFieldJson.java
+++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/json/BlockedFieldJson.java
@@ -1,3 +1,22 @@
+/*-
+ * #%L
+ * HAPI FHIR - Master Data Management
+ * %%
+ * Copyright (C) 2014 - 2023 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
package ca.uhn.fhir.mdm.blocklist.json;
import ca.uhn.fhir.model.api.IModelJson;
diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/svc/IBlockListRuleProvider.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/svc/IBlockListRuleProvider.java
index a761f60ae6d..98df4c067d9 100644
--- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/svc/IBlockListRuleProvider.java
+++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/svc/IBlockListRuleProvider.java
@@ -1,3 +1,22 @@
+/*-
+ * #%L
+ * HAPI FHIR - Master Data Management
+ * %%
+ * Copyright (C) 2014 - 2023 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
package ca.uhn.fhir.mdm.blocklist.svc;
import ca.uhn.fhir.mdm.blocklist.json.BlockListJson;
diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/svc/IBlockRuleEvaluationSvc.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/svc/IBlockRuleEvaluationSvc.java
index 836235859d1..9231912f707 100644
--- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/svc/IBlockRuleEvaluationSvc.java
+++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/blocklist/svc/IBlockRuleEvaluationSvc.java
@@ -1,3 +1,22 @@
+/*-
+ * #%L
+ * HAPI FHIR - Master Data Management
+ * %%
+ * Copyright (C) 2014 - 2023 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
package ca.uhn.fhir.mdm.blocklist.svc;
import org.hl7.fhir.instance.model.api.IAnyResource;
diff --git a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/similarity/HapiNumericSimilarity.java b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/similarity/HapiNumericSimilarity.java
index 96aa0f0c5ec..f3bb3962165 100644
--- a/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/similarity/HapiNumericSimilarity.java
+++ b/hapi-fhir-server-mdm/src/main/java/ca/uhn/fhir/mdm/rules/similarity/HapiNumericSimilarity.java
@@ -1,3 +1,22 @@
+/*-
+ * #%L
+ * HAPI FHIR - Master Data Management
+ * %%
+ * Copyright (C) 2014 - 2023 Smile CDR, Inc.
+ * %%
+ * 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%
+ */
package ca.uhn.fhir.mdm.rules.similarity;
import ca.uhn.fhir.context.FhirContext;
diff --git a/hapi-fhir-server-openapi/pom.xml b/hapi-fhir-server-openapi/pom.xml
index 1310d658df0..c9fa2e74c2f 100644
--- a/hapi-fhir-server-openapi/pom.xml
+++ b/hapi-fhir-server-openapi/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-server/pom.xml b/hapi-fhir-server/pom.xml
index 3cbf7ba03bd..0b53a996257 100644
--- a/hapi-fhir-server/pom.xml
+++ b/hapi-fhir-server/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java
index 0f4e9ad4350..b9f0ee2cf8d 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/SimpleBundleProvider.java
@@ -25,6 +25,7 @@ import org.apache.commons.lang3.builder.ToStringBuilder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
+import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.List;
@@ -61,6 +62,15 @@ public class SimpleBundleProvider implements IBundleProvider {
this(theList, null);
}
+ /**
+ * Constructor
+ *
+ * @since 6.8.0
+ */
+ public SimpleBundleProvider(IBaseResource... theList) {
+ this(Arrays.asList(theList), null);
+ }
+
public SimpleBundleProvider(List extends IBaseResource> theList, String theUuid) {
myList = theList;
myUuid = theUuid;
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java
index 0a9435c84f1..40c74261f2f 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java
@@ -159,7 +159,7 @@ public class LoggingInterceptor {
StringLookup lookup = new MyLookup(theServletRequest, theException, theRequestDetails);
StringSubstitutor subs = new StringSubstitutor(lookup, "${", "}", '\\');
- // Actuall log the line
+ // Actually log the line
String line = subs.replace(myErrorMessageFormat);
myLogger.info(line);
}
diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml
index cd3e6ff8e96..8362787a73e 100644
--- a/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml
+++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-api/pom.xml
@@ -7,7 +7,7 @@
hapi-fhir-serviceloadersca.uhn.hapi.fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml
index f16508d221f..970b932c69d 100644
--- a/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml
+++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-caffeine/pom.xml
@@ -7,7 +7,7 @@
hapi-fhir-serviceloadersca.uhn.hapi.fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
@@ -21,7 +21,7 @@
ca.uhn.hapi.fhirhapi-fhir-caching-api
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT
diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml
index 6df3fa323c4..02e08752241 100644
--- a/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml
+++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-guava/pom.xml
@@ -7,7 +7,7 @@
hapi-fhir-serviceloadersca.uhn.hapi.fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
diff --git a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml
index acecfa6335e..a4ed61d50b8 100644
--- a/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml
+++ b/hapi-fhir-serviceloaders/hapi-fhir-caching-testing/pom.xml
@@ -7,7 +7,7 @@
hapi-fhirca.uhn.hapi.fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../../pom.xml
diff --git a/hapi-fhir-serviceloaders/pom.xml b/hapi-fhir-serviceloaders/pom.xml
index 8cd85a97f27..c35da94f679 100644
--- a/hapi-fhir-serviceloaders/pom.xml
+++ b/hapi-fhir-serviceloaders/pom.xml
@@ -5,7 +5,7 @@
hapi-deployable-pomca.uhn.hapi.fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml
index 4c8870e87a9..0304c22d60f 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml
index 528a49f0de9..44fcf6beafc 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-apache/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir-spring-boot-samples
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOThapi-fhir-spring-boot-sample-client-apache
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml
index fd04ab88699..49fecaf11bb 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-client-okhttp/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir-spring-boot-samples
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml
index a13f1a9c3c1..47351dd792a 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/hapi-fhir-spring-boot-sample-server-jersey/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir-spring-boot-samples
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml
index 8b655dfcb5e..4476b0f1980 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-samples/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir-spring-boot
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT
diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml
index fad308a93b7..7baf3ecdccd 100644
--- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml
+++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-starter/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-spring-boot/pom.xml b/hapi-fhir-spring-boot/pom.xml
index d227f9afd6b..7ac8c6d0685 100644
--- a/hapi-fhir-spring-boot/pom.xml
+++ b/hapi-fhir-spring-boot/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
diff --git a/hapi-fhir-sql-migrate/pom.xml b/hapi-fhir-sql-migrate/pom.xml
index 2ac6a404a36..add3d4f0070 100644
--- a/hapi-fhir-sql-migrate/pom.xml
+++ b/hapi-fhir-sql-migrate/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-batch2-jobs/pom.xml b/hapi-fhir-storage-batch2-jobs/pom.xml
index 088f5b64278..f1cc3fea2b3 100644
--- a/hapi-fhir-storage-batch2-jobs/pom.xml
+++ b/hapi-fhir-storage-batch2-jobs/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-batch2-test-utilities/pom.xml b/hapi-fhir-storage-batch2-test-utilities/pom.xml
index b33a242385b..c892b18b709 100644
--- a/hapi-fhir-storage-batch2-test-utilities/pom.xml
+++ b/hapi-fhir-storage-batch2-test-utilities/pom.xml
@@ -7,7 +7,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-batch2/pom.xml b/hapi-fhir-storage-batch2/pom.xml
index 946474ba180..c3bc64f4fd4 100644
--- a/hapi-fhir-storage-batch2/pom.xml
+++ b/hapi-fhir-storage-batch2/pom.xml
@@ -7,7 +7,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-cr/pom.xml b/hapi-fhir-storage-cr/pom.xml
index 0e067c2195b..40d037e4ba1 100644
--- a/hapi-fhir-storage-cr/pom.xml
+++ b/hapi-fhir-storage-cr/pom.xml
@@ -7,7 +7,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-mdm/pom.xml b/hapi-fhir-storage-mdm/pom.xml
index 3ab563f001b..749bbd937c8 100644
--- a/hapi-fhir-storage-mdm/pom.xml
+++ b/hapi-fhir-storage-mdm/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage-test-utilities/pom.xml b/hapi-fhir-storage-test-utilities/pom.xml
index 7c43992c8c0..be63f1af44f 100644
--- a/hapi-fhir-storage-test-utilities/pom.xml
+++ b/hapi-fhir-storage-test-utilities/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-storage/pom.xml b/hapi-fhir-storage/pom.xml
index 4c7205fad8f..36fa3a79489 100644
--- a/hapi-fhir-storage/pom.xml
+++ b/hapi-fhir-storage/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-dstu2.1/pom.xml b/hapi-fhir-structures-dstu2.1/pom.xml
index 0877617b503..f258cfc11ce 100644
--- a/hapi-fhir-structures-dstu2.1/pom.xml
+++ b/hapi-fhir-structures-dstu2.1/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-dstu2/pom.xml b/hapi-fhir-structures-dstu2/pom.xml
index 68c23223669..1551e8e737f 100644
--- a/hapi-fhir-structures-dstu2/pom.xml
+++ b/hapi-fhir-structures-dstu2/pom.xml
@@ -4,7 +4,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-dstu3/pom.xml b/hapi-fhir-structures-dstu3/pom.xml
index 1e3aab6e6b6..1a9aefc3675 100644
--- a/hapi-fhir-structures-dstu3/pom.xml
+++ b/hapi-fhir-structures-dstu3/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java
index 378da6bbf91..9cb7278e2be 100644
--- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java
+++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/fluentpath/FhirPathDstu3.java
@@ -8,6 +8,7 @@ import ca.uhn.fhir.fhirpath.IFhirPathEvaluationContext;
import ca.uhn.fhir.i18n.Msg;
import org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.dstu3.model.Base;
+import org.hl7.fhir.dstu3.model.ExpressionNode;
import org.hl7.fhir.dstu3.model.IdType;
import org.hl7.fhir.dstu3.model.TypeDetails;
import org.hl7.fhir.dstu3.utils.FHIRPathEngine;
@@ -21,7 +22,7 @@ import javax.annotation.Nonnull;
public class FhirPathDstu3 implements IFhirPath {
- private FHIRPathEngine myEngine;
+ private final FHIRPathEngine myEngine;
public FhirPathDstu3(FhirContext theCtx) {
IValidationSupport validationSupport = theCtx.getValidationSupport();
@@ -31,22 +32,40 @@ public class FhirPathDstu3 implements IFhirPath {
@SuppressWarnings("unchecked")
@Override
public List evaluate(IBase theInput, String thePath, Class theReturnType) {
+ ExpressionNode parsed;
+ try {
+ parsed = myEngine.parse(thePath);
+ } catch (FHIRException e) {
+ throw new FhirPathExecutionException(Msg.code(2408) + e);
+ }
+ return (List) evaluate(theInput, parsed, theReturnType);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List evaluate(
+ IBase theInput, IParsedExpression theParsedExpression, Class theReturnType) {
+ ExpressionNode expressionNode = ((ParsedExpression) theParsedExpression).myParsedExpression;
+ return (List) evaluate(theInput, expressionNode, theReturnType);
+ }
+
+ @Nonnull
+ private List evaluate(
+ IBase theInput, ExpressionNode expressionNode, Class theReturnType) {
List result;
try {
- result = myEngine.evaluate((Base) theInput, thePath);
+ result = myEngine.evaluate((Base) theInput, expressionNode);
} catch (FHIRException e) {
throw new FhirPathExecutionException(Msg.code(607) + e);
}
- for (Base next : result) {
+ for (IBase next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
- throw new FhirPathExecutionException(
- Msg.code(608) + "FluentPath expression \"" + thePath + "\" returned unexpected type "
- + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
+ throw new FhirPathExecutionException(Msg.code(608) + "FhirPath expression returned unexpected type "
+ + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}
-
- return (List) result;
+ return result;
}
@Override
@@ -55,8 +74,14 @@ public class FhirPathDstu3 implements IFhirPath {
}
@Override
- public void parse(String theExpression) {
- myEngine.parse(theExpression);
+ public Optional evaluateFirst(
+ IBase theInput, IParsedExpression theParsedExpression, Class theReturnType) {
+ return evaluate(theInput, theParsedExpression, theReturnType).stream().findFirst();
+ }
+
+ @Override
+ public IParsedExpression parse(String theExpression) {
+ return new ParsedExpression(myEngine.parse(theExpression));
}
@Override
@@ -100,4 +125,13 @@ public class FhirPathDstu3 implements IFhirPath {
}
});
}
+
+ private static class ParsedExpression implements IParsedExpression {
+
+ private final ExpressionNode myParsedExpression;
+
+ public ParsedExpression(ExpressionNode theParsedExpression) {
+ myParsedExpression = theParsedExpression;
+ }
+ }
}
diff --git a/hapi-fhir-structures-hl7org-dstu2/pom.xml b/hapi-fhir-structures-hl7org-dstu2/pom.xml
index 1b7b695b925..d9241cd91f9 100644
--- a/hapi-fhir-structures-hl7org-dstu2/pom.xml
+++ b/hapi-fhir-structures-hl7org-dstu2/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-r4/pom.xml b/hapi-fhir-structures-r4/pom.xml
index 68a6783f394..bc88f2807da 100644
--- a/hapi-fhir-structures-r4/pom.xml
+++ b/hapi-fhir-structures-r4/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java
index 72d6396638c..c8c27d02a28 100644
--- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java
+++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/fluentpath/FhirPathR4.java
@@ -11,6 +11,7 @@ import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4.model.Base;
+import org.hl7.fhir.r4.model.ExpressionNode;
import org.hl7.fhir.r4.model.IdType;
import org.hl7.fhir.r4.model.TypeDetails;
import org.hl7.fhir.r4.model.ValueSet;
@@ -35,22 +36,40 @@ public class FhirPathR4 implements IFhirPath {
@SuppressWarnings("unchecked")
@Override
public List evaluate(IBase theInput, String thePath, Class theReturnType) {
+ ExpressionNode parsed;
+ try {
+ parsed = myEngine.parse(thePath);
+ } catch (FHIRException e) {
+ throw new FhirPathExecutionException(Msg.code(2409) + e);
+ }
+ return (List) evaluate(theInput, parsed, theReturnType);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List evaluate(
+ IBase theInput, IParsedExpression theParsedExpression, Class theReturnType) {
+ ExpressionNode expressionNode = ((ParsedExpression) theParsedExpression).myParsedExpression;
+ return (List) evaluate(theInput, expressionNode, theReturnType);
+ }
+
+ @Nonnull
+ private List evaluate(
+ IBase theInput, ExpressionNode expressionNode, Class theReturnType) {
List result;
try {
- result = myEngine.evaluate((Base) theInput, thePath);
+ result = myEngine.evaluate((Base) theInput, expressionNode);
} catch (FHIRException e) {
- throw new FhirPathExecutionException(Msg.code(255) + e);
+ throw new FhirPathExecutionException(Msg.code(255) + e.getMessage(), e);
}
- for (Base next : result) {
+ for (IBase next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
- throw new FhirPathExecutionException(
- Msg.code(256) + "FluentPath expression \"" + thePath + "\" returned unexpected type "
- + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
+ throw new FhirPathExecutionException(Msg.code(256) + "FhirPath expression returned unexpected type "
+ + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}
-
- return (List) result;
+ return result;
}
@Override
@@ -59,8 +78,14 @@ public class FhirPathR4 implements IFhirPath {
}
@Override
- public void parse(String theExpression) {
- myEngine.parse(theExpression);
+ public Optional evaluateFirst(
+ IBase theInput, IParsedExpression theParsedExpression, Class theReturnType) {
+ return evaluate(theInput, theParsedExpression, theReturnType).stream().findFirst();
+ }
+
+ @Override
+ public IParsedExpression parse(String theExpression) {
+ return new ParsedExpression(myEngine.parse(theExpression));
}
@Override
@@ -116,4 +141,13 @@ public class FhirPathR4 implements IFhirPath {
}
});
}
+
+ private static class ParsedExpression implements IParsedExpression {
+
+ private final ExpressionNode myParsedExpression;
+
+ public ParsedExpression(ExpressionNode theParsedExpression) {
+ myParsedExpression = theParsedExpression;
+ }
+ }
}
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java
index 24749141df2..56f6d21f437 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/BinaryClientTest.java
@@ -29,6 +29,8 @@ import org.mockito.ArgumentCaptor;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
import static org.hamcrest.CoreMatchers.containsString;
import static org.hamcrest.MatcherAssert.assertThat;
@@ -122,6 +124,7 @@ public class BinaryClientTest {
}
+
private interface IClient extends IBasicClient {
@Read(type = Binary.class)
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java
index 852c9e1cab4..cc7a3115efa 100644
--- a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/client/GenericClientR4Test.java
@@ -48,6 +48,7 @@ import org.apache.http.client.methods.HttpPut;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
+import org.hamcrest.CoreMatchers;
import org.hamcrest.Matchers;
import org.hamcrest.core.StringContains;
import org.hl7.fhir.instance.model.api.IBaseBundle;
@@ -1023,6 +1024,40 @@ public class GenericClientR4Test extends BaseGenericClientR4Test {
assertEquals("http://example.com/fhir/$opname", capt.getAllValues().get(0).getURI().toASCIIString());
}
+ @Test
+ public void testOperationReturningArbitraryBinaryContentTextual_ReturnResourceType() throws Exception {
+ IParser p = ourCtx.newXmlParser();
+
+ Parameters inputParams = new Parameters();
+ inputParams.addParameter().setName("name").setValue(new BooleanType(true));
+
+ final String respString = "VALUE";
+ ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class);
+ when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse);
+ when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
+ when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", "text/html"));
+ when(myHttpResponse.getEntity().getContent()).thenAnswer(t -> new ReaderInputStream(new StringReader(respString), StandardCharsets.UTF_8));
+ when(myHttpResponse.getAllHeaders()).thenReturn(new Header[]{
+ new BasicHeader("content-type", "text/html")
+ });
+
+ IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
+
+ Binary binary = client
+ .operation()
+ .onServer()
+ .named("opname")
+ .withParameters(inputParams)
+ .returnResourceType(Binary.class)
+ .execute();
+
+ assertEquals(respString, new String(binary.getContent(), Charsets.UTF_8));
+ assertEquals("text/html", binary.getContentType());
+
+ assertEquals("http://example.com/fhir/$opname", capt.getAllValues().get(0).getURI().toASCIIString());
+ }
+
+
/**
* Invoke an operation that returns HTML
* as a response (a HAPI FHIR server could accomplish this by returning
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
index 56f6853282a..bdc8a63bed9 100644
--- 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
@@ -134,7 +134,7 @@ public class FhirPathFilterInterceptorTest {
String responseText = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
ourLog.info("Response:\n{}", responseText);
assertEquals(400, response.getStatusLine().getStatusCode());
- assertThat(responseText, containsString(Msg.code(327) + "Error parsing FHIRPath expression: "+Msg.code(255) + "org.hl7.fhir.exceptions.PathEngineException: Error evaluating FHIRPath expression: left operand to * can only have 1 value, but has 8 values (@char 1)"));
+ assertThat(responseText, containsString("left operand to * can only have 1 value, but has 8 values"));
}
}
diff --git a/hapi-fhir-structures-r4b/pom.xml b/hapi-fhir-structures-r4b/pom.xml
index 4fd819f4ea6..a9df6762948 100644
--- a/hapi-fhir-structures-r4b/pom.xml
+++ b/hapi-fhir-structures-r4b/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-structures-r4b/src/main/java/org/hl7/fhir/r4b/hapi/fhirpath/FhirPathR4B.java b/hapi-fhir-structures-r4b/src/main/java/org/hl7/fhir/r4b/hapi/fhirpath/FhirPathR4B.java
index af901839eba..78cc8cbc1e4 100644
--- a/hapi-fhir-structures-r4b/src/main/java/org/hl7/fhir/r4b/hapi/fhirpath/FhirPathR4B.java
+++ b/hapi-fhir-structures-r4b/src/main/java/org/hl7/fhir/r4b/hapi/fhirpath/FhirPathR4B.java
@@ -11,6 +11,7 @@ import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r4b.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r4b.model.Base;
+import org.hl7.fhir.r4b.model.ExpressionNode;
import org.hl7.fhir.r4b.model.IdType;
import org.hl7.fhir.r4b.model.TypeDetails;
import org.hl7.fhir.r4b.model.ValueSet;
@@ -22,7 +23,7 @@ import javax.annotation.Nonnull;
public class FhirPathR4B implements IFhirPath {
- private FHIRPathEngine myEngine;
+ private final FHIRPathEngine myEngine;
public FhirPathR4B(FhirContext theCtx) {
IValidationSupport validationSupport = theCtx.getValidationSupport();
@@ -32,22 +33,40 @@ public class FhirPathR4B implements IFhirPath {
@SuppressWarnings("unchecked")
@Override
public List evaluate(IBase theInput, String thePath, Class theReturnType) {
+ ExpressionNode parsed;
+ try {
+ parsed = myEngine.parse(thePath);
+ } catch (FHIRException e) {
+ throw new FhirPathExecutionException(Msg.code(2410) + e);
+ }
+ return (List) evaluate(theInput, parsed, theReturnType);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List evaluate(
+ IBase theInput, IParsedExpression theParsedExpression, Class theReturnType) {
+ ExpressionNode expressionNode = ((ParsedExpression) theParsedExpression).myParsedExpression;
+ return (List) evaluate(theInput, expressionNode, theReturnType);
+ }
+
+ @Nonnull
+ private List evaluate(
+ IBase theInput, ExpressionNode expressionNode, Class theReturnType) {
List result;
try {
- result = myEngine.evaluate((Base) theInput, thePath);
+ result = myEngine.evaluate((Base) theInput, expressionNode);
} catch (FHIRException e) {
throw new FhirPathExecutionException(Msg.code(2154) + e);
}
- for (Base next : result) {
+ for (IBase next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
- throw new FhirPathExecutionException(
- Msg.code(2155) + "FluentPath expression \"" + thePath + "\" returned unexpected type "
- + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
+ throw new FhirPathExecutionException(Msg.code(2155) + "FhirPath expression returned unexpected type "
+ + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}
-
- return (List) result;
+ return result;
}
@Override
@@ -56,8 +75,14 @@ public class FhirPathR4B implements IFhirPath {
}
@Override
- public void parse(String theExpression) {
- myEngine.parse(theExpression);
+ public Optional evaluateFirst(
+ IBase theInput, IParsedExpression theParsedExpression, Class theReturnType) {
+ return evaluate(theInput, theParsedExpression, theReturnType).stream().findFirst();
+ }
+
+ @Override
+ public IParsedExpression parse(String theExpression) {
+ return new ParsedExpression(myEngine.parse(theExpression));
}
@Override
@@ -113,4 +138,13 @@ public class FhirPathR4B implements IFhirPath {
}
});
}
+
+ private static class ParsedExpression implements IParsedExpression {
+
+ private final ExpressionNode myParsedExpression;
+
+ public ParsedExpression(ExpressionNode theParsedExpression) {
+ myParsedExpression = theParsedExpression;
+ }
+ }
}
diff --git a/hapi-fhir-structures-r5/pom.xml b/hapi-fhir-structures-r5/pom.xml
index 9d4ec1e2e1c..3d85ce48a16 100644
--- a/hapi-fhir-structures-r5/pom.xml
+++ b/hapi-fhir-structures-r5/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
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 79af5cdb729..eb5d3124b9c 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
@@ -11,6 +11,7 @@ import org.hl7.fhir.exceptions.PathEngineException;
import org.hl7.fhir.instance.model.api.IBase;
import org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext;
import org.hl7.fhir.r5.model.Base;
+import org.hl7.fhir.r5.model.ExpressionNode;
import org.hl7.fhir.r5.model.IdType;
import org.hl7.fhir.r5.model.TypeDetails;
import org.hl7.fhir.r5.model.ValueSet;
@@ -22,7 +23,7 @@ import javax.annotation.Nonnull;
public class FhirPathR5 implements IFhirPath {
- private FHIRPathEngine myEngine;
+ private final FHIRPathEngine myEngine;
public FhirPathR5(FhirContext theCtx) {
IValidationSupport validationSupport = theCtx.getValidationSupport();
@@ -30,25 +31,43 @@ public class FhirPathR5 implements IFhirPath {
myEngine.setDoNotEnforceAsSingletonRule(true);
}
- @SuppressWarnings("unchecked")
+ @SuppressWarnings({"unchecked", "unchecked"})
@Override
public List evaluate(IBase theInput, String thePath, Class theReturnType) {
+ ExpressionNode parsed;
+ try {
+ parsed = myEngine.parse(thePath);
+ } catch (FHIRException e) {
+ throw new FhirPathExecutionException(Msg.code(2411) + e);
+ }
+ return (List) evaluate(theInput, parsed, theReturnType);
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public List evaluate(
+ IBase theInput, IParsedExpression theParsedExpression, Class theReturnType) {
+ ExpressionNode expressionNode = ((ParsedExpression) theParsedExpression).myParsedExpression;
+ return (List) evaluate(theInput, expressionNode, theReturnType);
+ }
+
+ @Nonnull
+ private List evaluate(
+ IBase theInput, ExpressionNode expressionNode, Class theReturnType) {
List result;
try {
- result = myEngine.evaluate((Base) theInput, thePath);
+ result = myEngine.evaluate((Base) theInput, expressionNode);
} catch (FHIRException e) {
throw new FhirPathExecutionException(Msg.code(198) + e);
}
- for (Base next : result) {
+ for (IBase next : result) {
if (!theReturnType.isAssignableFrom(next.getClass())) {
- throw new FhirPathExecutionException(
- Msg.code(199) + "FluentPath expression \"" + thePath + "\" returned unexpected type "
- + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
+ throw new FhirPathExecutionException(Msg.code(199) + "FhirPath expression returned unexpected type "
+ + next.getClass().getSimpleName() + " - Expected " + theReturnType.getName());
}
}
-
- return (List) result;
+ return result;
}
@Override
@@ -57,8 +76,14 @@ public class FhirPathR5 implements IFhirPath {
}
@Override
- public void parse(String theExpression) {
- myEngine.parse(theExpression);
+ public Optional evaluateFirst(
+ IBase theInput, IParsedExpression theParsedExpression, Class theReturnType) {
+ return evaluate(theInput, theParsedExpression, theReturnType).stream().findFirst();
+ }
+
+ @Override
+ public IParsedExpression parse(String theExpression) {
+ return new ParsedExpression(myEngine.parse(theExpression));
}
@Override
@@ -114,4 +139,13 @@ public class FhirPathR5 implements IFhirPath {
}
});
}
+
+ private static class ParsedExpression implements IParsedExpression {
+
+ private final ExpressionNode myParsedExpression;
+
+ public ParsedExpression(ExpressionNode theParsedExpression) {
+ myParsedExpression = theParsedExpression;
+ }
+ }
}
diff --git a/hapi-fhir-test-utilities/pom.xml b/hapi-fhir-test-utilities/pom.xml
index aca2a5ce85d..62ddd54bac4 100644
--- a/hapi-fhir-test-utilities/pom.xml
+++ b/hapi-fhir-test-utilities/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-deployable-pom
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../hapi-deployable-pom/pom.xml
diff --git a/hapi-fhir-testpage-overlay/pom.xml b/hapi-fhir-testpage-overlay/pom.xml
index db3587992ea..b8c034fc801 100644
--- a/hapi-fhir-testpage-overlay/pom.xml
+++ b/hapi-fhir-testpage-overlay/pom.xml
@@ -5,7 +5,7 @@
ca.uhn.hapi.fhirhapi-fhir
- 6.7.14-SNAPSHOT
+ 6.7.15-SNAPSHOT../pom.xml
@@ -77,6 +77,13 @@
hapi-fhir-structures-r5${project.version}
+
+ ca.uhn.hapi.fhir
+ hapi-fhir-jpaserver-hfql
+ ${project.version}
+ compile
+
+
com.google.code.gson
@@ -141,6 +148,10 @@
+
+ org.webjars.npm
+ ace-builds
+ org.webjars.npmbootstrap
diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java
index 6aca81d697d..0b9d14f3162 100644
--- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java
+++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/BaseController.java
@@ -101,7 +101,7 @@ public class BaseController {
return retVal.toArray(new Header[retVal.size()]);
}
- private String format(String theResultBody, EncodingEnum theEncodingEnum) {
+ private static String format(String theResultBody, EncodingEnum theEncodingEnum) {
String str = StringEscapeUtils.escapeHtml4(theResultBody);
if (str == null || theEncodingEnum == null) {
return str;
@@ -402,6 +402,11 @@ public class BaseController {
theModel.put("requiredParamExtension", ExtensionConstants.PARAM_IS_REQUIRED);
theModel.put("conf", capabilityStatement);
+
+ boolean supportsHfql = capabilityStatement.getRestFirstRep().getOperation().stream()
+ .anyMatch(t -> "hfql-execute".equals(t.getName()));
+ theModel.put("supportsHfql", supportsHfql);
+
return capabilityStatement;
}
@@ -696,4 +701,8 @@ public class BaseController {
}
return retVal;
}
+
+ public static String formatAsJson(String theInput) {
+ return format(defaultString(theInput), EncodingEnum.JSON);
+ }
}
diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java
index e0ad5f3dfdd..93aff29f414 100644
--- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java
+++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/Controller.java
@@ -4,6 +4,11 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.i18n.Msg;
+import ca.uhn.fhir.jpa.fql.executor.HfqlDataTypeEnum;
+import ca.uhn.fhir.jpa.fql.executor.IHfqlExecutionResult;
+import ca.uhn.fhir.jpa.fql.jdbc.RemoteHfqlExecutionResult;
+import ca.uhn.fhir.jpa.fql.parser.HfqlStatement;
+import ca.uhn.fhir.jpa.fql.provider.HfqlRestProvider;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
import ca.uhn.fhir.model.primitive.BoundCodeDt;
@@ -14,21 +19,14 @@ import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.client.impl.GenericClient;
-import ca.uhn.fhir.rest.gclient.ICreateTyped;
-import ca.uhn.fhir.rest.gclient.IHistory;
-import ca.uhn.fhir.rest.gclient.IHistoryTyped;
-import ca.uhn.fhir.rest.gclient.IHistoryUntyped;
-import ca.uhn.fhir.rest.gclient.IQuery;
-import ca.uhn.fhir.rest.gclient.IUntypedQuery;
import ca.uhn.fhir.rest.gclient.NumberClientParam.IMatches;
-import ca.uhn.fhir.rest.gclient.QuantityClientParam;
import ca.uhn.fhir.rest.gclient.QuantityClientParam.IAndUnits;
-import ca.uhn.fhir.rest.gclient.StringClientParam;
-import ca.uhn.fhir.rest.gclient.TokenClientParam;
+import ca.uhn.fhir.rest.gclient.*;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.to.model.HomeRequest;
import ca.uhn.fhir.to.model.ResourceRequest;
import ca.uhn.fhir.to.model.TransactionRequest;
+import ca.uhn.fhir.to.util.HfqlRenderingUtil;
import ca.uhn.fhir.util.StopWatch;
import com.google.gson.stream.JsonWriter;
import org.apache.commons.lang3.StringUtils;
@@ -41,9 +39,12 @@ import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseConformance;
import org.hl7.fhir.instance.model.api.IBaseParameters;
import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.model.Parameters;
import org.springframework.ui.ModelMap;
import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RequestMethod;
+import org.springframework.web.bind.annotation.RequestParam;
import java.io.IOException;
import java.io.StringWriter;
@@ -51,20 +52,20 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.TreeSet;
+import java.util.stream.Collectors;
+import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import static ca.uhn.fhir.rest.server.provider.ProviderConstants.DIFF_OPERATION_NAME;
import static ca.uhn.fhir.util.UrlUtil.sanitizeUrlPart;
-import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
-import static org.apache.commons.lang3.StringUtils.defaultString;
-import static org.apache.commons.lang3.StringUtils.isBlank;
-import static org.apache.commons.lang3.StringUtils.isNotBlank;
+import static org.apache.commons.lang3.StringUtils.*;
@org.springframework.stereotype.Controller()
public class Controller extends BaseController {
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Controller.class);
+ public static final int ROW_LIMIT = 200;
@RequestMapping(value = {"/about"})
public String actionAbout(
@@ -202,7 +203,7 @@ public class Controller extends BaseController {
final BindingResult theBindingResult,
final ModelMap theModel) {
addCommonParams(theServletRequest, theRequest, theModel);
- ourLog.info(theServletRequest.toString());
+ theModel.put("page", "home");
return "home";
}
@@ -337,6 +338,98 @@ public class Controller extends BaseController {
return "resource";
}
+ @RequestMapping(
+ value = {"/hfql"},
+ method = RequestMethod.GET)
+ public String actionHfqlHome(
+ HttpServletRequest theServletRequest,
+ @RequestParam(value = "hfql-query", required = false) String theHfqlQuery,
+ final HomeRequest theRequest,
+ final BindingResult theBindingResult,
+ final ModelMap theModel) {
+ addCommonParamsForHfql(theServletRequest, theRequest, theModel);
+
+ String query = theHfqlQuery;
+ if (isBlank(query)) {
+ query = "SELECT\n" + " id AS ID, meta.versionId AS Version,\n"
+ + " name[0].family AS FamilyName, name[0].given[0] AS GivenName,\n"
+ + " identifier AS Identifiers\n"
+ + "FROM\n"
+ + " Patient";
+ }
+
+ theModel.put("query", query);
+ return "hfql";
+ }
+
+ @RequestMapping(
+ value = {"/hfql"},
+ method = RequestMethod.POST)
+ public String actionHfqlExecuteQuery(
+ HttpServletRequest theServletRequest,
+ @RequestParam("hfql-query") String theHfqlQuery,
+ final HomeRequest theRequest,
+ final BindingResult theBindingResult,
+ final ModelMap theModel) {
+ addCommonParamsForHfql(theServletRequest, theRequest, theModel);
+
+ ourLog.info("Executing HFQL query: {}", theHfqlQuery);
+ StopWatch sw = new StopWatch();
+
+ List> rows = new ArrayList<>();
+ try {
+ IHfqlExecutionResult result = executeHfqlStatement(theServletRequest, theHfqlQuery, theRequest, ROW_LIMIT);
+ while (result.hasNext()) {
+ List nextRowValues = result.getNextRow().getRowValues();
+ if (nextRowValues.size() >= 1) {
+ List nextRow = nextRowValues.stream()
+ .map(t -> t != null ? t.toString() : null)
+ .collect(Collectors.toList());
+ rows.add(nextRow);
+ }
+ }
+
+ List columnNames = result.getStatement().getSelectClauses().stream()
+ .map(HfqlStatement.SelectClause::getAlias)
+ .collect(Collectors.toList());
+ theModel.put("columnNames", columnNames);
+ List columnTypes = result.getStatement().getSelectClauses().stream()
+ .map(t -> t.getDataType().name())
+ .collect(Collectors.toList());
+ theModel.put("columnTypes", columnTypes);
+
+ } catch (IOException e) {
+ ourLog.warn("Failed to execute HFQL query: {}", e.toString());
+ theModel.put("columnNames", List.of("Error"));
+ theModel.put("columnTypes", List.of(HfqlDataTypeEnum.STRING));
+ rows = List.of(List.of(e.getMessage()));
+ }
+
+ theModel.put("HfqlRenderingUtil", new HfqlRenderingUtil());
+ theModel.put("query", theHfqlQuery);
+ theModel.put("resultRows", rows);
+ theModel.put("executionTime", sw.toString());
+
+ return "hfql";
+ }
+
+ @Nonnull
+ protected IHfqlExecutionResult executeHfqlStatement(
+ HttpServletRequest theServletRequest, String theHfqlQuery, HomeRequest theRequest, int theRowLimit)
+ throws IOException {
+ Parameters requestParameters =
+ HfqlRestProvider.newQueryRequestParameters(theHfqlQuery, theRowLimit, theRowLimit);
+ GenericClient client = theRequest.newClient(theServletRequest, getContext(theRequest), myConfig, null);
+ return new RemoteHfqlExecutionResult(requestParameters, client);
+ }
+
+ protected org.hl7.fhir.r5.model.CapabilityStatement addCommonParamsForHfql(
+ HttpServletRequest theServletRequest, HomeRequest theRequest, ModelMap theModel) {
+ theModel.put("page", "hfql");
+ theModel.put("rowLimit", ROW_LIMIT);
+ return super.addCommonParams(theServletRequest, theRequest, theModel);
+ }
+
private void populateModelForResource(
HttpServletRequest theServletRequest, HomeRequest theRequest, ModelMap theModel) {
org.hl7.fhir.r5.model.CapabilityStatement conformance =
@@ -739,10 +832,6 @@ public class Controller extends BaseController {
return "result";
}
- private static ResultType getReturnedTypeBasedOnOperation(@Nullable String operationName) {
- return DIFF_OPERATION_NAME.equals(operationName) ? ResultType.PARAMETERS : ResultType.BUNDLE;
- }
-
private void doActionHistory(
HttpServletRequest theReq,
HomeRequest theRequest,
@@ -1102,4 +1191,8 @@ public class Controller extends BaseController {
return true;
}
+
+ private static ResultType getReturnedTypeBasedOnOperation(@Nullable String operationName) {
+ return DIFF_OPERATION_NAME.equals(operationName) ? ResultType.PARAMETERS : ResultType.BUNDLE;
+ }
}
diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/FhirTesterMvcConfig.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/FhirTesterMvcConfig.java
index 7cc02fd2a19..4030fd750ee 100644
--- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/FhirTesterMvcConfig.java
+++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/FhirTesterMvcConfig.java
@@ -24,6 +24,7 @@ public class FhirTesterMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(@Nonnull ResourceHandlerRegistry theRegistry) {
+ WebUtil.webJarAddAceBuilds(theRegistry);
WebUtil.webJarAddBoostrap(theRegistry);
WebUtil.webJarAddJQuery(theRegistry);
WebUtil.webJarAddFontAwesome(theRegistry);
diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/model/HomeRequest.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/model/HomeRequest.java
index 0ab72bc77a7..c7db6634533 100644
--- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/model/HomeRequest.java
+++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/model/HomeRequest.java
@@ -13,6 +13,7 @@ import ca.uhn.fhir.to.Controller;
import ca.uhn.fhir.to.TesterConfig;
import org.springframework.web.bind.annotation.ModelAttribute;
+import javax.annotation.Nullable;
import javax.servlet.http.HttpServletRequest;
import static org.apache.commons.lang3.StringUtils.*;
@@ -122,8 +123,11 @@ public class HomeRequest {
HttpServletRequest theRequest,
FhirContext theContext,
TesterConfig theConfig,
- Controller.CaptureInterceptor theInterceptor) {
+ @Nullable Controller.CaptureInterceptor theInterceptor) {
theContext.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
+ theContext.getRestfulClientFactory().setConnectTimeout(60 * 1000);
+ theContext.getRestfulClientFactory().setConnectionRequestTimeout(60 * 1000);
+ theContext.getRestfulClientFactory().setSocketTimeout(60 * 1000);
GenericClient retVal;
ITestingUiClientFactory clientFactory = theConfig.getClientFactory();
@@ -157,7 +161,9 @@ public class HomeRequest {
}
}
- retVal.registerInterceptor(theInterceptor);
+ if (theInterceptor != null) {
+ retVal.registerInterceptor(theInterceptor);
+ }
final String remoteAddr = org.slf4j.MDC.get("req.remoteAddr");
retVal.registerInterceptor(new IClientInterceptor() {
diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/util/HfqlRenderingUtil.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/util/HfqlRenderingUtil.java
new file mode 100644
index 00000000000..b60b859a8eb
--- /dev/null
+++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/util/HfqlRenderingUtil.java
@@ -0,0 +1,10 @@
+package ca.uhn.fhir.to.util;
+
+import ca.uhn.fhir.to.BaseController;
+
+public class HfqlRenderingUtil {
+
+ public String formatAsJson(String theInput) {
+ return BaseController.formatAsJson(theInput);
+ }
+}
diff --git a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/util/WebUtil.java b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/util/WebUtil.java
index d2c16d0fff9..395831cfecb 100644
--- a/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/util/WebUtil.java
+++ b/hapi-fhir-testpage-overlay/src/main/java/ca/uhn/fhir/to/util/WebUtil.java
@@ -47,6 +47,10 @@ public class WebUtil {
.addResourceLocations("classpath:/META-INF/resources/webjars/" + name + "/" + version + "/");
}
+ public static void webJarAddAceBuilds(ResourceHandlerRegistry theRegistry) {
+ WebUtil.addStaticResourceWebJar(theRegistry, "org.webjars.npm", "ace-builds");
+ }
+
public static void webJarAddAwesomeCheckbox(ResourceHandlerRegistry theRegistry) {
WebUtil.addStaticResourceWebJar(theRegistry, "org.webjars.bower", "awesome-bootstrap-checkbox");
}
diff --git a/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/hfql.html b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/hfql.html
new file mode 100644
index 00000000000..0f6ec7d7149
--- /dev/null
+++ b/hapi-fhir-testpage-overlay/src/main/webapp/WEB-INF/templates/hfql.html
@@ -0,0 +1,145 @@
+
+
+
+
+
+
+
+
+
+
+
+
+