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