Merging master into working branch.

# Conflicts:
#	hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoConfig.java
This commit is contained in:
Diederik Muylwyk 2019-08-19 02:28:30 -04:00
commit 8590e0ca4d
154 changed files with 6427 additions and 814 deletions

View File

@ -16,6 +16,7 @@ ca.uhn.fhir.jpa.entity.ResourceTable/
*.war
*.ear
overlays/
# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*

View File

@ -1,3 +0,0 @@
1549576047000
(?:[^/]+/)*?[^/]*?
META-INF(?:$|/.+)

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -631,7 +631,10 @@ public class FhirContext {
* Performance Note: <b>This method is cheap</b> to call, and may be called once for every message being processed
* without incurring any performance penalty
* </p>
*
* @deprecated THIS FEATURE IS NOT YET COMPLETE
*/
@Deprecated
public IParser newRDFParser() {
return new RDFParser(this, myParserErrorHandler, Lang.TURTLE);
}

View File

@ -374,6 +374,61 @@ public enum Pointcut {
),
/**
* <b>Server Hook:</b>
* This method is called after the server implementation method has been called, but before any attempt
* to stream the response back to the client, specifically for GraphQL requests (as these do not fit
* cleanly into the model provided by {@link #SERVER_OUTGOING_RESPONSE}).
* <p>
* Hooks may accept the following parameters:
* <ul>
* <li>
* ca.uhn.fhir.rest.api.server.RequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request.
* </li>
* <li>
* ca.uhn.fhir.rest.server.servlet.ServletRequestDetails - A bean containing details about the request that is about to be processed, including details such as the
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
* pulled out of the servlet request. This parameter is identical to the RequestDetails parameter above but will
* only be populated when operating in a RestfulServer implementation. It is provided as a convenience.
* </li>
* <li>
* java.lang.String - The GraphQL query
* </li>
* <li>
* java.lang.String - The GraphQL response
* </li>
* <li>
* javax.servlet.http.HttpServletRequest - The servlet request, when running in a servlet environment
* </li>
* <li>
* javax.servlet.http.HttpServletResponse - The servlet response, when running in a servlet environment
* </li>
* </ul>
* </p>
* <p>
* Hook methods may return <code>true</code> or <code>void</code> if processing should continue normally.
* This is generally the right thing to do. If your interceptor is providing a response rather than
* letting HAPI handle the response normally, you must return <code>false</code>. In this case,
* no further processing will occur and no further interceptors will be called.
* </p>
* <p>
* Hook methods may also throw {@link AuthenticationException} to indicate that the interceptor
* has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401
* will be returned to the client.
*/
SERVER_OUTGOING_GRAPHQL_RESPONSE(boolean.class,
"ca.uhn.fhir.rest.api.server.RequestDetails",
"ca.uhn.fhir.rest.server.servlet.ServletRequestDetails",
"java.lang.String",
"java.lang.String",
"javax.servlet.http.HttpServletRequest",
"javax.servlet.http.HttpServletResponse"
),
/**
* <b>Server Hook:</b>
* This method is called when an OperationOutcome is being returned in response to a failure.

View File

@ -138,6 +138,7 @@ public class Constants {
* Used in paging links
*/
public static final String PARAM_BUNDLETYPE = "_bundletype";
public static final String PARAM_FILTER = "_filter";
public static final String PARAM_CONTENT = "_content";
public static final String PARAM_COUNT = "_count";
public static final String PARAM_DELETE = "_delete";
@ -218,6 +219,7 @@ public class Constants {
public static final int MAX_RESOURCE_NAME_LENGTH = 100;
public static final String CACHE_CONTROL_PRIVATE = "private";
public static final int STATUS_HTTP_412_PAYLOAD_TOO_LARGE = 413;
public static final String OPERATION_NAME_GRAPHQL = "$graphql";
static {
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);

View File

@ -28,6 +28,8 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.apache.commons.lang3.StringUtils.isBlank;
public enum EncodingEnum {
JSON(Constants.CT_FHIR_JSON, Constants.CT_FHIR_JSON_NEW, Constants.FORMAT_JSON) {
@ -49,7 +51,9 @@ public enum EncodingEnum {
public IParser newParser(FhirContext theContext) {
return theContext.newRDFParser();
}
};
},
;
/**
* "json"
@ -164,7 +168,10 @@ public enum EncodingEnum {
* is found.
* <p>
* <b>This method is lenient!</b> Things like "application/xml" will return {@link EncodingEnum#XML}
* even if the "+fhir" part is missing from the expected content type.
* even if the "+fhir" part is missing from the expected content type. Also,
* spaces are treated as a plus (i.e. "application/fhir json" will be treated as
* "application/fhir+json" in order to account for unescaped spaces in URL
* parameters)
* </p>
*/
public static EncodingEnum forContentType(final String theContentType) {
@ -195,12 +202,35 @@ public enum EncodingEnum {
}
}
private static String getTypeWithoutCharset(final String theContentType) {
if (theContentType == null) {
static String getTypeWithoutCharset(final String theContentType) {
if (isBlank(theContentType)) {
return null;
} else {
String[] contentTypeSplitted = theContentType.split(";");
return contentTypeSplitted[0];
int start = 0;
for (; start < theContentType.length(); start++) {
if (theContentType.charAt(start) != ' ') {
break;
}
}
int end = start;
for (; end < theContentType.length(); end++) {
if (theContentType.charAt(end) == ';') {
break;
}
}
for (; end > start; end--) {
if (theContentType.charAt(end - 1) != ' ') {
break;
}
}
String retVal = theContentType.substring(start, end);
if (retVal.contains(" ")) {
retVal = retVal.replace(' ', '+');
}
return retVal;
}
}

View File

@ -29,6 +29,7 @@ import org.apache.commons.lang3.builder.EqualsBuilder;
import org.apache.commons.lang3.builder.HashCodeBuilder;
import org.apache.commons.lang3.builder.ToStringBuilder;
import org.apache.commons.lang3.builder.ToStringStyle;
import org.hl7.fhir.instance.model.api.IBaseCoding;
import static org.apache.commons.lang3.StringUtils.defaultString;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
@ -67,6 +68,16 @@ public class TokenParam extends BaseParam /*implements IQueryParameterType*/ {
this(toSystemValue(theIdentifierDt.getSystemElement()), theIdentifierDt.getValueElement().getValue());
}
/**
* Construct a {@link TokenParam} from the {@link IBaseCoding#getSystem()} () system} and
* {@link IBaseCoding#getCode()} () code} of a {@link IBaseCoding} instance.
*
* @param theCoding The coding
*/
public TokenParam(IBaseCoding theCoding) {
this(theCoding.getSystem(), theCoding.getCode());
}
public TokenParam(String theSystem, String theValue) {
setSystem(theSystem);
setValue(theValue);

View File

@ -163,7 +163,16 @@ public class UrlUtil {
if (theString != null) {
for (int i = 0; i < theString.length(); i++) {
char nextChar = theString.charAt(i);
if (nextChar == '<' || nextChar == '"') {
switch (nextChar) {
case '\'':
case '"':
case '<':
case '>':
case '\n':
case '\r':
return true;
}
if (nextChar < ' ') {
return true;
}
}
@ -348,7 +357,17 @@ public class UrlUtil {
/**
* This method specifically HTML-encodes the &quot; and
* &lt; characters in order to prevent injection attacks
* &lt; characters in order to prevent injection attacks.
*
* The following characters are escaped:
* <ul>
* <li>&apos;</li>
* <li>&quot;</li>
* <li>&lt;</li>
* <li>&gt;</li>
* <li>\n (newline)</li>
* </ul>
*
*/
public static String sanitizeUrlPart(CharSequence theString) {
if (theString == null) {
@ -364,6 +383,10 @@ public class UrlUtil {
char nextChar = theString.charAt(j);
switch (nextChar) {
/*
* NB: If you add a constant here, you also need to add it
* to isNeedsSanitization()!!
*/
case '\'':
buffer.append("&apos;");
break;
@ -373,8 +396,19 @@ public class UrlUtil {
case '<':
buffer.append("&lt;");
break;
case '>':
buffer.append("&gt;");
break;
case '\n':
buffer.append("&#10;");
break;
case '\r':
buffer.append("&#13;");
break;
default:
buffer.append(nextChar);
if (nextChar >= ' ') {
buffer.append(nextChar);
}
break;
}

View File

@ -31,7 +31,8 @@ public enum VersionEnum {
V3_6_0,
V3_7_0,
V3_8_0,
V4_0_0;
V4_0_0,
V4_1_0;
public static VersionEnum latestVersion() {
VersionEnum[] values = VersionEnum.values();

View File

@ -56,4 +56,9 @@ public interface IBase extends Serializable {
*/
List<String> getFormatCommentsPost();
/**
* Returns the FHIR type name for the given element, e.g. "Patient" or "unsignedInt"
*/
default String fhirType() { return null; }
}

View File

@ -60,23 +60,58 @@
<tr th:class="${rowStat.odd}? 'hapiTableOfValuesRowOdd' : 'hapiTableOfValuesRowEven'" class="hapiTableOfValuesRowOdd">
<td>
<th:block th:switch="${fhirVersion}">
<th:block th:case="'DSTU1'">
<th:block th:if="${not result.resource.name.textElement.empty}" th:text="${result.resource.name.textElement.value}"/>
<th:block th:if="${result.resource.name.textElement.empty} and ${not #lists.isEmpty(result.resource.name.coding)} and ${not result.resource.name.coding[0].empty} and ${not result.resource.name.coding[0].displayElement.empty}" th:text="${result.resource.name.coding[0].display}"/>
<th:block th:if="${result.resource.name.textElement.empty} and ${not #lists.isEmpty(result.resource.name.coding)} and ${not result.resource.name.coding[0].empty} and ${result.resource.name.coding[0].displayElement.empty}" th:text="'?'"/>
</th:block>
<th:block th:case="'DSTU2'">
<th:block th:if="${not result.resource.code.textElement.empty}" th:text="${result.resource.code.textElement.value}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${not result.resource.code.coding[0].displayElement.empty}" th:text="${result.resource.code.coding[0].display}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${result.resource.code.coding[0].displayElement.empty}" th:text="'?'"/>
</th:block>
<th:block th:case="'DSTU2_HL7ORG'">
<th:block th:if="${not result.resource.code.textElement.empty}" th:text="${result.resource.code.textElement.value}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${not result.resource.code.coding[0].displayElement.empty}" th:text="${result.resource.code.coding[0].display}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${result.resource.code.coding[0].displayElement.empty}" th:text="'?'"/>
</th:block>
<th:block th:case="'DSTU2_1'">
<th:block th:if="${not result.resource.code.textElement.empty}" th:text="${result.resource.code.textElement.value}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${not result.resource.code.coding[0].displayElement.empty}" th:text="${result.resource.code.coding[0].display}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${result.resource.code.coding[0].displayElement.empty}" th:text="'?'"/>
</th:block>
<th:block th:case="'DSTU3'">
<th:block th:if="${not result.resource.code.textElement.empty}" th:text="${result.resource.code.textElement.value}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${not result.resource.code.coding[0].displayElement.empty}" th:text="${result.resource.code.coding[0].display}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${result.resource.code.coding[0].displayElement.empty}" th:text="'?'"/>
</th:block>
<th:block th:case="*">
<th:block th:if="${not result.resource.code.textElement.empty}" th:text="${result.resource.code.textElement.value}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${not result.resource.code.coding[0].displayElement.empty}" th:text="${result.resource.code.coding[0].display}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${result.resource.code.coding[0].displayElement.empty}" th:text="'?'"/>
<th:block th:if="${not result.resource.code.textElement.empty}" th:text="${result.resource.code.textElement.value}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${not result.resource.code.coding[0].displayElement.empty}" th:text="${result.resource.code.coding[0].display}"/>
<th:block th:if="${result.resource.code.textElement.empty} and ${not #lists.isEmpty(result.resource.code.coding)} and ${not result.resource.code.coding[0].empty} and ${result.resource.code.coding[0].displayElement.empty}" th:text="'?'"/>
</th:block>
</th:block>
<!--/*--> Hb <!--*/-->
</td>
<td th:narrative="${result.resource.value}">2.2 g/L</td>
<td>
<th:block th:if="${not result.resource.interpretation.textElement.empty}" th:text="${result.resource.interpretation.text}"/>
<th:block th:if="${result.resource.interpretation.textElement.empty} and ${not result.resource.interpretation.coding.empty} and ${not result.resource.interpretation.coding[0].displayElement.empty}" th:text="${result.resource.interpretation.coding[0].display}"/>
<th:block th:switch="${fhirVersion}">
<th:block th:case="'DSTU2'">
<th:block th:if="${not result.resource.interpretation.textElement.empty}" th:text="${result.resource.interpretation.text}"/>
<th:block th:if="${result.resource.interpretation.textElement.empty} and ${not result.resource.interpretation.coding.empty} and ${not result.resource.interpretation.coding[0].displayElement.empty}" th:text="${result.resource.interpretation.coding[0].display}"/>
</th:block>
<th:block th:case="'DSTU2_HL7ORG'">
<th:block th:if="${not result.resource.interpretation.textElement.empty}" th:text="${result.resource.interpretation.text}"/>
<th:block th:if="${result.resource.interpretation.textElement.empty} and ${not result.resource.interpretation.coding.empty} and ${not result.resource.interpretation.coding[0].displayElement.empty}" th:text="${result.resource.interpretation.coding[0].display}"/>
</th:block>
<th:block th:case="'DSTU2_1'">
<th:block th:if="${not result.resource.interpretation.textElement.empty}" th:text="${result.resource.interpretation.text}"/>
<th:block th:if="${result.resource.interpretation.textElement.empty} and ${not result.resource.interpretation.coding.empty} and ${not result.resource.interpretation.coding[0].displayElement.empty}" th:text="${result.resource.interpretation.coding[0].display}"/>
</th:block>
<th:block th:case="'DSTU3'">
<th:block th:if="${not result.resource.interpretation.textElement.empty}" th:text="${result.resource.interpretation.text}"/>
<th:block th:if="${result.resource.interpretation.textElement.empty} and ${not result.resource.interpretation.coding.empty} and ${not result.resource.interpretation.coding[0].displayElement.empty}" th:text="${result.resource.interpretation.coding[0].display}"/>
</th:block>
<th:block th:case="*">
<th:block th:if="${not result.resource.interpretation.empty} and ${not result.resource.interpretation[0].textElement.empty}" th:text="${result.resource.interpretation[0].text}"/>
<th:block th:if="${not result.resource.interpretation.empty} and ${result.resource.interpretation[0].textElement.empty} and ${not result.resource.interpretation[0].coding.empty} and ${not result.resource.interpretation[0].coding[0].displayElement.empty}" th:text="${result.resource.interpretation.coding[0].display}"/>
</th:block>
</th:block>
<!--/*--> N <!--*/-->
</td>
<td>
@ -90,16 +125,31 @@
</tr>
<th:block th:switch="${fhirVersion}">
<th:block th:case="'DSTU3'">
<tr th:if="${not result.resource.commentElement.empty}" th:class="${rowStat.odd}? 'hapiTableOfValuesRowOdd' : 'hapiTableOfValuesRowEven'" class="hapiTableOfValuesRowOdd">
<td th:text="${result.resource.commentElement.value}" colspan="5">This is a comment</td>
</tr>
</th:block>
<th:block th:case="*">
<th:block th:case="'DSTU2'">
<tr th:if="${not result.resource.commentsElement.empty}" th:class="${rowStat.odd}? 'hapiTableOfValuesRowOdd' : 'hapiTableOfValuesRowEven'" class="hapiTableOfValuesRowOdd">
<td th:text="${result.resource.commentsElement.value}" colspan="5">This is a comment</td>
</tr>
</th:block>
<th:block th:case="'DSTU2_HL7ORG'">
<tr th:if="${not result.resource.commentElement.empty}" th:class="${rowStat.odd}? 'hapiTableOfValuesRowOdd' : 'hapiTableOfValuesRowEven'" class="hapiTableOfValuesRowOdd">
<td th:text="${result.resource.commentElement.value}" colspan="5">This is a comment</td>
</tr>
</th:block>
<th:block th:case="'DSTU2_1'">
<tr th:if="${not result.resource.commentElement.empty}" th:class="${rowStat.odd}? 'hapiTableOfValuesRowOdd' : 'hapiTableOfValuesRowEven'" class="hapiTableOfValuesRowOdd">
<td th:text="${result.resource.commentElement.value}" colspan="5">This is a comment</td>
</tr>
</th:block>
<th:block th:case="'DSTU3'">
<tr th:if="${not result.resource.commentElement.empty}" th:class="${rowStat.odd}? 'hapiTableOfValuesRowOdd' : 'hapiTableOfValuesRowEven'" class="hapiTableOfValuesRowOdd">
<td th:text="${result.resource.commentElement.value}" colspan="5">This is a comment</td>
</tr>
</th:block>
<th:block th:case="*">
<tr th:if="${not result.resource.note.empty}" th:class="${rowStat.odd}? 'hapiTableOfValuesRowOdd' : 'hapiTableOfValuesRowEven'" class="hapiTableOfValuesRowOdd">
<td th:text="${result.resource.note[0].text}" colspan="5">This is a comment</td>
</tr>
</th:block>
</th:block>
<!--/* The following are just examples that don't appear in the generated narrative -->

View File

@ -0,0 +1,24 @@
package ca.uhn.fhir.rest.api;
import org.junit.Test;
import static org.junit.Assert.*;
public class EncodingEnumTest {
@Test
public void getTypeWithoutCharset() {
assertEquals("text/plain", EncodingEnum.getTypeWithoutCharset("text/plain"));
assertEquals("text/plain", EncodingEnum.getTypeWithoutCharset(" text/plain"));
assertEquals("text/plain", EncodingEnum.getTypeWithoutCharset(" text/plain; charset=utf-8"));
assertEquals("text/plain", EncodingEnum.getTypeWithoutCharset(" text/plain ; charset=utf-8"));
}
@Test
public void getTypeWithSpace() {
assertEquals("application/fhir+xml", EncodingEnum.getTypeWithoutCharset("application/fhir xml"));
assertEquals("application/fhir+xml", EncodingEnum.getTypeWithoutCharset("application/fhir xml; charset=utf-8"));
assertEquals("application/fhir+xml", EncodingEnum.getTypeWithoutCharset("application/fhir xml ; charset=utf-8"));
}
}

View File

@ -59,4 +59,15 @@ public class UrlUtilTest {
}
@Test
public void testSanitize() {
assertEquals(" &apos; ", UrlUtil.sanitizeUrlPart(" ' "));
assertEquals(" &lt; ", UrlUtil.sanitizeUrlPart(" < "));
assertEquals(" &gt; ", UrlUtil.sanitizeUrlPart(" > "));
assertEquals(" &quot; ", UrlUtil.sanitizeUrlPart(" \" "));
assertEquals(" &#10; ", UrlUtil.sanitizeUrlPart(" \n "));
assertEquals(" &#13; ", UrlUtil.sanitizeUrlPart(" \r "));
assertEquals(" ", UrlUtil.sanitizeUrlPart(" \0 "));
}
}

View File

@ -3,14 +3,14 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-bom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<packaging>pom</packaging>
<name>HAPI FHIR BOM</name>
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-cli</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -24,10 +24,8 @@ import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.server.ETagSupportEnum;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
import org.springframework.web.context.ContextLoaderListener;
import org.springframework.web.context.WebApplicationContext;

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<artifactId>hapi-deployable-pom</artifactId>
<groupId>ca.uhn.hapi.fhir</groupId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

View File

@ -11,7 +11,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -6,7 +6,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.interceptor.executor.InterceptorService;
import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider;
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
@ -23,6 +24,7 @@ import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
import ca.uhn.fhir.rest.server.interceptor.consent.IConsentContextServices;
import org.hibernate.jpa.HibernatePersistenceProvider;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.*;
@ -109,6 +111,12 @@ public abstract class BaseConfig implements SchedulingConfigurer {
public abstract FhirContext fhirContext();
@Bean
@Lazy
public IGraphQLStorageServices graphqlStorageServices() {
return new JpaStorageServices();
}
@Bean
public ScheduledExecutorFactoryBean scheduledExecutorService() {
ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();

View File

@ -8,6 +8,7 @@ import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.jpa.dao.dstu3.TransactionProcessorVersionAdapterDstu3;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
@ -73,6 +74,12 @@ public class BaseDstu3Config extends BaseConfig {
return retVal;
}
@Bean(name = GRAPHQL_PROVIDER_NAME)
@Lazy
public GraphQLProvider graphQLProvider() {
return new GraphQLProvider(fhirContextDstu3(), validationSupportChainDstu3(), graphqlStorageServices());
}
@Bean
public TransactionProcessor.ITransactionProcessorVersionAdapter transactionProcessorVersionFacade() {
return new TransactionProcessorVersionAdapterDstu3();

View File

@ -21,12 +21,13 @@ import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
import ca.uhn.fhir.validation.IValidatorModule;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.r4.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import org.hl7.fhir.r4.hapi.validation.CachingValidationSupport;
import org.hl7.fhir.r4.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.utils.GraphQLEngine;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@ -91,12 +92,6 @@ public class BaseR4Config extends BaseConfig {
return new GraphQLProvider(fhirContextR4(), validationSupportChainR4(), graphqlStorageServices());
}
@Bean
@Lazy
public GraphQLEngine.IGraphQLStorageServices graphqlStorageServices() {
return new JpaStorageServices();
}
@Bean(name = "myInstanceValidatorR4")
@Lazy
public IValidatorModule instanceValidatorR4() {

View File

@ -8,7 +8,7 @@ import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
import ca.uhn.fhir.jpa.dao.TransactionProcessor;
import ca.uhn.fhir.jpa.dao.r5.TransactionProcessorVersionAdapterR5;
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR5;
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR5;
@ -21,11 +21,9 @@ import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR5;
import ca.uhn.fhir.validation.IValidatorModule;
import org.apache.commons.lang3.time.DateUtils;
import org.hl7.fhir.r5.hapi.ctx.IValidationSupport;
import org.hl7.fhir.r5.hapi.rest.server.GraphQLProvider;
import org.hl7.fhir.r5.hapi.validation.CachingValidationSupport;
import org.hl7.fhir.r5.hapi.validation.FhirInstanceValidator;
import org.hl7.fhir.r5.model.Bundle;
import org.hl7.fhir.r5.utils.GraphQLEngine;
import org.hl7.fhir.r5.utils.IResourceValidator;
import org.springframework.beans.factory.annotation.Autowire;
import org.springframework.context.annotation.Bean;
@ -85,17 +83,11 @@ public class BaseR5Config extends BaseConfig {
return new TransactionProcessor<>();
}
// @Bean(name = GRAPHQL_PROVIDER_NAME)
// @Lazy
// public GraphQLProvider graphQLProvider() {
// return new GraphQLProvider(fhirContextR5(), validationSupportChainR5(), graphqlStorageServices());
// }
//
// @Bean
// @Lazy
// public GraphQLEngine.IGraphQLStorageServices graphqlStorageServices() {
// return new JpaStorageServices();
// }
@Bean(name = GRAPHQL_PROVIDER_NAME)
@Lazy
public GraphQLProvider graphQLProvider() {
return new GraphQLProvider(fhirContextR5(), validationSupportChainR5(), graphqlStorageServices());
}
@Bean(name = "myInstanceValidatorR5")
@Lazy

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry;
@ -149,6 +150,7 @@ public class DaoConfig {
* EXPERIMENTAL - Do not use in production! Do not change default of {@code false}!
*/
private boolean myPreExpandValueSetsExperimental = false;
private boolean myFilterParameterEnabled = false;
/**
* EXPERIMENTAL - Do not use in production! Do not change default of {@code 0}!
*/
@ -984,7 +986,7 @@ public class DaoConfig {
* and other FHIR features may not behave as expected when referential integrity is not
* preserved. Use this feature with caution.
* </p>
* @see ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor
* @see CascadingDeleteInterceptor
*/
public boolean isEnforceReferentialIntegrityOnDelete() {
return myEnforceReferentialIntegrityOnDelete;
@ -998,7 +1000,7 @@ public class DaoConfig {
* and other FHIR features may not behave as expected when referential integrity is not
* preserved. Use this feature with caution.
* </p>
* @see ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor
* @see CascadingDeleteInterceptor
*/
public void setEnforceReferentialIntegrityOnDelete(boolean theEnforceReferentialIntegrityOnDelete) {
myEnforceReferentialIntegrityOnDelete = theEnforceReferentialIntegrityOnDelete;
@ -1644,6 +1646,28 @@ public class DaoConfig {
myPreExpandValueSetsExperimental = thePreExpandValueSetsExperimental;
}
/**
* If set to <code>true</code> the _filter search parameter will be enabled on this server. Note that _filter
* is very powerful, but also potentially dangerous as it can allow a user to create a query for which there
* are no indexes or efficient query plans for the database to leverage while performing the query.
* As a result, this feature is recommended only for servers where the querying applications are known in advance
* and a database administrator can properly tune the database for the resulting queries.
*/
public boolean isFilterParameterEnabled() {
return myFilterParameterEnabled;
}
/**
* If set to <code>true</code> the _filter search parameter will be enabled on this server. Note that _filter
* is very powerful, but also potentially dangerous as it can allow a user to create a query for which there
* are no indexes or efficient query plans for the database to leverage while performing the query.
* As a result, this feature is recommended only for servers where the querying applications are known in advance
* and a database administrator can properly tune the database for the resulting queries.
*/
public void setFilterParameterEnabled(boolean theFilterParameterEnabled) {
myFilterParameterEnabled = theFilterParameterEnabled;
}
/**
* EXPERIMENTAL - Do not use in production!
* <p>

View File

@ -138,7 +138,7 @@ public class DaoRegistry implements ApplicationContextAware, IDaoRegistry {
@Override
public boolean isResourceTypeSupported(String theResourceType) {
return mySupportedResourceTypes.contains(theResourceType);
return mySupportedResourceTypes == null || mySupportedResourceTypes.contains(theResourceType);
}
private void init() {

View File

@ -43,7 +43,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2<Patient>im
super();
}
private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequest) {
private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequest) {
SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) {
paramMap.setCount(theCount.getValue());
@ -70,21 +70,21 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2<Patient>im
}
@Override
public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) {
public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getResourceName(), null);
notifyInterceptors(RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE, requestDetails);
return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails);
return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails);
}
@Override
public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) {
public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) {
// Notify interceptors
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequestDetails, getResourceName(), null);
notifyInterceptors(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, requestDetails);
return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails);
return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails);
}
}

View File

@ -1,5 +1,14 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import javax.servlet.http.HttpServletRequest;
/*
@ -21,19 +30,12 @@ import javax.servlet.http.HttpServletRequest;
* limitations under the License.
* #L%
*/
import org.hl7.fhir.instance.model.api.*;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.StringAndListParam;
public interface IFhirResourceDaoPatient<T extends IBaseResource> extends IFhirResourceDao<T> {
IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdate, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails);
IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdate, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails);
IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSortSpec, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails);
IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSortSpec, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails);
}

View File

@ -28,12 +28,13 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
import ca.uhn.fhir.rest.param.DateRangeParam;
import org.hl7.fhir.instance.model.api.IBaseResource;
import javax.persistence.EntityManager;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import javax.persistence.EntityManager;
public interface ISearchBuilder {
IResultIterator createQuery(SearchParameterMap theParams, SearchRuntimeDetails theSearchRuntime, RequestDetails theRequest);

View File

@ -0,0 +1,617 @@
package ca.uhn.fhir.jpa.dao;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import org.apache.commons.lang3.StringUtils;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class SearchFilterParser {
private static final String XML_DATE_PATTERN = "[0-9]{4}(-(0[1-9]|1[0-2])(-(0[0-9]|[1-2][0-9]|3[0-1])(T([01][0-9]|2[0-3]):[0-5][0-9]:([0-5][0-9]|60)(\\.[0-9]+)?(Z|([+\\-])((0[0-9]|1[0-3]):[0-5][0-9]|14:00))?)?)?)?";
private static final Pattern XML_DATE_MATCHER = Pattern.compile(XML_DATE_PATTERN);
private static final List<String> CODES_CompareOperation = Arrays.asList("eq", "ne", "co", "sw", "ew", "gt", "lt", "ge", "le", "pr", "po", "ss", "sb", "in", "re");
private static final List<String> CODES_LogicalOperation = Arrays.asList("and", "or", "not");
private String original = null;
private int cursor;
private boolean isDate(String s) {
Matcher m = XML_DATE_MATCHER.matcher(s);
return m.matches();
}
private FilterLexType peek() throws FilterSyntaxException {
FilterLexType result;
while ((cursor < original.length()) && (original.charAt(cursor) == ' ')) {
cursor++;
}
if (cursor >= original.length()) {
result = FilterLexType.fsltEnded;
} else {
if (((original.charAt(cursor) >= 'a') && (original.charAt(cursor) <= 'z')) ||
((original.charAt(cursor) >= 'A') && (original.charAt(cursor) <= 'Z')) ||
(original.charAt(cursor) == '_')) {
result = FilterLexType.fsltName;
} else if ((original.charAt(cursor) >= '0') && (original.charAt(cursor) <= '9')) {
result = FilterLexType.fsltNumber;
} else if (original.charAt(cursor) == '"') {
result = FilterLexType.fsltString;
} else if (original.charAt(cursor) == '.') {
result = FilterLexType.fsltDot;
} else if (original.charAt(cursor) == '(') {
result = FilterLexType.fsltOpen;
} else if (original.charAt(cursor) == ')') {
result = FilterLexType.fsltClose;
} else if (original.charAt(cursor) == '[') {
result = FilterLexType.fsltOpenSq;
} else if (original.charAt(cursor) == ']') {
result = FilterLexType.fsltCloseSq;
} else {
throw new FilterSyntaxException(String.format("Unknown Character \"%s\" at %d",
peekCh(),
cursor));
}
}
return result;
}
private String peekCh() {
String result;
if (cursor > original.length()) {
result = "[end!]";
} else {
result = original.substring(cursor, cursor + 1);
}
return result;
}
private String consumeName() {
String result;
int i = cursor;
do {
i++;
} while ((i <= original.length() - 1) &&
(((original.charAt(i) >= 'a') && (original.charAt(i) <= 'z')) ||
((original.charAt(i) >= 'A') && (original.charAt(i) <= 'Z')) ||
((original.charAt(i) >= '0') && (original.charAt(i) <= '9')) ||
(original.charAt(i) == '-') ||
(original.charAt(i) == '_') ||
(original.charAt(i) == ':')));
result = original.substring(cursor,
i/* - cursor*/);
cursor = i;
return result;
}
private String consumeToken() {
String result;
int i = cursor;
do {
i++;
} while ((i <= original.length() - 1) &&
(original.charAt(i) > 32) &&
(!StringUtils.isWhitespace(original.substring(i, i + 1))) &&
(original.charAt(i) != ')') &&
(original.charAt(i) != ']'));
result = original.substring(cursor,
i/* - cursor*/);
cursor = i;
return result;
}
private String consumeNumberOrDate() {
String result;
int i = cursor;
do {
i++;
} while ((i <= original.length() - 1) &&
(((original.charAt(i) >= '0') && (original.charAt(i) <= '9')) ||
(original.charAt(i) == '.') ||
(original.charAt(i) == '-') ||
(original.charAt(i) == ':') ||
(original.charAt(i) == '+') ||
(original.charAt(i) == 'T')));
result = original.substring(cursor,
i/* - cursor*/);
cursor = i;
return result;
}
private String consumeString() throws FilterSyntaxException {
// int l = 0;
cursor++;
StringBuilder str = new StringBuilder(original.length());
// setLength(result, length(original)); // can't be longer than that
while ((cursor <= original.length()) && (original.charAt(cursor) != '"')) {
// l++;
if (original.charAt(cursor) != '\\') {
str.append(original.charAt(cursor));
// str.setCharAt(l, original.charAt(cursor));
} else {
cursor++;
if (original.charAt(cursor) == '"') {
str.append('"');
// str.setCharAt(l, '"');
} else if (original.charAt(cursor) == 't') {
str.append('\t');
// str.setCharAt(l, '\t');
} else if (original.charAt(cursor) == 'r') {
str.append('\r');
// str.setCharAt(l, '\r');
} else if (original.charAt(cursor) == 'n') {
str.append('\n');
// str.setCharAt(l, '\n');
} else {
throw new FilterSyntaxException(String.format("Unknown escape sequence at %d",
cursor));
}
}
cursor++;
}
// SetLength(result, l);
if ((cursor > original.length()) || (original.charAt(cursor) != '"')) {
throw new FilterSyntaxException(String.format("Problem with string termination at %d",
cursor));
}
if (str.length() == 0) {
throw new FilterSyntaxException(String.format("Problem with string at %d cannot be empty",
cursor));
}
cursor++;
return str.toString();
}
private Filter parse() throws FilterSyntaxException {
Filter result = parseOpen();
if (cursor < original.length()) {
throw new FilterSyntaxException(String.format("Expression did not terminate at %d",
cursor));
}
return result;
}
private Filter parseOpen() throws FilterSyntaxException {
Filter result;
String s;
FilterParameterGroup grp;
if (peek() == FilterLexType.fsltOpen) {
cursor++;
grp = new FilterParameterGroup();
grp.setContained(parseOpen());
if (peek() != FilterLexType.fsltClose) {
throw new FilterSyntaxException(String.format("Expected ')' at %d but found %s",
cursor,
peekCh()));
}
cursor++;
FilterLexType lexType = peek();
if (lexType == FilterLexType.fsltName) {
result = parseLogical(grp);
} else if ((lexType == FilterLexType.fsltEnded) || (lexType == FilterLexType.fsltClose) || (lexType == FilterLexType.fsltCloseSq)) {
result = grp;
} else {
throw new FilterSyntaxException(String.format("Unexpected Character %s at %d",
peekCh(),
cursor));
}
} else {
s = consumeName();
if (s.compareToIgnoreCase("not") == 0) {
result = parseLogical(null);
} else {
result = parseParameter(s);
}
}
return result;
}
private Filter parseLogical(Filter filter) throws FilterSyntaxException {
Filter result = null;
String s;
FilterLogical logical;
if (filter == null) {
s = "not";
} else {
s = consumeName();
if ((!s.equals("or")) && (!s.equals("and")) && (!s.equals("not"))) {
throw new FilterSyntaxException(String.format("Unexpected Name %s at %d",
s,
cursor));
}
logical = new FilterLogical();
logical.setFilter1(filter);
if (s.compareToIgnoreCase("or") == 0) {
logical.setOperation(FilterLogicalOperation.or);
} else if (s.compareToIgnoreCase("not") == 0) {
logical.setOperation(FilterLogicalOperation.not);
} else {
logical.setOperation(FilterLogicalOperation.and);
}
logical.setFilter2(parseOpen());
result = logical;
}
return result;
}
private FilterParameterPath parsePath(String name) throws FilterSyntaxException {
FilterParameterPath result = new FilterParameterPath();
result.setName(name);
if (peek() == FilterLexType.fsltOpenSq) {
cursor++;
result.setFilter(parseOpen());
if (peek() != FilterLexType.fsltCloseSq) {
throw new FilterSyntaxException(String.format("Expected ']' at %d but found %c",
cursor,
peekCh()));
}
cursor++;
}
if (peek() == FilterLexType.fsltDot) {
cursor++;
if (peek() != FilterLexType.fsltName) {
throw new FilterSyntaxException(String.format("Unexpected Character %c at %d",
peekCh(),
cursor));
}
result.setNext(parsePath(consumeName()));
} else if (result.getFilter() != null) {
throw new FilterSyntaxException(String.format("Expected '.' at %d but found %c",
cursor,
peekCh()));
}
return result;
}
private Filter parseParameter(String name) throws FilterSyntaxException {
Filter result;
String s;
FilterParameter filter = new FilterParameter();
// 1. the path
filter.setParamPath(parsePath(name));
if (peek() != FilterLexType.fsltName) {
throw new FilterSyntaxException(String.format("Unexpected Character %s at %d",
peekCh(),
cursor));
}
s = consumeName();
int index = CODES_CompareOperation.indexOf(s);
if (index == -1) {
throw new FilterSyntaxException(String.format("Unknown operation %s at %d",
s,
cursor));
}
filter.setOperation(CompareOperation.values()[index]);
FilterLexType lexType = peek();
if (lexType == FilterLexType.fsltName) {
filter.setValue(consumeToken());
filter.setValueType(FilterValueType.token);
} else if (lexType == FilterLexType.fsltNumber) {
filter.setValue(consumeNumberOrDate());
filter.setValueType(FilterValueType.numberOrDate);
} else if (lexType == FilterLexType.fsltString) {
filter.setValue(consumeString());
filter.setValueType(FilterValueType.string);
} else {
throw new FilterSyntaxException(String.format("Unexpected Character %s at %d",
peekCh(),
cursor));
}
// check operation / value type results
if (filter.getOperation() == CompareOperation.pr) {
if ((filter.getValue().compareToIgnoreCase("true") != 0) &&
(filter.getValue().compareToIgnoreCase("false") != 0)) {
throw new FilterSyntaxException(String.format("Value %s not valid for operation %s at %d",
filter.getValue(),
CODES_CompareOperation.get(filter.getOperation().ordinal()),
cursor));
}
} else if (filter.getOperation() == CompareOperation.po) {
if (!isDate(filter.getValue())) {
throw new FilterSyntaxException(String.format("Value %s not valid for operation %s at %d",
filter.getValue(),
CODES_CompareOperation.get(filter.getOperation().ordinal()),
cursor));
}
}
lexType = peek();
if (lexType == FilterLexType.fsltName) {
result = parseLogical(filter);
} else if ((lexType == FilterLexType.fsltEnded) || (lexType == FilterLexType.fsltClose) || (lexType == FilterLexType.fsltCloseSq)) {
result = filter;
} else {
throw new FilterSyntaxException(String.format("Unexpected Character %s at %d",
peekCh(),
cursor));
}
return result;
}
public enum CompareOperation {
eq,
ne,
co,
sw,
ew,
gt,
lt,
ge,
le,
pr,
po,
ss,
sb,
in,
re
}
public enum FilterLogicalOperation {
and,
or,
not
}
public enum FilterItemType {
parameter,
logical,
parameterGroup
}
public enum FilterValueType {
token,
string,
numberOrDate
}
public enum FilterLexType {
fsltEnded,
fsltName,
fsltString,
fsltNumber,
fsltDot,
fsltOpen,
fsltClose,
fsltOpenSq,
fsltCloseSq
}
abstract public static class Filter {
private FilterItemType itemType;
public FilterItemType getFilterItemType() {
return itemType;
}
}
public static class FilterParameterPath {
private String FName;
private Filter FFilter;
private FilterParameterPath FNext;
public String getName() {
return FName;
}
public void setName(String value) {
FName = value;
}
public Filter getFilter() {
return FFilter;
}
public void setFilter(Filter value) {
FFilter = value;
}
public FilterParameterPath getNext() {
return FNext;
}
public void setNext(FilterParameterPath value) {
FNext = value;
}
@Override
public String toString() {
String result;
if (getFilter() != null) {
result = getName() + "[" + getFilter().toString() + "]";
} else {
result = getName();
}
if (getNext() != null) {
result += "." + getNext().toString();
}
return result;
}
}
public static class FilterParameterGroup extends Filter {
private Filter FContained;
public Filter getContained() {
return FContained;
}
public void setContained(Filter value) {
FContained = value;
}
@Override
public String toString() {
return "(" + FContained.toString() + ")";
}
}
public static class FilterParameter extends Filter {
private FilterParameterPath FParamPath;
private CompareOperation FOperation;
private String FValue;
private FilterValueType FValueType;
FilterParameterPath getParamPath() {
return FParamPath;
}
void setParamPath(FilterParameterPath value) {
FParamPath = value;
}
public CompareOperation getOperation() {
return FOperation;
}
public void setOperation(CompareOperation value) {
FOperation = value;
}
public String getValue() {
return FValue;
}
public void setValue(String value) {
FValue = value;
}
public FilterValueType getValueType() {
return FValueType;
}
public void setValueType(FilterValueType FValueType) {
this.FValueType = FValueType;
}
@Override
public String toString() {
if (FValueType == FilterValueType.string) {
return getParamPath().toString() + " " + CODES_CompareOperation.get(getOperation().ordinal()) + " \"" + getValue() + "\"";
} else {
return getParamPath().toString() + " " + CODES_CompareOperation.get(getOperation().ordinal()) + " " + getValue();
}
}
}
public static class FilterLogical extends Filter {
private Filter FFilter1;
private FilterLogicalOperation FOperation;
private Filter FFilter2;
Filter getFilter1() {
return FFilter1;
}
void setFilter1(Filter FFilter1) {
this.FFilter1 = FFilter1;
}
public FilterLogicalOperation getOperation() {
return FOperation;
}
public void setOperation(FilterLogicalOperation FOperation) {
this.FOperation = FOperation;
}
Filter getFilter2() {
return FFilter2;
}
void setFilter2(Filter FFilter2) {
this.FFilter2 = FFilter2;
}
@Override
public String toString() {
return FFilter1.toString() + " " + CODES_LogicalOperation.get(getOperation().ordinal()) + " " + FFilter2.toString();
}
}
static class FilterSyntaxException extends Exception {
FilterSyntaxException(String theMessage) {
super(theMessage);
}
}
static public Filter parse(String expression) throws FilterSyntaxException {
SearchFilterParser parser = new SearchFilterParser();
parser.original = expression;
parser.cursor = 0;
return parser.parse();
}
}

View File

@ -423,6 +423,10 @@ public class TransactionProcessor<BUNDLE extends IBaseBundle, BUNDLEENTRY> {
url = url.substring(0, qIndex);
}
if (url.length() > 0 && url.charAt(0) == '/') {
url = url.substring(1);
}
requestDetails.setRequestPath(url);
requestDetails.setFhirServerBase(theRequestDetails.getFhirServerBase());

View File

@ -41,7 +41,7 @@ import ca.uhn.fhir.rest.param.*;
public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3<Patient>implements IFhirResourceDaoPatient<Patient> {
private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequest) {
private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequest) {
SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) {
paramMap.setCount(theCount.getValue());
@ -68,13 +68,13 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3<Patient>im
}
@Override
public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) {
return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails);
public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) {
return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails);
}
@Override
public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) {
return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails);
public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) {
return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails);
}
}

View File

@ -41,7 +41,7 @@ import ca.uhn.fhir.rest.param.*;
public class FhirResourceDaoPatientR4 extends FhirResourceDaoR4<Patient>implements IFhirResourceDaoPatient<Patient> {
private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequest) {
private IBundleProvider doEverythingOperation(IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequest) {
SearchParameterMap paramMap = new SearchParameterMap();
if (theCount != null) {
paramMap.setCount(theCount.getValue());
@ -68,13 +68,13 @@ public class FhirResourceDaoPatientR4 extends FhirResourceDaoR4<Patient>implemen
}
@Override
public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) {
return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails);
public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) {
return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails);
}
@Override
public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) {
return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails);
public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) {
return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theFilter, theRequestDetails);
}
}

View File

@ -68,12 +68,12 @@ public class FhirResourceDaoPatientR5 extends FhirResourceDaoR5<Patient> impleme
}
@Override
public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) {
public IBundleProvider patientInstanceEverything(HttpServletRequest theServletRequest, IIdType theId, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) {
return doEverythingOperation(theId, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails);
}
@Override
public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, RequestDetails theRequestDetails) {
public IBundleProvider patientTypeEverything(HttpServletRequest theServletRequest, IPrimitiveType<Integer> theCount, DateRangeParam theLastUpdated, SortSpec theSort, StringAndListParam theContent, StringAndListParam theNarrative, StringAndListParam theFilter, RequestDetails theRequestDetails) {
return doEverythingOperation(null, theCount, theLastUpdated, theSort, theContent, theNarrative, theRequestDetails);
}

View File

@ -24,7 +24,6 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
import ca.uhn.fhir.jpa.model.entity.BaseHasResource;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.model.api.IQueryParameterType;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -33,22 +32,21 @@ import ca.uhn.fhir.rest.param.*;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseReference;
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.IdType;
import org.hl7.fhir.r4.model.Reference;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.r4.utils.GraphQLEngine;
import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.Value;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implements GraphQLEngine.IGraphQLStorageServices {
public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implements IGraphQLStorageServices {
private static final int MAX_SEARCH_SIZE = 500;
private IFhirResourceDao<? extends IBaseResource> getDao(String theResourceType) {
RuntimeResourceDefinition typeDef = getContext().getResourceDefinition(theResourceType);
@ -57,13 +55,13 @@ public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implement
@Transactional(propagation = Propagation.NEVER)
@Override
public void listResources(Object theAppInfo, String theType, List<Argument> theSearchParams, List<Resource> theMatches) throws FHIRException {
public void listResources(Object theAppInfo, String theType, List<Argument> theSearchParams, List<IBaseResource> theMatches) throws FHIRException {
RuntimeResourceDefinition typeDef = getContext().getResourceDefinition(theType);
IFhirResourceDao<? extends IBaseResource> dao = getDao(typeDef.getImplementingClass());
SearchParameterMap params = new SearchParameterMap();
params.setLoadSynchronousUpTo(500);
params.setLoadSynchronousUpTo(MAX_SEARCH_SIZE);
for (Argument nextArgument : theSearchParams) {
@ -114,40 +112,37 @@ public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implement
size = response.preferredPageSize();
}
for (IBaseResource next : response.getResources(0, size)) {
theMatches.add((Resource) next);
}
theMatches.addAll(response.getResources(0, size));
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public Resource lookup(Object theAppInfo, String theType, String theId) throws FHIRException {
public IBaseResource lookup(Object theAppInfo, String theType, String theId) throws FHIRException {
IIdType refId = getContext().getVersion().newIdType();
refId.setValue(theType + "/" + theId);
return lookup(theAppInfo, refId);
}
private Resource lookup(Object theAppInfo, IIdType theRefId) {
private IBaseResource lookup(Object theAppInfo, IIdType theRefId) {
IFhirResourceDao<? extends IBaseResource> dao = getDao(theRefId.getResourceType());
RequestDetails requestDetails = (RequestDetails) theAppInfo;
return (Resource) dao.read(theRefId, requestDetails, false);
return dao.read(theRefId, requestDetails, false);
}
@Transactional(propagation = Propagation.REQUIRED)
@Override
public ReferenceResolution lookup(Object theAppInfo, Resource theContext, Reference theReference) throws FHIRException {
IdType refId = new IdType(theReference.getReference());
Resource outcome = lookup(theAppInfo, refId);
@Override
public ReferenceResolution lookup(Object theAppInfo, IBaseResource theContext, IBaseReference theReference) throws FHIRException {
IBaseResource outcome = lookup(theAppInfo, theReference.getReferenceElement());
if (outcome == null) {
return null;
}
return null;
}
return new ReferenceResolution(theContext, outcome);
}
@Transactional(propagation = Propagation.NEVER)
@Override
public Bundle search(Object theAppInfo, String theType, List<Argument> theSearchParams) throws FHIRException {
public IBaseBundle search(Object theAppInfo, String theType, List<Argument> theSearchParams) throws FHIRException {
throw new NotImplementedOperationException("Not yet able to handle this GraphQL request");
}
}

View File

@ -66,6 +66,10 @@ public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu
@OperationParam(name = Constants.PARAM_TEXT, min=0, max=OperationParam.MAX_UNLIMITED)
List<StringDt> theNarrative,
@Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
@OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringDt> theFilter,
@Sort
SortSpec theSortSpec,
@ -74,7 +78,7 @@ public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails);
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails);
} finally {
endRequest(theServletRequest);
}
@ -104,6 +108,10 @@ public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu
@OperationParam(name = Constants.PARAM_TEXT, min=0, max=OperationParam.MAX_UNLIMITED)
List<StringDt> theNarrative,
@Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
@OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringDt> theFilter,
@Sort
SortSpec theSortSpec,
@ -112,7 +120,7 @@ public class BaseJpaResourceProviderPatientDstu2 extends JpaResourceProviderDstu
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails);
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails);
} finally {
endRequest(theServletRequest);
}

View File

@ -0,0 +1,165 @@
package ca.uhn.fhir.jpa.provider;
/*-
* #%L
* HAPI FHIR JPA Server
* %%
* Copyright (C) 2014 - 2019 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.support.IContextValidationSupport;
import ca.uhn.fhir.rest.annotation.GraphQL;
import ca.uhn.fhir.rest.annotation.GraphQLQuery;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Initialize;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.Validate;
import org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.utilities.graphql.IGraphQLEngine;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.hl7.fhir.utilities.graphql.ObjectValue;
import org.hl7.fhir.utilities.graphql.Parser;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.function.Supplier;
public class GraphQLProvider {
private final Supplier<IGraphQLEngine> engineFactory;
private final IGraphQLStorageServices myStorageServices;
private Logger ourLog = LoggerFactory.getLogger(GraphQLProvider.class);
/**
* Constructor which uses a default context and validation support object
*
* @param theStorageServices The storage services (this object will be used to retrieve various resources as required by the GraphQL engine)
*/
public GraphQLProvider(IGraphQLStorageServices theStorageServices) {
this(FhirContext.forR4(), null, theStorageServices);
}
/**
* Constructor which uses the given worker context
*
* @param theFhirContext The HAPI FHIR Context object
* @param theValidationSupport The HAPI Validation Support object, or null
* @param theStorageServices The storage services (this object will be used to retrieve various resources as required by the GraphQL engine)
*/
public GraphQLProvider(@Nonnull FhirContext theFhirContext, @Nullable IContextValidationSupport theValidationSupport, @Nonnull IGraphQLStorageServices theStorageServices) {
Validate.notNull(theFhirContext, "theFhirContext must not be null");
Validate.notNull(theStorageServices, "theStorageServices must not be null");
switch (theFhirContext.getVersion().getVersion()) {
case DSTU3: {
IValidationSupport validationSupport = (IValidationSupport) theValidationSupport;
validationSupport = ObjectUtils.defaultIfNull(validationSupport, new org.hl7.fhir.dstu3.hapi.ctx.DefaultProfileValidationSupport());
org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext workerContext = new org.hl7.fhir.dstu3.hapi.ctx.HapiWorkerContext(theFhirContext, validationSupport);
engineFactory = () -> new org.hl7.fhir.dstu3.utils.GraphQLEngine(workerContext);
break;
}
case R4: {
org.hl7.fhir.r4.hapi.ctx.IValidationSupport validationSupport = (org.hl7.fhir.r4.hapi.ctx.IValidationSupport) theValidationSupport;
validationSupport = ObjectUtils.defaultIfNull(validationSupport, new org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport());
org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext workerContext = new org.hl7.fhir.r4.hapi.ctx.HapiWorkerContext(theFhirContext, validationSupport);
engineFactory = () -> new org.hl7.fhir.r4.utils.GraphQLEngine(workerContext);
break;
}
case R5: {
org.hl7.fhir.r5.hapi.ctx.IValidationSupport validationSupport = (org.hl7.fhir.r5.hapi.ctx.IValidationSupport) theValidationSupport;
validationSupport = ObjectUtils.defaultIfNull(validationSupport, new org.hl7.fhir.r5.hapi.ctx.DefaultProfileValidationSupport());
org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext workerContext = new org.hl7.fhir.r5.hapi.ctx.HapiWorkerContext(theFhirContext, validationSupport);
engineFactory = () -> new org.hl7.fhir.r5.utils.GraphQLEngine(workerContext);
break;
}
case DSTU2:
case DSTU2_HL7ORG:
case DSTU2_1:
default: {
throw new UnsupportedOperationException("GraphQL not supported for version: " + theFhirContext.getVersion().getVersion());
}
}
myStorageServices = theStorageServices;
}
@GraphQL
public String processGraphQlRequest(ServletRequestDetails theRequestDetails, @IdParam IIdType theId, @GraphQLQuery String theQuery) {
IGraphQLEngine engine = engineFactory.get();
engine.setAppInfo(theRequestDetails);
engine.setServices(myStorageServices);
try {
engine.setGraphQL(Parser.parse(theQuery));
} catch (Exception theE) {
throw new InvalidRequestException("Unable to parse GraphQL Expression: " + theE.toString());
}
try {
if (theId != null) {
IBaseResource focus = myStorageServices.lookup(theRequestDetails, theId.getResourceType(), theId.getIdPart());
engine.setFocus(focus);
}
engine.execute();
StringBuilder outputBuilder = new StringBuilder();
ObjectValue output = engine.getOutput();
output.write(outputBuilder, 0, "\n");
return outputBuilder.toString();
} catch (Exception e) {
StringBuilder b = new StringBuilder();
b.append("Unable to execute GraphQL Expression: ");
int statusCode = 500;
if (e instanceof BaseServerResponseException) {
b.append("HTTP ");
statusCode = ((BaseServerResponseException) e).getStatusCode();
b.append(statusCode);
b.append(" ");
} else {
// This means it's a bug, so let's log
ourLog.error("Failure during GraphQL processing", e);
}
b.append(e.getMessage());
throw new UnclassifiedServerFailureException(statusCode, b.toString());
}
}
@Initialize
public void initialize(RestfulServer theServer) {
ourLog.trace("Initializing GraphQL provider");
if (!theServer.getFhirContext().getVersion().getVersion().isEqualOrNewerThan(FhirVersionEnum.DSTU3)) {
throw new ConfigurationException("Can not use " + getClass().getName() + " provider on server with FHIR " + theServer.getFhirContext().getVersion().getVersion().name() + " context");
}
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
@ -23,8 +25,6 @@ import org.hl7.fhir.dstu3.model.UnsignedIntType;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR JPA Server
@ -74,6 +74,10 @@ public class BaseJpaResourceProviderPatientDstu3 extends JpaResourceProviderDstu
@OperationParam(name = Constants.PARAM_TEXT, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theNarrative,
@Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
@OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theFilter,
@Sort
SortSpec theSortSpec,
@ -82,7 +86,7 @@ public class BaseJpaResourceProviderPatientDstu3 extends JpaResourceProviderDstu
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails);
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails);
} finally {
endRequest(theServletRequest);
}
@ -112,6 +116,10 @@ public class BaseJpaResourceProviderPatientDstu3 extends JpaResourceProviderDstu
@OperationParam(name = Constants.PARAM_TEXT, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theNarrative,
@Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
@OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theFilter,
@Sort
SortSpec theSortSpec,
@ -120,7 +128,7 @@ public class BaseJpaResourceProviderPatientDstu3 extends JpaResourceProviderDstu
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails);
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails);
} finally {
endRequest(theServletRequest);
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.jpa.provider.r4;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoPatient;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.model.api.annotation.Description;
@ -23,8 +25,6 @@ import org.hl7.fhir.r4.model.UnsignedIntType;
import java.util.List;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*
* #%L
* HAPI FHIR JPA Server
@ -74,6 +74,10 @@ public class BaseJpaResourceProviderPatientR4 extends JpaResourceProviderR4<Pati
@OperationParam(name = Constants.PARAM_TEXT, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theNarrative,
@Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
@OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theFilter,
@Sort
SortSpec theSortSpec,
@ -82,7 +86,7 @@ public class BaseJpaResourceProviderPatientR4 extends JpaResourceProviderR4<Pati
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails);
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails);
} finally {
endRequest(theServletRequest);
}
@ -112,6 +116,10 @@ public class BaseJpaResourceProviderPatientR4 extends JpaResourceProviderR4<Pati
@OperationParam(name = Constants.PARAM_TEXT, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theNarrative,
@Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
@OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theFilter,
@Sort
SortSpec theSortSpec,
@ -120,7 +128,7 @@ public class BaseJpaResourceProviderPatientR4 extends JpaResourceProviderR4<Pati
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails);
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails);
} finally {
endRequest(theServletRequest);
}

