Apply SearchNarrowingInterceptor to conditional URLs (#5712)
* Fix #5110 - Failure in tx processing * Test fix * Work on narrowins * Add changelog * Docs cleanup * Fix compile error * Rollback incompatible change * Test fix * Test fix * Force update * Test fixes * Build fixes * Bump HTMLUnit * Update hapi-fhir-docs/src/main/resources/ca/uhn/hapi/fhir/changelog/7_2_0/5712-apply-searchnarrowing-to-conditional-urls.yaml Co-authored-by: Ken Stevens <khstevens@gmail.com> * Address review comments * Version bump to 7.1.4-SNAPSHOT * Spotless * Roll back version --------- Co-authored-by: Ken Stevens <khstevens@gmail.com>
This commit is contained in:
parent
c7f413d6ea
commit
ec525f4457
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -627,6 +627,8 @@ public class BundleUtil {
|
||||||
//noinspection EnumSwitchStatementWhichMissesCases
|
//noinspection EnumSwitchStatementWhichMissesCases
|
||||||
switch (requestType) {
|
switch (requestType) {
|
||||||
case PUT:
|
case PUT:
|
||||||
|
case DELETE:
|
||||||
|
case PATCH:
|
||||||
conditionalUrl = url != null && url.contains("?") ? url : null;
|
conditionalUrl = url != null && url.contains("?") ? url : null;
|
||||||
break;
|
break;
|
||||||
case POST:
|
case POST:
|
||||||
|
|
|
@ -315,6 +315,7 @@ public class UrlUtil {
|
||||||
return theCtx.getResourceDefinition(resourceName);
|
return theCtx.getResourceDefinition(resourceName);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nonnull
|
||||||
public static Map<String, String[]> parseQueryString(String theQueryString) {
|
public static Map<String, String[]> parseQueryString(String theQueryString) {
|
||||||
HashMap<String, List<String>> map = new HashMap<>();
|
HashMap<String, List<String>> map = new HashMap<>();
|
||||||
parseQueryString(theQueryString, map);
|
parseQueryString(theQueryString, map);
|
||||||
|
|
|
@ -69,4 +69,12 @@ public class BundleEntryMutator {
|
||||||
BaseRuntimeChildDefinition resourceChild = myEntryDefinition.getChildByName("resource");
|
BaseRuntimeChildDefinition resourceChild = myEntryDefinition.getChildByName("resource");
|
||||||
resourceChild.getMutator().setValue(myEntry, theUpdatedResource);
|
resourceChild.getMutator().setValue(myEntry, theUpdatedResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setRequestIfNoneExist(FhirContext theFhirContext, String theIfNoneExist) {
|
||||||
|
BaseRuntimeChildDefinition requestUrlChildDef = myRequestChildContentsDef.getChildByName("ifNoneExist");
|
||||||
|
IPrimitiveType<?> url = ParametersUtil.createString(theFhirContext, theIfNoneExist);
|
||||||
|
for (IBase nextRequest : myRequestChildDef.getAccessor().getValues(myEntry)) {
|
||||||
|
requestUrlChildDef.getMutator().addValue(nextRequest, url);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,7 @@
|
||||||
package ca.uhn.fhir.util.bundle;
|
package ca.uhn.fhir.util.bundle;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
public class ModifiableBundleEntry {
|
public class ModifiableBundleEntry {
|
||||||
|
@ -58,4 +59,16 @@ public class ModifiableBundleEntry {
|
||||||
public void setResource(IBaseResource theUpdatedResource) {
|
public void setResource(IBaseResource theUpdatedResource) {
|
||||||
myBundleEntryMutator.setResource(theUpdatedResource);
|
myBundleEntryMutator.setResource(theUpdatedResource);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public RequestTypeEnum getRequestMethod() {
|
||||||
|
return myBundleEntryParts.getRequestType();
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getConditionalUrl() {
|
||||||
|
return myBundleEntryParts.getConditionalUrl();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setRequestIfNoneExist(FhirContext theFhirContext, String theIfNoneExist) {
|
||||||
|
myBundleEntryMutator.setRequestIfNoneExist(theFhirContext, theIfNoneExist);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-bom</artifactId>
|
<artifactId>hapi-fhir-bom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>HAPI FHIR BOM</name>
|
<name>HAPI FHIR BOM</name>
|
||||||
|
@ -12,7 +12,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-cli</artifactId>
|
<artifactId>hapi-fhir-cli</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -55,13 +55,13 @@ import java.util.List;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
@SuppressWarnings("unused")
|
|
||||||
/**
|
/**
|
||||||
* Examples integrated into our documentation.
|
* Examples integrated into our documentation.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings({"unused", "WriteOnlyObject", "UnnecessaryLocalVariable"})
|
||||||
public class AuthorizationInterceptors {
|
public class AuthorizationInterceptors {
|
||||||
|
|
||||||
public class PatientResourceProvider implements IResourceProvider {
|
public static class PatientResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Class<? extends IBaseResource> getResourceType() {
|
public Class<? extends IBaseResource> getResourceType() {
|
||||||
|
@ -74,8 +74,8 @@ public class AuthorizationInterceptors {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings({"ConstantConditions", "InnerClassMayBeStatic"})
|
||||||
// START SNIPPET: patientAndAdmin
|
// START SNIPPET: patientAndAdmin
|
||||||
@SuppressWarnings("ConstantConditions")
|
|
||||||
public class PatientAndAdminAuthorizationInterceptor extends AuthorizationInterceptor {
|
public class PatientAndAdminAuthorizationInterceptor extends AuthorizationInterceptor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -265,6 +265,7 @@ public class AuthorizationInterceptors {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("InnerClassMayBeStatic")
|
||||||
// START SNIPPET: narrowing
|
// START SNIPPET: narrowing
|
||||||
public class MyPatientSearchNarrowingInterceptor extends SearchNarrowingInterceptor {
|
public class MyPatientSearchNarrowingInterceptor extends SearchNarrowingInterceptor {
|
||||||
|
|
||||||
|
@ -300,6 +301,13 @@ public class AuthorizationInterceptors {
|
||||||
}
|
}
|
||||||
// END SNIPPET: narrowing
|
// END SNIPPET: narrowing
|
||||||
|
|
||||||
|
public void narrowingConditional() {
|
||||||
|
// START SNIPPET: narrowingConditional
|
||||||
|
SearchNarrowingInterceptor interceptor = new SearchNarrowingInterceptor();
|
||||||
|
interceptor.setNarrowConditionalUrls(true);
|
||||||
|
// END SNIPPET: narrowingConditional
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("SpellCheckingInspection")
|
@SuppressWarnings("SpellCheckingInspection")
|
||||||
public void rsNarrowing() {
|
public void rsNarrowing() {
|
||||||
RestfulServer restfulServer = new RestfulServer();
|
RestfulServer restfulServer = new RestfulServer();
|
||||||
|
@ -330,6 +338,7 @@ public class AuthorizationInterceptors {
|
||||||
// END SNIPPET: rsnarrowing
|
// END SNIPPET: rsnarrowing
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("InnerClassMayBeStatic")
|
||||||
// START SNIPPET: narrowingByCode
|
// START SNIPPET: narrowingByCode
|
||||||
public class MyCodeSearchNarrowingInterceptor extends SearchNarrowingInterceptor {
|
public class MyCodeSearchNarrowingInterceptor extends SearchNarrowingInterceptor {
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
---
|
||||||
|
type: add
|
||||||
|
jira: SMILE-7971
|
||||||
|
issue: 5712
|
||||||
|
title: "The SearchNarrowingInterceptor can now optionally be configured to also apply
|
||||||
|
URL narrowing to conditional URLs used by conditional create/update/delete/patch
|
||||||
|
operations, both as raw HTTP transactions as well as within FHIR transaction
|
||||||
|
Bundles."
|
|
@ -193,7 +193,7 @@ HAPI FHIR provides an interceptor that can be used to implement consent rules an
|
||||||
|
|
||||||
# Security: Search Narrowing
|
# Security: Search Narrowing
|
||||||
|
|
||||||
HAPI FHIR provides an interceptor that can be used to implement consent rules and directives. See [Consent Interceptor](/docs/security/consent_interceptor.html) for more information.
|
HAPI FHIR provides an interceptor that can be used to implement consent rules and directives. See [Search Narrowing Interceptor](/docs/security/search_narrowing_interceptor.html) for more information.
|
||||||
|
|
||||||
|
|
||||||
# Security: Rejecting Unsupported HTTP Verbs
|
# Security: Rejecting Unsupported HTTP Verbs
|
||||||
|
|
|
@ -25,6 +25,24 @@ An example of this interceptor follows:
|
||||||
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|narrowing}}
|
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|narrowing}}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
# Narrowing Conditional URLs
|
||||||
|
|
||||||
|
By default, this interceptor will narrow URLs for FHIR search operations only. The
|
||||||
|
interceptor can also be configured to narrow URLs on conditional operations.
|
||||||
|
|
||||||
|
When this feature is enabled request URLs are also narrowed for the following FHIR operations:
|
||||||
|
|
||||||
|
* Conditional Create (The `If-None-Exist` header is narrowed)
|
||||||
|
* Conditional Update (The request URL is narrowed if it is a conditional URL)
|
||||||
|
* Conditional Delete (The request URL is narrowed if it is a conditional URL)
|
||||||
|
* Conditional Patch (The request URL is narrowed if it is a conditional URL)
|
||||||
|
|
||||||
|
The following example shows how to enable conditional URL narrowing on the interceptor.
|
||||||
|
|
||||||
|
```java
|
||||||
|
{{snippet:classpath:/ca/uhn/hapi/fhir/docs/AuthorizationInterceptors.java|narrowingConditional}}
|
||||||
|
```
|
||||||
|
|
||||||
<a name="constraining-by-valueset-membership"/>
|
<a name="constraining-by-valueset-membership"/>
|
||||||
|
|
||||||
# Constraining by ValueSet Membership
|
# Constraining by ValueSet Membership
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -117,6 +117,16 @@ public class JaxRsRequest extends RequestDetails {
|
||||||
return requestHeader == null ? Collections.<String>emptyList() : requestHeader;
|
return requestHeader == null ? Collections.<String>emptyList() : requestHeader;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeader(String theName, String theValue) {
|
||||||
|
throw new UnsupportedOperationException(Msg.code(2499) + "Headers can not be modified in JAX-RS");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeaders(String theName, List<String> theValue) {
|
||||||
|
throw new UnsupportedOperationException(Msg.code(2500) + "Headers can not be modified in JAX-RS");
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getAttribute(String theAttributeName) {
|
public Object getAttribute(String theAttributeName) {
|
||||||
return myAttributes.get(theAttributeName);
|
return myAttributes.get(theAttributeName);
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -3,7 +3,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -567,12 +567,13 @@ public class SystemProviderR4Test extends BaseJpaR4Test {
|
||||||
MyAnonymousInterceptor1 interceptor1 = new MyAnonymousInterceptor1();
|
MyAnonymousInterceptor1 interceptor1 = new MyAnonymousInterceptor1();
|
||||||
ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED, interceptor1);
|
ourRestServer.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED, interceptor1);
|
||||||
MySearchNarrowingInterceptor interceptor2 = new MySearchNarrowingInterceptor();
|
MySearchNarrowingInterceptor interceptor2 = new MySearchNarrowingInterceptor();
|
||||||
|
interceptor2.setNarrowConditionalUrls(true);
|
||||||
ourRestServer.getInterceptorService().registerInterceptor(interceptor2);
|
ourRestServer.getInterceptorService().registerInterceptor(interceptor2);
|
||||||
try {
|
try {
|
||||||
myClient.transaction().withBundle(input).execute();
|
myClient.transaction().withBundle(input).execute();
|
||||||
assertEquals(1, counter0.get());
|
assertEquals(1, counter0.get());
|
||||||
assertEquals(1, counter1.get());
|
assertEquals(1, counter1.get());
|
||||||
assertEquals(5, counter2.get());
|
assertEquals(1, counter2.get());
|
||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
ourRestServer.getInterceptorService().unregisterInterceptor(interceptor1);
|
ourRestServer.getInterceptorService().unregisterInterceptor(interceptor1);
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -6,7 +6,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -249,6 +249,24 @@ public abstract class RequestDetails {
|
||||||
|
|
||||||
public abstract List<String> getHeaders(String name);
|
public abstract List<String> getHeaders(String name);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new header
|
||||||
|
*
|
||||||
|
* @param theName The header name
|
||||||
|
* @param theValue The header value
|
||||||
|
* @since 7.2.0
|
||||||
|
*/
|
||||||
|
public abstract void addHeader(String theName, String theValue);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces any existing header(s) with the given name using a List of new header values
|
||||||
|
*
|
||||||
|
* @param theName The header name
|
||||||
|
* @param theValue The header value
|
||||||
|
* @since 7.2.0
|
||||||
|
*/
|
||||||
|
public abstract void setHeaders(String theName, List<String> theValue);
|
||||||
|
|
||||||
public IIdType getId() {
|
public IIdType getId() {
|
||||||
return myId;
|
return myId;
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,9 +33,9 @@ import ca.uhn.fhir.rest.server.IPagingProvider;
|
||||||
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
|
||||||
import com.google.common.collect.ImmutableListMultimap;
|
import com.google.common.collect.ImmutableListMultimap;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import com.google.common.collect.MultimapBuilder;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -124,13 +124,27 @@ public class SystemRequestDetails extends RequestDetails {
|
||||||
return headers.get(name);
|
return headers.get(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void addHeader(String theName, String theValue) {
|
public void addHeader(String theName, String theValue) {
|
||||||
if (myHeaders == null) {
|
initHeaderMap();
|
||||||
myHeaders = ArrayListMultimap.create();
|
|
||||||
}
|
|
||||||
myHeaders.put(theName, theValue);
|
myHeaders.put(theName, theValue);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeaders(String theName, List<String> theValues) {
|
||||||
|
initHeaderMap();
|
||||||
|
myHeaders.putAll(theName, theValues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initHeaderMap() {
|
||||||
|
if (myHeaders == null) {
|
||||||
|
// Make sure we are case-insensitive on keys
|
||||||
|
myHeaders = MultimapBuilder.treeKeys(String.CASE_INSENSITIVE_ORDER)
|
||||||
|
.arrayListValues()
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getAttribute(String theAttributeName) {
|
public Object getAttribute(String theAttributeName) {
|
||||||
return null;
|
return null;
|
||||||
|
@ -145,7 +159,7 @@ public class SystemRequestDetails extends RequestDetails {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Reader getReader() throws IOException {
|
public Reader getReader() {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Server Framework
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request object for {@link ca.uhn.fhir.interceptor.api.Pointcut#STORAGE_CONDITIONAL_URL_PREPROCESS}
|
||||||
|
*/
|
||||||
|
public class ConditionalUrlRequest {}
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*-
|
||||||
|
* #%L
|
||||||
|
* HAPI FHIR - Server Framework
|
||||||
|
* %%
|
||||||
|
* Copyright (C) 2014 - 2024 Smile CDR, Inc.
|
||||||
|
* %%
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
* #L%
|
||||||
|
*/
|
||||||
|
package ca.uhn.fhir.rest.server.interceptor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Request object for {@link ca.uhn.fhir.interceptor.api.Pointcut#STORAGE_CONDITIONAL_URL_PREPROCESS}
|
||||||
|
*/
|
||||||
|
public class ConditionalUrlResponse {}
|
|
@ -30,21 +30,21 @@ import ca.uhn.fhir.interceptor.api.Hook;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||||
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||||
import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
|
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletSubRequestDetails;
|
|
||||||
import ca.uhn.fhir.rest.server.util.ServletRequestUtil;
|
|
||||||
import ca.uhn.fhir.util.BundleUtil;
|
import ca.uhn.fhir.util.BundleUtil;
|
||||||
import ca.uhn.fhir.util.FhirTerser;
|
import ca.uhn.fhir.util.FhirTerser;
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import ca.uhn.fhir.util.ValidateUtil;
|
import ca.uhn.fhir.util.ValidateUtil;
|
||||||
import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
|
import ca.uhn.fhir.util.bundle.ModifiableBundleEntry;
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import com.google.common.collect.MultimapBuilder;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
import jakarta.annotation.Nullable;
|
import jakarta.annotation.Nullable;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
@ -65,6 +65,8 @@ import java.util.Set;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This interceptor can be used to automatically narrow the scope of searches in order to
|
* This interceptor can be used to automatically narrow the scope of searches in order to
|
||||||
* automatically restrict the searches to specific compartments.
|
* automatically restrict the searches to specific compartments.
|
||||||
|
@ -88,12 +90,26 @@ import java.util.stream.Collectors;
|
||||||
*
|
*
|
||||||
* @see AuthorizationInterceptor
|
* @see AuthorizationInterceptor
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("JavadocLinkAsPlainText")
|
||||||
public class SearchNarrowingInterceptor {
|
public class SearchNarrowingInterceptor {
|
||||||
|
|
||||||
public static final String POST_FILTERING_LIST_ATTRIBUTE_NAME =
|
public static final String POST_FILTERING_LIST_ATTRIBUTE_NAME =
|
||||||
SearchNarrowingInterceptor.class.getName() + "_POST_FILTERING_LIST";
|
SearchNarrowingInterceptor.class.getName() + "_POST_FILTERING_LIST";
|
||||||
private IValidationSupport myValidationSupport;
|
private IValidationSupport myValidationSupport;
|
||||||
private int myPostFilterLargeValueSetThreshold = 500;
|
private int myPostFilterLargeValueSetThreshold = 500;
|
||||||
|
private boolean myNarrowConditionalUrls;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If set to {@literal true} (default is {@literal false}), conditional URLs such
|
||||||
|
* as the If-None-Exist header used for Conditional Create operations will
|
||||||
|
* also be narrowed.
|
||||||
|
*
|
||||||
|
* @param theNarrowConditionalUrls Should we narrow conditional URLs in requests
|
||||||
|
* @since 7.2.0
|
||||||
|
*/
|
||||||
|
public void setNarrowConditionalUrls(boolean theNarrowConditionalUrls) {
|
||||||
|
myNarrowConditionalUrls = theNarrowConditionalUrls;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Supplies a threshold over which any ValueSet-based rules will be applied by
|
* Supplies a threshold over which any ValueSet-based rules will be applied by
|
||||||
|
@ -126,6 +142,68 @@ public class SearchNarrowingInterceptor {
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method handles narrowing for FHIR search/create/update/patch operations.
|
||||||
|
*
|
||||||
|
* @see #hookIncomingRequestPreHandled(ServletRequestDetails, HttpServletRequest, HttpServletResponse) This method narrows FHIR transaction bundles
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
|
||||||
|
@Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)
|
||||||
|
public void hookIncomingRequestPostProcessed(
|
||||||
|
RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse)
|
||||||
|
throws AuthenticationException {
|
||||||
|
|
||||||
|
// We don't support this operation type yet
|
||||||
|
RestOperationTypeEnum restOperationType = theRequestDetails.getRestOperationType();
|
||||||
|
Validate.isTrue(restOperationType != RestOperationTypeEnum.SEARCH_SYSTEM);
|
||||||
|
|
||||||
|
switch (restOperationType) {
|
||||||
|
case EXTENDED_OPERATION_INSTANCE:
|
||||||
|
case EXTENDED_OPERATION_TYPE: {
|
||||||
|
if ("$everything".equals(theRequestDetails.getOperation())) {
|
||||||
|
narrowEverythingOperation(theRequestDetails);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case SEARCH_TYPE:
|
||||||
|
narrowTypeSearch(theRequestDetails);
|
||||||
|
break;
|
||||||
|
case CREATE:
|
||||||
|
narrowIfNoneExistHeader(theRequestDetails);
|
||||||
|
break;
|
||||||
|
case DELETE:
|
||||||
|
case UPDATE:
|
||||||
|
case PATCH:
|
||||||
|
narrowRequestUrl(theRequestDetails, restOperationType);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method narrows FHIR transaction operations (because this pointcut
|
||||||
|
* is called after the request body is parsed).
|
||||||
|
*
|
||||||
|
* @see #hookIncomingRequestPostProcessed(RequestDetails, HttpServletRequest, HttpServletResponse) This method narrows FHIR search/create/update/etc operations
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
|
||||||
|
@Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED)
|
||||||
|
public void hookIncomingRequestPreHandled(
|
||||||
|
ServletRequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse)
|
||||||
|
throws AuthenticationException {
|
||||||
|
|
||||||
|
if (theRequestDetails.getRestOperationType() != null) {
|
||||||
|
switch (theRequestDetails.getRestOperationType()) {
|
||||||
|
case TRANSACTION:
|
||||||
|
case BATCH:
|
||||||
|
IBaseBundle bundle = (IBaseBundle) theRequestDetails.getResource();
|
||||||
|
FhirContext ctx = theRequestDetails.getFhirContext();
|
||||||
|
BundleEntryUrlProcessor processor = new BundleEntryUrlProcessor(ctx, theRequestDetails);
|
||||||
|
BundleUtil.processEntries(ctx, bundle, processor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subclasses should override this method to supply the set of compartments that
|
* Subclasses should override this method to supply the set of compartments that
|
||||||
* the user making the request should actually have access to.
|
* the user making the request should actually have access to.
|
||||||
|
@ -143,54 +221,214 @@ public class SearchNarrowingInterceptor {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Hook(Pointcut.SERVER_INCOMING_REQUEST_POST_PROCESSED)
|
/**
|
||||||
public boolean hookIncomingRequestPostProcessed(
|
* For the $everything operation, we only do code narrowing, and in this case
|
||||||
RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse)
|
* we're not actually even making any changes to the request. All we do here is
|
||||||
throws AuthenticationException {
|
* ensure that an attribute is added to the request, which is picked up later
|
||||||
// We don't support this operation type yet
|
* by {@link SearchNarrowingConsentService}.
|
||||||
Validate.isTrue(theRequestDetails.getRestOperationType() != RestOperationTypeEnum.SEARCH_SYSTEM);
|
*/
|
||||||
|
private void narrowEverythingOperation(RequestDetails theRequestDetails) {
|
||||||
|
AuthorizedList authorizedList = buildAuthorizedList(theRequestDetails);
|
||||||
|
if (authorizedList != null) {
|
||||||
|
buildParameterListForAuthorizedCodes(
|
||||||
|
theRequestDetails, theRequestDetails.getResourceName(), authorizedList);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void narrowIfNoneExistHeader(RequestDetails theRequestDetails) {
|
||||||
|
if (myNarrowConditionalUrls) {
|
||||||
|
String ifNoneExist = theRequestDetails.getHeader(Constants.HEADER_IF_NONE_EXIST);
|
||||||
|
if (isNotBlank(ifNoneExist)) {
|
||||||
|
String newConditionalUrl = narrowConditionalUrlForCompartmentOnly(
|
||||||
|
theRequestDetails, ifNoneExist, true, theRequestDetails.getResourceName());
|
||||||
|
if (newConditionalUrl != null) {
|
||||||
|
theRequestDetails.setHeaders(Constants.HEADER_IF_NONE_EXIST, List.of(newConditionalUrl));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void narrowRequestUrl(RequestDetails theRequestDetails, RestOperationTypeEnum theRestOperationType) {
|
||||||
|
if (myNarrowConditionalUrls) {
|
||||||
|
String conditionalUrl = theRequestDetails.getConditionalUrl(theRestOperationType);
|
||||||
|
if (isNotBlank(conditionalUrl)) {
|
||||||
|
String newConditionalUrl = narrowConditionalUrlForCompartmentOnly(
|
||||||
|
theRequestDetails, conditionalUrl, false, theRequestDetails.getResourceName());
|
||||||
|
if (newConditionalUrl != null) {
|
||||||
|
String newCompleteUrl = theRequestDetails
|
||||||
|
.getCompleteUrl()
|
||||||
|
.substring(
|
||||||
|
0,
|
||||||
|
theRequestDetails.getCompleteUrl().indexOf('?') + 1)
|
||||||
|
+ newConditionalUrl;
|
||||||
|
theRequestDetails.setCompleteUrl(newCompleteUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does not narrow codes
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
private String narrowConditionalUrlForCompartmentOnly(
|
||||||
|
RequestDetails theRequestDetails,
|
||||||
|
@Nonnull String theConditionalUrl,
|
||||||
|
boolean theIncludeUpToQuestionMarkInResponse,
|
||||||
|
String theResourceName) {
|
||||||
|
AuthorizedList authorizedList = buildAuthorizedList(theRequestDetails);
|
||||||
|
return narrowConditionalUrl(
|
||||||
|
theRequestDetails,
|
||||||
|
theConditionalUrl,
|
||||||
|
theIncludeUpToQuestionMarkInResponse,
|
||||||
|
theResourceName,
|
||||||
|
false,
|
||||||
|
authorizedList);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private String narrowConditionalUrl(
|
||||||
|
RequestDetails theRequestDetails,
|
||||||
|
@Nonnull String theConditionalUrl,
|
||||||
|
boolean theIncludeUpToQuestionMarkInResponse,
|
||||||
|
String theResourceName,
|
||||||
|
boolean theNarrowCodes,
|
||||||
|
AuthorizedList theAuthorizedList) {
|
||||||
|
if (theAuthorizedList == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
ListMultimap<String, String> parametersToAdd =
|
||||||
|
buildParameterListForAuthorizedCompartment(theRequestDetails, theResourceName, theAuthorizedList);
|
||||||
|
|
||||||
|
if (theNarrowCodes) {
|
||||||
|
ListMultimap<String, String> parametersToAddForCodes =
|
||||||
|
buildParameterListForAuthorizedCodes(theRequestDetails, theResourceName, theAuthorizedList);
|
||||||
|
if (parametersToAdd == null) {
|
||||||
|
parametersToAdd = parametersToAddForCodes;
|
||||||
|
} else if (parametersToAddForCodes != null) {
|
||||||
|
parametersToAdd.putAll(parametersToAddForCodes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String newConditionalUrl = null;
|
||||||
|
if (parametersToAdd != null) {
|
||||||
|
|
||||||
|
String query = theConditionalUrl;
|
||||||
|
int qMarkIndex = theConditionalUrl.indexOf('?');
|
||||||
|
if (qMarkIndex != -1) {
|
||||||
|
query = theConditionalUrl.substring(qMarkIndex + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, String[]> inputParams = UrlUtil.parseQueryString(query);
|
||||||
|
Map<String, String[]> newParameters = applyCompartmentParameters(parametersToAdd, true, inputParams);
|
||||||
|
|
||||||
|
StringBuilder newUrl = new StringBuilder();
|
||||||
|
if (theIncludeUpToQuestionMarkInResponse) {
|
||||||
|
newUrl.append(qMarkIndex != -1 ? theConditionalUrl.substring(0, qMarkIndex + 1) : "?");
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean first = true;
|
||||||
|
for (Map.Entry<String, String[]> nextEntry : newParameters.entrySet()) {
|
||||||
|
for (String nextValue : nextEntry.getValue()) {
|
||||||
|
if (isNotBlank(nextValue)) {
|
||||||
|
if (first) {
|
||||||
|
first = false;
|
||||||
|
} else {
|
||||||
|
newUrl.append("&");
|
||||||
|
}
|
||||||
|
newUrl.append(UrlUtil.escapeUrlParam(nextEntry.getKey()));
|
||||||
|
newUrl.append("=");
|
||||||
|
newUrl.append(UrlUtil.escapeUrlParam(nextValue));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
newConditionalUrl = newUrl.toString();
|
||||||
|
}
|
||||||
|
return newConditionalUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void narrowTypeSearch(RequestDetails theRequestDetails) {
|
||||||
|
|
||||||
// N.B do not add code above this for filtering, this should only ever occur on search.
|
// N.B do not add code above this for filtering, this should only ever occur on search.
|
||||||
if (shouldSkipNarrowing(theRequestDetails)) {
|
if (shouldSkipNarrowing(theRequestDetails)) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthorizedList authorizedList = buildAuthorizedList(theRequestDetails);
|
AuthorizedList authorizedList = buildAuthorizedList(theRequestDetails);
|
||||||
if (authorizedList == null) {
|
if (authorizedList == null) {
|
||||||
return true;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add rules to request so that the SearchNarrowingConsentService can pick them up
|
String resourceName = theRequestDetails.getResourceName();
|
||||||
|
|
||||||
|
// Narrow request URL for compartments
|
||||||
|
ListMultimap<String, String> parametersToAdd =
|
||||||
|
buildParameterListForAuthorizedCompartment(theRequestDetails, resourceName, authorizedList);
|
||||||
|
if (parametersToAdd != null) {
|
||||||
|
applyParametersToRequestDetails(theRequestDetails, parametersToAdd, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Narrow request URL for codes - Add rules to request so that the SearchNarrowingConsentService can pick them
|
||||||
|
// up
|
||||||
|
ListMultimap<String, String> parameterToOrValues =
|
||||||
|
buildParameterListForAuthorizedCodes(theRequestDetails, resourceName, authorizedList);
|
||||||
|
if (parameterToOrValues != null) {
|
||||||
|
applyParametersToRequestDetails(theRequestDetails, parameterToOrValues, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ListMultimap<String, String> buildParameterListForAuthorizedCodes(
|
||||||
|
RequestDetails theRequestDetails, String resourceName, AuthorizedList authorizedList) {
|
||||||
List<AllowedCodeInValueSet> postFilteringList = getPostFilteringList(theRequestDetails);
|
List<AllowedCodeInValueSet> postFilteringList = getPostFilteringList(theRequestDetails);
|
||||||
if (authorizedList.getAllowedCodeInValueSets() != null) {
|
if (authorizedList.getAllowedCodeInValueSets() != null) {
|
||||||
postFilteringList.addAll(authorizedList.getAllowedCodeInValueSets());
|
postFilteringList.addAll(authorizedList.getAllowedCodeInValueSets());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
List<AllowedCodeInValueSet> allowedCodeInValueSet = authorizedList.getAllowedCodeInValueSets();
|
||||||
|
ListMultimap<String, String> parameterToOrValues = null;
|
||||||
|
if (allowedCodeInValueSet != null) {
|
||||||
|
FhirContext context = theRequestDetails.getServer().getFhirContext();
|
||||||
|
RuntimeResourceDefinition resourceDef = context.getResourceDefinition(resourceName);
|
||||||
|
parameterToOrValues = processAllowedCodes(resourceDef, allowedCodeInValueSet);
|
||||||
|
}
|
||||||
|
return parameterToOrValues;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ListMultimap<String, String> buildParameterListForAuthorizedCompartment(
|
||||||
|
RequestDetails theRequestDetails, String theResourceName, @Nullable AuthorizedList theAuthorizedList) {
|
||||||
|
if (theAuthorizedList == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
FhirContext ctx = theRequestDetails.getServer().getFhirContext();
|
||||||
RuntimeResourceDefinition resDef = ctx.getResourceDefinition(theRequestDetails.getResourceName());
|
RuntimeResourceDefinition resDef = ctx.getResourceDefinition(theResourceName);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Create a map of search parameter values that need to be added to the
|
* Create a map of search parameter values that need to be added to the
|
||||||
* given request
|
* given request
|
||||||
*/
|
*/
|
||||||
Collection<String> compartments = authorizedList.getAllowedCompartments();
|
Collection<String> compartments = theAuthorizedList.getAllowedCompartments();
|
||||||
|
ListMultimap<String, String> parametersToAdd = null;
|
||||||
if (compartments != null) {
|
if (compartments != null) {
|
||||||
Map<String, List<String>> parameterToOrValues =
|
parametersToAdd =
|
||||||
processResourcesOrCompartments(theRequestDetails, resDef, compartments, true);
|
processResourcesOrCompartments(theRequestDetails, resDef, compartments, true, theResourceName);
|
||||||
applyParametersToRequestDetails(theRequestDetails, parameterToOrValues, true);
|
|
||||||
}
|
|
||||||
Collection<String> resources = authorizedList.getAllowedInstances();
|
|
||||||
if (resources != null) {
|
|
||||||
Map<String, List<String>> parameterToOrValues =
|
|
||||||
processResourcesOrCompartments(theRequestDetails, resDef, resources, false);
|
|
||||||
applyParametersToRequestDetails(theRequestDetails, parameterToOrValues, true);
|
|
||||||
}
|
|
||||||
List<AllowedCodeInValueSet> allowedCodeInValueSet = authorizedList.getAllowedCodeInValueSets();
|
|
||||||
if (allowedCodeInValueSet != null) {
|
|
||||||
Map<String, List<String>> parameterToOrValues = processAllowedCodes(resDef, allowedCodeInValueSet);
|
|
||||||
applyParametersToRequestDetails(theRequestDetails, parameterToOrValues, false);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
Collection<String> resources = theAuthorizedList.getAllowedInstances();
|
||||||
|
if (resources != null) {
|
||||||
|
ListMultimap<String, String> parameterToOrValues =
|
||||||
|
processResourcesOrCompartments(theRequestDetails, resDef, resources, false, theResourceName);
|
||||||
|
if (parametersToAdd == null) {
|
||||||
|
parametersToAdd = parameterToOrValues;
|
||||||
|
} else if (parameterToOrValues != null) {
|
||||||
|
parametersToAdd.putAll(parameterToOrValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return parametersToAdd;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -201,102 +439,26 @@ public class SearchNarrowingInterceptor {
|
||||||
&& !"$everything".equalsIgnoreCase(theRequestDetails.getOperation());
|
&& !"$everything".equalsIgnoreCase(theRequestDetails.getOperation());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Hook(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED)
|
|
||||||
public void hookIncomingRequestPreHandled(
|
|
||||||
ServletRequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse)
|
|
||||||
throws AuthenticationException {
|
|
||||||
if (theRequestDetails.getRestOperationType() != RestOperationTypeEnum.TRANSACTION) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IBaseBundle bundle = (IBaseBundle) theRequestDetails.getResource();
|
|
||||||
FhirContext ctx = theRequestDetails.getFhirContext();
|
|
||||||
BundleEntryUrlProcessor processor =
|
|
||||||
new BundleEntryUrlProcessor(ctx, theRequestDetails, theRequest, theResponse);
|
|
||||||
BundleUtil.processEntries(ctx, bundle, processor);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void applyParametersToRequestDetails(
|
private void applyParametersToRequestDetails(
|
||||||
RequestDetails theRequestDetails,
|
RequestDetails theRequestDetails,
|
||||||
@Nullable Map<String, List<String>> theParameterToOrValues,
|
@Nullable ListMultimap<String, String> theParameterToOrValues,
|
||||||
boolean thePatientIdMode) {
|
boolean thePatientIdMode) {
|
||||||
|
Map<String, String[]> inputParameters = theRequestDetails.getParameters();
|
||||||
if (theParameterToOrValues != null) {
|
if (theParameterToOrValues != null) {
|
||||||
Map<String, String[]> newParameters = new HashMap<>(theRequestDetails.getParameters());
|
Map<String, String[]> newParameters =
|
||||||
for (Map.Entry<String, List<String>> nextEntry : theParameterToOrValues.entrySet()) {
|
applyCompartmentParameters(theParameterToOrValues, thePatientIdMode, inputParameters);
|
||||||
String nextParamName = nextEntry.getKey();
|
|
||||||
List<String> nextAllowedValues = nextEntry.getValue();
|
|
||||||
|
|
||||||
if (!newParameters.containsKey(nextParamName)) {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If we don't already have a parameter of the given type, add one
|
|
||||||
*/
|
|
||||||
String nextValuesJoined = ParameterUtil.escapeAndJoinOrList(nextAllowedValues);
|
|
||||||
String[] paramValues = {nextValuesJoined};
|
|
||||||
newParameters.put(nextParamName, paramValues);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If the client explicitly requested the given parameter already, we'll
|
|
||||||
* just update the request to have the intersection of the values that the client
|
|
||||||
* requested, and the values that the user is allowed to see
|
|
||||||
*/
|
|
||||||
String[] existingValues = newParameters.get(nextParamName);
|
|
||||||
|
|
||||||
if (thePatientIdMode) {
|
|
||||||
List<String> nextAllowedValueIds = nextAllowedValues.stream()
|
|
||||||
.map(t -> t.lastIndexOf("/") > -1 ? t.substring(t.lastIndexOf("/") + 1) : t)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
boolean restrictedExistingList = false;
|
|
||||||
for (int i = 0; i < existingValues.length; i++) {
|
|
||||||
|
|
||||||
String nextExistingValue = existingValues[i];
|
|
||||||
List<String> nextRequestedValues =
|
|
||||||
QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, nextExistingValue);
|
|
||||||
List<String> nextPermittedValues = ListUtils.union(
|
|
||||||
ListUtils.intersection(nextRequestedValues, nextAllowedValues),
|
|
||||||
ListUtils.intersection(nextRequestedValues, nextAllowedValueIds));
|
|
||||||
if (nextPermittedValues.size() > 0) {
|
|
||||||
restrictedExistingList = true;
|
|
||||||
existingValues[i] = ParameterUtil.escapeAndJoinOrList(nextPermittedValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* If none of the values that were requested by the client overlap at all
|
|
||||||
* with the values that the user is allowed to see, the client shouldn't
|
|
||||||
* get *any* results back. We return an error code indicating that the
|
|
||||||
* caller is forbidden from accessing the resources they requested.
|
|
||||||
*/
|
|
||||||
if (!restrictedExistingList) {
|
|
||||||
throw new ForbiddenOperationException(Msg.code(2026) + "Value not permitted for parameter "
|
|
||||||
+ UrlUtil.escapeUrlParam(nextParamName));
|
|
||||||
}
|
|
||||||
|
|
||||||
} else {
|
|
||||||
|
|
||||||
int existingValuesCount = existingValues.length;
|
|
||||||
String[] newValues =
|
|
||||||
Arrays.copyOf(existingValues, existingValuesCount + nextAllowedValues.size());
|
|
||||||
for (int i = 0; i < nextAllowedValues.size(); i++) {
|
|
||||||
newValues[existingValuesCount + i] = nextAllowedValues.get(i);
|
|
||||||
}
|
|
||||||
newParameters.put(nextParamName, newValues);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
theRequestDetails.setParameters(newParameters);
|
theRequestDetails.setParameters(newParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Map<String, List<String>> processResourcesOrCompartments(
|
private ListMultimap<String, String> processResourcesOrCompartments(
|
||||||
RequestDetails theRequestDetails,
|
RequestDetails theRequestDetails,
|
||||||
RuntimeResourceDefinition theResDef,
|
RuntimeResourceDefinition theResDef,
|
||||||
Collection<String> theResourcesOrCompartments,
|
Collection<String> theResourcesOrCompartments,
|
||||||
boolean theAreCompartments) {
|
boolean theAreCompartments,
|
||||||
Map<String, List<String>> retVal = null;
|
String theResourceName) {
|
||||||
|
ListMultimap<String, String> retVal = null;
|
||||||
|
|
||||||
String lastCompartmentName = null;
|
String lastCompartmentName = null;
|
||||||
String lastSearchParamName = null;
|
String lastSearchParamName = null;
|
||||||
|
@ -315,7 +477,7 @@ public class SearchNarrowingInterceptor {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
|
|
||||||
if (compartmentName.equalsIgnoreCase(theRequestDetails.getResourceName())) {
|
if (compartmentName.equalsIgnoreCase(theResourceName)) {
|
||||||
|
|
||||||
searchParamName = "_id";
|
searchParamName = "_id";
|
||||||
|
|
||||||
|
@ -331,10 +493,9 @@ public class SearchNarrowingInterceptor {
|
||||||
|
|
||||||
if (searchParamName != null) {
|
if (searchParamName != null) {
|
||||||
if (retVal == null) {
|
if (retVal == null) {
|
||||||
retVal = new HashMap<>();
|
retVal = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
}
|
}
|
||||||
List<String> orValues = retVal.computeIfAbsent(searchParamName, t -> new ArrayList<>());
|
retVal.put(searchParamName, nextCompartment);
|
||||||
orValues.add(nextCompartment);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -342,9 +503,9 @@ public class SearchNarrowingInterceptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private Map<String, List<String>> processAllowedCodes(
|
private ListMultimap<String, String> processAllowedCodes(
|
||||||
RuntimeResourceDefinition theResDef, List<AllowedCodeInValueSet> theAllowedCodeInValueSet) {
|
RuntimeResourceDefinition theResDef, List<AllowedCodeInValueSet> theAllowedCodeInValueSet) {
|
||||||
Map<String, List<String>> retVal = null;
|
ListMultimap<String, String> retVal = null;
|
||||||
|
|
||||||
for (AllowedCodeInValueSet next : theAllowedCodeInValueSet) {
|
for (AllowedCodeInValueSet next : theAllowedCodeInValueSet) {
|
||||||
String resourceName = next.getResourceName();
|
String resourceName = next.getResourceName();
|
||||||
|
@ -371,9 +532,9 @@ public class SearchNarrowingInterceptor {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (retVal == null) {
|
if (retVal == null) {
|
||||||
retVal = new HashMap<>();
|
retVal = MultimapBuilder.hashKeys().arrayListValues().build();
|
||||||
}
|
}
|
||||||
retVal.computeIfAbsent(paramName, k -> new ArrayList<>()).add(valueSetUrl);
|
retVal.put(paramName, valueSetUrl);
|
||||||
}
|
}
|
||||||
|
|
||||||
return retVal;
|
return retVal;
|
||||||
|
@ -408,7 +569,7 @@ public class SearchNarrowingInterceptor {
|
||||||
Set<String> queryParameters = theRequestDetails.getParameters().keySet();
|
Set<String> queryParameters = theRequestDetails.getParameters().keySet();
|
||||||
|
|
||||||
List<RuntimeSearchParam> searchParams = theResDef.getSearchParamsForCompartmentName(compartmentName);
|
List<RuntimeSearchParam> searchParams = theResDef.getSearchParamsForCompartmentName(compartmentName);
|
||||||
if (searchParams.size() > 0) {
|
if (!searchParams.isEmpty()) {
|
||||||
|
|
||||||
// Resources like Observation have several fields that add the resource to
|
// Resources like Observation have several fields that add the resource to
|
||||||
// the compartment. In the case of Observation, it's subject, patient and performer.
|
// the compartment. In the case of Observation, it's subject, patient and performer.
|
||||||
|
@ -467,41 +628,75 @@ public class SearchNarrowingInterceptor {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class BundleEntryUrlProcessor implements Consumer<ModifiableBundleEntry> {
|
@Nonnull
|
||||||
private final FhirContext myFhirContext;
|
private static Map<String, String[]> applyCompartmentParameters(
|
||||||
private final ServletRequestDetails myRequestDetails;
|
@Nonnull ListMultimap<String, String> theParameterToOrValues,
|
||||||
private final HttpServletRequest myRequest;
|
boolean thePatientIdMode,
|
||||||
private final HttpServletResponse myResponse;
|
Map<String, String[]> theInputParameters) {
|
||||||
|
Map<String, String[]> newParameters = new HashMap<>(theInputParameters);
|
||||||
|
for (String nextParamName : theParameterToOrValues.keySet()) {
|
||||||
|
List<String> nextAllowedValues = theParameterToOrValues.get(nextParamName);
|
||||||
|
|
||||||
public BundleEntryUrlProcessor(
|
if (!newParameters.containsKey(nextParamName)) {
|
||||||
FhirContext theFhirContext,
|
|
||||||
ServletRequestDetails theRequestDetails,
|
/*
|
||||||
HttpServletRequest theRequest,
|
* If we don't already have a parameter of the given type, add one
|
||||||
HttpServletResponse theResponse) {
|
*/
|
||||||
myFhirContext = theFhirContext;
|
String nextValuesJoined = ParameterUtil.escapeAndJoinOrList(nextAllowedValues);
|
||||||
myRequestDetails = theRequestDetails;
|
String[] paramValues = {nextValuesJoined};
|
||||||
myRequest = theRequest;
|
newParameters.put(nextParamName, paramValues);
|
||||||
myResponse = theResponse;
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the client explicitly requested the given parameter already, we'll
|
||||||
|
* just update the request to have the intersection of the values that the client
|
||||||
|
* requested, and the values that the user is allowed to see
|
||||||
|
*/
|
||||||
|
String[] existingValues = newParameters.get(nextParamName);
|
||||||
|
|
||||||
|
if (thePatientIdMode) {
|
||||||
|
List<String> nextAllowedValueIds = nextAllowedValues.stream()
|
||||||
|
.map(t -> t.lastIndexOf("/") > -1 ? t.substring(t.lastIndexOf("/") + 1) : t)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
boolean restrictedExistingList = false;
|
||||||
|
for (int i = 0; i < existingValues.length; i++) {
|
||||||
|
|
||||||
|
String nextExistingValue = existingValues[i];
|
||||||
|
List<String> nextRequestedValues =
|
||||||
|
QualifiedParamList.splitQueryStringByCommasIgnoreEscape(null, nextExistingValue);
|
||||||
|
List<String> nextPermittedValues = ListUtils.union(
|
||||||
|
ListUtils.intersection(nextRequestedValues, nextAllowedValues),
|
||||||
|
ListUtils.intersection(nextRequestedValues, nextAllowedValueIds));
|
||||||
|
if (!nextPermittedValues.isEmpty()) {
|
||||||
|
restrictedExistingList = true;
|
||||||
|
existingValues[i] = ParameterUtil.escapeAndJoinOrList(nextPermittedValues);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
/*
|
||||||
public void accept(ModifiableBundleEntry theModifiableBundleEntry) {
|
* If none of the values that were requested by the client overlap at all
|
||||||
ArrayListMultimap<String, String> paramValues = ArrayListMultimap.create();
|
* with the values that the user is allowed to see, the client shouldn't
|
||||||
|
* get *any* results back. We return an error code indicating that the
|
||||||
String url = theModifiableBundleEntry.getRequestUrl();
|
* caller is forbidden from accessing the resources they requested.
|
||||||
|
*/
|
||||||
ServletSubRequestDetails subServletRequestDetails =
|
if (!restrictedExistingList) {
|
||||||
ServletRequestUtil.getServletSubRequestDetails(myRequestDetails, url, paramValues);
|
throw new ForbiddenOperationException(Msg.code(2026) + "Value not permitted for parameter "
|
||||||
BaseMethodBinding method =
|
+ UrlUtil.escapeUrlParam(nextParamName));
|
||||||
subServletRequestDetails.getServer().determineResourceMethod(subServletRequestDetails, url);
|
|
||||||
RestOperationTypeEnum restOperationType = method.getRestOperationType();
|
|
||||||
subServletRequestDetails.setRestOperationType(restOperationType);
|
|
||||||
|
|
||||||
hookIncomingRequestPostProcessed(subServletRequestDetails, myRequest, myResponse);
|
|
||||||
|
|
||||||
theModifiableBundleEntry.setRequestUrl(
|
|
||||||
myFhirContext, ServletRequestUtil.extractUrl(subServletRequestDetails));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
|
||||||
|
int existingValuesCount = existingValues.length;
|
||||||
|
String[] newValues = Arrays.copyOf(existingValues, existingValuesCount + nextAllowedValues.size());
|
||||||
|
for (int i = 0; i < nextAllowedValues.size(); i++) {
|
||||||
|
newValues[existingValuesCount + i] = nextAllowedValues.get(i);
|
||||||
|
}
|
||||||
|
newParameters.put(nextParamName, newValues);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return newParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
static List<AllowedCodeInValueSet> getPostFilteringList(RequestDetails theRequestDetails) {
|
static List<AllowedCodeInValueSet> getPostFilteringList(RequestDetails theRequestDetails) {
|
||||||
|
@ -517,4 +712,82 @@ public class SearchNarrowingInterceptor {
|
||||||
static List<AllowedCodeInValueSet> getPostFilteringListOrNull(RequestDetails theRequestDetails) {
|
static List<AllowedCodeInValueSet> getPostFilteringListOrNull(RequestDetails theRequestDetails) {
|
||||||
return (List<AllowedCodeInValueSet>) theRequestDetails.getAttribute(POST_FILTERING_LIST_ATTRIBUTE_NAME);
|
return (List<AllowedCodeInValueSet>) theRequestDetails.getAttribute(POST_FILTERING_LIST_ATTRIBUTE_NAME);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private class BundleEntryUrlProcessor implements Consumer<ModifiableBundleEntry> {
|
||||||
|
private final FhirContext myFhirContext;
|
||||||
|
private final ServletRequestDetails myRequestDetails;
|
||||||
|
private final AuthorizedList myAuthorizedList;
|
||||||
|
|
||||||
|
public BundleEntryUrlProcessor(FhirContext theFhirContext, ServletRequestDetails theRequestDetails) {
|
||||||
|
myFhirContext = theFhirContext;
|
||||||
|
myRequestDetails = theRequestDetails;
|
||||||
|
myAuthorizedList = buildAuthorizedList(theRequestDetails);
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
|
||||||
|
@Override
|
||||||
|
public void accept(ModifiableBundleEntry theModifiableBundleEntry) {
|
||||||
|
if (myAuthorizedList == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
RequestTypeEnum method = theModifiableBundleEntry.getRequestMethod();
|
||||||
|
String requestUrl = theModifiableBundleEntry.getRequestUrl();
|
||||||
|
if (method != null && isNotBlank(requestUrl)) {
|
||||||
|
|
||||||
|
String resourceType = UrlUtil.parseUrl(requestUrl).getResourceType();
|
||||||
|
|
||||||
|
switch (method) {
|
||||||
|
case GET: {
|
||||||
|
String existingRequestUrl = theModifiableBundleEntry.getRequestUrl();
|
||||||
|
String newConditionalUrl = narrowConditionalUrl(
|
||||||
|
myRequestDetails, existingRequestUrl, false, resourceType, true, myAuthorizedList);
|
||||||
|
if (isNotBlank(newConditionalUrl)) {
|
||||||
|
newConditionalUrl = resourceType + "?" + newConditionalUrl;
|
||||||
|
theModifiableBundleEntry.setRequestUrl(myFhirContext, newConditionalUrl);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case POST: {
|
||||||
|
if (myNarrowConditionalUrls) {
|
||||||
|
String existingConditionalUrl = theModifiableBundleEntry.getConditionalUrl();
|
||||||
|
if (isNotBlank(existingConditionalUrl)) {
|
||||||
|
String newConditionalUrl = narrowConditionalUrl(
|
||||||
|
myRequestDetails,
|
||||||
|
existingConditionalUrl,
|
||||||
|
true,
|
||||||
|
resourceType,
|
||||||
|
false,
|
||||||
|
myAuthorizedList);
|
||||||
|
if (isNotBlank(newConditionalUrl)) {
|
||||||
|
theModifiableBundleEntry.setRequestIfNoneExist(myFhirContext, newConditionalUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case PUT:
|
||||||
|
case DELETE:
|
||||||
|
case PATCH: {
|
||||||
|
if (myNarrowConditionalUrls) {
|
||||||
|
String existingConditionalUrl = theModifiableBundleEntry.getConditionalUrl();
|
||||||
|
if (isNotBlank(existingConditionalUrl)) {
|
||||||
|
String newConditionalUrl = narrowConditionalUrl(
|
||||||
|
myRequestDetails,
|
||||||
|
existingConditionalUrl,
|
||||||
|
true,
|
||||||
|
resourceType,
|
||||||
|
false,
|
||||||
|
myAuthorizedList);
|
||||||
|
if (isNotBlank(newConditionalUrl)) {
|
||||||
|
theModifiableBundleEntry.setRequestUrl(myFhirContext, newConditionalUrl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -28,6 +28,8 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import com.google.common.collect.MultimapBuilder;
|
||||||
import jakarta.annotation.Nonnull;
|
import jakarta.annotation.Nonnull;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
@ -59,6 +61,7 @@ public class ServletRequestDetails extends RequestDetails {
|
||||||
private RestfulServer myServer;
|
private RestfulServer myServer;
|
||||||
private HttpServletRequest myServletRequest;
|
private HttpServletRequest myServletRequest;
|
||||||
private HttpServletResponse myServletResponse;
|
private HttpServletResponse myServletResponse;
|
||||||
|
private ListMultimap<String, String> myHeaders;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor for testing only
|
* Constructor for testing only
|
||||||
|
@ -129,17 +132,63 @@ public class ServletRequestDetails extends RequestDetails {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHeader(String name) {
|
public String getHeader(String name) {
|
||||||
|
// For efficiency, we only make a copy of the request headers if we need to
|
||||||
|
// modify them
|
||||||
|
if (myHeaders != null) {
|
||||||
|
List<String> values = myHeaders.get(name);
|
||||||
|
if (values.isEmpty()) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
|
return values.get(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
return getServletRequest().getHeader(name);
|
return getServletRequest().getHeader(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<String> getHeaders(String name) {
|
public List<String> getHeaders(String name) {
|
||||||
|
// For efficiency, we only make a copy of the request headers if we need to
|
||||||
|
// modify them
|
||||||
|
if (myHeaders != null) {
|
||||||
|
return myHeaders.get(name);
|
||||||
|
}
|
||||||
Enumeration<String> headers = getServletRequest().getHeaders(name);
|
Enumeration<String> headers = getServletRequest().getHeaders(name);
|
||||||
return headers == null
|
return headers == null
|
||||||
? Collections.emptyList()
|
? Collections.emptyList()
|
||||||
: Collections.list(getServletRequest().getHeaders(name));
|
: Collections.list(getServletRequest().getHeaders(name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void addHeader(String theName, String theValue) {
|
||||||
|
initHeaders();
|
||||||
|
myHeaders.put(theName, theValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void setHeaders(String theName, List<String> theValue) {
|
||||||
|
initHeaders();
|
||||||
|
myHeaders.removeAll(theName);
|
||||||
|
myHeaders.putAll(theName, theValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initHeaders() {
|
||||||
|
if (myHeaders == null) {
|
||||||
|
// Make sure we are case-insensitive for header names
|
||||||
|
myHeaders = MultimapBuilder.treeKeys(String.CASE_INSENSITIVE_ORDER)
|
||||||
|
.arrayListValues()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
Enumeration<String> headerNames = getServletRequest().getHeaderNames();
|
||||||
|
while (headerNames.hasMoreElements()) {
|
||||||
|
String nextName = headerNames.nextElement();
|
||||||
|
Enumeration<String> values = getServletRequest().getHeaders(nextName);
|
||||||
|
while (values.hasMoreElements()) {
|
||||||
|
myHeaders.put(nextName, values.nextElement());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object getAttribute(String theAttributeName) {
|
public Object getAttribute(String theAttributeName) {
|
||||||
Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank");
|
Validate.notBlank(theAttributeName, "theAttributeName must not be null or blank");
|
||||||
|
|
|
@ -19,34 +19,38 @@
|
||||||
*/
|
*/
|
||||||
package ca.uhn.fhir.rest.server.servlet;
|
package ca.uhn.fhir.rest.server.servlet;
|
||||||
|
|
||||||
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import com.google.common.collect.MultimapBuilder;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
public class ServletSubRequestDetails extends ServletRequestDetails {
|
public class ServletSubRequestDetails extends ServletRequestDetails {
|
||||||
|
|
||||||
private final ServletRequestDetails myWrap;
|
private final ServletRequestDetails myWrap;
|
||||||
private Map<String, List<String>> myHeaders = new HashMap<>();
|
/**
|
||||||
|
* Map with case-insensitive keys
|
||||||
|
*/
|
||||||
|
private final ListMultimap<String, String> myHeaders = MultimapBuilder.treeKeys(String.CASE_INSENSITIVE_ORDER)
|
||||||
|
.arrayListValues()
|
||||||
|
.build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param theRequestDetails The parent request details
|
* @param theRequestDetails The parent request details
|
||||||
*/
|
*/
|
||||||
public ServletSubRequestDetails(ServletRequestDetails theRequestDetails) {
|
public ServletSubRequestDetails(@Nonnull ServletRequestDetails theRequestDetails) {
|
||||||
super(theRequestDetails.getInterceptorBroadcaster());
|
super(theRequestDetails.getInterceptorBroadcaster());
|
||||||
|
|
||||||
myWrap = theRequestDetails;
|
myWrap = theRequestDetails;
|
||||||
|
|
||||||
if (theRequestDetails != null) {
|
|
||||||
Map<String, List<String>> headers = theRequestDetails.getHeaders();
|
Map<String, List<String>> headers = theRequestDetails.getHeaders();
|
||||||
for (Map.Entry<String, List<String>> next : headers.entrySet()) {
|
for (Map.Entry<String, List<String>> next : headers.entrySet()) {
|
||||||
myHeaders.put(next.getKey().toLowerCase(), next.getValue());
|
myHeaders.putAll(next.getKey(), next.getValue());
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -60,16 +64,15 @@ public class ServletSubRequestDetails extends ServletRequestDetails {
|
||||||
return myWrap.getServletResponse();
|
return myWrap.getServletResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
public void addHeader(String theName, String theValue) {
|
public void addHeader(String theName, String theValue) {
|
||||||
String lowerCase = theName.toLowerCase();
|
myHeaders.put(theName, theValue);
|
||||||
List<String> list = myHeaders.computeIfAbsent(lowerCase, k -> new ArrayList<>());
|
|
||||||
list.add(theValue);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getHeader(String theName) {
|
public String getHeader(String theName) {
|
||||||
List<String> list = myHeaders.get(theName.toLowerCase());
|
List<String> list = myHeaders.get(theName);
|
||||||
if (list == null || list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return list.get(0);
|
return list.get(0);
|
||||||
|
@ -78,7 +81,7 @@ public class ServletSubRequestDetails extends ServletRequestDetails {
|
||||||
@Override
|
@Override
|
||||||
public List<String> getHeaders(String theName) {
|
public List<String> getHeaders(String theName) {
|
||||||
List<String> list = myHeaders.get(theName.toLowerCase());
|
List<String> list = myHeaders.get(theName.toLowerCase());
|
||||||
if (list == null || list.isEmpty()) {
|
if (list.isEmpty()) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return list;
|
return list;
|
||||||
|
|
|
@ -68,22 +68,4 @@ public class ServletRequestUtil {
|
||||||
theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url);
|
theRequestDetails.getServer().populateRequestDetailsFromRequestPath(requestDetails, url);
|
||||||
return requestDetails;
|
return requestDetails;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static String extractUrl(ServletRequestDetails theRequestDetails) {
|
|
||||||
StringBuilder b = new StringBuilder();
|
|
||||||
for (Map.Entry<String, String[]> next :
|
|
||||||
theRequestDetails.getParameters().entrySet()) {
|
|
||||||
for (String nextValue : next.getValue()) {
|
|
||||||
if (b.length() == 0) {
|
|
||||||
b.append('?');
|
|
||||||
} else {
|
|
||||||
b.append('&');
|
|
||||||
}
|
|
||||||
b.append(UrlUtil.escapeUrlParam(next.getKey()));
|
|
||||||
b.append('=');
|
|
||||||
b.append(UrlUtil.escapeUrlParam(nextValue));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return theRequestDetails.getRequestPath() + b.toString();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,16 +1,32 @@
|
||||||
package ca.uhn.fhir.rest.server.servlet;
|
package ca.uhn.fhir.rest.server.servlet;
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import org.apache.commons.collections4.iterators.IteratorEnumeration;
|
||||||
|
import org.hamcrest.Matchers;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import jakarta.servlet.http.HttpServletRequest;
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.Mock;
|
||||||
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
|
|
||||||
|
import java.util.Enumeration;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
import static org.mockito.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
|
@ExtendWith(MockitoExtension.class)
|
||||||
class ServletRequestDetailsTest {
|
class ServletRequestDetailsTest {
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private HttpServletRequest myHttpServletRequest;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRewriteHistoryHeader() {
|
public void testRewriteHistoryHeader() {
|
||||||
ServletRequestDetails servletRequestDetails = new ServletRequestDetails();
|
ServletRequestDetails servletRequestDetails = new ServletRequestDetails();
|
||||||
|
@ -41,4 +57,24 @@ class ServletRequestDetailsTest {
|
||||||
assertFalse(servletRequestDetails.isRewriteHistory());
|
assertFalse(servletRequestDetails.isRewriteHistory());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testAddHeader() {
|
||||||
|
ServletRequestDetails srd = new ServletRequestDetails();
|
||||||
|
srd.setServletRequest(myHttpServletRequest);
|
||||||
|
when(myHttpServletRequest.getHeaderNames()).thenReturn(new IteratorEnumeration<>(List.of("Foo").iterator()));
|
||||||
|
when(myHttpServletRequest.getHeaders(eq("Foo"))).thenReturn(new IteratorEnumeration<>(List.of("Bar", "Baz").iterator()));
|
||||||
|
|
||||||
|
srd.addHeader("Name", "Value");
|
||||||
|
srd.addHeader("Name", "Value2");
|
||||||
|
|
||||||
|
// Verify added headers (make sure we're case insensitive)
|
||||||
|
assertEquals("Value", srd.getHeader("NAME"));
|
||||||
|
assertThat(srd.getHeaders("name"), Matchers.contains("Value", "Value2"));
|
||||||
|
|
||||||
|
// Verify original headers (make sure we're case insensitive)
|
||||||
|
assertEquals("Bar", srd.getHeader("FOO"));
|
||||||
|
assertThat(srd.getHeaders("foo"), Matchers.contains("Bar", "Baz"));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
@ -21,7 +21,7 @@
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-caching-api</artifactId>
|
<artifactId>hapi-fhir-caching-api</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
<artifactId>hapi-fhir-serviceloaders</artifactId>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-spring-boot</artifactId>
|
<artifactId>hapi-fhir-spring-boot</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,12 +4,20 @@ import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.support.IValidationSupport;
|
import ca.uhn.fhir.context.support.IValidationSupport;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
import ca.uhn.fhir.model.api.IQueryParameterOr;
|
||||||
import ca.uhn.fhir.model.api.IQueryParameterType;
|
import ca.uhn.fhir.model.api.IQueryParameterType;
|
||||||
|
import ca.uhn.fhir.rest.annotation.ConditionalUrlParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Create;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Delete;
|
||||||
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Patch;
|
||||||
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
import ca.uhn.fhir.rest.annotation.Transaction;
|
import ca.uhn.fhir.rest.annotation.Transaction;
|
||||||
import ca.uhn.fhir.rest.annotation.TransactionParam;
|
import ca.uhn.fhir.rest.annotation.TransactionParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Update;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
|
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||||
|
@ -23,12 +31,18 @@ import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException;
|
||||||
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
import ca.uhn.fhir.test.utilities.server.RestfulServerExtension;
|
||||||
|
import ca.uhn.fhir.util.BundleBuilder;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
|
import jakarta.annotation.Nonnull;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.Bundle;
|
import org.hl7.fhir.r4.model.Bundle;
|
||||||
|
import org.hl7.fhir.r4.model.IdType;
|
||||||
import org.hl7.fhir.r4.model.Observation;
|
import org.hl7.fhir.r4.model.Observation;
|
||||||
|
import org.hl7.fhir.r4.model.Parameters;
|
||||||
import org.hl7.fhir.r4.model.Patient;
|
import org.hl7.fhir.r4.model.Patient;
|
||||||
import org.hl7.fhir.r4.model.Reference;
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
import org.hl7.fhir.r4.model.Resource;
|
import org.hl7.fhir.r4.model.Resource;
|
||||||
|
@ -44,16 +58,19 @@ import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
import jakarta.annotation.Nonnull;
|
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import static ca.uhn.fhir.util.UrlUtil.escapeUrlParam;
|
import static ca.uhn.fhir.util.UrlUtil.escapeUrlParam;
|
||||||
import static java.util.Collections.singletonList;
|
import static java.util.Collections.singletonList;
|
||||||
import static java.util.Collections.singletonMap;
|
import static java.util.Collections.singletonMap;
|
||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.equalTo;
|
||||||
|
import static org.hamcrest.Matchers.startsWith;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
import static org.junit.jupiter.api.Assertions.fail;
|
||||||
|
@ -77,10 +94,13 @@ public class SearchNarrowingInterceptorTest {
|
||||||
private static List<Resource> ourReturn;
|
private static List<Resource> ourReturn;
|
||||||
private static AuthorizedList ourNextAuthorizedList;
|
private static AuthorizedList ourNextAuthorizedList;
|
||||||
private static Bundle.BundleEntryRequestComponent ourLastBundleRequest;
|
private static Bundle.BundleEntryRequestComponent ourLastBundleRequest;
|
||||||
|
private static String ourLastConditionalUrl;
|
||||||
|
|
||||||
private IGenericClient myClient;
|
private IGenericClient myClient;
|
||||||
@Mock
|
@Mock
|
||||||
private IValidationSupport myValidationSupport;
|
private IValidationSupport myValidationSupport;
|
||||||
private MySearchNarrowingInterceptor myInterceptor;
|
private MySearchNarrowingInterceptor myInterceptor;
|
||||||
|
|
||||||
@RegisterExtension
|
@RegisterExtension
|
||||||
private RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx)
|
private RestfulServerExtension myRestfulServerExtension = new RestfulServerExtension(ourCtx)
|
||||||
.registerProvider(new DummyObservationResourceProvider())
|
.registerProvider(new DummyObservationResourceProvider())
|
||||||
|
@ -99,8 +119,11 @@ public class SearchNarrowingInterceptorTest {
|
||||||
ourLastPerformerParam = null;
|
ourLastPerformerParam = null;
|
||||||
ourLastCodeParam = null;
|
ourLastCodeParam = null;
|
||||||
ourNextAuthorizedList = null;
|
ourNextAuthorizedList = null;
|
||||||
|
ourLastConditionalUrl = null;
|
||||||
|
|
||||||
myInterceptor = new MySearchNarrowingInterceptor();
|
myInterceptor = new MySearchNarrowingInterceptor();
|
||||||
|
myInterceptor.setNarrowConditionalUrls(true);
|
||||||
|
|
||||||
myRestfulServerExtension.registerInterceptor(myInterceptor);
|
myRestfulServerExtension.registerInterceptor(myInterceptor);
|
||||||
|
|
||||||
myClient = myRestfulServerExtension.getFhirClient();
|
myClient = myRestfulServerExtension.getFhirClient();
|
||||||
|
@ -299,7 +322,7 @@ public class SearchNarrowingInterceptorTest {
|
||||||
.execute();
|
.execute();
|
||||||
|
|
||||||
assertEquals("transaction", ourLastHitMethod);
|
assertEquals("transaction", ourLastHitMethod);
|
||||||
assertEquals("Patient?_id=" + URLEncoder.encode("Patient/123,Patient/456"), ourLastBundleRequest.getUrl());
|
assertEquals("Patient?_id=" + UrlUtil.escapeUrlParam("Patient/123,Patient/456"), ourLastBundleRequest.getUrl());
|
||||||
}
|
}
|
||||||
@Test
|
@Test
|
||||||
public void testNarrow_OnlyAppliesToSearches() {
|
public void testNarrow_OnlyAppliesToSearches() {
|
||||||
|
@ -539,6 +562,319 @@ public class SearchNarrowingInterceptorTest {
|
||||||
assertThat(toStrings(ourLastIdParam), Matchers.contains("Patient/123,Patient/456"));
|
assertThat(toStrings(ourLastIdParam), Matchers.contains("Patient/123,Patient/456"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalCreate_Patient() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addResources("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.create()
|
||||||
|
.resource(new Patient().setActive(true))
|
||||||
|
.conditionalByUrl("Patient?active=true")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Patient.create", ourLastHitMethod);
|
||||||
|
assertThat(ourLastConditionalUrl, startsWith("/Patient?"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("active=true"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("_id=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalCreate_Patient_Disabled() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addResources("Patient/123", "Patient/456");
|
||||||
|
myInterceptor.setNarrowConditionalUrls(false);
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.create()
|
||||||
|
.resource(new Patient().setActive(true))
|
||||||
|
.conditionalByUrl("Patient?active=true")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Patient.create", ourLastHitMethod);
|
||||||
|
assertEquals("/Patient?active=true", ourLastConditionalUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalCreate_Patient_ReturnNull() {
|
||||||
|
ourNextAuthorizedList = null;
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.create()
|
||||||
|
.resource(new Patient().setActive(true))
|
||||||
|
.conditionalByUrl("Patient?active=true")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Patient.create", ourLastHitMethod);
|
||||||
|
assertThat(ourLastConditionalUrl, equalTo("/Patient?active=true"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalCreate_Observation() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.create()
|
||||||
|
.resource(new Observation().setStatus(Observation.ObservationStatus.FINAL))
|
||||||
|
.conditionalByUrl("Observation?status=final")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Observation.create", ourLastHitMethod);
|
||||||
|
assertThat(ourLastConditionalUrl, startsWith("/Observation?"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("status=final"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("patient=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalCreate_Observation_InTransaction() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
BundleBuilder bb = new BundleBuilder(ourCtx);
|
||||||
|
bb.addTransactionCreateEntry(new Observation().setStatus(Observation.ObservationStatus.FINAL)).conditional("Observation?status=final");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.transaction()
|
||||||
|
.withBundle(bb.getBundle())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("transaction", ourLastHitMethod);
|
||||||
|
assertEquals("Observation", ourLastBundleRequest.getUrl());
|
||||||
|
assertThat(ourLastBundleRequest.getIfNoneExist(), startsWith("Observation?"));
|
||||||
|
assertThat(ourLastBundleRequest.getIfNoneExist(), containsString("status=final"));
|
||||||
|
assertThat(ourLastBundleRequest.getIfNoneExist(), containsString("patient=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalCreate_Observation_InTransaction_Disabled() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
myInterceptor.setNarrowConditionalUrls(false);
|
||||||
|
|
||||||
|
BundleBuilder bb = new BundleBuilder(ourCtx);
|
||||||
|
bb.addTransactionCreateEntry(new Observation().setStatus(Observation.ObservationStatus.FINAL)).conditional("Observation?status=final");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.transaction()
|
||||||
|
.withBundle(bb.getBundle())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("transaction", ourLastHitMethod);
|
||||||
|
assertEquals("Observation", ourLastBundleRequest.getUrl());
|
||||||
|
assertEquals("Observation?status=final", ourLastBundleRequest.getIfNoneExist());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalUpdate_Patient() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addResources("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.update()
|
||||||
|
.resource(new Patient().setActive(true))
|
||||||
|
.conditionalByUrl("Patient?active=true")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Patient.update", ourLastHitMethod);
|
||||||
|
assertThat(ourLastConditionalUrl, startsWith("Patient?"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("active=true"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("_id=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalUpdate_Patient_Disabled() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addResources("Patient/123", "Patient/456");
|
||||||
|
myInterceptor.setNarrowConditionalUrls(false);
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.update()
|
||||||
|
.resource(new Patient().setActive(true))
|
||||||
|
.conditionalByUrl("Patient?active=true")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Patient.update", ourLastHitMethod);
|
||||||
|
assertEquals("Patient?active=true", ourLastConditionalUrl);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalUpdate_Observation() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.update()
|
||||||
|
.resource(new Observation().setStatus(Observation.ObservationStatus.FINAL))
|
||||||
|
.conditionalByUrl("Observation?status=final")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Observation.update", ourLastHitMethod);
|
||||||
|
assertThat(ourLastConditionalUrl, startsWith("Observation?"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("status=final"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("patient=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalUpdate_Observation_InTransaction() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
BundleBuilder bb = new BundleBuilder(ourCtx);
|
||||||
|
bb.addTransactionUpdateEntry(new Observation().setStatus(Observation.ObservationStatus.FINAL)).conditional("Observation?status=final");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.transaction()
|
||||||
|
.withBundle(bb.getBundle())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("transaction", ourLastHitMethod);
|
||||||
|
assertThat(ourLastBundleRequest.getUrl(), startsWith("Observation?"));
|
||||||
|
assertThat(ourLastBundleRequest.getUrl(), containsString("status=final"));
|
||||||
|
assertThat(ourLastBundleRequest.getUrl(), containsString("patient=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalUpdate_Observation_InTransaction_NoConditionalUrl() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
BundleBuilder bb = new BundleBuilder(ourCtx);
|
||||||
|
bb.addTransactionUpdateEntry(new Observation().setStatus(Observation.ObservationStatus.FINAL).setId("Observation/ABC"));
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.transaction()
|
||||||
|
.withBundle(bb.getBundle())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("transaction", ourLastHitMethod);
|
||||||
|
assertEquals("Observation/ABC", ourLastBundleRequest.getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalDelete_Patient() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addResources("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.delete()
|
||||||
|
.resourceConditionalByUrl("Patient?active=true")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Patient.delete", ourLastHitMethod);
|
||||||
|
assertThat(ourLastConditionalUrl, startsWith("Patient?"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("active=true"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("_id=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalDelete_Observation() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.delete()
|
||||||
|
.resourceConditionalByUrl("Observation?status=final")
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Observation.delete", ourLastHitMethod);
|
||||||
|
assertThat(ourLastConditionalUrl, startsWith("Observation?"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("status=final"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("patient=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalDelete_Observation_InTransaction() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
BundleBuilder bb = new BundleBuilder(ourCtx);
|
||||||
|
bb.addTransactionDeleteConditionalEntry("Observation?status=final");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.transaction()
|
||||||
|
.withBundle(bb.getBundle())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("transaction", ourLastHitMethod);
|
||||||
|
assertThat(ourLastBundleRequest.getUrl(), startsWith("Observation?"));
|
||||||
|
assertThat(ourLastBundleRequest.getUrl(), containsString("status=final"));
|
||||||
|
assertThat(ourLastBundleRequest.getUrl(), containsString("patient=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalPatch_Patient() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addResources("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.patch()
|
||||||
|
.withFhirPatch(new Parameters())
|
||||||
|
.conditional(Patient.class)
|
||||||
|
.whereMap(Map.of("active", List.of("true")))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Patient.patch", ourLastHitMethod);
|
||||||
|
assertThat(ourLastConditionalUrl, startsWith("Patient?"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("active=true"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("_id=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalPatch_Observation() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.patch()
|
||||||
|
.withFhirPatch(new Parameters())
|
||||||
|
.conditional(Observation.class)
|
||||||
|
.whereMap(Map.of("status", List.of("final")))
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("Observation.patch", ourLastHitMethod);
|
||||||
|
assertThat(ourLastConditionalUrl, startsWith("Observation?"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("status=final"));
|
||||||
|
assertThat(ourLastConditionalUrl, containsString("patient=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testNarrowCompartment_ConditionalPatch_Observation_InTransaction() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
BundleBuilder bb = new BundleBuilder(ourCtx);
|
||||||
|
bb.addTransactionFhirPatchEntry(new Parameters()).conditional("Observation?status=final");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.transaction()
|
||||||
|
.withBundle(bb.getBundle())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("transaction", ourLastHitMethod);
|
||||||
|
assertThat(ourLastBundleRequest.getUrl(), startsWith("Observation?"));
|
||||||
|
assertThat(ourLastBundleRequest.getUrl(), containsString("status=final"));
|
||||||
|
assertThat(ourLastBundleRequest.getUrl(), containsString("patient=" + escapeUrlParam("Patient/123,Patient/456")));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testTransactionWithNonConditionalDeleteNotModified() {
|
||||||
|
ourNextAuthorizedList = new AuthorizedList()
|
||||||
|
.addCompartments("Patient/123", "Patient/456");
|
||||||
|
|
||||||
|
BundleBuilder bb = new BundleBuilder(ourCtx);
|
||||||
|
bb.addTransactionDeleteEntry("Patient", "ABC");
|
||||||
|
|
||||||
|
myClient
|
||||||
|
.transaction()
|
||||||
|
.withBundle(bb.getBundle())
|
||||||
|
.execute();
|
||||||
|
|
||||||
|
assertEquals("transaction", ourLastHitMethod);
|
||||||
|
assertEquals("Patient/ABC", ourLastBundleRequest.getUrl());
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
private List<String> toStrings(BaseAndListParam<? extends IQueryParameterOr<?>> theParams) {
|
private List<String> toStrings(BaseAndListParam<? extends IQueryParameterOr<?>> theParams) {
|
||||||
List<? extends IQueryParameterOr<? extends IQueryParameterType>> valuesAsQueryTokens = theParams.getValuesAsQueryTokens();
|
List<? extends IQueryParameterOr<? extends IQueryParameterType>> valuesAsQueryTokens = theParams.getValuesAsQueryTokens();
|
||||||
|
|
||||||
|
@ -572,6 +908,7 @@ public class SearchNarrowingInterceptorTest {
|
||||||
TestUtil.randomizeLocaleAndTimezone();
|
TestUtil.randomizeLocaleAndTimezone();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -590,8 +927,37 @@ public class SearchNarrowingInterceptorTest {
|
||||||
return ourReturn;
|
return ourReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Create
|
||||||
|
public MethodOutcome create(@ResourceParam IBaseResource theResource, @ConditionalUrlParam String theConditionalUrl) {
|
||||||
|
ourLastHitMethod = "Patient.create";
|
||||||
|
ourLastConditionalUrl = theConditionalUrl;
|
||||||
|
return new MethodOutcome(new IdType("Patient/123"), true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Update
|
||||||
|
public MethodOutcome update(@ResourceParam IBaseResource theResource, @ConditionalUrlParam String theConditionalUrl) {
|
||||||
|
ourLastHitMethod = "Patient.update";
|
||||||
|
ourLastConditionalUrl = theConditionalUrl;
|
||||||
|
return new MethodOutcome(new IdType("Patient/123"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
public MethodOutcome delete(@IdParam IIdType theId, @ConditionalUrlParam String theConditionalUrl) {
|
||||||
|
ourLastHitMethod = "Patient.delete";
|
||||||
|
ourLastConditionalUrl = theConditionalUrl;
|
||||||
|
return new MethodOutcome(new IdType("Patient/123"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch
|
||||||
|
public MethodOutcome patch(@IdParam IIdType theId, @ResourceParam IBaseResource theResource, @ConditionalUrlParam String theConditionalUrl, PatchTypeEnum thePatchType) {
|
||||||
|
ourLastHitMethod = "Patient.patch";
|
||||||
|
ourLastConditionalUrl = theConditionalUrl;
|
||||||
|
return new MethodOutcome(new IdType("Patient/123"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
public static class DummyObservationResourceProvider implements IResourceProvider {
|
public static class DummyObservationResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -617,6 +983,34 @@ public class SearchNarrowingInterceptorTest {
|
||||||
return ourReturn;
|
return ourReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Create
|
||||||
|
public MethodOutcome create(@ResourceParam IBaseResource theResource, @ConditionalUrlParam String theConditionalUrl) {
|
||||||
|
ourLastHitMethod = "Observation.create";
|
||||||
|
ourLastConditionalUrl = theConditionalUrl;
|
||||||
|
return new MethodOutcome(new IdType("Observation/123"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Update
|
||||||
|
public MethodOutcome update(@ResourceParam IBaseResource theResource, @ConditionalUrlParam String theConditionalUrl) {
|
||||||
|
ourLastHitMethod = "Observation.update";
|
||||||
|
ourLastConditionalUrl = theConditionalUrl;
|
||||||
|
return new MethodOutcome(new IdType("Observation/123"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete
|
||||||
|
public MethodOutcome delete(@IdParam IIdType theId, @ConditionalUrlParam String theConditionalUrl) {
|
||||||
|
ourLastHitMethod = "Observation.delete";
|
||||||
|
ourLastConditionalUrl = theConditionalUrl;
|
||||||
|
return new MethodOutcome(new IdType("Observation/123"), true);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Patch
|
||||||
|
public MethodOutcome patch(@IdParam IIdType theId, @ResourceParam IBaseResource theResource, @ConditionalUrlParam String theConditionalUrl, PatchTypeEnum thePatchType) {
|
||||||
|
ourLastHitMethod = "Observation.patch";
|
||||||
|
ourLastConditionalUrl = theConditionalUrl;
|
||||||
|
return new MethodOutcome(new IdType("Observation/123"), true);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class DummySystemProvider {
|
public static class DummySystemProvider {
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
3
pom.xml
3
pom.xml
|
@ -9,7 +9,7 @@
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<name>HAPI-FHIR</name>
|
<name>HAPI-FHIR</name>
|
||||||
<description>An open-source implementation of the FHIR specification in Java.</description>
|
<description>An open-source implementation of the FHIR specification in Java.</description>
|
||||||
|
@ -3241,3 +3241,4 @@
|
||||||
</profile>
|
</profile>
|
||||||
</profiles>
|
</profiles>
|
||||||
</project>
|
</project>
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
|
@ -5,7 +5,7 @@
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>7.1.3-SNAPSHOT</version>
|
<version>7.1.4-SNAPSHOT</version>
|
||||||
|
|
||||||
<relativePath>../../pom.xml</relativePath>
|
<relativePath>../../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
Loading…
Reference in New Issue