View File

@ -74,6 +74,10 @@ public class BaseJpaResourceProviderPatientR5 extends JpaResourceProviderR5<Pati
@OperationParam(name = Constants.PARAM_TEXT, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theNarrative,
@Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
@OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theFilter,
@Sort
SortSpec theSortSpec,
@ -82,7 +86,7 @@ public class BaseJpaResourceProviderPatientR5 extends JpaResourceProviderR5<Pati
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails);
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientInstanceEverything(theServletRequest, theId, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails);
} finally {
endRequest(theServletRequest);
}
@ -112,6 +116,10 @@ public class BaseJpaResourceProviderPatientR5 extends JpaResourceProviderR5<Pati
@OperationParam(name = Constants.PARAM_TEXT, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theNarrative,
@Description(shortDefinition = "Filter the resources to return only resources matching the given _filter filter (note that this filter is applied only to results which link to the given patient, not to the patient itself or to supporting resources linked to by the matched resources)")
@OperationParam(name = Constants.PARAM_FILTER, min = 0, max = OperationParam.MAX_UNLIMITED)
List<StringType> theFilter,
@Sort
SortSpec theSortSpec,
@ -120,7 +128,7 @@ public class BaseJpaResourceProviderPatientR5 extends JpaResourceProviderR5<Pati
startRequest(theServletRequest);
try {
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), theRequestDetails);
return ((IFhirResourceDaoPatient<Patient>) getDao()).patientTypeEverything(theServletRequest, theCount, theLastUpdated, theSortSpec, toStringAndList(theContent), toStringAndList(theNarrative), toStringAndList(theFilter), theRequestDetails);
} finally {
endRequest(theServletRequest);
}

View File

@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.util.CurrentThreadCaptureQueriesListener;
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
import ca.uhn.fhir.validation.ResultSeverityEnum;
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
import org.apache.commons.dbcp2.BasicDataSource;
import org.hibernate.dialect.H2Dialect;
@ -106,9 +107,10 @@ public class TestR4Config extends BaseJavaConfigR4 {
retVal.setPassword("");
retVal.setMaxTotal(ourMaxThreads);
SLF4JLogLevel level = SLF4JLogLevel.INFO;
DataSource dataSource = ProxyDataSourceBuilder
.create(retVal)
// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
.logQueryBySlf4j(level, "SQL")
// .logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
// .countQuery(new ThreadQueryCountHolder())
.beforeQuery(new BlockLargeNumbersOfParamsListener())

View File

@ -0,0 +1,79 @@
package ca.uhn.fhir.jpa.dao;
import ca.uhn.fhir.util.TestUtil;
import org.junit.AfterClass;
import org.junit.Assert;
import org.junit.Test;
public class SearchFilterSyntaxTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchFilterSyntaxTest.class);
private void testParse(String theExpression) throws SearchFilterParser.FilterSyntaxException {
SearchFilterParser.Filter filter = SearchFilterParser.parse(theExpression);
ourLog.info("Source: {}", theExpression);
ourLog.info("Parsed: {}", filter.toString());
Assert.assertNotNull("Parsing failed - returned null",
filter);
Assert.assertEquals(String.format("Expression mismatch: found %s, expecting %s",
filter.toString(),
theExpression),
theExpression,
filter.toString());
}
@Test
public void testString() throws SearchFilterParser.FilterSyntaxException {
testParse("userName eq \"bjensen\"");
}
@Test
public void testToken() throws SearchFilterParser.FilterSyntaxException {
testParse("name eq loinc|1234");
}
@Test
public void testUrl() throws SearchFilterParser.FilterSyntaxException {
testParse("name in http://loinc.org/vs/LP234");
}
@Test
public void testDate() throws SearchFilterParser.FilterSyntaxException {
testParse("date ge 2010-10-10");
}
@Test
public void testSubsumes() throws SearchFilterParser.FilterSyntaxException {
testParse("code sb snomed|diabetes");
}
@Test
public void testSubsumesId() throws SearchFilterParser.FilterSyntaxException {
testParse("code ss snomed|diabetes-NIDDM-stage-1");
}
@Test
public void testFilter() throws SearchFilterParser.FilterSyntaxException {
testParse("related[type eq comp].target pr false");
}
@Test
public void testFilter2() throws SearchFilterParser.FilterSyntaxException {
testParse("related[type eq comp and this lt that].target pr false");
}
@Test
public void testParentheses() throws SearchFilterParser.FilterSyntaxException {
testParse("((userName eq \"bjensen\") or (userName eq \"jdoe\")) and (code sb snomed|diabetes)");
}
@Test
public void testPrecedence() throws SearchFilterParser.FilterSyntaxException {
testParse("this eq that and this1 eq that1");
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -132,6 +132,9 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
@Qualifier("myPatientDaoDstu2")
protected IFhirResourceDaoPatient<Patient> myPatientDao;
@Autowired
@Qualifier("myConformanceDaoDstu2")
protected IFhirResourceDao<Conformance> myConformanceDao;
@Autowired
@Qualifier("myGroupDaoDstu2")
protected IFhirResourceDao<Group> myGroupDao;
@Autowired

View File

@ -251,16 +251,16 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mySrd));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId1, devId1));
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obstext1")));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, mySrd));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId1, devId1));
request = mock(HttpServletRequest.class);
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, mySrd));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId1, obsId2, devId1));
/*
@ -276,7 +276,7 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mySrd));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId1, obsId4, devId1));
/*
@ -292,7 +292,7 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mySrd));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId4));
}
@ -343,11 +343,11 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mySrd));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId1, devId1));
request = mock(HttpServletRequest.class);
actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId1, obsId2, devId1, ptId2, obsId3));
/*
@ -363,7 +363,7 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mySrd));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId1, obsId4, devId1));
/*
@ -379,7 +379,7 @@ public class FhirResourceDaoDstu2SearchFtTest extends BaseJpaDstu2Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mySrd));
actual = toUnqualifiedVersionlessIds(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mySrd));
assertThat(actual, containsInAnyOrder(ptId1, obsId4));
}

View File

@ -92,11 +92,11 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
IIdType moId = myMedicationOrderDao.create(mo, mySrd).getId().toUnqualifiedVersionless();
HttpServletRequest request = mock(HttpServletRequest.class);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2));
request = mock(HttpServletRequest.class);
resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, mySrd);
resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId));
}

View File

@ -6,6 +6,7 @@ import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
import ca.uhn.fhir.model.dstu2.resource.Conformance;
import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
@ -18,6 +19,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.AfterClass;
import org.junit.Test;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
@ -84,6 +86,18 @@ public class FhirResourceDaoDstu2UpdateTest extends BaseJpaDstu2Test {
myPatientDao.update(p, mySrd);
}
/**
* Make sure we can upload a real example resource from the DSTU2 argonaut example pack
*/
@Test
public void testUploadArgonautExampleConformance() throws IOException {
Conformance conformance = loadResourceFromClasspath(Conformance.class, "/dstu2/Conformance-server.json");
conformance.setId("");
myConformanceDao.create(conformance);
assertEquals(1, myConformanceDao.search(new SearchParameterMap().setLoadSynchronous(true)).size().intValue());
}
/**
* Per the spec, update should preserve tags and security labels but not profiles
*/

View File

@ -334,16 +334,16 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1)));
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obstext1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1)));
request = mock(HttpServletRequest.class);
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1)));
/*
@ -359,7 +359,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1)));
/*
@ -375,7 +375,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4)));
}
@ -426,11 +426,11 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1)));
request = mock(HttpServletRequest.class);
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1, ptId2, obsId3)));
/*
@ -446,7 +446,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1)));
/*
@ -462,7 +462,7 @@ public class FhirResourceDaoDstu3SearchFtTest extends BaseJpaDstu3Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4)));
}

View File

@ -171,11 +171,11 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
IIdType moId = myMedicationRequestDao.create(mo, mySrd).getId().toUnqualifiedVersionless();
HttpServletRequest request = mock(HttpServletRequest.class);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2));
request = mock(HttpServletRequest.class);
resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, mySrd);
resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId));
}
@ -202,7 +202,7 @@ public class FhirResourceDaoDstu3SearchNoFtTest extends BaseJpaDstu3Test {
SearchParameterMap map = new SearchParameterMap();
map.setEverythingMode(EverythingModeEnum.PATIENT_INSTANCE);
IPrimitiveType<Integer> count = new IntegerType(1000);
IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, mySrd);
IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, null, mySrd);
TreeSet<String> ids = new TreeSet<>(toUnqualifiedVersionlessIdValues(everything));
assertThat(ids, hasItem("List/A161444"));

View File

@ -464,12 +464,6 @@ public class ConsentEventsDaoR4Test extends BaseJpaR4SystemTest {
ourLog.info("Allowing IDs: {}", nonBlocked);
try {
throw new Exception();
} catch (Exception e) {
ourLog.error("Trace", e);
}
}
}

View File

@ -0,0 +1,177 @@
package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.jpa.util.TestUtil;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import org.hamcrest.Matchers;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r4.model.CarePlan;
import org.hl7.fhir.r4.model.Patient;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test;
import java.util.List;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
@SuppressWarnings({"Duplicates"})
public class FhirResourceDaoR4FilterTest extends BaseJpaR4Test {
@After
public void after() {
myDaoConfig.setFilterParameterEnabled(new DaoConfig().isFilterParameterEnabled());
}
@Before
public void before() {
myDaoConfig.setFilterParameterEnabled(true);
}
@Test
public void testMalformedFilter() {
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("name eq smith))"));
try {
myPatientDao.search(map);
fail();
} catch (InvalidRequestException e) {
assertEquals("Error parsing _filter syntax: Expression did not terminate at 13", e.getMessage());
}
}
@Test
public void testBrackets() {
Patient p = new Patient();
p.addName().setFamily("Smith").addGiven("John");
p.setActive(true);
String id1 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue();
p = new Patient();
p.addName().setFamily("Jones").addGiven("Frank");
p.setActive(false);
String id2 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("name eq smith"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, containsInAnyOrder(id1));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("(name eq smith) or (name eq jones)"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, containsInAnyOrder(id1, id2));
}
@Test
public void testStringComparatorEq() {
Patient p = new Patient();
p.addName().setFamily("Smith").addGiven("John");
p.setActive(true);
String id1 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue();
p = new Patient();
p.addName().setFamily("Jones").addGiven("Frank");
p.setActive(false);
String id2 = myPatientDao.create(p).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("name eq smi"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, Matchers.empty());
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("name eq smith"));
found = toUnqualifiedVersionlessIdValues(myPatientDao.search(map));
assertThat(found, containsInAnyOrder(id1));
}
@Test
public void testReferenceComparatorEq() {
Patient p = new Patient();
p.addName().setFamily("Smith").addGiven("John");
p.setActive(true);
IIdType ptId = myPatientDao.create(p).getId().toUnqualifiedVersionless();
p = new Patient();
p.addName().setFamily("Smith").addGiven("John2");
p.setActive(true);
IIdType ptId2 = myPatientDao.create(p).getId().toUnqualifiedVersionless();
p = new Patient();
p.addName().setFamily("Smith").addGiven("John3");
p.setActive(true);
IIdType ptId3 = myPatientDao.create(p).getId().toUnqualifiedVersionless();
CarePlan cp = new CarePlan();
cp.getSubject().setReference(ptId.getValue());
String cpId = myCarePlanDao.create(cp).getId().toUnqualifiedVersionless().getValue();
cp = new CarePlan();
cp.addActivity().getDetail().addPerformer().setReference(ptId2.getValue());
String cpId2 = myCarePlanDao.create(cp).getId().toUnqualifiedVersionless().getValue();
SearchParameterMap map;
List<String> found;
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("subject eq " + ptId.getValue()));
found = toUnqualifiedVersionlessIdValues(myCarePlanDao.search(map));
assertThat(found, containsInAnyOrder(cpId));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("subject eq " + ptId.getIdPart()));
found = toUnqualifiedVersionlessIdValues(myCarePlanDao.search(map));
assertThat(found, containsInAnyOrder(cpId));
map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("(subject eq " + ptId.getIdPart() + ") or (performer eq " + ptId2.getValue() + ")"));
found = toUnqualifiedVersionlessIdValues(myCarePlanDao.search(map));
assertThat(found, containsInAnyOrder(cpId, cpId2));
}
@Test
public void testFilterDisabled() {
myDaoConfig.setFilterParameterEnabled(false);
SearchParameterMap map = new SearchParameterMap();
map.setLoadSynchronous(true);
map.add(Constants.PARAM_FILTER, new StringParam("name eq smith"));
try {
myPatientDao.search(map);
} catch (InvalidRequestException e) {
assertEquals("_filter parameter is disabled on this server", e.getMessage());
}
}
@AfterClass
public static void afterClassClearContext() {
TestUtil.clearAllStaticFieldsForUnitTest();
}
}

View File

@ -494,6 +494,56 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
}
@Test
public void testSearchForExtensionReferenceWithTwoPaths() {
Practitioner p1 = new Practitioner();
p1.addName().setFamily("P1");
IIdType p1id = myPractitionerDao.create(p1).getId().toUnqualifiedVersionless();
Practitioner p2 = new Practitioner();
p2.addName().setFamily("P2");
IIdType p2id = myPractitionerDao.create(p2).getId().toUnqualifiedVersionless();
SearchParameter sp = new SearchParameter();
sp.addBase("DiagnosticReport");
sp.setCode("fooBar");
sp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.REFERENCE);
sp.setTitle("FOO AND BAR");
sp.setExpression("DiagnosticReport.extension('http://foo') | DiagnosticReport.extension('http://bar')");
sp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL);
sp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(sp, mySrd);
mySearchParamRegistry.forceRefresh();
DiagnosticReport dr1 = new DiagnosticReport();
dr1.addExtension("http://foo", new Reference(p1id.getValue()));
IIdType dr1id = myDiagnosticReportDao.create(dr1).getId().toUnqualifiedVersionless();
DiagnosticReport dr2 = new DiagnosticReport();
dr2.addExtension("http://bar", new Reference(p2id.getValue()));
IIdType dr2id = myDiagnosticReportDao.create(dr2).getId().toUnqualifiedVersionless();
SearchParameterMap map;
IBundleProvider results;
List<String> foundResources;
// Find one
map = new SearchParameterMap();
map.add(sp.getCode(), new ReferenceParam(p1id.getValue()));
results = myDiagnosticReportDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, containsInAnyOrder(dr1id.getValue()));
// Find both
map = new SearchParameterMap();
map.add(sp.getCode(), new ReferenceOrListParam().addOr(new ReferenceParam(p1id.getValue())).addOr(new ReferenceParam(p2id.getValue())));
results = myDiagnosticReportDao.search(map);
foundResources = toUnqualifiedVersionlessIdValues(results);
assertThat(foundResources, containsInAnyOrder(dr1id.getValue(), dr2id.getValue()));
}
@Test
public void testSearchForExtensionReferenceWithTarget() {
SearchParameter siblingSp = new SearchParameter();

View File

@ -345,16 +345,16 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1)));
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obstext1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, param, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1)));
request = mock(HttpServletRequest.class);
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, null, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1)));
/*
@ -370,7 +370,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1)));
/*
@ -386,7 +386,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientInstanceEverything(request, ptId1, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4)));
}
@ -437,11 +437,11 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, devId1)));
request = mock(HttpServletRequest.class);
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId2, devId1, ptId2, obsId3)));
/*
@ -457,7 +457,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId1, obsId4, devId1)));
/*
@ -473,7 +473,7 @@ public class FhirResourceDaoR4SearchFtTest extends BaseJpaR4Test {
param = new StringAndListParam();
param.addAnd(new StringOrListParam().addOr(new StringParam("obsvalue1")));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, mockSrd()));
actual = toUnqualifiedVersionlessIdValues(myPatientDao.patientTypeEverything(request, null, null, null, param, null, null, mockSrd()));
assertThat(actual, containsInAnyOrder(toValues(ptId1, obsId4)));
}

View File

@ -470,11 +470,11 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
IIdType moId = myMedicationRequestDao.create(mo, mySrd).getId().toUnqualifiedVersionless();
HttpServletRequest request = mock(HttpServletRequest.class);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2));
request = mock(HttpServletRequest.class);
resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, mySrd);
resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId));
}
@ -502,7 +502,7 @@ public class FhirResourceDaoR4SearchNoFtTest extends BaseJpaR4Test {
SearchParameterMap map = new SearchParameterMap();
map.setEverythingMode(EverythingModeEnum.PATIENT_INSTANCE);
IPrimitiveType<Integer> count = new IntegerType(1000);
IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, mySrd);
IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, null, mySrd);
TreeSet<String> ids = new TreeSet<>(toUnqualifiedVersionlessIdValues(everything));
assertThat(ids, hasItem("List/A161444"));

View File

@ -292,11 +292,11 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
IIdType moId = myMedicationRequestDao.create(mo, mySrd).getId().toUnqualifiedVersionless();
HttpServletRequest request = mock(HttpServletRequest.class);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, mySrd);
IBundleProvider resp = myPatientDao.patientTypeEverything(request, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId, patId2));
request = mock(HttpServletRequest.class);
resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, mySrd);
resp = myPatientDao.patientInstanceEverything(request, patId, null, null, null, null, null, null, mySrd);
assertThat(toUnqualifiedVersionlessIds(resp), containsInAnyOrder(orgId, medId, patId, moId));
}
@ -324,9 +324,9 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
SearchParameterMap map = new SearchParameterMap();
map.setEverythingMode(EverythingModeEnum.PATIENT_INSTANCE);
IPrimitiveType<Integer> count = new IntegerType(1000);
IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, mySrd);
IBundleProvider everything = myPatientDao.patientInstanceEverything(mySrd.getServletRequest(), new IdType("Patient/A161443"), count, null, null, null, null, null, mySrd);
TreeSet<String> ids = new TreeSet<String>(toUnqualifiedVersionlessIdValues(everything));
TreeSet<String> ids = new TreeSet<>(toUnqualifiedVersionlessIdValues(everything));
assertThat(ids, hasItem("List/A161444"));
assertThat(ids, hasItem("List/A161468"));
assertThat(ids, hasItem("List/A161500"));
@ -335,7 +335,7 @@ public class FhirResourceDaoR4SearchNoHashesTest extends BaseJpaR4Test {
ourLog.info("Actual {} - {}", ids.size(), ids);
assertEquals(allIds, ids);
ids = new TreeSet<String>();
ids = new TreeSet<>();
for (int i = 0; i < everything.size(); i++) {
for (IBaseResource next : everything.getResources(i, i + 1)) {
ids.add(next.getIdElement().toUnqualifiedVersionless().getValue());

View File

@ -710,7 +710,6 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
@Test
public void testCreateLongString() {
//@formatter:off
String input = "<NamingSystem>\n" +
" <name value=\"NDF-RT (National Drug File Reference Terminology)\"/>\n" +
" <status value=\"draft\"/>\n" +
@ -728,12 +727,13 @@ public class FhirResourceDaoR4Test extends BaseJpaR4Test {
" <preferred value=\"false\"/>\n" +
" </uniqueId>\n" +
" </NamingSystem>";
//@formatter:on
NamingSystem res = myFhirCtx.newXmlParser().parseResource(NamingSystem.class, input);
IIdType id = myNamingSystemDao.create(res, mySrd).getId().toUnqualifiedVersionless();
assertThat(toUnqualifiedVersionlessIdValues(myNamingSystemDao.search(new SearchParameterMap(NamingSystem.SP_NAME, new StringParam("NDF")).setLoadSynchronous(true))), org.hamcrest.Matchers.contains(id.getValue()));
SearchParameterMap params = new SearchParameterMap(NamingSystem.SP_NAME, new StringParam("NDF")).setLoadSynchronous(true);
IBundleProvider result = myNamingSystemDao.search(params);
assertThat(toUnqualifiedVersionlessIdValues(result), org.hamcrest.Matchers.contains(id.getValue()));
}
@Test

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.rest.server;
package ca.uhn.fhir.jpa.provider;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.annotation.OptionalParam;
@ -6,6 +6,10 @@ import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
@ -18,11 +22,14 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.exceptions.FHIRException;
import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.r4.hapi.rest.server.GraphQLProvider;
import org.hl7.fhir.r4.model.*;
import org.hl7.fhir.r4.utils.GraphQLEngine;
import org.hl7.fhir.r4.model.Bundle;
import org.hl7.fhir.r4.model.HumanName;
import org.hl7.fhir.r4.model.Patient;
import org.hl7.fhir.r4.model.Resource;
import org.hl7.fhir.utilities.graphql.Argument;
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
@ -34,9 +41,8 @@ import java.util.List;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.*;
import ca.uhn.fhir.test.utilities.JettyUtil;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
public class GraphQLR4ProviderTest {
@ -216,9 +222,9 @@ public class GraphQLR4ProviderTest {
}
private static class MyStorageServices implements GraphQLEngine.IGraphQLStorageServices {
private static class MyStorageServices implements IGraphQLStorageServices {
@Override
public void listResources(Object theAppInfo, String theType, List<Argument> theSearchParams, List<Resource> theMatches) throws FHIRException {
public void listResources(Object theAppInfo, String theType, List<Argument> theSearchParams, List<IBaseResource> theMatches) throws FHIRException {
ourLog.info("listResources of {} - {}", theType, theSearchParams);
if (theSearchParams.size() == 1) {
@ -264,8 +270,8 @@ public class GraphQLR4ProviderTest {
}
@Override
public ReferenceResolution lookup(Object theAppInfo, Resource theContext, Reference theReference) throws FHIRException {
ourLog.info("lookup from {} to {}", theContext.getIdElement().getValue(), theReference.getReference());
public IGraphQLStorageServices.ReferenceResolution lookup(Object theAppInfo, IBaseResource theContext, IBaseReference theReference) throws FHIRException {
ourLog.info("lookup from {} to {}", theContext.getIdElement().getValue(), theReference.getReferenceElement().getValue());
return null;
}

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.dstu3.BaseJpaDstu3Test;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
@ -97,6 +98,8 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
SubscriptionTriggeringProvider subscriptionTriggeringProvider = myAppCtx.getBean(SubscriptionTriggeringProvider.class);
ourRestServer.registerProvider(subscriptionTriggeringProvider);
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
JpaConformanceProviderDstu3 confProvider = new JpaConformanceProviderDstu3(ourRestServer, mySystemDao, myDaoConfig);
confProvider.setImplementationDescription("THIS IS THE DESC");
ourRestServer.setServerConformanceProvider(confProvider);

View File

@ -0,0 +1,92 @@
package ca.uhn.fhir.jpa.provider.dstu3;
import ca.uhn.fhir.util.TestUtil;
import ca.uhn.fhir.util.UrlUtil;
import org.apache.commons.io.IOUtils;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.dstu3.model.Patient;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
import static org.junit.Assert.assertEquals;
public class GraphQLProviderDstu3Test extends BaseResourceProviderDstu3Test {
private Logger ourLog = LoggerFactory.getLogger(GraphQLProviderDstu3Test.class);
private IIdType myPatientId0;
@Test
public void testInstanceSimpleRead() throws IOException {
initTestPatients();
String query = "{name{family,given}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/Patient/" + myPatientId0.getIdPart() + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(TestUtil.stripReturns("{\n" +
" \"name\":[{\n" +
" \"family\":\"FAM\",\n" +
" \"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
" },{\n" +
" \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
" }]\n" +
"}"), TestUtil.stripReturns(resp));
}
}
@Test
public void testSystemSimpleSearch() throws IOException {
initTestPatients();
String query = "{PatientList(given:\"given\"){name{family,given}}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(TestUtil.stripReturns("{\n" +
" \"PatientList\":[{\n" +
" \"name\":[{\n" +
" \"family\":\"FAM\",\n" +
" \"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
" },{\n" +
" \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
" }]\n" +
" },{\n" +
" \"name\":[{\n" +
" \"given\":[\"GivenOnlyB1\",\"GivenOnlyB2\"]\n" +
" }]\n" +
" }]\n" +
"}"), TestUtil.stripReturns(resp));
}
}
private void initTestPatients() {
Patient p = new Patient();
p.addName()
.setFamily("FAM")
.addGiven("GIVEN1")
.addGiven("GIVEN2");
p.addName()
.addGiven("GivenOnly1")
.addGiven("GivenOnly2");
myPatientId0 = ourClient.create().resource(p).execute().getId().toUnqualifiedVersionless();
p = new Patient();
p.addName()
.addGiven("GivenOnlyB1")
.addGiven("GivenOnlyB2");
ourClient.create().resource(p).execute();
}
}

View File

@ -5,6 +5,7 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.TimeUnit;
@ -14,10 +15,10 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.dstu3.model.Bundle;
import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.dstu3.model.Bundle.*;
import org.hl7.fhir.dstu3.model.Enumerations.AdministrativeGender;
import org.hl7.fhir.dstu3.model.Patient;
import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*;
import ca.uhn.fhir.context.FhirContext;
@ -31,6 +32,8 @@ import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.TestUtil;
import javax.validation.constraints.NotNull;
public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test {
private static RestfulServer myRestServer;
@ -262,6 +265,24 @@ public class SystemProviderTransactionSearchDstu3Test extends BaseJpaDstu3Test {
}
}
@Test
public void testTransactionGetStartsWithSlash() {
IIdType patientId = ourClient.create().resource(new Patient()).execute().getId().toUnqualifiedVersionless();
Bundle input = new Bundle();
input.setType(BundleType.BATCH);
input.setId("bundle-batch-test");
input.addEntry().getRequest().setMethod(HTTPVerb.GET)
.setUrl("/Patient?_id="+patientId.getIdPart());
Bundle output = ourClient.transaction().withBundle(input).execute();
ourLog.info(myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(output));
assertThat(output.getEntryFirstRep().getResponse().getStatus(), startsWith("200"));
Bundle respBundle = (Bundle) output.getEntry().get(0).getResource();
List<String> actualIds = toIds(respBundle);
assertThat(actualIds, containsInAnyOrder(patientId.getValue()));
}
private List<String> toIds(Bundle theRespBundle) {
ArrayList<String> retVal = new ArrayList<String>();
for (BundleEntryComponent next : theRespBundle.getEntry()) {

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
@ -71,7 +72,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
protected IGenericClient ourClient;
ResourceCountCache ourResourceCountsCache;
private TerminologyUploaderProvider myTerminologyUploaderProvider;
private Object ourGraphQLProvider;
private boolean ourRestHookSubscriptionInterceptorRequested;
@Autowired
@ -105,10 +105,10 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
ourRestServer.setDefaultResponseEncoding(EncodingEnum.XML);
myTerminologyUploaderProvider = myAppCtx.getBean(TerminologyUploaderProvider.class);
ourGraphQLProvider = myAppCtx.getBean("myGraphQLProvider");
myDaoRegistry = myAppCtx.getBean(DaoRegistry.class);
ourRestServer.registerProviders(mySystemProvider, myTerminologyUploaderProvider, ourGraphQLProvider);
ourRestServer.registerProviders(mySystemProvider, myTerminologyUploaderProvider);
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
JpaConformanceProviderR4 confProvider = new JpaConformanceProviderR4(ourRestServer, mySystemDao, myDaoConfig);
confProvider.setImplementationDescription("THIS IS THE DESC");

View File

@ -27,20 +27,17 @@ public class GraphQLProviderR4Test extends BaseResourceProviderR4Test {
String query = "{name{family,given}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/Patient/" + myPatientId0.getIdPart() + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
CloseableHttpResponse response = ourHttpClient.execute(httpGet);
try {
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(TestUtil.stripReturns(resp), TestUtil.stripReturns("{\n" +
assertEquals(TestUtil.stripReturns("{\n" +
" \"name\":[{\n" +
" \"family\":\"FAM\",\n" +
" \"given\":[\"GIVEN1\",\"GIVEN2\"]\n" +
" },{\n" +
" \"given\":[\"GivenOnly1\",\"GivenOnly2\"]\n" +
" }]\n" +
"}"));
} finally {
IOUtils.closeQuietly(response);
"}"), TestUtil.stripReturns(resp));
}
}
@ -52,8 +49,7 @@ public class GraphQLProviderR4Test extends BaseResourceProviderR4Test {
String query = "{PatientList(given:\"given\"){name{family,given}}}";
HttpGet httpGet = new HttpGet(ourServerBase + "/$graphql?query=" + UrlUtil.escapeUrlParam(query));
CloseableHttpResponse response = ourHttpClient.execute(httpGet);
try {
try (CloseableHttpResponse response = ourHttpClient.execute(httpGet)) {
String resp = IOUtils.toString(response.getEntity().getContent(), StandardCharsets.UTF_8);
ourLog.info(resp);
assertEquals(TestUtil.stripReturns("{\n" +
@ -70,10 +66,7 @@ public class GraphQLProviderR4Test extends BaseResourceProviderR4Test {
" }]\n" +
" }]\n" +
"}"), TestUtil.stripReturns(resp));
} finally {
IOUtils.closeQuietly(response);
}
}
private void initTestPatients() {

View File

@ -4,6 +4,7 @@ import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
import ca.uhn.fhir.jpa.dao.DaoRegistry;
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.dao.r5.BaseJpaR5Test;
import ca.uhn.fhir.jpa.provider.GraphQLProvider;
import ca.uhn.fhir.jpa.provider.TerminologyUploaderProvider;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
@ -108,8 +109,7 @@ public abstract class BaseResourceProviderR5Test extends BaseJpaR5Test {
myDaoRegistry = myAppCtx.getBean(DaoRegistry.class);
ourRestServer.registerProviders(mySystemProvider, myTerminologyUploaderProvider);
// ourGraphQLProvider = myAppCtx.getBean("myGraphQLProvider");
// ourRestServer.registerProvider(ourGraphQLProvider);
ourRestServer.registerProvider(myAppCtx.getBean(GraphQLProvider.class));
JpaConformanceProviderR5 confProvider = new JpaConformanceProviderR5(ourRestServer, mySystemDao, myDaoConfig);
confProvider.setImplementationDescription("THIS IS THE DESC");

View File

@ -3,7 +3,12 @@ package ca.uhn.fhir.jpa.stresstest;
import ca.uhn.fhir.jpa.config.TestR4Config;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
import ca.uhn.fhir.jpa.search.SearchCoordinatorSvcImpl;
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
import ca.uhn.fhir.rest.api.SortSpec;
import ca.uhn.fhir.rest.client.api.IGenericClient;
import ca.uhn.fhir.test.utilities.UnregisterScheduledProcessor;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.server.IBundleProvider;
@ -32,10 +37,9 @@ import org.junit.Before;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.TestPropertySource;
import org.springframework.test.util.AopTestUtils;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.*;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -64,7 +68,8 @@ public class StressTestR4Test extends BaseResourceProviderR4Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StressTestR4Test.class);
private RequestValidatingInterceptor myRequestValidatingInterceptor;
@Autowired
private IPagingProvider myPagingProvider;
private DatabaseBackedPagingProvider myPagingProvider;
private int myPreviousMaxPageSize;
@Override
@After
@ -74,6 +79,12 @@ public class StressTestR4Test extends BaseResourceProviderR4Test {
ourRestServer.unregisterInterceptor(myRequestValidatingInterceptor);
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.ENABLED);
myPagingProvider.setMaximumPageSize(myPreviousMaxPageSize);
SearchCoordinatorSvcImpl searchCoordinator = AopTestUtils.getTargetObject(mySearchCoordinatorSvc);
searchCoordinator.setLoadingThrottleForUnitTests(null);
myDaoConfig.setSearchPreFetchThresholds(new DaoConfig().getSearchPreFetchThresholds());
}
@Override
@ -85,8 +96,92 @@ public class StressTestR4Test extends BaseResourceProviderR4Test {
FhirInstanceValidator module = new FhirInstanceValidator();
module.setValidationSupport(myValidationSupport);
myRequestValidatingInterceptor.addValidatorModule(module);
myPreviousMaxPageSize = myPagingProvider.getMaximumPageSize();
myPagingProvider.setMaximumPageSize(300);
}
@Autowired
private ISearchCoordinatorSvc mySearchCoordinatorSvc;
@Test
public void testNoDuplicatesInSearchResults() throws Exception {
int resourceCount = 1000;
int queryCount = 30;
myDaoConfig.setSearchPreFetchThresholds(Lists.newArrayList(50, 200, -1));
SearchCoordinatorSvcImpl searchCoordinator = AopTestUtils.getTargetObject(mySearchCoordinatorSvc);
searchCoordinator.setLoadingThrottleForUnitTests(10);
Bundle bundle = new Bundle();
for (int i = 0; i < resourceCount; i++) {
Observation o = new Observation();
o.setId("A" + leftPad(Integer.toString(i), 4, '0'));
o.setEffective( DateTimeType.now());
o.setStatus(Observation.ObservationStatus.FINAL);
bundle.addEntry().setFullUrl(o.getId()).setResource(o).getRequest().setMethod(HTTPVerb.PUT).setUrl("Observation/A" + i);
}
StopWatch sw = new StopWatch();
ourLog.info("Saving {} resources", bundle.getEntry().size());
mySystemDao.transaction(null, bundle);
ourLog.info("Saved {} resources in {}", bundle.getEntry().size(), sw.toString());
Map<String, IBaseResource> ids = new HashMap<>();
IGenericClient fhirClient = this.ourClient;
String url = ourServerBase + "/Observation?date=gt2000&_sort=-_lastUpdated";
int pageIndex = 0;
ourLog.info("Loading page {}", pageIndex);
Bundle searchResult = fhirClient
.search()
.byUrl(url)
.count(queryCount)
.returnBundle(Bundle.class)
.execute();
while(true) {
List<String> passIds = searchResult
.getEntry()
.stream()
.map(t -> t.getResource().getIdElement().getValue())
.collect(Collectors.toList());
int index = 0;
for (String nextId : passIds) {
Resource nextResource = searchResult.getEntry().get(index).getResource();
if (ids.containsKey(nextId)) {
String previousContent = fhirClient.getFhirContext().newJsonParser().encodeResourceToString(ids.get(nextId));
String newContent = fhirClient.getFhirContext().newJsonParser().encodeResourceToString(nextResource);
throw new Exception("Duplicate ID " + nextId + " found at index " + index + " of page " + pageIndex + "\n\nPrevious: " + previousContent + "\n\nNew: " + newContent);
}
ids.put(nextId, nextResource);
index++;
}
if (searchResult.getLink(Constants.LINK_NEXT) == null) {
break;
} else {
if (searchResult.getEntry().size() != queryCount) {
throw new Exception("Page had " + searchResult.getEntry().size() + " resources");
}
if (passIds.size() != queryCount) {
throw new Exception("Page had " + passIds.size() + " unique ids");
}
}
pageIndex++;
ourLog.info("Loading page {}: {}", pageIndex, searchResult.getLink(Constants.LINK_NEXT).getUrl());
searchResult = fhirClient.loadPage().next(searchResult).execute();
}
assertEquals(resourceCount, ids.size());
}
@Test
public void testPageThroughLotsOfPages() {
myDaoConfig.setIndexMissingFields(DaoConfig.IndexEnabledEnum.DISABLED);

View File

@ -0,0 +1,240 @@
package ca.uhn.fhir.jpa.subscription;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.r5.BaseResourceProviderR5Test;
import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscribableChannel;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.test.utilities.JettyUtil;
import ca.uhn.fhir.util.BundleUtil;
import com.google.common.collect.Lists;
import net.ttddyy.dsproxy.QueryCount;
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.r5.model.*;
import org.junit.*;
import org.springframework.beans.factory.annotation.Autowired;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
@Ignore
public abstract class BaseSubscriptionsR5Test extends BaseResourceProviderR5Test {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSubscriptionsR5Test.class);
private static Server ourListenerServer;
protected static int ourListenerPort;
protected static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
protected static List<String> ourHeaders = Collections.synchronizedList(new ArrayList<>());
private static SingleQueryCountHolder ourCountHolder;
@Autowired
private SingleQueryCountHolder myCountHolder;
@Autowired
protected SubscriptionTestUtil mySubscriptionTestUtil;
@Autowired
protected SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
protected CountingInterceptor myCountingInterceptor;
protected static List<Observation> ourCreatedObservations = Collections.synchronizedList(Lists.newArrayList());
protected static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
private static String ourListenerServerBase;
protected List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
@After
public void afterUnregisterRestHookListener() {
for (IIdType next : mySubscriptionIds) {
IIdType nextId = next.toUnqualifiedVersionless();
ourLog.info("Deleting: {}", nextId);
ourClient.delete().resourceById(nextId).execute();
}
mySubscriptionIds.clear();
myDaoConfig.setAllowMultipleDelete(true);
ourLog.info("Deleting all subscriptions");
ourClient.delete().resourceConditionalByUrl("Subscription?status=active").execute();
ourClient.delete().resourceConditionalByUrl("Observation?code:missing=false").execute();
ourLog.info("Done deleting all subscriptions");
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
}
@Before
public void beforeRegisterRestHookListener() {
mySubscriptionTestUtil.registerRestHookInterceptor();
}
@Before
public void beforeReset() throws Exception {
ourCreatedObservations.clear();
ourUpdatedObservations.clear();
ourContentTypes.clear();
ourHeaders.clear();
// Delete all Subscriptions
if (ourClient != null) {
Bundle allSubscriptions = ourClient.search().forResource(Subscription.class).returnBundle(Bundle.class).execute();
for (IBaseResource next : BundleUtil.toListOfResources(myFhirCtx, allSubscriptions)) {
ourClient.delete().resource(next).execute();
}
waitForActivatedSubscriptionCount(0);
}
LinkedBlockingQueueSubscribableChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest();
if (processingChannel != null) {
processingChannel.clearInterceptorsForUnitTest();
}
myCountingInterceptor = new CountingInterceptor();
if (processingChannel != null) {
processingChannel.addInterceptorForUnitTest(myCountingInterceptor);
}
}
protected Subscription createSubscription(String theCriteria, String thePayload) {
Subscription subscription = newSubscription(theCriteria, thePayload);
MethodOutcome methodOutcome = ourClient.create().resource(subscription).execute();
subscription.setId(methodOutcome.getId().getIdPart());
mySubscriptionIds.add(methodOutcome.getId());
return subscription;
}
protected Subscription newSubscription(String theCriteria, String thePayload) {
Subscription subscription = new Subscription();
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
subscription.setCriteria(theCriteria);
Subscription.SubscriptionChannelComponent channel = subscription.getChannel();
channel.setType(Subscription.SubscriptionChannelType.RESTHOOK);
channel.setPayload(thePayload);
channel.setEndpoint(ourListenerServerBase);
return subscription;
}
protected void waitForQueueToDrain() throws InterruptedException {
mySubscriptionTestUtil.waitForQueueToDrain();
}
@PostConstruct
public void initializeOurCountHolder() {
ourCountHolder = myCountHolder;
}
protected Observation sendObservation(String code, String system) {
Observation observation = new Observation();
CodeableConcept codeableConcept = new CodeableConcept();
observation.setCode(codeableConcept);
observation.getIdentifierFirstRep().setSystem("foo").setValue("1");
Coding coding = codeableConcept.addCoding();
coding.setCode(code);
coding.setSystem(system);
observation.setStatus(Observation.ObservationStatus.FINAL);
IIdType id = myObservationDao.create(observation).getId();
observation.setId(id);
return observation;
}
public static class ObservationListener implements IResourceProvider {
@Create
public MethodOutcome create(@ResourceParam Observation theObservation, HttpServletRequest theRequest) {
ourLog.info("Received Listener Create");
ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", ""));
ourCreatedObservations.add(theObservation);
extractHeaders(theRequest);
return new MethodOutcome(new IdType("Observation/1"), true);
}
private void extractHeaders(HttpServletRequest theRequest) {
Enumeration<String> headerNamesEnum = theRequest.getHeaderNames();
while (headerNamesEnum.hasMoreElements()) {
String nextName = headerNamesEnum.nextElement();
Enumeration<String> valueEnum = theRequest.getHeaders(nextName);
while (valueEnum.hasMoreElements()) {
String nextValue = valueEnum.nextElement();
ourHeaders.add(nextName + ": " + nextValue);
}
}
}
@Override
public Class<? extends IBaseResource> getResourceType() {
return Observation.class;
}
@Update
public MethodOutcome update(@ResourceParam Observation theObservation, HttpServletRequest theRequest) {
ourLog.info("Received Listener Update");
ourUpdatedObservations.add(theObservation);
ourContentTypes.add(theRequest.getHeader(Constants.HEADER_CONTENT_TYPE).replaceAll(";.*", ""));
extractHeaders(theRequest);
return new MethodOutcome(new IdType("Observation/1"), false);
}
}
@AfterClass
public static void reportTotalSelects() {
ourLog.info("Total database select queries: {}", getQueryCount().getSelect());
}
private static QueryCount getQueryCount() {
return ourCountHolder.getQueryCountMap().get("");
}
@BeforeClass
public static void startListenerServer() throws Exception {
RestfulServer ourListenerRestServer = new RestfulServer(FhirContext.forR5());
ObservationListener obsListener = new ObservationListener();
ourListenerRestServer.setResourceProviders(obsListener);
ourListenerServer = new Server(0);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(ourListenerRestServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
ourListenerServer.setHandler(proxyHandler);
JettyUtil.startServer(ourListenerServer);
ourListenerPort = JettyUtil.getPortForStartedServer(ourListenerServer);
ourListenerServerBase = "http://localhost:" + ourListenerPort + "/fhir/context";
}
@AfterClass
public static void stopListenerServer() throws Exception {
JettyUtil.closeServer(ourListenerServer);
}
}

View File

@ -141,7 +141,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
assertEquals(1, messages[msgIdx].getHeader("Content-Type").length);
assertEquals("text/plain; charset=us-ascii", messages[msgIdx].getHeader("Content-Type")[0]);
String foundBody = GreenMailUtil.getBody(messages[msgIdx]);
assertEquals("A subscription update has been received", foundBody);
assertEquals("", foundBody);
}

View File

@ -112,9 +112,13 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
return observation;
}
/**
* Tests an email subscription with payload set to XML. The email sent must include content in the body of the email that is formatted as XML.
* @throws Exception
*/
@Test
public void testEmailSubscriptionNormal() throws Exception {
String payload = "This is the body";
String payload = "application/fhir+xml";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
@ -138,13 +142,21 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals(1, received.get(0).getAllRecipients().length);
assertEquals("foo@example.com", ((InternetAddress) received.get(0).getAllRecipients()[0]).getAddress());
assertEquals("text/plain; charset=us-ascii", received.get(0).getContentType());
assertEquals("This is the body", received.get(0).getContent().toString().trim());
assertEquals(mySubscriptionIds.get(0).toUnqualifiedVersionless().getValue(), received.get(0).getHeader("X-FHIR-Subscription")[0]);
// Expect the body of the email subscription to be an Observation formatted as XML
Observation parsedObservation = (Observation) ourClient.getFhirContext().newXmlParser().parseResource(received.get(0).getContent().toString().trim());
assertEquals("SNOMED-CT", parsedObservation.getCode().getCodingFirstRep().getSystem());
assertEquals("1000000050", parsedObservation.getCode().getCodingFirstRep().getCode());
}
/**
* Tests an email subscription with payload set to JSON. The email sent must include content in the body of the email that is formatted as JSON.
* @throws Exception
*/
@Test
public void testEmailSubscriptionWithCustom() throws Exception {
String payload = "This is the body";
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
@ -185,13 +197,21 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals("foo@example.com", ((InternetAddress) received.get(0).getAllRecipients()[0]).getAddress());
assertEquals("text/plain; charset=us-ascii", received.get(0).getContentType());
assertEquals("This is a subject", received.get(0).getSubject().toString().trim());
assertEquals("This is the body", received.get(0).getContent().toString().trim());
assertEquals(mySubscriptionIds.get(0).toUnqualifiedVersionless().getValue(), received.get(0).getHeader("X-FHIR-Subscription")[0]);
// Expect the body of the email subscription to be an Observation formatted as JSON
Observation parsedObservation = (Observation) ourClient.getFhirContext().newJsonParser().parseResource(received.get(0).getContent().toString().trim());
assertEquals("SNOMED-CT", parsedObservation.getCode().getCodingFirstRep().getSystem());
assertEquals("1000000050", parsedObservation.getCode().getCodingFirstRep().getCode());
}
/**
* Tests an email subscription with no payload. When the email is sent, the body of the email must be empty.
* @throws Exception
*/
@Test
public void testEmailSubscriptionWithCustomNoMailtoOnFrom() throws Exception {
String payload = "This is the body";
String payload = "";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
@ -231,7 +251,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
assertEquals("foo@example.com", ((InternetAddress) received.get(0).getAllRecipients()[0]).getAddress());
assertEquals("text/plain; charset=us-ascii", received.get(0).getContentType());
assertEquals("This is a subject", received.get(0).getSubject().toString().trim());
assertEquals("This is the body", received.get(0).getContent().toString().trim());
assertEquals("", received.get(0).getContent().toString().trim());
assertEquals(mySubscriptionIds.get(0).toUnqualifiedVersionless().getValue(), received.get(0).getHeader("X-FHIR-Subscription")[0]);
ourLog.info("Subscription: {}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(ourClient.history().onInstance(id).andReturnBundle(Bundle.class).execute()));

View File

@ -175,13 +175,15 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
@Test
public void testRestHookSubscription() throws Exception {
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
String criteria1 = "Observation?code=SNOMED-CT|" + code;
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111";
createSubscription(criteria1, null, ourNotificationListenerServer,
Collections.singletonList(new StringType("Authorization: abc-def")));
createSubscription(criteria2, null, ourNotificationListenerServer);
ourLog.debug("Sending first observation");
sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification with authorization header

View File

@ -0,0 +1,957 @@
package ca.uhn.fhir.jpa.subscription.resthook;
import ca.uhn.fhir.jpa.config.StoppableSubscriptionDeliveringRestHookSubscriber;
import ca.uhn.fhir.jpa.model.util.JpaConstants;
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR5Test;
import ca.uhn.fhir.rest.api.CacheControlDirective;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.r5.model.*;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
/**
* Test the rest-hook subscriptions
*/
public class RestHookTestR5Test extends BaseSubscriptionsR5Test {
private static final Logger ourLog = LoggerFactory.getLogger(RestHookTestR5Test.class);
@Autowired
StoppableSubscriptionDeliveringRestHookSubscriber myStoppableSubscriptionDeliveringRestHookSubscriber;
@After
public void cleanupStoppableSubscriptionDeliveringRestHookSubscriber() {
ourLog.info("@After");
myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(null);
myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
}
@Test
public void testRestHookSubscriptionApplicationFhirJson() throws Exception {
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
createSubscription(criteria1, payload);
createSubscription(criteria2, payload);
waitForActivatedSubscriptionCount(2);
sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
}
@Test
public void testUpdatesHaveCorrectMetadata() throws Exception {
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?";
createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
/*
* Send version 1
*/
Observation obs = sendObservation(code, "SNOMED-CT");
obs = myObservationDao.read(obs.getIdElement().toUnqualifiedVersionless());
// Should see 1 subscription notification
waitForQueueToDrain();
int idx = 0;
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(idx));
assertEquals("1", ourUpdatedObservations.get(idx).getIdElement().getVersionIdPart());
assertEquals("1", ourUpdatedObservations.get(idx).getMeta().getVersionId());
assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
assertEquals("1", ourUpdatedObservations.get(idx).getIdentifierFirstRep().getValue());
/*
* Send version 2
*/
obs.getIdentifierFirstRep().setSystem("foo").setValue("2");
myObservationDao.update(obs);
obs = myObservationDao.read(obs.getIdElement().toUnqualifiedVersionless());
// Should see 1 subscription notification
waitForQueueToDrain();
idx++;
waitForSize(0, ourCreatedObservations);
waitForSize(2, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(idx));
assertEquals("2", ourUpdatedObservations.get(idx).getIdElement().getVersionIdPart());
assertEquals("2", ourUpdatedObservations.get(idx).getMeta().getVersionId());
assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
assertEquals("2", ourUpdatedObservations.get(idx).getIdentifierFirstRep().getValue());
}
@Test
public void testPlaceholderReferencesInTransactionAreResolvedCorrectly() throws Exception {
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?";
createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
// Create a transaction that should match
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
Patient patient = new Patient();
patient.setId(IdType.newRandomUuid());
patient.getIdentifierFirstRep().setSystem("foo").setValue("AAA");
bundle.addEntry().setResource(patient).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Patient");
Observation observation = new Observation();
observation.getIdentifierFirstRep().setSystem("foo").setValue("1");
observation.getCode().addCoding().setCode(code).setSystem("SNOMED-CT");
observation.setStatus(Observation.ObservationStatus.FINAL);
observation.getSubject().setReference(patient.getId());
bundle.addEntry().setResource(observation).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Observation");
// Send the transaction
mySystemDao.transaction(null, bundle);
waitForSize(1, ourUpdatedObservations);
assertThat(ourUpdatedObservations.get(0).getSubject().getReference(), matchesPattern("Patient/[0-9]+"));
}
@Test
public void testUpdatesHaveCorrectMetadataUsingTransactions() throws Exception {
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?";
createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
/*
* Send version 1
*/
Observation observation = new Observation();
observation.getIdentifierFirstRep().setSystem("foo").setValue("1");
observation.getCode().addCoding().setCode(code).setSystem("SNOMED-CT");
observation.setStatus(Observation.ObservationStatus.FINAL);
Bundle bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
bundle.addEntry().setResource(observation).getRequest().setMethod(Bundle.HTTPVerb.POST).setUrl("Observation");
Bundle responseBundle = mySystemDao.transaction(null, bundle);
Observation obs = myObservationDao.read(new IdType(responseBundle.getEntry().get(0).getResponse().getLocation()));
// Should see 1 subscription notification
waitForQueueToDrain();
int idx = 0;
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(idx));
assertEquals("1", ourUpdatedObservations.get(idx).getIdElement().getVersionIdPart());
assertEquals("1", ourUpdatedObservations.get(idx).getMeta().getVersionId());
assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
assertEquals("1", ourUpdatedObservations.get(idx).getIdentifierFirstRep().getValue());
/*
* Send version 2
*/
observation = new Observation();
observation.setId(obs.getId());
observation.getIdentifierFirstRep().setSystem("foo").setValue("2");
observation.getCode().addCoding().setCode(code).setSystem("SNOMED-CT");
observation.setStatus(Observation.ObservationStatus.FINAL);
bundle = new Bundle();
bundle.setType(Bundle.BundleType.TRANSACTION);
bundle.addEntry().setResource(observation).getRequest().setMethod(Bundle.HTTPVerb.PUT).setUrl(obs.getIdElement().toUnqualifiedVersionless().getValue());
mySystemDao.transaction(null, bundle);
obs = myObservationDao.read(obs.getIdElement().toUnqualifiedVersionless());
// Should see 1 subscription notification
waitForQueueToDrain();
idx++;
waitForSize(0, ourCreatedObservations);
waitForSize(2, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(idx));
assertEquals("2", ourUpdatedObservations.get(idx).getIdElement().getVersionIdPart());
assertEquals("2", ourUpdatedObservations.get(idx).getMeta().getVersionId());
assertEquals(obs.getMeta().getLastUpdatedElement().getValueAsString(), ourUpdatedObservations.get(idx).getMeta().getLastUpdatedElement().getValueAsString());
assertEquals("2", ourUpdatedObservations.get(idx).getIdentifierFirstRep().getValue());
}
@Test
public void testRepeatedDeliveries() throws Exception {
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?";
createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
for (int i = 0; i < 100; i++) {
Observation observation = new Observation();
observation.getIdentifierFirstRep().setSystem("foo").setValue("ID" + i);
observation.getCode().addCoding().setCode(code).setSystem("SNOMED-CT");
observation.setStatus(Observation.ObservationStatus.FINAL);
myObservationDao.create(observation);
}
waitForSize(100, ourUpdatedObservations);
}
@Test
public void testActiveSubscriptionShouldntReActivate() throws Exception {
String criteria = "Observation?code=111111111&_format=xml";
String payload = "application/fhir+json";
createSubscription(criteria, payload);
waitForActivatedSubscriptionCount(1);
for (int i = 0; i < 5; i++) {
int changes = this.mySubscriptionLoader.doSyncSubscriptionsForUnitTest();
assertEquals(0, changes);
}
}
@Test
public void testRestHookSubscriptionNoopUpdateDoesntTriggerNewDelivery() throws Exception {
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
createSubscription(criteria1, payload);
createSubscription(criteria2, payload);
waitForActivatedSubscriptionCount(2);
Observation obs = sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
// Send an update with no changes
obs.setId(obs.getIdElement().toUnqualifiedVersionless());
ourClient.update().resource(obs).execute();
// Should be no further deliveries
Thread.sleep(1000);
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
}
@Test
public void testRestHookSubscriptionApplicationJsonDisableVersionIdInDelivery() throws Exception {
String payload = "application/json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
waitForActivatedSubscriptionCount(0);
Subscription subscription1 = createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
ourLog.info("** About to send observation");
Observation observation1 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
IdType idElement = ourUpdatedObservations.get(0).getIdElement();
assertEquals(observation1.getIdElement().getIdPart(), idElement.getIdPart());
// VersionId is present
assertEquals(observation1.getIdElement().getVersionIdPart(), idElement.getVersionIdPart());
subscription1
.getChannel()
.addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true"));
ourLog.info("** About to update subscription");
int modCount = myCountingInterceptor.getSentCount();
ourClient.update().resource(subscription1).execute();
waitForSize(modCount + 1, () -> myCountingInterceptor.getSentCount());
ourLog.info("** About to send observation");
Observation observation2 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(2, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(1));
idElement = ourUpdatedObservations.get(1).getIdElement();
assertEquals(observation2.getIdElement().getIdPart(), idElement.getIdPart());
// Now VersionId is stripped
assertEquals(null, idElement.getVersionIdPart());
}
@Test
public void testRestHookSubscriptionDoesntGetLatestVersionByDefault() throws Exception {
String payload = "application/json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
waitForActivatedSubscriptionCount(0);
createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
myStoppableSubscriptionDeliveringRestHookSubscriber.pause();
final CountDownLatch countDownLatch = new CountDownLatch(1);
myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(countDownLatch);
ourLog.info("** About to send observation");
Observation observation = sendObservation(code, "SNOMED-CT");
assertEquals("1", observation.getIdElement().getVersionIdPart());
assertNull(observation.getNoteFirstRep().getText());
observation.getNoteFirstRep().setText("changed");
MethodOutcome methodOutcome = ourClient.update().resource(observation).execute();
assertEquals("2", methodOutcome.getId().getVersionIdPart());
assertEquals("changed", observation.getNoteFirstRep().getText());
// Wait for our two delivery channel threads to be paused
assertTrue(countDownLatch.await(5L, TimeUnit.SECONDS));
// Open the floodgates!
myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
waitForSize(0, ourCreatedObservations);
waitForSize(2, ourUpdatedObservations);
Observation observation1 = ourUpdatedObservations.get(0);
Observation observation2 = ourUpdatedObservations.get(1);
assertEquals("1", observation1.getIdElement().getVersionIdPart());
assertNull(observation1.getNoteFirstRep().getText());
assertEquals("2", observation2.getIdElement().getVersionIdPart());
assertEquals("changed", observation2.getNoteFirstRep().getText());
}
@Test
public void testRestHookSubscriptionGetsLatestVersionWithFlag() throws Exception {
String payload = "application/json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
waitForActivatedSubscriptionCount(0);
Subscription subscription = newSubscription(criteria1, payload);
subscription
.getChannel()
.addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true"));
ourClient.create().resource(subscription).execute();
waitForActivatedSubscriptionCount(1);
myStoppableSubscriptionDeliveringRestHookSubscriber.pause();
final CountDownLatch countDownLatch = new CountDownLatch(1);
myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(countDownLatch);
ourLog.info("** About to send observation");
Observation observation = sendObservation(code, "SNOMED-CT");
assertEquals("1", observation.getIdElement().getVersionIdPart());
assertNull(observation.getNoteFirstRep().getText());
observation.getNoteFirstRep().setText("changed");
MethodOutcome methodOutcome = ourClient.update().resource(observation).execute();
assertEquals("2", methodOutcome.getId().getVersionIdPart());
assertEquals("changed", observation.getNoteFirstRep().getText());
// Wait for our two delivery channel threads to be paused
assertTrue(countDownLatch.await(5L, TimeUnit.SECONDS));
// Open the floodgates!
myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
waitForSize(0, ourCreatedObservations);
waitForSize(2, ourUpdatedObservations);
Observation observation1 = ourUpdatedObservations.get(0);
Observation observation2 = ourUpdatedObservations.get(1);
assertEquals("2", observation1.getIdElement().getVersionIdPart());
assertEquals("changed", observation1.getNoteFirstRep().getText());
assertEquals("2", observation2.getIdElement().getVersionIdPart());
assertEquals("changed", observation2.getNoteFirstRep().getText());
}
@Test
public void testRestHookSubscriptionApplicationJson() throws Exception {
String payload = "application/json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload);
waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
assertEquals("1", ourUpdatedObservations.get(0).getIdElement().getVersionIdPart());
Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId());
Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.setCriteria(criteria1);
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
waitForQueueToDrain();
Observation observation2 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
// Should see two subscription notifications
waitForSize(0, ourCreatedObservations);
waitForSize(3, ourUpdatedObservations);
ourClient.delete().resourceById(new IdType("Subscription/" + subscription2.getId())).execute();
waitForQueueToDrain();
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
// Should see only one subscription notification
waitForSize(0, ourCreatedObservations);
waitForSize(4, ourUpdatedObservations);
Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept = new CodeableConcept();
observation3.setCode(codeableConcept);
Coding coding = codeableConcept.addCoding();
coding.setCode(code + "111");
coding.setSystem("SNOMED-CT");
ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute();
// Should see no subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(4, ourUpdatedObservations);
Observation observation3a = ourClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept1 = new CodeableConcept();
observation3a.setCode(codeableConcept1);
Coding coding1 = codeableConcept1.addCoding();
coding1.setCode(code);
coding1.setSystem("SNOMED-CT");
ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute();
// Should see only one subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(5, ourUpdatedObservations);
assertFalse(subscription1.getId().equals(subscription2.getId()));
assertFalse(observation1.getId().isEmpty());
assertFalse(observation2.getId().isEmpty());
}
@Test
public void testRestHookSubscriptionApplicationJsonDatabase() throws Exception {
// Same test as above, but now run it using database matching
myDaoConfig.setEnableInMemorySubscriptionMatching(false);
String payload = "application/json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload);
waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
assertEquals("1", ourUpdatedObservations.get(0).getIdElement().getVersionIdPart());
Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId());
Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.setCriteria(criteria1);
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
waitForQueueToDrain();
Observation observation2 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
// Should see two subscription notifications
waitForSize(0, ourCreatedObservations);
waitForSize(3, ourUpdatedObservations);
ourClient.delete().resourceById(new IdType("Subscription/" + subscription2.getId())).execute();
waitForQueueToDrain();
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
// Should see only one subscription notification
waitForSize(0, ourCreatedObservations);
waitForSize(4, ourUpdatedObservations);
Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept = new CodeableConcept();
observation3.setCode(codeableConcept);
Coding coding = codeableConcept.addCoding();
coding.setCode(code + "111");
coding.setSystem("SNOMED-CT");
ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute();
// Should see no subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(4, ourUpdatedObservations);
Observation observation3a = ourClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept1 = new CodeableConcept();
observation3a.setCode(codeableConcept1);
Coding coding1 = codeableConcept1.addCoding();
coding1.setCode(code);
coding1.setSystem("SNOMED-CT");
ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute();
// Should see only one subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(5, ourUpdatedObservations);
assertFalse(subscription1.getId().equals(subscription2.getId()));
assertFalse(observation1.getId().isEmpty());
assertFalse(observation2.getId().isEmpty());
}
@Test
public void testRestHookSubscriptionApplicationXml() throws Exception {
String payload = "application/xml";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload);
waitForActivatedSubscriptionCount(2);
ourLog.info("** About to send obervation");
Observation observation1 = sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
Subscription subscriptionTemp = ourClient.read(Subscription.class, subscription2.getId());
Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.setCriteria(criteria1);
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
waitForQueueToDrain();
Observation observation2 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
// Should see two subscription notifications
waitForSize(0, ourCreatedObservations);
waitForSize(3, ourUpdatedObservations);
ourClient.delete().resourceById(new IdType("Subscription/" + subscription2.getId())).execute();
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
// Should see only one subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(4, ourUpdatedObservations);
Observation observation3 = ourClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept = new CodeableConcept();
observation3.setCode(codeableConcept);
Coding coding = codeableConcept.addCoding();
coding.setCode(code + "111");
coding.setSystem("SNOMED-CT");
ourClient.update().resource(observation3).withId(observation3.getIdElement()).execute();
// Should see no subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(4, ourUpdatedObservations);
Observation observation3a = ourClient.read(Observation.class, observationTemp3.getId());
CodeableConcept codeableConcept1 = new CodeableConcept();
observation3a.setCode(codeableConcept1);
Coding coding1 = codeableConcept1.addCoding();
coding1.setCode(code);
coding1.setSystem("SNOMED-CT");
ourClient.update().resource(observation3a).withId(observation3a.getIdElement()).execute();
// Should see only one subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(5, ourUpdatedObservations);
assertFalse(subscription1.getId().equals(subscription2.getId()));
assertFalse(observation1.getId().isEmpty());
assertFalse(observation2.getId().isEmpty());
}
@Test
public void testSubscriptionTriggerViaSubscription() throws Exception {
String payload = "application/xml";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
ourLog.info("** About to send obervation");
Observation observation = new Observation();
observation.addIdentifier().setSystem("foo").setValue("bar1");
observation.setId(IdType.newRandomUuid().getValue());
CodeableConcept codeableConcept = new CodeableConcept()
.addCoding(new Coding().setCode(code).setSystem("SNOMED-CT"));
observation.setCode(codeableConcept);
observation.setStatus(Observation.ObservationStatus.FINAL);
Patient patient = new Patient();
patient.addIdentifier().setSystem("foo").setValue("bar2");
patient.setId(IdType.newRandomUuid().getValue());
patient.setActive(true);
observation.getSubject().setReference(patient.getId());
Bundle requestBundle = new Bundle();
requestBundle.setType(Bundle.BundleType.TRANSACTION);
requestBundle.addEntry()
.setResource(observation)
.setFullUrl(observation.getId())
.getRequest()
.setUrl("Obervation?identifier=foo|bar1")
.setMethod(Bundle.HTTPVerb.PUT);
requestBundle.addEntry()
.setResource(patient)
.setFullUrl(patient.getId())
.getRequest()
.setUrl("Patient?identifier=foo|bar2")
.setMethod(Bundle.HTTPVerb.PUT);
ourClient.transaction().withBundle(requestBundle).execute();
// Should see 1 subscription notification
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
Observation obs = ourUpdatedObservations.get(0);
ourLog.info("Observation content: {}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(obs));
}
@Test
public void testUpdateSubscriptionToMatchLater() throws Exception {
String payload = "application/xml";
String code = "1000000050";
String criteriaBad = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
ourLog.info("** About to create non-matching subscription");
Subscription subscription2 = createSubscription(criteriaBad, payload);
ourLog.info("** About to send observation that wont match");
Observation observation1 = sendObservation(code, "SNOMED-CT");
// Criteria didn't match, shouldn't see any updates
waitForQueueToDrain();
Thread.sleep(1000);
assertEquals(0, ourUpdatedObservations.size());
Subscription subscriptionTemp = ourClient.read().resource(Subscription.class).withId(subscription2.getId()).execute();
Assert.assertNotNull(subscriptionTemp);
String criteriaGood = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
subscriptionTemp.setCriteria(criteriaGood);
ourLog.info("** About to update subscription");
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
waitForQueueToDrain();
ourLog.info("** About to send Observation 2");
Observation observation2 = sendObservation(code, "SNOMED-CT");
waitForQueueToDrain();
// Should see a subscription notification this time
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
ourClient.delete().resourceById(new IdType("Subscription/" + subscription2.getId())).execute();
Observation observationTemp3 = sendObservation(code, "SNOMED-CT");
// No more matches
Thread.sleep(1000);
assertEquals(1, ourUpdatedObservations.size());
}
@Test
public void testRestHookSubscriptionApplicationXmlJson() throws Exception {
String payload = "application/fhir+xml";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
String criteria2 = "Observation?code=SNOMED-CT|" + code + "111&_format=xml";
Subscription subscription1 = createSubscription(criteria1, payload);
Subscription subscription2 = createSubscription(criteria2, payload);
waitForActivatedSubscriptionCount(2);
Observation observation1 = sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_XML_NEW, ourContentTypes.get(0));
}
@Test
public void testRestHookSubscriptionInvalidCriteria() throws Exception {
String payload = "application/xml";
String criteria1 = "Observation?codeeeee=SNOMED-CT";
try {
createSubscription(criteria1, payload);
fail();
} catch (UnprocessableEntityException e) {
assertEquals("HTTP 422 Unprocessable Entity: Invalid subscription criteria submitted: Observation?codeeeee=SNOMED-CT Failed to parse match URL[Observation?codeeeee=SNOMED-CT] - Resource type Observation does not have a parameter with name: codeeeee", e.getMessage());
}
}
@Test
public void testSubscriptionWithHeaders() throws Exception {
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
// Add some headers, and we'll also turn back to requested status for fun
Subscription subscription = createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
subscription.getChannel().addHeader("X-Foo: FOO");
subscription.getChannel().addHeader("X-Bar: BAR");
subscription.setStatus(Subscription.SubscriptionStatus.REQUESTED);
ourClient.update().resource(subscription).execute();
waitForQueueToDrain();
sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
assertThat(ourHeaders, hasItem("X-Foo: FOO"));
assertThat(ourHeaders, hasItem("X-Bar: BAR"));
}
@Test
public void testDisableSubscription() throws Exception {
String payload = "application/fhir+json";
String code = "1000000050";
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
Subscription subscription = createSubscription(criteria1, payload);
waitForActivatedSubscriptionCount(1);
sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
// Disable
subscription.setStatus(Subscription.SubscriptionStatus.OFF);
ourClient.update().resource(subscription).execute();
waitForQueueToDrain();
// Send another object
sendObservation(code, "SNOMED-CT");
// Should see 1 subscription notification
waitForQueueToDrain();
waitForSize(0, ourCreatedObservations);
waitForSize(1, ourUpdatedObservations);
}
@Test(expected = UnprocessableEntityException.class)
public void testInvalidProvenanceParam() {
String payload = "application/fhir+json";
String criteriabad = "Provenance?activity=http://hl7.org/fhir/v3/DocumentCompletion%7CAU";
Subscription subscription = newSubscription(criteriabad, payload);
ourClient.create().resource(subscription).execute();
}
@Test(expected = UnprocessableEntityException.class)
public void testInvalidProcedureRequestParam() {
String payload = "application/fhir+json";
String criteriabad = "ProcedureRequest?intent=instance-order&category=Laboratory";
Subscription subscription = newSubscription(criteriabad, payload);
ourClient.create().resource(subscription).execute();
}
@Test(expected = UnprocessableEntityException.class)
public void testInvalidBodySiteParam() {
String payload = "application/fhir+json";
String criteriabad = "BodySite?accessType=Catheter";
Subscription subscription = newSubscription(criteriabad, payload);
ourClient.create().resource(subscription).execute();
}
@Test
public void testGoodSubscriptionPersists() {
assertEquals(0, subscriptionCount());
String payload = "application/fhir+json";
String criteriaGood = "Patient?gender=male";
Subscription subscription = newSubscription(criteriaGood, payload);
ourClient.create().resource(subscription).execute();
assertEquals(1, subscriptionCount());
}
private int subscriptionCount() {
IBaseBundle found = ourClient.search().forResource(Subscription.class).cacheControl(new CacheControlDirective().setNoCache(true)).execute();
return toUnqualifiedVersionlessIdValues(found).size();
}
@Test
public void testSubscriptionWithNoStatusIsRejected() {
Subscription subscription = newSubscription("Observation?", "application/json");
subscription.setStatus(null);
try {
ourClient.create().resource(subscription).execute();
fail();
} catch (UnprocessableEntityException e) {
assertThat(e.getMessage(), containsString("Can not process submitted Subscription - Subscription.status must be populated on this server"));
}
}
@Test
public void testBadSubscriptionDoesntPersist() {
assertEquals(0, subscriptionCount());
String payload = "application/fhir+json";
String criteriaBad = "BodySite?accessType=Catheter";
Subscription subscription = newSubscription(criteriaBad, payload);
try {
ourClient.create().resource(subscription).execute();
} catch (UnprocessableEntityException e) {
ourLog.info("Expected exception", e);
}
assertEquals(0, subscriptionCount());
}
@Test
public void testCustomSearchParam() throws Exception {
String criteria = "Observation?accessType=Catheter,PD%20Catheter";
SearchParameter sp = new SearchParameter();
sp.addBase("Observation");
sp.setCode("accessType");
sp.setType(Enumerations.SearchParamType.TOKEN);
sp.setExpression("Observation.extension('Observation#accessType')");
sp.setXpathUsage(SearchParameter.XPathUsageType.NORMAL);
sp.setStatus(Enumerations.PublicationStatus.ACTIVE);
mySearchParameterDao.create(sp);
mySearchParamRegistry.forceRefresh();
createSubscription(criteria, "application/json");
waitForActivatedSubscriptionCount(1);
{
Observation bodySite = new Observation();
bodySite.addExtension().setUrl("Observation#accessType").setValue(new Coding().setCode("Catheter"));
MethodOutcome methodOutcome = ourClient.create().resource(bodySite).execute();
assertEquals(true, methodOutcome.getCreated());
waitForQueueToDrain();
waitForSize(1, ourUpdatedObservations);
}
{
Observation observation = new Observation();
observation.addExtension().setUrl("Observation#accessType").setValue(new Coding().setCode("PD Catheter"));
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
assertEquals(true, methodOutcome.getCreated());
waitForQueueToDrain();
waitForSize(2, ourUpdatedObservations);
}
{
Observation observation = new Observation();
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
assertEquals(true, methodOutcome.getCreated());
waitForQueueToDrain();
waitForSize(2, ourUpdatedObservations);
}
{
Observation observation = new Observation();
observation.addExtension().setUrl("Observation#accessType").setValue(new Coding().setCode("XXX"));
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
assertEquals(true, methodOutcome.getCreated());
waitForQueueToDrain();
waitForSize(2, ourUpdatedObservations);
}
}
}

File diff suppressed because one or more lines are too long

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -141,6 +141,41 @@
<artifactId>commons-dbcp2</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>42.2.2</version>
</dependency>
<dependency>
<groupId>org.bitbucket.b_c</groupId>
<artifactId>jose4j</artifactId>
<version>0.6.3</version>
</dependency>
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180130</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
<version>2.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.3.RELEASE</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.dataformat</groupId>
<artifactId>jackson-dataformat-xml</artifactId>
<version>2.9.4</version>
</dependency>
<!-- This example uses Derby embedded database. If you are using another database such as Mysql or Oracle, you may omit the following dependencies and replace them with an appropriate database client
dependency for your database platform. -->
<dependency>
@ -244,8 +279,8 @@
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
@ -288,8 +323,8 @@
<executions>
<execution>
<goals>
<goal>integration-test</goal>
<goal>verify</goal>
<!-- <goal>integration-test</goal> -->
<!-- <goal>verify</goal> -->
</goals>
</execution>
</executions>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -26,6 +26,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.sql.SQLException;
import java.util.Set;
public class ModifyColumnTask extends BaseTableColumnTypeTask<ModifyColumnTask> {
@ -33,10 +34,17 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask<ModifyColumnTask>
@Override
public void execute() {
public void execute() throws SQLException {
String existingType;
boolean nullable;
Set<String> columnNames = JdbcUtils.getColumnNames(getConnectionProperties(), getTableName());
if (!columnNames.contains(getColumnName())) {
ourLog.info("Column {} doesn't exist on table {} - No action performed", getColumnName(), getTableName());
return;
}
try {
existingType = JdbcUtils.getColumnType(getConnectionProperties(), getTableName(), getColumnName());
nullable = JdbcUtils.isColumnNullable(getConnectionProperties(), getTableName(), getColumnName());
@ -115,5 +123,4 @@ public class ModifyColumnTask extends BaseTableColumnTypeTask<ModifyColumnTask>
executeSql(getTableName(), sqlNotNull);
}
}
}

View File

@ -5,6 +5,7 @@ import org.junit.Test;
import java.sql.SQLException;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.*;
public class ModifyColumnTest extends BaseTest {
@ -111,4 +112,20 @@ public class ModifyColumnTest extends BaseTest {
}
@Test
public void testColumnDoesntAlreadyExist() throws SQLException {
executeSql("create table SOMETABLE (PID bigint, TEXTCOL varchar(255))");
ModifyColumnTask task = new ModifyColumnTask();
task.setTableName("SOMETABLE");
task.setColumnName("SOMECOLUMN");
task.setDescription("Make nullable");
task.setNullable(true);
getMigrator().addTask(task);
getMigrator().migrate();
assertThat(JdbcUtils.getColumnNames(getConnectionProperties(), "SOMETABLE"), containsInAnyOrder("PID", "TEXTCOL"));
}
}

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -46,6 +46,15 @@ public class LogicalReferenceHelper {
}
}
/*
* Account for common logical references
*/
if (theId.getValue().startsWith("http://fhir.org/guides/argonaut/")) {
return true;
}
return false;
}

View File

@ -4,7 +4,7 @@
<parent>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-deployable-pom</artifactId>
<version>4.0.0-SNAPSHOT</version>
<version>4.1.0-SNAPSHOT</version>
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
</parent>

View File

@ -34,6 +34,8 @@ import org.hl7.fhir.instance.model.api.IBaseReference;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Extension;
import org.hl7.fhir.r4.model.codesystems.SubscriptionStatus;
import org.hl7.fhir.r5.model.Subscription;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
@ -67,6 +69,8 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
return canonicalizeDstu3(theSubscription);
case R4:
return canonicalizeR4(theSubscription);
case R5:
return canonicalizeR5(theSubscription);
case DSTU2_HL7ORG:
case DSTU2_1:
default:
@ -169,6 +173,14 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
.stream()
.collect(Collectors.groupingBy(t -> t.getUrl(), mapping(t -> t.getValueAsPrimitive().getValueAsString(), toList())));
}
case R5: {
org.hl7.fhir.r5.model.Subscription subscription = (org.hl7.fhir.r5.model.Subscription) theSubscription;
return subscription
.getChannel()
.getExtension()
.stream()
.collect(Collectors.groupingBy(t -> t.getUrl(), mapping(t -> t.getValueAsPrimitive().getValueAsString(), toList())));
}
case DSTU2_HL7ORG:
case DSTU2_1:
default: {
@ -232,6 +244,56 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
return retVal;
}
private CanonicalSubscription canonicalizeR5(IBaseResource theSubscription) {
org.hl7.fhir.r5.model.Subscription subscription = (org.hl7.fhir.r5.model.Subscription) theSubscription;
CanonicalSubscription retVal = new CanonicalSubscription();
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode()));
retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
retVal.setCriteriaString(subscription.getCriteria());
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
retVal.setHeaders(subscription.getChannel().getHeader());
retVal.setChannelExtensions(extractExtension(subscription));
retVal.setIdElement(subscription.getIdElement());
retVal.setPayloadString(subscription.getChannel().getPayload());
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
String from;
String subjectTemplate;
try {
from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
} catch (FHIRException theE) {
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
}
retVal.getEmailDetails().setFrom(from);
retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
}
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) {
String stripVersionIds;
String deliverLatestVersion;
try {
stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
} catch (FHIRException theE) {
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
}
retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds));
retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion));
}
List<org.hl7.fhir.r5.model.Extension> topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
if (topicExts.size() > 0) {
IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive();
if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) {
throw new PreconditionFailedException("Topic reference must be an EventDefinition");
}
}
return retVal;
}
public String getCriteria(IBaseResource theSubscription) {
switch (myFhirContext.getVersion().getVersion()) {
case DSTU2:
@ -240,6 +302,8 @@ public class SubscriptionCanonicalizer<S extends IBaseResource> {
return ((org.hl7.fhir.dstu3.model.Subscription) theSubscription).getCriteria();
case R4:
return ((org.hl7.fhir.r4.model.Subscription) theSubscription).getCriteria();
case R5:
return ((org.hl7.fhir.r5.model.Subscription) theSubscription).getCriteria();
case DSTU2_1:
case DSTU2_HL7ORG:
default:

View File

@ -53,7 +53,7 @@ public class SubscriptionLoader {
private ISubscriptionProvider mySubscriptionProvider;
@Autowired
private SubscriptionRegistry mySubscriptionRegistry;
@Autowired
@Autowired(required = false)
private IDaoRegistry myDaoRegistry;
private final Object mySyncSubscriptionsLock = new Object();
@ -65,7 +65,7 @@ public class SubscriptionLoader {
@SuppressWarnings("unused")
@Scheduled(fixedDelay = DateUtils.MILLIS_PER_MINUTE)
public void syncSubscriptions() {
if (!myDaoRegistry.isResourceTypeSupported("Subscription")) {
if (myDaoRegistry != null && !myDaoRegistry.isResourceTypeSupported("Subscription")) {
return;
}
if (!mySyncSubscriptionsSemaphore.tryAcquire()) {

View File

@ -20,9 +20,10 @@ package ca.uhn.fhir.jpa.subscription.module.config;
* #L%
*/
import ca.uhn.fhir.jpa.searchparam.config.SearchParamDstu2Config;
import ca.uhn.fhir.jpa.searchparam.config.SearchParamDstu3Config;
import org.springframework.context.annotation.Import;
@Import({SearchParamDstu3Config.class})
@Import({SearchParamDstu2Config.class})
public class SubscriptionDstu2Config extends BaseSubscriptionConfig {
}

View File

@ -24,5 +24,5 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
public interface IResourceRetriever {
IBaseResource getResource(IIdType id);
IBaseResource getResource(IIdType theId);
}

View File

@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.rest.api.EncodingEnum;
import com.fasterxml.jackson.annotation.JsonAutoDetect;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonInclude;
@ -73,6 +74,14 @@ public class ResourceDeliveryMessage extends BaseResourceMessage implements IRes
return retVal;
}
public String getPayloadString() {
if (this.myPayloadString != null) {
return this.myPayloadString;
}
return "";
}
public IIdType getPayloadId(FhirContext theCtx) {
IIdType retVal = null;
if (myPayloadId != null) {
@ -89,9 +98,9 @@ public class ResourceDeliveryMessage extends BaseResourceMessage implements IRes
mySubscription = theSubscription;
}
public void setPayload(FhirContext theCtx, IBaseResource thePayload) {
public void setPayload(FhirContext theCtx, IBaseResource thePayload, EncodingEnum theEncoding) {
myPayload = thePayload;
myPayloadString = theCtx.newJsonParser().encodeResourceToString(thePayload);
myPayloadString = theEncoding.newParser(theCtx).encodeResourceToString(thePayload);
myPayloadId = thePayload.getIdElement().toUnqualified().getValue();
}

View File

@ -57,10 +57,8 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) {
IBaseResource payloadResource = getAndMassagePayload(theMsg, theSubscription);
if (payloadResource == null) {
return;
}
// Regardless of whether we have a payload, the rest-hook should be sent.
doDelivery(theMsg, theSubscription, thePayloadType, theClient, payloadResource);
}
@ -117,8 +115,13 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
if (payloadResource == null || theSubscription.getRestHookDetails().isDeliverLatestVersion()) {
IIdType payloadId = theMsg.getPayloadId(myFhirContext);
try {
payloadResource = myResourceRetriever.getResource(payloadId.toVersionless());
if (payloadId != null) {
payloadResource = myResourceRetriever.getResource(payloadId.toVersionless());
} else {
return null;
}
} catch (ResourceGoneException e) {
ourLog.warn("Resource {} is deleted, not going to deliver for subscription {}", payloadId.toVersionless(), theSubscription.getIdElement(myFhirContext));
return null;
@ -152,10 +155,6 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
String payloadString = subscription.getPayloadString();
EncodingEnum payloadType = null;
if (payloadString != null) {
if (payloadString.contains(";")) {
payloadString = payloadString.substring(0, payloadString.indexOf(';'));
}
payloadString = payloadString.trim();
payloadType = EncodingEnum.forContentType(payloadString);
}

View File

@ -10,6 +10,8 @@ import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
import ca.uhn.fhir.rest.api.Constants;
import ca.uhn.fhir.rest.api.EncodingEnum;
import org.apache.commons.lang3.StringUtils;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType;
@ -24,6 +26,7 @@ import org.springframework.stereotype.Service;
import java.util.Collection;
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
/*-
@ -103,6 +106,7 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
private void doMatchActiveSubscriptionsAndDeliver(ResourceModifiedMessage theMsg) {
IIdType resourceId = theMsg.getId(myFhirContext);
Boolean isText = false;
Collection<ActiveSubscription> subscriptions = mySubscriptionRegistry.getAll();
@ -134,15 +138,21 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
matchResult.isInMemory() ? "in-memory" : "by querying the repository");
IBaseResource payload = theMsg.getNewPayload(myFhirContext);
CanonicalSubscription subscription = nextActiveSubscription.getSubscription();
EncodingEnum encoding = null;
if (subscription.getPayloadString() != null && !subscription.getPayloadString().isEmpty()) {
encoding = EncodingEnum.forContentType(subscription.getPayloadString());
isText = subscription.getPayloadString().equals(Constants.CT_TEXT);
}
encoding = defaultIfNull(encoding, EncodingEnum.JSON);
ResourceDeliveryMessage deliveryMsg = new ResourceDeliveryMessage();
deliveryMsg.setPayload(myFhirContext, payload);
deliveryMsg.setSubscription(nextActiveSubscription.getSubscription());
deliveryMsg.setPayload(myFhirContext, payload, encoding);
deliveryMsg.setSubscription(subscription);
deliveryMsg.setOperationType(theMsg.getOperationType());
deliveryMsg.copyAdditionalPropertiesFrom(theMsg);
if (payload == null) {
deliveryMsg.setPayloadId(theMsg.getId(myFhirContext));
}
// Interceptor call: SUBSCRIPTION_RESOURCE_MATCHED
HookParams params = new HookParams()

View File

@ -20,10 +20,12 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber.email;
* #L%
*/
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
import ca.uhn.fhir.jpa.subscription.module.subscriber.BaseSubscriptionDeliverySubscriber;
import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage;
import ca.uhn.fhir.rest.api.EncodingEnum;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -43,6 +45,8 @@ public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliv
@Autowired
private ModelConfig myModelConfig;
@Autowired
private FhirContext myCtx;
private IEmailSender myEmailSender;
@ -66,13 +70,21 @@ public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliv
}
}
String payload = "";
if (isNotBlank(subscription.getPayloadString())) {
EncodingEnum encoding = EncodingEnum.forContentType(subscription.getPayloadString());
if (encoding != null) {
payload = theMessage.getPayloadString();
}
}
String from = processEmailAddressUri(defaultString(subscription.getEmailDetails().getFrom(), myModelConfig.getEmailFromAddress()));
String subjectTemplate = defaultString(subscription.getEmailDetails().getSubjectTemplate(), provideDefaultSubjectTemplate());
EmailDetails details = new EmailDetails();
details.setTo(destinationAddresses);
details.setFrom(from);
details.setBodyTemplate(subscription.getPayloadString());
details.setBodyTemplate(payload);
details.setSubjectTemplate(subjectTemplate);
details.setSubscription(subscription.getIdElement(myFhirContext));

View File

@ -20,7 +20,7 @@ public class TestSubscriptionDstu3Config extends SubscriptionDstu3Config {
@Bean
@Primary
public ISubscriptionProvider subsriptionProvider() {
public ISubscriptionProvider subscriptionProvider() {
return new MockFhirClientSubscriptionProvider();
}

Some files were not shown because too many files have changed in this diff Show More