Merge remote-tracking branch 'remotes/origin/master' into im_20200728_term_multi_version_support
This commit is contained in:
commit
bd94ff96cc
|
@ -3,7 +3,7 @@ HAPI FHIR
|
|||
|
||||
HAPI FHIR - Java API for HL7 FHIR Clients and Servers
|
||||
|
||||
[![Build Status](https://dev.azure.com/jamesagnew214/jamesagnew214/_apis/build/status/jamesagnew.hapi-fhir?branchName=master)](https://dev.azure.com/jamesagnew214/jamesagnew214/_build/latest?definitionId=1&branchName=master)
|
||||
[![Build Status](https://dev.azure.com/hapifhir/HAPI%20FHIR/_apis/build/status/jamesagnew.hapi-fhir?branchName=master)](https://dev.azure.com/hapifhir/HAPI%20FHIR/_build/latest?definitionId=1&branchName=master)
|
||||
[![codecov](https://codecov.io/gh/jamesagnew/hapi-fhir/branch/master/graph/badge.svg)](https://codecov.io/gh/jamesagnew/hapi-fhir)
|
||||
[![Maven Central](https://maven-badges.herokuapp.com/maven-central/ca.uhn.hapi.fhir/hapi-fhir-base/badge.svg)](http://search.maven.org/#search|ga|1|ca.uhn.hapi.fhir)
|
||||
[![License](https://img.shields.io/badge/license-apache%202.0-60C060.svg)](https://hapifhir.io/hapi-fhir/license.html)
|
||||
|
|
|
@ -79,7 +79,7 @@
|
|||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>21.0</version>
|
||||
<version>24.1.1</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.inject.extensions</groupId>
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -27,7 +27,7 @@ import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
|||
*/
|
||||
public class NumberClientParam extends BaseClientParam implements IParam {
|
||||
|
||||
private String myParamName;
|
||||
private final String myParamName;
|
||||
|
||||
public NumberClientParam(String theParamName) {
|
||||
myParamName = theParamName;
|
||||
|
@ -37,12 +37,12 @@ public class NumberClientParam extends BaseClientParam implements IParam {
|
|||
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), Long.toString(theNumber));
|
||||
return new StringCriterion<>(getParamName(), Long.toString(theNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), (theNumber));
|
||||
return new StringCriterion<>(getParamName(), (theNumber));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -56,12 +56,12 @@ public class NumberClientParam extends BaseClientParam implements IParam {
|
|||
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), ParamPrefixEnum.GREATERTHAN, Long.toString(theNumber));
|
||||
return new StringCriterion<>(getParamName(), ParamPrefixEnum.GREATERTHAN, Long.toString(theNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), ParamPrefixEnum.GREATERTHAN, (theNumber));
|
||||
return new StringCriterion<>(getParamName(), ParamPrefixEnum.GREATERTHAN, (theNumber));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -70,12 +70,12 @@ public class NumberClientParam extends BaseClientParam implements IParam {
|
|||
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), ParamPrefixEnum.GREATERTHAN_OR_EQUALS, Long.toString(theNumber));
|
||||
return new StringCriterion<>(getParamName(), ParamPrefixEnum.GREATERTHAN_OR_EQUALS, Long.toString(theNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), ParamPrefixEnum.GREATERTHAN_OR_EQUALS, (theNumber));
|
||||
return new StringCriterion<>(getParamName(), ParamPrefixEnum.GREATERTHAN_OR_EQUALS, (theNumber));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -84,12 +84,12 @@ public class NumberClientParam extends BaseClientParam implements IParam {
|
|||
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), ParamPrefixEnum.LESSTHAN, Long.toString(theNumber));
|
||||
return new StringCriterion<>(getParamName(), ParamPrefixEnum.LESSTHAN, Long.toString(theNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), ParamPrefixEnum.LESSTHAN, (theNumber));
|
||||
return new StringCriterion<>(getParamName(), ParamPrefixEnum.LESSTHAN, (theNumber));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -98,12 +98,12 @@ public class NumberClientParam extends BaseClientParam implements IParam {
|
|||
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), ParamPrefixEnum.LESSTHAN_OR_EQUALS, Long.toString(theNumber));
|
||||
return new StringCriterion<>(getParamName(), ParamPrefixEnum.LESSTHAN_OR_EQUALS, Long.toString(theNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), ParamPrefixEnum.LESSTHAN_OR_EQUALS, (theNumber));
|
||||
return new StringCriterion<>(getParamName(), ParamPrefixEnum.LESSTHAN_OR_EQUALS, (theNumber));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -112,12 +112,12 @@ public class NumberClientParam extends BaseClientParam implements IParam {
|
|||
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), thePrefix, Long.toString(theNumber));
|
||||
return new StringCriterion<>(getParamName(), thePrefix, Long.toString(theNumber));
|
||||
}
|
||||
|
||||
@Override
|
||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
||||
return new StringCriterion<NumberClientParam>(getParamName(), thePrefix, (theNumber));
|
||||
return new StringCriterion<>(getParamName(), thePrefix, (theNumber));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -56,7 +56,7 @@ public class NumberParam extends BaseParamWithPrefix<NumberParam> implements IQu
|
|||
* Constructor
|
||||
*
|
||||
* @param theValue
|
||||
* A string value, e.g. ">5.0"
|
||||
* A string value, e.g. "gt5.0"
|
||||
*/
|
||||
public NumberParam(String theValue) {
|
||||
setValueAsQueryToken(null, null, null, theValue);
|
||||
|
|
|
@ -16,10 +16,16 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.net.MalformedURLException;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.util.*;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultIfBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
|
@ -370,7 +376,7 @@ public class UrlUtil {
|
|||
/**
|
||||
* This method specifically HTML-encodes the " and
|
||||
* < characters in order to prevent injection attacks.
|
||||
*
|
||||
* <p>
|
||||
* The following characters are escaped:
|
||||
* <ul>
|
||||
* <li>'</li>
|
||||
|
@ -379,7 +385,6 @@ public class UrlUtil {
|
|||
* <li>></li>
|
||||
* <li>\n (newline)</li>
|
||||
* </ul>
|
||||
*
|
||||
*/
|
||||
public static String sanitizeUrlPart(CharSequence theString) {
|
||||
if (theString == null) {
|
||||
|
@ -432,6 +437,21 @@ public class UrlUtil {
|
|||
return theString.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies the same logic as {@link #sanitizeUrlPart(CharSequence)} but against an array, returning an array with the
|
||||
* same strings as the input but with sanitization applied
|
||||
*/
|
||||
public static String[] sanitizeUrlPart(String[] theParameterValues) {
|
||||
String[] retVal = null;
|
||||
if (theParameterValues != null) {
|
||||
retVal = new String[theParameterValues.length];
|
||||
for (int i = 0; i < theParameterValues.length; i++) {
|
||||
retVal[i] = sanitizeUrlPart(theParameterValues[i]);
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private static Map<String, String[]> toQueryStringMap(HashMap<String, List<String>> map) {
|
||||
HashMap<String, String[]> retVal = new HashMap<>();
|
||||
for (Entry<String, List<String>> nextEntry : map.entrySet()) {
|
||||
|
|
|
@ -64,7 +64,8 @@ public enum VersionEnum {
|
|||
V5_0_0,
|
||||
V5_0_1,
|
||||
V5_0_2,
|
||||
V5_1_0;
|
||||
V5_1_0,
|
||||
V5_2_0;
|
||||
|
||||
public static VersionEnum latestVersion() {
|
||||
VersionEnum[] values = VersionEnum.values();
|
||||
|
|
|
@ -3,14 +3,14 @@
|
|||
<modelVersion>4.0.0</modelVersion>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-bom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>HAPI FHIR BOM</name>
|
||||
|
||||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-cli</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
@ -106,8 +107,9 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
|
||||
public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {
|
||||
JpaTransactionManager retVal = new JpaTransactionManager();
|
||||
retVal.setEntityManagerFactory(entityManagerFactory);
|
||||
return retVal;
|
||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.demo;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.binstore.DatabaseBlobBinaryStorageSvcImpl;
|
||||
import ca.uhn.fhir.jpa.binstore.IBinaryStorageSvc;
|
||||
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu3;
|
||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
|
@ -31,6 +33,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
|||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
@ -97,10 +100,12 @@ public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Primary
|
||||
@Bean
|
||||
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
|
||||
public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {
|
||||
JpaTransactionManager retVal = new JpaTransactionManager();
|
||||
retVal.setEntityManagerFactory(entityManagerFactory);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -26,6 +26,8 @@ import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
|||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.binstore.BinaryAccessProvider;
|
||||
import ca.uhn.fhir.jpa.binstore.BinaryStorageInterceptor;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
||||
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
|
||||
|
@ -180,6 +182,8 @@ public class JpaServerDemo extends RestfulServer {
|
|||
|
||||
getInterceptorService().registerInterceptor(new ResponseHighlighterInterceptor());
|
||||
|
||||
registerProvider(myAppCtx.getBean(BinaryAccessProvider.class));
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -73,13 +73,13 @@
|
|||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-structures-dstu2</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-jpaserver-subscription</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<scope>compile</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -96,7 +96,7 @@
|
|||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-testpage-overlay</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<classifier>classes</classifier>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 1856
|
||||
title: "The subscription delivery queue in the JPA server was erroniously keeping both a copy of the serialized and the
|
||||
title: "The subscription delivery queue in the JPA server was erroneously keeping both a copy of the serialized and the
|
||||
deserialized payload in memory for each entry in the queue, doubling the memory requirements. This also caused failures
|
||||
when delivering XML payloads in some configurations. This has been corrected."
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2021
|
||||
title: "Added [EMPI](https://hapifhir.io/hapi-fhir/docs/server_jpa_empi/empi.html) functionality, including phonetic
|
||||
indexing, asynchronous rules-based patient and practitioner matching when resources are created and updated. A number of
|
||||
[EMPI Operations](https://hapifhir.io/hapi-fhir/docs/server_jpa_empi/empi_operations.html) are provided to
|
||||
maintain EMPI links (e.g. resolving possible matches and possible duplicates). Also batch operations
|
||||
are provided to identify links in existing patients and practitioners, and to 'wipe clean' all EMPI data and re-run
|
||||
the batch as the empi matching rules are refined."
|
|
@ -0,0 +1,6 @@
|
|||
---
|
||||
type: fix
|
||||
issue: 2022
|
||||
title: When performing a resource $expunge in the JPA server, in-memory caches caused issues if a
|
||||
forced ID was reused quickly enough (as can be the case in some testing scenarios). Thanks to
|
||||
GitHub user @janvdpol for reporting!"
|
|
@ -0,0 +1,7 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2023
|
||||
title: "The JPA server maintains a cache of active SearchParameeter resources that can cause misleading results
|
||||
if a SearchParameter is changed and other resources that would be indexed by the changed SearchParameter are updated
|
||||
before the cache refreshes. A new interceptor has been added that should force a refresh sooner, especially on
|
||||
non-clustered systems."
|
|
@ -0,0 +1,5 @@
|
|||
---
|
||||
type: add
|
||||
issue: 2025
|
||||
title: "A new interceptor has been added for the JPA server that selectively allows resource deletions to proceed even if
|
||||
there are valid references to the candidate for deletion from other resources that are not being deleted."
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
type: security
|
||||
issue: 2026
|
||||
title: "An XSS vulnerability was reported in the HAPI FHIR Testpage Overlay module. Thanks to Will Davison of NCC Group (Manchester UK) for disclosing this vulnerability.
|
||||
|
||||
Users of the HAPI FHIR Testpage Overlay can use a specially crafted URL to exploit an XSS vulnerability in this module, allowing arbitrary JavaScript to be executed in the user's browser. The impact of this vulnerability is believed to be low, as this module is intended for testing and not believed to be widely used for any production purposes. Nonetheless, we recommend all users of the affected module upgrade immediately.
|
||||
|
||||
A complete audit of the affected codebase has been completed in order to detect and resolve any similar issues."
|
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
type: change
|
||||
issue: 237
|
||||
title: "The R5 structure methods for working with extensions on arbtrary fields, e.g.
|
||||
title: "The R5 structure methods for working with extensions on arbitrary fields, e.g.
|
||||
`getExtensionByUrl(String)`, `removeExtension(String)`, `getExtensionsByUrl(String)`
|
||||
`hasExtension(String)`, and `getExtensionString(String)` have been enhanced so that they
|
||||
now return modifier extensions as well as the non-modifier extensions they previously
|
|
@ -0,0 +1,3 @@
|
|||
---
|
||||
release-date: "2020-08-13"
|
||||
codename: "Manticore"
|
|
@ -0,0 +1,8 @@
|
|||
---
|
||||
- item:
|
||||
type: "add"
|
||||
title: "The version of a few dependencies have been bumped to the latest versions
|
||||
(dependent HAPI modules listed in brackets):
|
||||
<ul>
|
||||
<li>Flyway (JPA): 6.4.1-> 6.5.4</li>
|
||||
</ul>"
|
|
@ -15,7 +15,7 @@ The following is a list of key subprojects you might open in your IDE:
|
|||
# Getting the Sources
|
||||
|
||||
<p style="float:right;">
|
||||
<a class="externalLink" href="https://dev.azure.com/jamesagnew214/jamesagnew214/_build/latest?definitionId=1&branchName=master"><img src="https://dev.azure.com/jamesagnew214/jamesagnew214/_apis/build/status/jamesagnew.hapi-fhir?branchName=master" alt="Build Status" class="img-fluid"/></a>
|
||||
<a class="externalLink" href="https://dev.azure.com/hapifhir/HAPI%20FHIR/_build/latest?definitionId=1&branchName=master"><img src="https://dev.azure.com/hapifhir/HAPI%20FHIR/_apis/build/status/jamesagnew.hapi-fhir?branchName=master" alt="Build Status" class="img-fluid"/></a>
|
||||
</p>
|
||||
|
||||
The best way to grab our sources is with Git. Grab the repository URL from our [GitHub page](https://github.com/jamesagnew/hapi-fhir). We try our best to ensure that the sources are always left in a buildable state. Check Azure Pipelines CI (see the image/link on the right) to see if the sources currently build.
|
||||
|
|
|
@ -180,8 +180,24 @@ The ResponseSizeCapturingInterceptor can be used to capture the number of charac
|
|||
|
||||
# JPA Server: Allow Cascading Deletes
|
||||
|
||||
* [CascadingDeleteInterceptor JavaDoc](/apidocs/hapi-fhir-jpaserver-base/ca/uhn/fhir/jpa/interceptor/CascadingDeleteInterceptor.html)
|
||||
* [CascadingDeleteInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/CascadingDeleteInterceptor.java)
|
||||
|
||||
The CascadingDeleteInterceptor allows clients to request deletes be cascaded to other resources that contain incoming references. See [Cascading Deletes](/docs/server_jpa/configuration.html#cascading-deletes) for more information.
|
||||
|
||||
|
||||
<a name="overridepathbasedreferentialintegrityfordeletesinterceptor"/>
|
||||
|
||||
# JPA Server: Disable Referential Integrity for Some Paths
|
||||
|
||||
* [OverridePathBasedReferentialIntegrityForDeletesInterceptor JavaDoc](/apidocs/hapi-fhir-jpaserver-base/ca/uhn/fhir/jpa/interceptor/OverridePathBasedReferentialIntegrityForDeletesInterceptor.html)
|
||||
* [OverridePathBasedReferentialIntegrityForDeletesInterceptor Source](https://github.com/jamesagnew/hapi-fhir/blob/master/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/interceptor/OverridePathBasedReferentialIntegrityForDeletesInterceptor.java)
|
||||
|
||||
The OverridePathBasedReferentialIntegrityForDeletesInterceptor can be registered and configured to allow resources to be deleted even if other resources have outgoing references to the deleted resource. While it is generally a bad idea to allow deletion of resources that are referred to from other resources, there are circumstances where it is desirable. For example, if you have Provenance or AuditEvent resources that refer to a Patient resource that was created in error, you might want to alow the Patient to be deleted while leaving the Provenance and AuditEvent resources intact (including the now-invalid outgoing references to that Patient).
|
||||
|
||||
This interceptor uses FHIRPath expressions to indicate the resource paths that should not have referential integrity applied to them. For example, if this interceptor is configured with a path of `AuditEvent.agent.who`, a Patient resource would be allowed to be deleted even if one or more AuditEvents had references in that path to the given Patient (unless other resources also had references to the Patient).
|
||||
|
||||
|
||||
# JPA Server: Retry on Version Conflicts
|
||||
|
||||
The UserRequestRetryVersionConflictsInterceptor allows clients to request that the server avoid version conflicts (HTTP 409) when two concurrent client requests attempt to modify the same resource. See [Version Conflicts](/docs/server_jpa/configuration.html#retry-on-version-conflict) for more information.
|
||||
|
|
|
@ -32,6 +32,8 @@ Creating your own interceptors is easy. Custom interceptor classes do not need t
|
|||
|
||||
* The method may have any of the parameters specified for the given [Pointcut](/apidocs/hapi-fhir-base/ca/uhn/fhir/interceptor/api/Pointcut.html).
|
||||
|
||||
* The method must be public.
|
||||
|
||||
The following example shows a simple request counter interceptor.
|
||||
|
||||
```java
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -34,8 +34,9 @@ import java.util.Collection;
|
|||
* time to time, even within minor point releases.
|
||||
*/
|
||||
public interface IDao {
|
||||
String RESOURCE_PID_KEY = "RESOURCE_PID";
|
||||
|
||||
MetadataKeyResourcePid RESOURCE_PID = new MetadataKeyResourcePid("RESOURCE_PID");
|
||||
MetadataKeyResourcePid RESOURCE_PID = new MetadataKeyResourcePid(RESOURCE_PID_KEY);
|
||||
|
||||
MetadataKeyCurrentlyReindexing CURRENTLY_REINDEXING = new MetadataKeyCurrentlyReindexing("CURRENTLY_REINDEXING");
|
||||
|
||||
|
|
|
@ -34,6 +34,7 @@ import java.util.function.Predicate;
|
|||
public class DeleteConflictList implements Iterable<DeleteConflict> {
|
||||
private final List<DeleteConflict> myList = new ArrayList<>();
|
||||
private final Set<String> myResourceIdsMarkedForDeletion;
|
||||
private final Set<String> myResourceIdsToIgnoreConflict;
|
||||
private int myRemoveModCount;
|
||||
|
||||
/**
|
||||
|
@ -41,6 +42,7 @@ public class DeleteConflictList implements Iterable<DeleteConflict> {
|
|||
*/
|
||||
public DeleteConflictList() {
|
||||
myResourceIdsMarkedForDeletion = new HashSet<>();
|
||||
myResourceIdsToIgnoreConflict = new HashSet<>();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -49,6 +51,7 @@ public class DeleteConflictList implements Iterable<DeleteConflict> {
|
|||
*/
|
||||
public DeleteConflictList(DeleteConflictList theParentList) {
|
||||
myResourceIdsMarkedForDeletion = theParentList.myResourceIdsMarkedForDeletion;
|
||||
myResourceIdsToIgnoreConflict = theParentList.myResourceIdsToIgnoreConflict;
|
||||
}
|
||||
|
||||
|
||||
|
@ -64,6 +67,18 @@ public class DeleteConflictList implements Iterable<DeleteConflict> {
|
|||
myResourceIdsMarkedForDeletion.add(theIdType.toUnqualifiedVersionless().getValue());
|
||||
}
|
||||
|
||||
public boolean isResourceIdToIgnoreConflict(IIdType theIdType) {
|
||||
Validate.notNull(theIdType);
|
||||
Validate.notBlank(theIdType.toUnqualifiedVersionless().getValue());
|
||||
return myResourceIdsToIgnoreConflict.contains(theIdType.toUnqualifiedVersionless().getValue());
|
||||
}
|
||||
|
||||
public void setResourceIdToIgnoreConflict(IIdType theIdType) {
|
||||
Validate.notNull(theIdType);
|
||||
Validate.notBlank(theIdType.toUnqualifiedVersionless().getValue());
|
||||
myResourceIdsToIgnoreConflict.add(theIdType.toUnqualifiedVersionless().getValue());
|
||||
}
|
||||
|
||||
public void add(DeleteConflict theDeleteConflict) {
|
||||
myList.add(theDeleteConflict);
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.config;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
|
@ -24,7 +25,9 @@ import ca.uhn.fhir.jpa.dao.index.DaoResourceLinkResolver;
|
|||
import ca.uhn.fhir.jpa.dao.tx.HapiTransactionService;
|
||||
import ca.uhn.fhir.jpa.entity.Search;
|
||||
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
||||
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
||||
import ca.uhn.fhir.jpa.interceptor.JpaConsentContextServices;
|
||||
import ca.uhn.fhir.jpa.interceptor.OverridePathBasedReferentialIntegrityForDeletesInterceptor;
|
||||
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
||||
import ca.uhn.fhir.jpa.packages.IHapiPackageCacheManager;
|
||||
import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc;
|
||||
|
@ -167,6 +170,12 @@ public abstract class BaseConfig {
|
|||
return new BatchJobSubmitterImpl();
|
||||
}
|
||||
|
||||
@Lazy
|
||||
@Bean
|
||||
public CascadingDeleteInterceptor cascadingDeleteInterceptor(FhirContext theFhirContext, DaoRegistry theDaoRegistry, IInterceptorBroadcaster theInterceptorBroadcaster) {
|
||||
return new CascadingDeleteInterceptor(theFhirContext, theDaoRegistry, theInterceptorBroadcaster);
|
||||
}
|
||||
|
||||
/**
|
||||
* This method should be overridden to provide an actual completed
|
||||
* bean, but it provides a partially completed entity manager
|
||||
|
@ -295,6 +304,12 @@ public abstract class BaseConfig {
|
|||
return new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public OverridePathBasedReferentialIntegrityForDeletesInterceptor overridePathBasedReferentialIntegrityForDeletesInterceptor() {
|
||||
return new OverridePathBasedReferentialIntegrityForDeletesInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IRequestPartitionHelperSvc requestPartitionHelperService() {
|
||||
return new RequestPartitionHelperSvc();
|
||||
|
|
|
@ -79,7 +79,6 @@ import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|||
import ca.uhn.fhir.rest.api.server.storage.TransactionDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
|
@ -114,10 +113,8 @@ import org.springframework.context.ApplicationContext;
|
|||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Repository;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.EntityManager;
|
||||
|
@ -636,7 +633,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
ResourceMetadataKeyEnum.VERSION.put(res, Long.toString(theEntity.getVersion()));
|
||||
ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
|
||||
ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
|
||||
IDao.RESOURCE_PID.put(res, theEntity.getId());
|
||||
IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
|
||||
|
||||
Collection<? extends BaseTag> tags = theTagList;
|
||||
if (theEntity.isHasTags()) {
|
||||
|
@ -708,7 +705,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||
res.setId(res.getIdElement().withVersion(theVersion.toString()));
|
||||
|
||||
res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
|
||||
IDao.RESOURCE_PID.put(res, theEntity.getId());
|
||||
IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
|
||||
|
||||
Collection<? extends BaseTag> tags = theTagList;
|
||||
|
||||
|
|
|
@ -994,22 +994,25 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public T read(IIdType theId) {
|
||||
return read(theId, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public T read(IIdType theId, RequestDetails theRequestDetails) {
|
||||
return read(theId, theRequestDetails, false);
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
public T read(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) {
|
||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
||||
|
||||
return myTransactionService.execute(theRequest, tx-> doRead(theId, theRequest, theDeletedOk));
|
||||
}
|
||||
|
||||
public T doRead(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) {
|
||||
assert TransactionSynchronizationManager.isActualTransactionActive();
|
||||
|
||||
// Notify interceptors
|
||||
if (theRequest != null) {
|
||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getResourceName(), theId);
|
||||
|
|
|
@ -51,6 +51,7 @@ public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao
|
|||
protected void postPersist(ResourceTable theEntity, SearchParameter theResource) {
|
||||
super.postPersist(theEntity, theResource);
|
||||
markAffectedResources(theResource);
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -46,6 +46,7 @@ import ca.uhn.fhir.jpa.model.entity.ForcedId;
|
|||
import ca.uhn.fhir.jpa.model.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.jpa.util.MemoryCacheService;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
|
@ -60,7 +61,11 @@ import org.springframework.data.domain.Pageable;
|
|||
import org.springframework.data.domain.Slice;
|
||||
import org.springframework.data.domain.SliceImpl;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.TransactionManager;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.transaction.support.TransactionSynchronization;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -108,6 +113,8 @@ public class ResourceExpungeService implements IResourceExpungeService {
|
|||
private ISearchParamPresentDao mySearchParamPresentDao;
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private MemoryCacheService myMemoryCacheService;
|
||||
|
||||
@Override
|
||||
@Transactional
|
||||
|
@ -158,6 +165,20 @@ public class ResourceExpungeService implements IResourceExpungeService {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* Once this transaction is committed, we will invalidate all memory caches
|
||||
* in order to avoid any caches having references to things that no longer
|
||||
* exist. This is a pretty brute-force way of addressing this, and could probably
|
||||
* be optimized, but expunge is hopefully not frequently called on busy servers
|
||||
* so it shouldn't be too big a deal.
|
||||
*/
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronization(){
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
myMemoryCacheService.invalidateAllCaches();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void expungeHistoricalVersion(RequestDetails theRequestDetails, Long theNextVersionId, AtomicInteger theRemainingCount) {
|
||||
|
|
|
@ -100,6 +100,8 @@ public class IdHelperService {
|
|||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
@Autowired
|
||||
private FhirContext myFhirCtx;
|
||||
@Autowired
|
||||
private MemoryCacheService myMemoryCacheService;
|
||||
|
||||
public void delete(ForcedId forcedId) {
|
||||
myForcedIdDao.deleteByPid(forcedId.getId());
|
||||
|
@ -123,9 +125,6 @@ public class IdHelperService {
|
|||
return matches.iterator().next();
|
||||
}
|
||||
|
||||
@Autowired
|
||||
private MemoryCacheService myMemoryCacheService;
|
||||
|
||||
/**
|
||||
* Given a resource type and ID, determines the internal persistent ID for the resource.
|
||||
*
|
||||
|
@ -389,7 +388,7 @@ public class IdHelperService {
|
|||
lookup
|
||||
.stream()
|
||||
.map(t -> new ResourceLookup((String) t[0], (Long) t[1], (Date) t[2]))
|
||||
.forEach(t->{
|
||||
.forEach(t -> {
|
||||
theTarget.add(t);
|
||||
if (!myDaoConfig.isDeleteEnabled()) {
|
||||
String nextKey = Long.toString(t.getResourceId());
|
||||
|
@ -432,19 +431,6 @@ public class IdHelperService {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public static boolean isValidPid(IIdType theId) {
|
||||
if (theId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String idPart = theId.getIdPart();
|
||||
return isValidPid(idPart);
|
||||
}
|
||||
|
||||
public static boolean isValidPid(String theIdPart) {
|
||||
return StringUtils.isNumeric(theIdPart);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Long getPidOrNull(IBaseResource theResource) {
|
||||
IAnyResource anyResource = (IAnyResource) theResource;
|
||||
|
@ -488,4 +474,17 @@ public class IdHelperService {
|
|||
}
|
||||
return optionalResource.get().getIdDt().toVersionless();
|
||||
}
|
||||
|
||||
public static boolean isValidPid(IIdType theId) {
|
||||
if (theId == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String idPart = theId.getIdPart();
|
||||
return isValidPid(idPart);
|
||||
}
|
||||
|
||||
public static boolean isValidPid(String theIdPart) {
|
||||
return StringUtils.isNumeric(theIdPart);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,27 +44,25 @@ import org.slf4j.Logger;
|
|||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class DeleteConflictService {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictService.class);
|
||||
public static final int FIRST_QUERY_RESULT_COUNT = 1;
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictService.class);
|
||||
public static int MAX_RETRY_ATTEMPTS = 10;
|
||||
public static String MAX_RETRY_ATTEMPTS_EXCEEDED_MSG = "Requested delete operation stopped before all conflicts were handled. May need to increase the configured Maximum Delete Conflict Query Count.";
|
||||
|
||||
@Autowired
|
||||
protected IResourceLinkDao myResourceLinkDao;
|
||||
@Autowired
|
||||
protected IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
@Autowired
|
||||
DeleteConflictFinderService myDeleteConflictFinderService;
|
||||
@Autowired
|
||||
DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
protected IResourceLinkDao myResourceLinkDao;
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
@Autowired
|
||||
protected IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
|
||||
public int validateOkToDelete(DeleteConflictList theDeleteConflicts, ResourceTable theEntity, boolean theForValidate, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
|
||||
|
@ -87,9 +85,9 @@ public class DeleteConflictService {
|
|||
++retryCount;
|
||||
}
|
||||
theDeleteConflicts.addAll(newConflicts);
|
||||
if(retryCount >= MAX_RETRY_ATTEMPTS && !theDeleteConflicts.isEmpty()) {
|
||||
if (retryCount >= MAX_RETRY_ATTEMPTS && !theDeleteConflicts.isEmpty()) {
|
||||
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(myFhirContext);
|
||||
OperationOutcomeUtil.addIssue(myFhirContext, oo, BaseHapiFhirDao.OO_SEVERITY_ERROR, MAX_RETRY_ATTEMPTS_EXCEEDED_MSG,null, "processing");
|
||||
OperationOutcomeUtil.addIssue(myFhirContext, oo, BaseHapiFhirDao.OO_SEVERITY_ERROR, MAX_RETRY_ATTEMPTS_EXCEEDED_MSG, null, "processing");
|
||||
throw new ResourceVersionConflictException(MAX_RETRY_ATTEMPTS_EXCEEDED_MSG, oo);
|
||||
}
|
||||
return retryCount;
|
||||
|
@ -123,7 +121,7 @@ public class DeleteConflictService {
|
|||
.add(RequestDetails.class, theRequest)
|
||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||
.add(TransactionDetails.class, theTransactionDetails);
|
||||
return (DeleteConflictOutcome)JpaInterceptorBroadcaster.doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS, hooks);
|
||||
return (DeleteConflictOutcome) JpaInterceptorBroadcaster.doCallHooksAndReturnObject(myInterceptorBroadcaster, theRequest, Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS, hooks);
|
||||
}
|
||||
|
||||
private void addConflictsToList(DeleteConflictList theDeleteConflicts, ResourceTable theEntity, List<ResourceLink> theResultList) {
|
||||
|
@ -142,26 +140,33 @@ public class DeleteConflictService {
|
|||
}
|
||||
|
||||
public static void validateDeleteConflictsEmptyOrThrowException(FhirContext theFhirContext, DeleteConflictList theDeleteConflicts) {
|
||||
if (theDeleteConflicts.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(theFhirContext);
|
||||
IBaseOperationOutcome oo = null;
|
||||
String firstMsg = null;
|
||||
|
||||
for (DeleteConflict next : theDeleteConflicts) {
|
||||
|
||||
if (theDeleteConflicts.isResourceIdToIgnoreConflict(next.getTargetId())) {
|
||||
continue;
|
||||
}
|
||||
|
||||
String msg = "Unable to delete " +
|
||||
next.getTargetId().toUnqualifiedVersionless().getValue() +
|
||||
" because at least one resource has a reference to this resource. First reference found was resource " +
|
||||
next.getSourceId().toUnqualifiedVersionless().getValue() +
|
||||
" in path " +
|
||||
next.getSourcePath();
|
||||
|
||||
if (firstMsg == null) {
|
||||
firstMsg = msg;
|
||||
oo = OperationOutcomeUtil.newInstance(theFhirContext);
|
||||
}
|
||||
OperationOutcomeUtil.addIssue(theFhirContext, oo, BaseHapiFhirDao.OO_SEVERITY_ERROR, msg, null, "processing");
|
||||
}
|
||||
|
||||
if (firstMsg == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
throw new ResourceVersionConflictException(firstMsg, oo);
|
||||
}
|
||||
|
||||
|
|
|
@ -71,6 +71,13 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
@Interceptor
|
||||
public class CascadingDeleteInterceptor {
|
||||
|
||||
/*
|
||||
* We keep the orders for the various handlers of {@link Pointcut#STORAGE_PRESTORAGE_DELETE_CONFLICTS} in one place
|
||||
* so it's easy to compare them
|
||||
*/
|
||||
public static final int OVERRIDE_PATH_BASED_REF_INTEGRITY_INTERCEPTOR_ORDER = 0;
|
||||
public static final int CASCADING_DELETE_INTERCEPTOR_ORDER = 1;
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(CascadingDeleteInterceptor.class);
|
||||
private static final String CASCADED_DELETES_KEY = CascadingDeleteInterceptor.class.getName() + "_CASCADED_DELETES_KEY";
|
||||
private static final String CASCADED_DELETES_FAILED_KEY = CascadingDeleteInterceptor.class.getName() + "_CASCADED_DELETES_FAILED_KEY";
|
||||
|
@ -94,7 +101,7 @@ public class CascadingDeleteInterceptor {
|
|||
myFhirContext = theFhirContext;
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS)
|
||||
@Hook(value = Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS, order = CASCADING_DELETE_INTERCEPTOR_ORDER)
|
||||
public DeleteConflictOutcome handleDeleteConflicts(DeleteConflictList theConflictList, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||
ourLog.debug("Have delete conflicts: {}", theConflictList);
|
||||
|
||||
|
|
|
@ -0,0 +1,127 @@
|
|||
package ca.uhn.fhir.jpa.interceptor;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2020 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.fhirpath.IFhirPath;
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.api.model.DeleteConflict;
|
||||
import ca.uhn.fhir.jpa.api.model.DeleteConflictList;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* This JPA interceptor can be configured with a collection of FHIRPath expressions, and will disable
|
||||
* referential integrity for target resources at those paths.
|
||||
* <p>
|
||||
* For example, suppose this interceptor is configured with a path of <code>AuditEvent.entity.what</code>,
|
||||
* and an AuditEvent resource exists in the repository that has a reference in that path to resource
|
||||
* <code>Patient/123</code>. Normally this reference would prevent the Patient resource from being deleted unless
|
||||
* the AuditEvent was first deleted as well (or a <a href="/hapi-fhir/docs/server_jpa/configuration.html#cascading-deletes">cascading delete</a> was used).
|
||||
* With this interceptor in place, the Patient resource could be deleted, and the AuditEvent would remain intact.
|
||||
* </p>
|
||||
*/
|
||||
@Interceptor
|
||||
public class OverridePathBasedReferentialIntegrityForDeletesInterceptor {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(OverridePathBasedReferentialIntegrityForDeletesInterceptor.class);
|
||||
private final Set<String> myPaths = new HashSet<>();
|
||||
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public OverridePathBasedReferentialIntegrityForDeletesInterceptor() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds a FHIRPath expression indicating a resource path that should be ignored when considering referential
|
||||
* integrity for deletes.
|
||||
*
|
||||
* @param thePath The FHIRPath expression, e.g. <code>AuditEvent.agent.who</code>
|
||||
*/
|
||||
public void addPath(String thePath) {
|
||||
getPaths().add(thePath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove all paths registered to this interceptor
|
||||
*/
|
||||
public void clearPaths() {
|
||||
getPaths().clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the paths that will be considered by this interceptor
|
||||
*
|
||||
* @see #addPath(String)
|
||||
*/
|
||||
public Set<String> getPaths() {
|
||||
return myPaths;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interceptor hook method. Do not invoke directly.
|
||||
*/
|
||||
@Hook(value = Pointcut.STORAGE_PRESTORAGE_DELETE_CONFLICTS, order = CascadingDeleteInterceptor.OVERRIDE_PATH_BASED_REF_INTEGRITY_INTERCEPTOR_ORDER)
|
||||
public void handleDeleteConflicts(DeleteConflictList theDeleteConflictList) {
|
||||
for (DeleteConflict nextConflict : theDeleteConflictList) {
|
||||
ourLog.info("Ignoring referential integrity deleting {} - Referred to from {} at path {}", nextConflict.getTargetId(), nextConflict.getSourceId(), nextConflict.getSourcePath());
|
||||
|
||||
IdDt sourceId = nextConflict.getSourceId();
|
||||
IdDt targetId = nextConflict.getTargetId();
|
||||
String targetIdValue = targetId.toVersionless().getValue();
|
||||
|
||||
IBaseResource sourceResource = myDaoRegistry.getResourceDao(sourceId.getResourceType()).read(sourceId);
|
||||
|
||||
IFhirPath fhirPath = myFhirContext.newFhirPath();
|
||||
for (String nextPath : myPaths) {
|
||||
List<IBaseReference> selections = fhirPath.evaluate(sourceResource, nextPath, IBaseReference.class);
|
||||
for (IBaseReference nextSelection : selections) {
|
||||
String selectionTargetValue = nextSelection.getReferenceElement().toVersionless().getValue();
|
||||
if (Objects.equals(targetIdValue, selectionTargetValue)) {
|
||||
theDeleteConflictList.setResourceIdToIgnoreConflict(nextConflict.getTargetId());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu2;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
||||
|
@ -28,6 +29,8 @@ import ca.uhn.fhir.model.primitive.DecimalDt;
|
|||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.NumberParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
|
@ -232,6 +235,47 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* See #2023
|
||||
*/
|
||||
@Test
|
||||
public void testNumberSearchParam() {
|
||||
SearchParameter numberParameter = new ca.uhn.fhir.model.dstu2.resource.SearchParameter();
|
||||
numberParameter.setId("future-appointment-count");
|
||||
numberParameter.setName("Future Appointment Count");
|
||||
numberParameter.setCode("future-appointment-count");
|
||||
numberParameter.setDescription("Count of future appointments for the patient");
|
||||
numberParameter.setUrl("http://integer");
|
||||
numberParameter.setStatus(ca.uhn.fhir.model.dstu2.valueset.ConformanceResourceStatusEnum.ACTIVE);
|
||||
numberParameter.setBase(ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum.PATIENT);
|
||||
numberParameter.setType(ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum.NUMBER);
|
||||
numberParameter.setXpathUsage(XPathUsageTypeEnum.NORMAL);
|
||||
numberParameter.setXpath("Patient.extension('http://integer')");
|
||||
mySearchParameterDao.update(numberParameter);
|
||||
|
||||
// This fires every 10 seconds
|
||||
mySearchParamRegistry.refreshCacheIfNecessary();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("future-appointment-count-pt");
|
||||
patient.setActive(true);
|
||||
patient.addUndeclaredExtension(false, "http://integer", new IntegerDt(1));
|
||||
myPatientDao.update(patient);
|
||||
|
||||
IBundleProvider search;
|
||||
|
||||
search = myPatientDao.search(SearchParameterMap.newSynchronous("future-appointment-count", new NumberParam(1)));
|
||||
assertEquals(1, search.size());
|
||||
|
||||
search = myPatientDao.search(SearchParameterMap.newSynchronous("future-appointment-count", new NumberParam("gt0")));
|
||||
assertEquals(1, search.size());
|
||||
|
||||
search = myPatientDao.search(SearchParameterMap.newSynchronous("future-appointment-count", new NumberParam("lt0")));
|
||||
assertEquals(0, search.size());
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testIncludeExtensionReferenceAsRecurse() {
|
||||
SearchParameter attendingSp = new SearchParameter();
|
||||
|
|
|
@ -144,7 +144,7 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
|
|||
} catch (PreconditionFailedException e) {
|
||||
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome());
|
||||
ourLog.info(ooString);
|
||||
assertThat(ooString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked"));
|
||||
assertThat(ooString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked"));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -340,7 +340,7 @@ public class FhirResourceDaoDstu3ValidateTest extends BaseJpaDstu3Test {
|
|||
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
|
||||
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
|
||||
ourLog.info(outputString);
|
||||
assertThat(outputString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked"));
|
||||
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -99,7 +99,7 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
|
|||
|
||||
|
||||
@Test
|
||||
public void testDeleteCircularReferenceInTransaction() throws IOException {
|
||||
public void testDeleteCircularReferenceInTransaction() {
|
||||
|
||||
// Create two resources with a circular reference
|
||||
Organization org1 = new Organization();
|
||||
|
@ -221,4 +221,12 @@ public class FhirResourceDaoR4DeleteTest extends BaseJpaR4Test {
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testDeleteIgnoreReferentialIntegrityForPaths() {
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhir.jpa.dao.r4;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
|
@ -9,7 +10,11 @@ import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
|||
import ca.uhn.fhir.jpa.model.search.StorageProcessingMessage;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.api.Include;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.XPathUsageTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||
import ca.uhn.fhir.rest.param.DateParam;
|
||||
import ca.uhn.fhir.rest.param.NumberParam;
|
||||
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
|
||||
|
@ -112,6 +117,46 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||
mySearchParamRegistry.forceRefresh();
|
||||
}
|
||||
|
||||
/**
|
||||
* See #2023
|
||||
*/
|
||||
@Test
|
||||
public void testNumberSearchParam() {
|
||||
SearchParameter numberParameter = new SearchParameter();
|
||||
numberParameter.setId("future-appointment-count");
|
||||
numberParameter.setName("Future Appointment Count");
|
||||
numberParameter.setCode("future-appointment-count");
|
||||
numberParameter.setDescription("Count of future appointments for the patient");
|
||||
numberParameter.setUrl("http://integer");
|
||||
numberParameter.setStatus(Enumerations.PublicationStatus.ACTIVE);
|
||||
numberParameter.addBase("Patient");
|
||||
numberParameter.setType(Enumerations.SearchParamType.NUMBER);
|
||||
numberParameter.setExpression("Patient.extension('http://integer')");
|
||||
mySearchParameterDao.update(numberParameter);
|
||||
|
||||
// This fires every 10 seconds
|
||||
mySearchParamRegistry.refreshCacheIfNecessary();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("future-appointment-count-pt");
|
||||
patient.setActive(true);
|
||||
patient.addExtension( "http://integer", new IntegerType(1));
|
||||
myPatientDao.update(patient);
|
||||
|
||||
IBundleProvider search;
|
||||
|
||||
search = myPatientDao.search(SearchParameterMap.newSynchronous("future-appointment-count", new NumberParam(1)));
|
||||
assertEquals(1, search.size());
|
||||
|
||||
search = myPatientDao.search(SearchParameterMap.newSynchronous("future-appointment-count", new NumberParam("gt0")));
|
||||
assertEquals(1, search.size());
|
||||
|
||||
search = myPatientDao.search(SearchParameterMap.newSynchronous("future-appointment-count", new NumberParam("lt0")));
|
||||
assertEquals(0, search.size());
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Draft search parameters should be ok even if they aren't completely valid
|
||||
*/
|
||||
|
|
|
@ -314,7 +314,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
outcome = (OperationOutcome) e.getOperationOutcome();
|
||||
String outcomeStr = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome);
|
||||
ourLog.info("Validation outcome: {}", outcomeStr);
|
||||
assertThat(outcomeStr, containsString("The Profile \\\"https://bb/StructureDefinition/BBDemographicAge\\\" definition allows for the type Quantity but found type string"));
|
||||
assertThat(outcomeStr, containsString("The Profile 'https://bb/StructureDefinition/BBDemographicAge' definition allows for the type Quantity but found type string"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -466,7 +466,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
obs.setSubject(new Reference("Group/123"));
|
||||
OperationOutcome oo = validateAndReturnOutcome(obs);
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||
assertEquals("Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
|
||||
assertEquals("Unable to resolve resource 'Group/123'", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
|
||||
|
||||
// Target of wrong type
|
||||
obs.setSubject(new Reference("Group/ABC"));
|
||||
|
@ -530,7 +530,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
obs.setSubject(new Reference("Group/123"));
|
||||
OperationOutcome oo = validateAndReturnOutcome(obs);
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||
assertEquals("Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
|
||||
assertEquals("Unable to resolve resource 'Group/123'", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
|
||||
|
||||
// Target of wrong type
|
||||
obs.setSubject(new Reference("Group/ABC"));
|
||||
|
@ -595,7 +595,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
obs.setSubject(new Reference("Group/123"));
|
||||
OperationOutcome oo = validateAndReturnOutcome(obs);
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
||||
assertEquals("Unable to resolve resource \"Group/123\"", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
|
||||
assertEquals("Unable to resolve resource 'Group/123'", oo.getIssueFirstRep().getDiagnostics(), encode(oo));
|
||||
|
||||
// Target of wrong type
|
||||
obs.setSubject(new Reference("Group/ABC"));
|
||||
|
@ -625,7 +625,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
" },\n" +
|
||||
" \"text\": {\n" +
|
||||
" \"status\": \"generated\",\n" +
|
||||
" \"div\": \"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\"></div>\"\n" +
|
||||
" \"div\": \"<div xmlns=\\\"http://www.w3.org/1999/xhtml\\\">HELLO</div>\"\n" +
|
||||
" },\n" +
|
||||
" \"url\": \"https://foo/bb\",\n" +
|
||||
" \"name\": \"BBBehaviourType\",\n" +
|
||||
|
@ -1134,7 +1134,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome();
|
||||
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
|
||||
ourLog.info(outputString);
|
||||
assertThat(outputString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked"));
|
||||
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1170,7 +1170,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome();
|
||||
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
|
||||
ourLog.info(outputString);
|
||||
assertThat(outputString, containsString("Profile reference \\\"http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid\\\" could not be resolved, so has not been checked"));
|
||||
assertThat(outputString, containsString("Profile reference 'http://example.com/StructureDefinition/testValidateResourceContainingProfileDeclarationInvalid' could not be resolved, so has not been checked"));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1453,7 +1453,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
fail("Didn't fail- response was " + encode);
|
||||
} catch (PreconditionFailedException e) {
|
||||
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
|
||||
assertEquals("No response answer found for required item \"link0\"", oo.getIssueFirstRep().getDiagnostics());
|
||||
assertEquals("No response answer found for required item 'link0'", oo.getIssueFirstRep().getDiagnostics());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1482,7 +1482,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
fail("Didn't fail- response was " + encode);
|
||||
} catch (PreconditionFailedException e) {
|
||||
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
|
||||
assertEquals("No response answer found for required item \"link0\"", oo.getIssueFirstRep().getDiagnostics());
|
||||
assertEquals("No response answer found for required item 'link0'", oo.getIssueFirstRep().getDiagnostics());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1506,7 +1506,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||
try {
|
||||
MethodOutcome validationOutcome = myQuestionnaireResponseDao.validate(qa, null, null, null, null, null, null);
|
||||
OperationOutcome oo = (OperationOutcome) validationOutcome.getOperationOutcome();
|
||||
assertEquals("The questionnaire \"http://foo/Questionnaire/DOES_NOT_EXIST\" could not be resolved, so no validation can be performed against the base questionnaire", oo.getIssueFirstRep().getDiagnostics());
|
||||
assertEquals("The questionnaire 'http://foo/Questionnaire/DOES_NOT_EXIST' could not be resolved, so no validation can be performed against the base questionnaire", oo.getIssueFirstRep().getDiagnostics());
|
||||
} catch (PreconditionFailedException e) {
|
||||
fail(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
|
||||
}
|
||||
|
|
|
@ -1,8 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
package ca.uhn.fhir.jpa.interceptor;
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
||||
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
|
@ -20,7 +20,6 @@ import org.hl7.fhir.r4.model.Organization;
|
|||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.Reference;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
|
@ -31,9 +30,9 @@ import static org.hamcrest.MatcherAssert.assertThat;
|
|||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class CascadingDeleteInterceptorR4Test extends BaseResourceProviderR4Test {
|
||||
public class CascadingDeleteInterceptorTest extends BaseResourceProviderR4Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CascadingDeleteInterceptorR4Test.class);
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CascadingDeleteInterceptorTest.class);
|
||||
private IIdType myDiagnosticReportId;
|
||||
|
||||
@Autowired
|
||||
|
@ -42,18 +41,13 @@ public class CascadingDeleteInterceptorR4Test extends BaseResourceProviderR4Test
|
|||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
|
||||
private IIdType myPatientId;
|
||||
@Autowired
|
||||
private CascadingDeleteInterceptor myDeleteInterceptor;
|
||||
private IIdType myObservationId;
|
||||
private IIdType myConditionId;
|
||||
private IIdType myEncounterId;
|
||||
|
||||
@Override
|
||||
@BeforeEach
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
myDeleteInterceptor = new CascadingDeleteInterceptor(myFhirCtx, myDaoRegistry, myInterceptorBroadcaster);
|
||||
}
|
||||
@Autowired
|
||||
private OverridePathBasedReferentialIntegrityForDeletesInterceptor myOverridePathBasedReferentialIntegrityForDeletesInterceptor;
|
||||
|
||||
@Override
|
||||
@AfterEach
|
||||
|
@ -161,6 +155,37 @@ public class CascadingDeleteInterceptorR4Test extends BaseResourceProviderR4Test
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteCascadingWithOverridePathBasedReferentialIntegrityForDeletesInterceptorAlsoRegistered() throws IOException {
|
||||
ourRestServer.getInterceptorService().registerInterceptor(myOverridePathBasedReferentialIntegrityForDeletesInterceptor);
|
||||
try {
|
||||
|
||||
createResources();
|
||||
|
||||
ourRestServer.getInterceptorService().registerInterceptor(myDeleteInterceptor);
|
||||
|
||||
HttpDelete delete = new HttpDelete(ourServerBase + "/" + myPatientId.getValue() + "?" + Constants.PARAMETER_CASCADE_DELETE + "=" + Constants.CASCADE_DELETE + "&_pretty=true");
|
||||
delete.addHeader(Constants.HEADER_ACCEPT, Constants.CT_FHIR_JSON_NEW);
|
||||
try (CloseableHttpResponse response = ourHttpClient.execute(delete)) {
|
||||
assertEquals(200, response.getStatusLine().getStatusCode());
|
||||
String deleteResponse = IOUtils.toString(response.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info("Response: {}", deleteResponse);
|
||||
assertThat(deleteResponse, containsString("Cascaded delete to "));
|
||||
}
|
||||
|
||||
try {
|
||||
ourLog.info("Reading {}", myPatientId);
|
||||
myClient.read().resource(Patient.class).withId(myPatientId).execute();
|
||||
fail();
|
||||
} catch (ResourceGoneException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
} finally {
|
||||
ourRestServer.getInterceptorService().unregisterInterceptor(myOverridePathBasedReferentialIntegrityForDeletesInterceptor);
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteCascadingWithCircularReference() throws IOException {
|
||||
|
|
@ -0,0 +1,141 @@
|
|||
package ca.uhn.fhir.jpa.interceptor;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
||||
import org.hl7.fhir.r4.model.AuditEvent;
|
||||
import org.hl7.fhir.r4.model.IdType;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class OverridePathBasedReferentialIntegrityForDeletesInterceptorTest extends BaseJpaR4Test {
|
||||
|
||||
@Autowired
|
||||
private OverridePathBasedReferentialIntegrityForDeletesInterceptor mySvc;
|
||||
|
||||
@Autowired
|
||||
private CascadingDeleteInterceptor myCascadingDeleteInterceptor;
|
||||
|
||||
@AfterEach
|
||||
public void after() {
|
||||
myInterceptorRegistry.unregisterInterceptor(mySvc);
|
||||
mySvc.clearPaths();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDeleteBlockedIfNoInterceptorInPlace() {
|
||||
Patient patient = new Patient();
|
||||
patient.setId("P");
|
||||
patient.setActive(true);
|
||||
myPatientDao.update(patient);
|
||||
|
||||
AuditEvent audit = new AuditEvent();
|
||||
audit.setId("A");
|
||||
audit.addAgent().getWho().setReference("Patient/P");
|
||||
myAuditEventDao.update(audit);
|
||||
|
||||
try {
|
||||
myPatientDao.delete(new IdType("Patient/P"));
|
||||
fail();
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
// good
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testAllowDelete() {
|
||||
mySvc.addPath("AuditEvent.agent.who");
|
||||
myInterceptorRegistry.registerInterceptor(mySvc);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("P");
|
||||
patient.setActive(true);
|
||||
myPatientDao.update(patient);
|
||||
|
||||
AuditEvent audit = new AuditEvent();
|
||||
audit.setId("A");
|
||||
audit.addAgent().getWho().setReference("Patient/P");
|
||||
myAuditEventDao.update(audit);
|
||||
|
||||
// Delete should proceed
|
||||
myPatientDao.delete(new IdType("Patient/P"));
|
||||
|
||||
// Make sure we're deleted
|
||||
try {
|
||||
myPatientDao.read(new IdType("Patient/P"));
|
||||
fail();
|
||||
} catch (ResourceGoneException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
// Search should still work
|
||||
IBundleProvider searchOutcome = myAuditEventDao.search(SearchParameterMap.newSynchronous(AuditEvent.SP_AGENT, new ReferenceParam("Patient/P")));
|
||||
assertEquals(1, searchOutcome.size());
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testWrongPath() {
|
||||
mySvc.addPath("AuditEvent.identifier");
|
||||
mySvc.addPath("Patient.agent.who");
|
||||
myInterceptorRegistry.registerInterceptor(mySvc);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("P");
|
||||
patient.setActive(true);
|
||||
myPatientDao.update(patient);
|
||||
|
||||
AuditEvent audit = new AuditEvent();
|
||||
audit.setId("A");
|
||||
audit.addAgent().getWho().setReference("Patient/P");
|
||||
myAuditEventDao.update(audit);
|
||||
|
||||
// Delete should proceed
|
||||
try {
|
||||
myPatientDao.delete(new IdType("Patient/P"));
|
||||
fail();
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
// good
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCombineWithCascadeDeleteInterceptor() {
|
||||
try {
|
||||
myInterceptorRegistry.registerInterceptor(myCascadingDeleteInterceptor);
|
||||
|
||||
mySvc.addPath("AuditEvent.agent.who");
|
||||
myInterceptorRegistry.registerInterceptor(mySvc);
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("P");
|
||||
patient.setActive(true);
|
||||
myPatientDao.update(patient);
|
||||
|
||||
AuditEvent audit = new AuditEvent();
|
||||
audit.setId("A");
|
||||
audit.addAgent().getWho().setReference("Patient/P");
|
||||
myAuditEventDao.update(audit);
|
||||
|
||||
// Delete should proceed
|
||||
myPatientDao.delete(new IdType("Patient/P"));
|
||||
|
||||
} finally {
|
||||
myInterceptorRegistry.unregisterInterceptor(myCascadingDeleteInterceptor);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -35,6 +35,12 @@ import java.util.HashSet;
|
|||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.dstu2.resource.SearchParameter;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.XPathUsageTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.gclient.NumberClientParam;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||
|
@ -168,6 +174,52 @@ public class ResourceProviderDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* See #2023
|
||||
*/
|
||||
@Test
|
||||
public void testCustomNumberSearchParam() {
|
||||
SearchParameter numberParameter = new SearchParameter();
|
||||
numberParameter.setId("future-appointment-count");
|
||||
numberParameter.setName("Future Appointment Count");
|
||||
numberParameter.setCode("future-appointment-count");
|
||||
numberParameter.setDescription("Count of future appointments for the patient");
|
||||
numberParameter.setUrl("http://integer");
|
||||
numberParameter.setStatus(ca.uhn.fhir.model.dstu2.valueset.ConformanceResourceStatusEnum.ACTIVE);
|
||||
numberParameter.setBase(ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum.PATIENT);
|
||||
numberParameter.setType(ca.uhn.fhir.model.dstu2.valueset.SearchParamTypeEnum.NUMBER);
|
||||
numberParameter.setXpathUsage(XPathUsageTypeEnum.NORMAL);
|
||||
numberParameter.setXpath("Patient.extension('http://integer')");
|
||||
ourClient.update().resource(numberParameter).execute();
|
||||
|
||||
// This fires every 10 seconds
|
||||
mySearchParamRegistry.refreshCacheIfNecessary();
|
||||
|
||||
Patient patient = new Patient();
|
||||
patient.setId("future-appointment-count-pt");
|
||||
patient.setActive(true);
|
||||
patient.addUndeclaredExtension(false, "http://integer", new IntegerDt(2));
|
||||
ourClient.update().resource(patient).execute();
|
||||
|
||||
Bundle futureAppointmentCountBundle2 = ourClient
|
||||
.search()
|
||||
.forResource(Patient.class)
|
||||
.where(new NumberClientParam("future-appointment-count").greaterThan().number(1))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
assertEquals(futureAppointmentCountBundle2.getTotal().intValue(), 1);
|
||||
|
||||
Bundle futureAppointmentCountBundle3 = ourClient
|
||||
.search()
|
||||
.forResource(Patient.class)
|
||||
.where(new NumberClientParam("future-appointment-count").exactly().number(2))
|
||||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
assertEquals(futureAppointmentCountBundle3.getTotal().intValue(), 1);
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* See #484
|
||||
*/
|
||||
|
|
|
@ -413,6 +413,8 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test {
|
|||
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Stores a binary large enough that it should live in binary storage
|
||||
*/
|
||||
|
@ -469,6 +471,77 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test {
|
|||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testWriteLargeBinaryToDocumentReference() throws IOException {
|
||||
byte[] bytes = new byte[134696];
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
bytes[i] = (byte) (((float)Byte.MAX_VALUE) * Math.random());
|
||||
}
|
||||
|
||||
DocumentReference dr = new DocumentReference();
|
||||
dr.addContent().getAttachment()
|
||||
.setContentType("application/pdf")
|
||||
.setSize(12345)
|
||||
.setTitle("hello")
|
||||
.setCreationElement(new DateTimeType("2002"));
|
||||
IIdType id = myClient.create().resource(dr).execute().getId().toUnqualifiedVersionless();
|
||||
|
||||
IAnonymousInterceptor interceptor = mock(IAnonymousInterceptor.class);
|
||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESHOW_RESOURCES, interceptor);
|
||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_CREATED, interceptor);
|
||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED, interceptor);
|
||||
|
||||
// Write using the operation
|
||||
|
||||
String path = ourServerBase +
|
||||
"/DocumentReference/" + id.getIdPart() + "/" +
|
||||
JpaConstants.OPERATION_BINARY_ACCESS_WRITE +
|
||||
"?path=DocumentReference.content.attachment";
|
||||
HttpPost post = new HttpPost(path);
|
||||
post.setEntity(new ByteArrayEntity(bytes, ContentType.IMAGE_JPEG));
|
||||
post.addHeader("Accept", "application/fhir+json; _pretty=true");
|
||||
String attachmentId;
|
||||
try (CloseableHttpResponse resp = ourHttpClient.execute(post)) {
|
||||
assertEquals(200, resp.getStatusLine().getStatusCode());
|
||||
assertThat(resp.getEntity().getContentType().getValue(), containsString("application/fhir+json"));
|
||||
|
||||
String response = IOUtils.toString(resp.getEntity().getContent(), Constants.CHARSET_UTF8);
|
||||
ourLog.info("Response: {}", response);
|
||||
|
||||
DocumentReference target = myFhirCtx.newJsonParser().parseResource(DocumentReference.class, response);
|
||||
|
||||
assertEquals(null, target.getContentFirstRep().getAttachment().getData());
|
||||
assertEquals("2", target.getMeta().getVersionId());
|
||||
attachmentId = target.getContentFirstRep().getAttachment().getDataElement().getExtensionString(HapiExtensions.EXT_EXTERNALIZED_BINARY_ID);
|
||||
assertThat(attachmentId, matchesPattern("[a-zA-Z0-9]{100}"));
|
||||
|
||||
}
|
||||
|
||||
verify(interceptor, timeout(5000).times(1)).invoke(eq(Pointcut.STORAGE_PRESHOW_RESOURCES), any());
|
||||
verify(interceptor, timeout(5000).times(1)).invoke(eq(Pointcut.STORAGE_PRESTORAGE_RESOURCE_UPDATED), any());
|
||||
verifyNoMoreInteractions(interceptor);
|
||||
|
||||
// Read it back using the operation
|
||||
|
||||
path = ourServerBase +
|
||||
"/DocumentReference/" + id.getIdPart() + "/" +
|
||||
JpaConstants.OPERATION_BINARY_ACCESS_READ +
|
||||
"?path=DocumentReference.content.attachment";
|
||||
HttpGet get = new HttpGet(path);
|
||||
try (CloseableHttpResponse resp = ourHttpClient.execute(get)) {
|
||||
|
||||
assertEquals(200, resp.getStatusLine().getStatusCode());
|
||||
assertEquals("image/jpeg", resp.getEntity().getContentType().getValue());
|
||||
assertEquals(bytes.length, resp.getEntity().getContentLength());
|
||||
|
||||
byte[] actualBytes = IOUtils.toByteArray(resp.getEntity().getContent());
|
||||
assertArrayEquals(bytes, actualBytes);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private IIdType createDocumentReference(boolean theSetData) {
|
||||
DocumentReference documentReference = new DocumentReference();
|
||||
Attachment attachment = documentReference
|
||||
|
|
|
@ -8,11 +8,12 @@ import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
|||
import ca.uhn.fhir.jpa.search.PersistedJpaSearchFirstPageBundleProvider;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.util.HapiExtensions;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.BooleanType;
|
||||
|
@ -23,7 +24,6 @@ import org.hl7.fhir.r4.model.Observation;
|
|||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.r4.model.SearchParameter;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
|
@ -36,7 +36,9 @@ import static org.awaitility.Awaitility.await;
|
|||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.empty;
|
||||
import static org.hamcrest.Matchers.not;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.fail;
|
||||
|
||||
public class ExpungeR4Test extends BaseResourceProviderR4Test {
|
||||
|
||||
|
@ -410,16 +412,16 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
|
|||
public void testExpungeEverythingWhereResourceInSearchResults() {
|
||||
createStandardPatients();
|
||||
|
||||
await().until(()-> runInTransaction(() -> mySearchEntityDao.count() == 0));
|
||||
await().until(()-> runInTransaction(() -> mySearchResultDao.count() == 0));
|
||||
await().until(() -> runInTransaction(() -> mySearchEntityDao.count() == 0));
|
||||
await().until(() -> runInTransaction(() -> mySearchResultDao.count() == 0));
|
||||
|
||||
PersistedJpaSearchFirstPageBundleProvider search = (PersistedJpaSearchFirstPageBundleProvider) myPatientDao.search(new SearchParameterMap());
|
||||
assertEquals(PersistedJpaSearchFirstPageBundleProvider.class, search.getClass());
|
||||
assertEquals(2, search.size().intValue());
|
||||
assertEquals(2, search.getResources(0, 2).size());
|
||||
|
||||
await().until(()-> runInTransaction(() -> mySearchEntityDao.count() == 1));
|
||||
await().until(()-> runInTransaction(() -> mySearchResultDao.count() == 2));
|
||||
await().until(() -> runInTransaction(() -> mySearchEntityDao.count() == 1));
|
||||
await().until(() -> runInTransaction(() -> mySearchResultDao.count() == 2));
|
||||
|
||||
mySystemDao.expunge(new ExpungeOptions()
|
||||
.setExpungeEverything(true), null);
|
||||
|
@ -465,4 +467,87 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
|
|||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testExpungeForcedIdAndThenReuseIt() {
|
||||
// Create with forced ID, and an Observation that links to it
|
||||
Patient p = new Patient();
|
||||
p.setId("TEST");
|
||||
p.setActive(true);
|
||||
p.addName().setFamily("FOO");
|
||||
myPatientDao.update(p);
|
||||
|
||||
Observation obs = new Observation();
|
||||
obs.setId("OBS");
|
||||
obs.getSubject().setReference("Patient/TEST");
|
||||
myObservationDao.update(obs);
|
||||
|
||||
// Make sure read works
|
||||
p = myPatientDao.read(new IdType("Patient/TEST"));
|
||||
assertTrue(p.getActive());
|
||||
|
||||
// Make sure search by ID works
|
||||
IBundleProvider outcome = myPatientDao.search(SearchParameterMap.newSynchronous("_id", new TokenParam("Patient/TEST")));
|
||||
p = (Patient) outcome.getResources(0, 1).get(0);
|
||||
assertTrue(p.getActive());
|
||||
|
||||
// Make sure search by Reference works
|
||||
outcome = myObservationDao.search(SearchParameterMap.newSynchronous(Observation.SP_SUBJECT, new ReferenceParam("Patient/TEST")));
|
||||
obs = (Observation) outcome.getResources(0, 1).get(0);
|
||||
assertEquals("OBS", obs.getIdElement().getIdPart());
|
||||
|
||||
// Delete and expunge
|
||||
myObservationDao.delete(new IdType("Observation/OBS"));
|
||||
myPatientDao.delete(new IdType("Patient/TEST"));
|
||||
myPatientDao.expunge(new ExpungeOptions()
|
||||
.setExpungeDeletedResources(true)
|
||||
.setExpungeOldVersions(true), null);
|
||||
myObservationDao.expunge(new ExpungeOptions()
|
||||
.setExpungeDeletedResources(true)
|
||||
.setExpungeOldVersions(true), null);
|
||||
runInTransaction(() -> assertThat(myResourceTableDao.findAll(), empty()));
|
||||
runInTransaction(() -> assertThat(myResourceHistoryTableDao.findAll(), empty()));
|
||||
runInTransaction(() -> assertThat(myForcedIdDao.findAll(), empty()));
|
||||
|
||||
// Create again with the same forced ID
|
||||
p = new Patient();
|
||||
p.setId("TEST");
|
||||
p.setActive(true);
|
||||
p.addName().setFamily("FOO");
|
||||
myPatientDao.update(p);
|
||||
|
||||
obs = new Observation();
|
||||
obs.setId("OBS");
|
||||
obs.getSubject().setReference("Patient/TEST");
|
||||
myObservationDao.update(obs);
|
||||
|
||||
// Make sure read works
|
||||
p = myPatientDao.read(new IdType("Patient/TEST"));
|
||||
assertTrue(p.getActive());
|
||||
|
||||
// Make sure search works
|
||||
outcome = myPatientDao.search(SearchParameterMap.newSynchronous("_id", new TokenParam("Patient/TEST")));
|
||||
p = (Patient) outcome.getResources(0, 1).get(0);
|
||||
assertTrue(p.getActive());
|
||||
|
||||
// Make sure search by Reference works
|
||||
outcome = myObservationDao.search(SearchParameterMap.newSynchronous(Observation.SP_SUBJECT, new ReferenceParam("Patient/TEST")));
|
||||
obs = (Observation) outcome.getResources(0, 1).get(0);
|
||||
assertEquals("OBS", obs.getIdElement().getIdPart());
|
||||
|
||||
// Delete and expunge
|
||||
myObservationDao.delete(new IdType("Observation/OBS"));
|
||||
myPatientDao.delete(new IdType("Patient/TEST"));
|
||||
myPatientDao.expunge(new ExpungeOptions()
|
||||
.setExpungeDeletedResources(true)
|
||||
.setExpungeOldVersions(true), null);
|
||||
myObservationDao.expunge(new ExpungeOptions()
|
||||
.setExpungeDeletedResources(true)
|
||||
.setExpungeOldVersions(true), null);
|
||||
runInTransaction(() -> assertThat(myResourceTableDao.findAll(), empty()));
|
||||
runInTransaction(() -> assertThat(myResourceHistoryTableDao.findAll(), empty()));
|
||||
runInTransaction(() -> assertThat(myForcedIdDao.findAll(), empty()));
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,13 +1,25 @@
|
|||
package ca.uhn.fhir.jpa.provider.r4;
|
||||
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.api.config.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.api.dao.IDao;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.model.util.JpaConstants;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.BooleanType;
|
||||
import org.hl7.fhir.r4.model.Parameters;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
@ -15,16 +27,24 @@ import static org.junit.jupiter.api.Assertions.assertNotNull;
|
|||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(HookInterceptorR4Test.class);
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(HookInterceptorR4Test.class);
|
||||
@Autowired
|
||||
IdHelperService myIdHelperService;
|
||||
|
||||
// @Override
|
||||
// @AfterEach
|
||||
// public void after( ) throws Exception {
|
||||
// super.after();
|
||||
//
|
||||
// myInterceptorRegistry.unregisterAllInterceptors();
|
||||
// }
|
||||
@BeforeEach
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
myDaoConfig.setExpungeEnabled(true);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void after() throws Exception {
|
||||
myDaoConfig.setExpungeEnabled(new DaoConfig().isExpungeEnabled());
|
||||
|
||||
super.after();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testOP_PRESTORAGE_RESOURCE_CREATED_ModifyResource() {
|
||||
|
@ -69,7 +89,7 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
|
|||
AtomicLong pid = new AtomicLong();
|
||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, (thePointcut, t) -> {
|
||||
IAnyResource resource = (IAnyResource) t.get(IBaseResource.class, 0);
|
||||
Long resourcePid = (Long) resource.getUserData("RESOURCE_PID");
|
||||
Long resourcePid = (Long) resource.getUserData(IDao.RESOURCE_PID_KEY);
|
||||
assertNotNull(resourcePid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||
pid.set(resourcePid);
|
||||
});
|
||||
|
@ -77,6 +97,72 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
|
|||
assertTrue(pid.get() > 0);
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSTORAGE_PRECOMMIT_RESOURCE_CREATED_hasCorrectPid() {
|
||||
AtomicLong pid = new AtomicLong();
|
||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, (thePointcut, t) -> {
|
||||
IAnyResource resource = (IAnyResource) t.get(IBaseResource.class, 0);
|
||||
Long resourcePid = (Long) resource.getUserData(IDao.RESOURCE_PID_KEY);
|
||||
assertNotNull(resourcePid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||
pid.set(resourcePid);
|
||||
});
|
||||
IIdType savedPatientId = myClient.create().resource(new Patient()).execute().getId();
|
||||
Long savedPatientPid = myIdHelperService.resolveResourcePersistentIdsWithCache(null, Collections.singletonList(savedPatientId)).get(0).getIdAsLong();
|
||||
assertEquals(savedPatientPid.longValue(), pid.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSTORAGE_PRESTORAGE_EXPUNGE_RESOURCE_hasCorrectPid() {
|
||||
AtomicLong pid = new AtomicLong();
|
||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRESTORAGE_EXPUNGE_RESOURCE, (thePointcut, t) -> {
|
||||
IAnyResource resource = (IAnyResource) t.get(IBaseResource.class, 0);
|
||||
Long resourcePid = (Long) resource.getUserData(IDao.RESOURCE_PID_KEY);
|
||||
assertNotNull(resourcePid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||
pid.set(resourcePid);
|
||||
});
|
||||
IIdType savedPatientId = myClient.create().resource(new Patient()).execute().getId();
|
||||
Long savedPatientPid = myIdHelperService.resolveResourcePersistentIdsWithCache(null, Collections.singletonList(savedPatientId)).get(0).getIdAsLong();
|
||||
|
||||
myClient.delete().resourceById(savedPatientId).execute();
|
||||
Parameters parameters = new Parameters();
|
||||
|
||||
parameters.addParameter().setName(JpaConstants.OPERATION_EXPUNGE_PARAM_EXPUNGE_DELETED_RESOURCES).setValue(new BooleanType(true));
|
||||
myClient
|
||||
.operation()
|
||||
.onInstance(savedPatientId)
|
||||
.named(JpaConstants.OPERATION_EXPUNGE)
|
||||
.withParameters(parameters)
|
||||
.execute();
|
||||
|
||||
assertEquals(savedPatientPid.longValue(), pid.get());
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSTORAGE_PRECOMMIT_RESOURCE_UPDATED_hasCorrectPid() {
|
||||
AtomicLong pidOld = new AtomicLong();
|
||||
AtomicLong pidNew = new AtomicLong();
|
||||
Patient patient = new Patient();
|
||||
IIdType savedPatientId = myClient.create().resource(patient).execute().getId();
|
||||
patient.setId(savedPatientId);
|
||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, (thePointcut, t) -> {
|
||||
IAnyResource resourceOld = (IAnyResource) t.get(IBaseResource.class, 0);
|
||||
IAnyResource resourceNew = (IAnyResource) t.get(IBaseResource.class, 1);
|
||||
Long resourceOldPid = (Long) resourceOld.getUserData(IDao.RESOURCE_PID_KEY);
|
||||
Long resourceNewPid = (Long) resourceNew.getUserData(IDao.RESOURCE_PID_KEY);
|
||||
assertNotNull(resourceOldPid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||
assertNotNull(resourceNewPid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||
pidOld.set(resourceOldPid);
|
||||
pidNew.set(resourceNewPid);
|
||||
});
|
||||
patient.setActive(true);
|
||||
myClient.update().resource(patient).execute();
|
||||
Long savedPatientPid = myIdHelperService.resolveResourcePersistentIdsWithCache(null, Collections.singletonList(savedPatientId)).get(0).getIdAsLong();
|
||||
assertEquals(savedPatientPid.longValue(), pidOld.get());
|
||||
assertEquals(savedPatientPid.longValue(), pidNew.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSTORAGE_PRECOMMIT_RESOURCE_UPDATED_hasPid() {
|
||||
AtomicLong oldPid = new AtomicLong();
|
||||
|
@ -84,12 +170,12 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
|
|||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, (thePointcut, t) -> {
|
||||
|
||||
IAnyResource oldResource = (IAnyResource) t.get(IBaseResource.class, 0);
|
||||
Long oldResourcePid = (Long) oldResource.getUserData("RESOURCE_PID");
|
||||
Long oldResourcePid = (Long) oldResource.getUserData(IDao.RESOURCE_PID_KEY);
|
||||
assertNotNull(oldResourcePid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||
oldPid.set(oldResourcePid);
|
||||
|
||||
IAnyResource newResource = (IAnyResource) t.get(IBaseResource.class, 1);
|
||||
Long newResourcePid = (Long) newResource.getUserData("RESOURCE_PID");
|
||||
Long newResourcePid = (Long) newResource.getUserData(IDao.RESOURCE_PID_KEY);
|
||||
assertNotNull(newResourcePid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||
newPid.set(newResourcePid);
|
||||
});
|
||||
|
|
|
@ -2359,7 +2359,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
|||
String respString = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
|
||||
ourLog.info(respString);
|
||||
assertEquals(412, resp.getStatusLine().getStatusCode());
|
||||
assertThat(respString, containsString("Profile reference \\\"http://foo/structuredefinition/myprofile\\\" could not be resolved, so has not been checked"));
|
||||
assertThat(respString, containsString("Profile reference 'http://foo/structuredefinition/myprofile' could not be resolved, so has not been checked"));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -55,13 +55,13 @@
|
|||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-test-utilities</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-jpaserver-test-utilities</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
|
|
@ -1,5 +1,25 @@
|
|||
package ca.uhn.fhir.jpa.empi.svc;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server - Enterprise Master Patient Index
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2020 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||
import ca.uhn.fhir.empi.log.Logs;
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -24,8 +24,10 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
|
||||
import ca.uhn.fhir.interceptor.api.Hook;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.interceptor.api.Interceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.sched.HapiJob;
|
||||
|
@ -38,8 +40,6 @@ import ca.uhn.fhir.jpa.searchparam.retry.Retrier;
|
|||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.DatatypeUtil;
|
||||
import ca.uhn.fhir.util.HapiExtensions;
|
||||
import ca.uhn.fhir.util.SearchParameterUtil;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -51,6 +51,7 @@ import org.slf4j.LoggerFactory;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -89,7 +90,8 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||
private volatile long myLastRefresh;
|
||||
|
||||
@Autowired
|
||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
private IInterceptorService myInterceptorBroadcaster;
|
||||
private RefreshSearchParameterCacheOnUpdate myInterceptor;
|
||||
|
||||
@Override
|
||||
public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
|
||||
|
@ -236,8 +238,16 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||
}
|
||||
|
||||
@PostConstruct
|
||||
public void postConstruct() {
|
||||
public void start() {
|
||||
myBuiltInSearchParams = createBuiltInSearchParamMap(myFhirContext);
|
||||
|
||||
myInterceptor = new RefreshSearchParameterCacheOnUpdate();
|
||||
myInterceptorBroadcaster.registerInterceptor(myInterceptor);
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void stop() {
|
||||
myInterceptorBroadcaster.unregisterInterceptor(myInterceptor);
|
||||
}
|
||||
|
||||
public int doRefresh(long theRefreshInterval) {
|
||||
|
@ -376,16 +386,6 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||
mySchedulerService.scheduleLocalJob(10 * DateUtils.MILLIS_PER_SECOND, jobDetail);
|
||||
}
|
||||
|
||||
public static class Job implements HapiJob {
|
||||
@Autowired
|
||||
private ISearchParamRegistry myTarget;
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext theContext) {
|
||||
myTarget.refreshCacheIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean refreshCacheIfNecessary() {
|
||||
if (myActiveSearchParams == null || System.currentTimeMillis() - REFRESH_INTERVAL > myLastRefresh) {
|
||||
|
@ -402,30 +402,12 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||
return Collections.unmodifiableMap(myActiveSearchParams);
|
||||
}
|
||||
|
||||
public static Map<String, Map<String, RuntimeSearchParam>> createBuiltInSearchParamMap(FhirContext theFhirContext) {
|
||||
Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<>();
|
||||
|
||||
Set<String> resourceNames = theFhirContext.getResourceTypes();
|
||||
|
||||
for (String resourceName : resourceNames) {
|
||||
RuntimeResourceDefinition nextResDef = theFhirContext.getResourceDefinition(resourceName);
|
||||
String nextResourceName = nextResDef.getName();
|
||||
HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<>();
|
||||
resourceNameToSearchParams.put(nextResourceName, nameToParam);
|
||||
|
||||
for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) {
|
||||
nameToParam.put(nextSp.getName(), nextSp);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(resourceNameToSearchParams);
|
||||
}
|
||||
|
||||
/**
|
||||
* All SearchParameters with the name "phonetic" encode the normalized index value using this phonetic encoder.
|
||||
*
|
||||
* @since 5.1.0
|
||||
*/
|
||||
|
||||
@Override
|
||||
public void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) {
|
||||
myPhoneticEncoder = thePhoneticEncoder;
|
||||
|
||||
|
@ -446,4 +428,58 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||
searchParam.setPhoneticEncoder(myPhoneticEncoder);
|
||||
}
|
||||
}
|
||||
|
||||
@Interceptor
|
||||
public class RefreshSearchParameterCacheOnUpdate {
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED)
|
||||
public void created(IBaseResource theResource) {
|
||||
handle(theResource);
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_DELETED)
|
||||
public void deleted(IBaseResource theResource) {
|
||||
handle(theResource);
|
||||
}
|
||||
|
||||
@Hook(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED)
|
||||
public void updated(IBaseResource theResource) {
|
||||
handle(theResource);
|
||||
}
|
||||
|
||||
private void handle(IBaseResource theResource) {
|
||||
if (theResource != null && myFhirContext.getResourceType(theResource).equals("SearchParameter")) {
|
||||
requestRefresh();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public static class Job implements HapiJob {
|
||||
@Autowired
|
||||
private ISearchParamRegistry myTarget;
|
||||
|
||||
@Override
|
||||
public void execute(JobExecutionContext theContext) {
|
||||
myTarget.refreshCacheIfNecessary();
|
||||
}
|
||||
}
|
||||
|
||||
public static Map<String, Map<String, RuntimeSearchParam>> createBuiltInSearchParamMap(FhirContext theFhirContext) {
|
||||
Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<>();
|
||||
|
||||
Set<String> resourceNames = theFhirContext.getResourceTypes();
|
||||
|
||||
for (String resourceName : resourceNames) {
|
||||
RuntimeResourceDefinition nextResDef = theFhirContext.getResourceDefinition(resourceName);
|
||||
String nextResourceName = nextResDef.getName();
|
||||
HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<>();
|
||||
resourceNameToSearchParams.put(nextResourceName, nameToParam);
|
||||
|
||||
for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) {
|
||||
nameToParam.put(nextSp.getName(), nextSp);
|
||||
}
|
||||
}
|
||||
return Collections.unmodifiableMap(resourceNameToSearchParams);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,6 +96,7 @@ public class SearchParameterCanonicalizer {
|
|||
String path = theNextSp.getXpath();
|
||||
RestSearchParameterTypeEnum paramType = null;
|
||||
RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null;
|
||||
if (theNextSp.getTypeElement().getValueAsEnum() != null) {
|
||||
switch (theNextSp.getTypeElement().getValueAsEnum()) {
|
||||
case COMPOSITE:
|
||||
paramType = RestSearchParameterTypeEnum.COMPOSITE;
|
||||
|
@ -122,6 +123,7 @@ public class SearchParameterCanonicalizer {
|
|||
paramType = RestSearchParameterTypeEnum.URI;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (theNextSp.getStatus() != null) {
|
||||
switch (theNextSp.getStatusElement().getValueAsEnum()) {
|
||||
case ACTIVE:
|
||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.searchparam.registry;
|
|||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
||||
import ca.uhn.fhir.interceptor.api.IInterceptorService;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
||||
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
||||
|
@ -46,7 +47,7 @@ public class SearchParamRegistryImplTest {
|
|||
@MockBean
|
||||
private ModelConfig myModelConfig;
|
||||
@MockBean
|
||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||
private IInterceptorService myInterceptorBroadcaster;
|
||||
|
||||
@Configuration
|
||||
static class SpringConfig {
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
@ -157,7 +157,7 @@
|
|||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-converter</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
|
|
@ -7,7 +7,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-sample-server-jpa</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-spring-boot</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.interceptor.api.HookParams;
|
||||
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
|
||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
||||
|
@ -29,11 +31,13 @@ import org.hl7.fhir.dstu3.model.OperationOutcome.IssueType;
|
|||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.jupiter.api.AfterAll;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeAll;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -44,11 +48,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||
public class ServerExceptionDstu3Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerExceptionDstu3Test.class);
|
||||
public static BaseServerResponseException ourException;
|
||||
public static Exception ourException;
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static RestfulServer ourServlet;
|
||||
|
||||
@AfterEach
|
||||
public void after() {
|
||||
ourException = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddHeadersNotFound() throws Exception {
|
||||
|
@ -56,8 +66,8 @@ public class ServerExceptionDstu3Test {
|
|||
OperationOutcome operationOutcome = new OperationOutcome();
|
||||
operationOutcome.addIssue().setCode(IssueType.BUSINESSRULE);
|
||||
|
||||
ourException = new ResourceNotFoundException("SOME MESSAGE");
|
||||
ourException.addResponseHeader("X-Foo", "BAR BAR");
|
||||
ourException = new ResourceNotFoundException("SOME MESSAGE")
|
||||
.addResponseHeader("X-Foo", "BAR BAR");
|
||||
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
||||
|
@ -99,6 +109,65 @@ public class ServerExceptionDstu3Test {
|
|||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodThrowsNonHapiUncheckedExceptionHandledCleanly() throws Exception {
|
||||
|
||||
ourException = new NullPointerException("Hello");
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=json");
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
assertEquals(500, status.getStatusLine().getStatusCode());
|
||||
byte[] responseContentBytes = IOUtils.toByteArray(status.getEntity().getContent());
|
||||
String responseContent = new String(responseContentBytes, Charsets.UTF_8);
|
||||
ourLog.info(status.getStatusLine().toString());
|
||||
ourLog.info(responseContent);
|
||||
assertThat(responseContent, containsString("\"diagnostics\":\"Failed to call access method: java.lang.NullPointerException: Hello\""));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMethodThrowsNonHapiCheckedExceptionHandledCleanly() throws Exception {
|
||||
|
||||
ourException = new IOException("Hello");
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=json");
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
assertEquals(500, status.getStatusLine().getStatusCode());
|
||||
byte[] responseContentBytes = IOUtils.toByteArray(status.getEntity().getContent());
|
||||
String responseContent = new String(responseContentBytes, Charsets.UTF_8);
|
||||
ourLog.info(status.getStatusLine().toString());
|
||||
ourLog.info(responseContent);
|
||||
assertThat(responseContent, containsString("\"diagnostics\":\"Failed to call access method: java.io.IOException: Hello\""));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testInterceptorThrowsNonHapiUncheckedExceptionHandledCleanly() throws Exception {
|
||||
|
||||
ourServlet.getInterceptorService().registerAnonymousInterceptor(Pointcut.SERVER_INCOMING_REQUEST_PRE_HANDLED, new IAnonymousInterceptor() {
|
||||
@Override
|
||||
public void invoke(Pointcut thePointcut, HookParams theArgs) {
|
||||
throw new NullPointerException("Hello");
|
||||
}
|
||||
});
|
||||
|
||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_format=json");
|
||||
try (CloseableHttpResponse status = ourClient.execute(httpGet)) {
|
||||
assertEquals(500, status.getStatusLine().getStatusCode());
|
||||
byte[] responseContentBytes = IOUtils.toByteArray(status.getEntity().getContent());
|
||||
String responseContent = new String(responseContentBytes, Charsets.UTF_8);
|
||||
ourLog.info(status.getStatusLine().toString());
|
||||
ourLog.info(responseContent);
|
||||
assertThat(responseContent, containsString("\"diagnostics\":\"Hello\""));
|
||||
}
|
||||
|
||||
ourServlet.getInterceptorService().unregisterAllInterceptors();
|
||||
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testPostWithNoBody() throws IOException {
|
||||
|
||||
|
@ -143,7 +212,10 @@ public class ServerExceptionDstu3Test {
|
|||
}
|
||||
|
||||
@Search()
|
||||
public List<Patient> search() {
|
||||
public List<Patient> search() throws Exception {
|
||||
if (ourException == null) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
throw ourException;
|
||||
}
|
||||
|
||||
|
@ -168,11 +240,11 @@ public class ServerExceptionDstu3Test {
|
|||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||
ourServlet = new RestfulServer(ourCtx);
|
||||
ourServlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||
|
||||
servlet.setResourceProviders(patientProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||
ourServlet.setResourceProviders(patientProvider);
|
||||
ServletHolder servletHolder = new ServletHolder(ourServlet);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
JettyUtil.startServer(ourServer);
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -33,6 +33,7 @@ import org.hl7.fhir.r5.model.ValueSet.ConceptSetComponent;
|
|||
import org.hl7.fhir.r5.terminologies.ValueSetExpander;
|
||||
import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||
import org.hl7.fhir.utilities.TranslationServices;
|
||||
import org.hl7.fhir.utilities.cache.BasePackageCacheManager;
|
||||
import org.hl7.fhir.utilities.cache.NpmPackage;
|
||||
import org.hl7.fhir.utilities.i18n.I18nBase;
|
||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
||||
|
@ -171,6 +172,11 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext
|
|||
return validateCode(theOptions, system, code, display, theVs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theCode, String theDisplay) {
|
||||
IValidationSupport.CodeValidationResult result = myValidationSupport.validateCode(new ValidationSupportContext(myValidationSupport), convertConceptValidationOptions(theOptions), theSystem, theCode, theDisplay, null);
|
||||
|
@ -406,7 +412,17 @@ public final class HapiWorkerContext extends I18nBase implements IWorkerContext
|
|||
}
|
||||
|
||||
@Override
|
||||
public void loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FHIRException {
|
||||
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader) throws FHIRException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadFromPackage(NpmPackage pi, IContextResourceLoader loader, String[] types) throws FHIRException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int loadFromPackageAndDependencies(NpmPackage pi, IContextResourceLoader loader, BasePackageCacheManager pcm) throws FHIRException {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,7 +5,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir</artifactId>
|
||||
<version>5.1.0-SNAPSHOT</version>
|
||||
<version>5.2.0-SNAPSHOT</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
|
|
@ -41,6 +41,7 @@ import java.io.UnsupportedEncodingException;
|
|||
import java.net.URLDecoder;
|
||||
import java.util.*;
|
||||
|
||||
import static ca.uhn.fhir.util.UrlUtil.sanitizeUrlPart;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
|
||||
public class BaseController {
|
||||
|
@ -49,7 +50,7 @@ public class BaseController {
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseController.class);
|
||||
@Autowired
|
||||
protected TesterConfig myConfig;
|
||||
private Map<FhirVersionEnum, FhirContext> myContexts = new HashMap<FhirVersionEnum, FhirContext>();
|
||||
private final Map<FhirVersionEnum, FhirContext> myContexts = new HashMap<FhirVersionEnum, FhirContext>();
|
||||
private List<String> myFilterHeaders;
|
||||
@Autowired
|
||||
private ITemplateEngine myTemplateEngine;
|
||||
|
@ -78,19 +79,6 @@ public class BaseController {
|
|||
return loadAndAddConf(theServletRequest, theRequest, theModel);
|
||||
}
|
||||
|
||||
private Header[] applyHeaderFilters(Header[] theAllHeaders) {
|
||||
if (myFilterHeaders == null || myFilterHeaders.isEmpty()) {
|
||||
return theAllHeaders;
|
||||
}
|
||||
ArrayList<Header> retVal = new ArrayList<Header>();
|
||||
for (Header next : theAllHeaders) {
|
||||
if (!myFilterHeaders.contains(next.getName().toLowerCase())) {
|
||||
retVal.add(next);
|
||||
}
|
||||
}
|
||||
return retVal.toArray(new Header[retVal.size()]);
|
||||
}
|
||||
|
||||
private Header[] applyHeaderFilters(Map<String, List<String>> theAllHeaders) {
|
||||
ArrayList<Header> retVal = new ArrayList<Header>();
|
||||
for (String nextKey : theAllHeaders.keySet()) {
|
||||
|
@ -274,7 +262,7 @@ public class BaseController {
|
|||
}
|
||||
|
||||
protected RuntimeResourceDefinition getResourceType(HomeRequest theRequest, HttpServletRequest theReq) throws ServletException {
|
||||
String resourceName = StringUtils.defaultString(theReq.getParameter(PARAM_RESOURCE));
|
||||
String resourceName = sanitizeUrlPart(defaultString(theReq.getParameter(PARAM_RESOURCE)));
|
||||
RuntimeResourceDefinition def = getContext(theRequest).getResourceDefinition(resourceName);
|
||||
if (def == null) {
|
||||
throw new ServletException("Invalid resourceName: " + resourceName);
|
||||
|
@ -317,7 +305,7 @@ public class BaseController {
|
|||
|
||||
ca.uhn.fhir.model.dstu2.resource.Conformance conformance;
|
||||
try {
|
||||
conformance = (ca.uhn.fhir.model.dstu2.resource.Conformance) client.fetchConformance().ofType(Conformance.class).execute();
|
||||
conformance = client.fetchConformance().ofType(Conformance.class).execute();
|
||||
} catch (Exception e) {
|
||||
ourLog.warn("Failed to load conformance statement, error was: {}", e.toString());
|
||||
theModel.put("errorMsg", toDisplayError("Failed to load conformance statement, error was: " + e.toString(), e));
|
||||
|
@ -326,7 +314,7 @@ public class BaseController {
|
|||
|
||||
theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(conformance));
|
||||
|
||||
Map<String, Number> resourceCounts = new HashMap<String, Number>();
|
||||
Map<String, Number> resourceCounts = new HashMap<>();
|
||||
long total = 0;
|
||||
for (ca.uhn.fhir.model.dstu2.resource.Conformance.Rest nextRest : conformance.getRest()) {
|
||||
for (ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource nextResource : nextRest.getResource()) {
|
||||
|
@ -385,7 +373,7 @@ public class BaseController {
|
|||
|
||||
theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(capabilityStatement));
|
||||
|
||||
Map<String, Number> resourceCounts = new HashMap<String, Number>();
|
||||
Map<String, Number> resourceCounts = new HashMap<>();
|
||||
long total = 0;
|
||||
|
||||
for (CapabilityStatementRestComponent nextRest : capabilityStatement.getRest()) {
|
||||
|
@ -446,7 +434,7 @@ public class BaseController {
|
|||
|
||||
theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(capabilityStatement));
|
||||
|
||||
Map<String, Number> resourceCounts = new HashMap<String, Number>();
|
||||
Map<String, Number> resourceCounts = new HashMap<>();
|
||||
long total = 0;
|
||||
|
||||
for (org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestComponent nextRest : capabilityStatement.getRest()) {
|
||||
|
@ -507,7 +495,7 @@ public class BaseController {
|
|||
|
||||
theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(capabilityStatement));
|
||||
|
||||
Map<String, Number> resourceCounts = new HashMap<String, Number>();
|
||||
Map<String, Number> resourceCounts = new HashMap<>();
|
||||
long total = 0;
|
||||
|
||||
for (org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent nextRest : capabilityStatement.getRest()) {
|
||||
|
@ -614,25 +602,6 @@ public class BaseController {
|
|||
protected void processAndAddLastClientInvocation(GenericClient theClient, ResultType theResultType, ModelMap theModelMap, long theLatency, String outcomeDescription,
|
||||
CaptureInterceptor theInterceptor, HomeRequest theRequest) {
|
||||
try {
|
||||
// ApacheHttpRequest lastRequest = theInterceptor.getLastRequest();
|
||||
// HttpResponse lastResponse = theInterceptor.getLastResponse();
|
||||
// String requestBody = null;
|
||||
// String requestUrl = lastRequest != null ? lastRequest.getApacheRequest().getURI().toASCIIString() : null;
|
||||
// String action = lastRequest != null ? lastRequest.getApacheRequest().getMethod() : null;
|
||||
// String resultStatus = lastResponse != null ? lastResponse.getStatusLine().toString() : null;
|
||||
// String resultBody = StringUtils.defaultString(theInterceptor.getLastResponseBody());
|
||||
//
|
||||
// if (lastRequest instanceof HttpEntityEnclosingRequest) {
|
||||
// HttpEntity entity = ((HttpEntityEnclosingRequest) lastRequest).getEntity();
|
||||
// if (entity.isRepeatable()) {
|
||||
// requestBody = IOUtils.toString(entity.getContent());
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// ContentType ct = lastResponse != null ? ContentType.get(lastResponse.getEntity()) : null;
|
||||
// String mimeType = ct != null ? ct.getMimeType() : null;
|
||||
|
||||
|
||||
IHttpRequest lastRequest = theInterceptor.getLastRequest();
|
||||
IHttpResponse lastResponse = theInterceptor.getLastResponse();
|
||||
String requestBody = null;
|
||||
|
|
|
@ -26,6 +26,7 @@ import ca.uhn.fhir.rest.gclient.TokenClientParam;
|
|||
import ca.uhn.fhir.to.model.HomeRequest;
|
||||
import ca.uhn.fhir.to.model.ResourceRequest;
|
||||
import ca.uhn.fhir.to.model.TransactionRequest;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import com.google.gson.stream.JsonWriter;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.hl7.fhir.dstu3.model.CapabilityStatement;
|
||||
|
@ -49,6 +50,7 @@ import java.util.Collections;
|
|||
import java.util.List;
|
||||
import java.util.TreeSet;
|
||||
|
||||
import static ca.uhn.fhir.util.UrlUtil.sanitizeUrlPart;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultIfEmpty;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
@ -128,7 +130,7 @@ public class Controller extends BaseController {
|
|||
return "resource";
|
||||
}
|
||||
|
||||
String id = StringUtils.defaultString(theServletRequest.getParameter("resource-delete-id"));
|
||||
String id = sanitizeUrlPart(defaultString(theServletRequest.getParameter("resource-delete-id")));
|
||||
if (StringUtils.isBlank(id)) {
|
||||
populateModelForResource(theServletRequest, theRequest, theModel);
|
||||
theModel.put("errorMsg", toDisplayError("No ID specified", null));
|
||||
|
@ -184,7 +186,7 @@ public class Controller extends BaseController {
|
|||
FhirContext context = getContext(theRequest);
|
||||
GenericClient client = theRequest.newClient(theReq, context, myConfig, interceptor);
|
||||
|
||||
String url = defaultString(theReq.getParameter("page-url"));
|
||||
String url = sanitizeUrlPart(defaultString(theReq.getParameter("page-url")));
|
||||
if (myConfig.isRefuseToFetchThirdPartyUrls()) {
|
||||
if (!url.startsWith(theModel.get("base").toString())) {
|
||||
ourLog.warn(logPrefix(theModel) + "Refusing to load page URL: {}", url);
|
||||
|
@ -230,7 +232,7 @@ public class Controller extends BaseController {
|
|||
theModel.put("errorMsg", toDisplayError(e.toString(), e));
|
||||
return "resource";
|
||||
}
|
||||
String id = StringUtils.defaultString(theServletRequest.getParameter("id"));
|
||||
String id = sanitizeUrlPart(defaultString(theServletRequest.getParameter("id")));
|
||||
if (StringUtils.isBlank(id)) {
|
||||
populateModelForResource(theServletRequest, theRequest, theModel);
|
||||
theModel.put("errorMsg", toDisplayError("No ID specified", null));
|
||||
|
@ -238,7 +240,7 @@ public class Controller extends BaseController {
|
|||
}
|
||||
ResultType returnsResource = ResultType.RESOURCE;
|
||||
|
||||
String versionId = StringUtils.defaultString(theServletRequest.getParameter("vid"));
|
||||
String versionId = sanitizeUrlPart(defaultString(theServletRequest.getParameter("vid")));
|
||||
String outcomeDescription;
|
||||
if (StringUtils.isBlank(versionId)) {
|
||||
versionId = null;
|
||||
|
@ -353,7 +355,7 @@ public class Controller extends BaseController {
|
|||
return "resource";
|
||||
}
|
||||
clientCodeJsonWriter.name("resource");
|
||||
clientCodeJsonWriter.value(theServletRequest.getParameter("resource"));
|
||||
clientCodeJsonWriter.value(sanitizeUrlPart(theServletRequest.getParameter("resource")));
|
||||
} else {
|
||||
query = search.forAllResources();
|
||||
clientCodeJsonWriter.name("resource");
|
||||
|
@ -394,7 +396,7 @@ public class Controller extends BaseController {
|
|||
|
||||
clientCodeJsonWriter.name("includes");
|
||||
clientCodeJsonWriter.beginArray();
|
||||
String[] incValues = theServletRequest.getParameterValues(Constants.PARAM_INCLUDE);
|
||||
String[] incValues = sanitizeUrlPart(theServletRequest.getParameterValues(Constants.PARAM_INCLUDE));
|
||||
if (incValues != null) {
|
||||
for (String next : incValues) {
|
||||
if (isNotBlank(next)) {
|
||||
|
@ -407,7 +409,7 @@ public class Controller extends BaseController {
|
|||
|
||||
clientCodeJsonWriter.name("revincludes");
|
||||
clientCodeJsonWriter.beginArray();
|
||||
String[] revIncValues = theServletRequest.getParameterValues(Constants.PARAM_REVINCLUDE);
|
||||
String[] revIncValues = sanitizeUrlPart(theServletRequest.getParameterValues(Constants.PARAM_REVINCLUDE));
|
||||
if (revIncValues != null) {
|
||||
for (String next : revIncValues) {
|
||||
if (isNotBlank(next)) {
|
||||
|
@ -418,7 +420,7 @@ public class Controller extends BaseController {
|
|||
}
|
||||
clientCodeJsonWriter.endArray();
|
||||
|
||||
String limit = theServletRequest.getParameter("resource-search-limit");
|
||||
String limit = sanitizeUrlPart(theServletRequest.getParameter("resource-search-limit"));
|
||||
if (isNotBlank(limit)) {
|
||||
if (!limit.matches("[0-9]+")) {
|
||||
populateModelForResource(theServletRequest, theRequest, theModel);
|
||||
|
@ -434,13 +436,13 @@ public class Controller extends BaseController {
|
|||
clientCodeJsonWriter.nullValue();
|
||||
}
|
||||
|
||||
String[] sort = theServletRequest.getParameterValues("sort_by");
|
||||
String[] sort = sanitizeUrlPart(theServletRequest.getParameterValues("sort_by"));
|
||||
if (sort != null) {
|
||||
for (String next : sort) {
|
||||
if (isBlank(next)) {
|
||||
continue;
|
||||
}
|
||||
String direction = theServletRequest.getParameter("sort_direction");
|
||||
String direction = sanitizeUrlPart(theServletRequest.getParameter("sort_direction"));
|
||||
if ("asc".equals(direction)) {
|
||||
query.sort().ascending(new StringClientParam(next));
|
||||
} else if ("desc".equals(direction)) {
|
||||
|
@ -545,6 +547,7 @@ public class Controller extends BaseController {
|
|||
type = def.getImplementingClass();
|
||||
}
|
||||
|
||||
// Don't sanitize this param, it's a raw resource body and may well be XML
|
||||
String body = validate ? theReq.getParameter("resource-validate-body") : theReq.getParameter("resource-create-body");
|
||||
if (isBlank(body)) {
|
||||
theModel.put("errorMsg", toDisplayError("No message body specified", null));
|
||||
|
@ -583,7 +586,7 @@ public class Controller extends BaseController {
|
|||
outcomeDescription = "Validate Resource";
|
||||
client.validate().resource(resource).prettyPrint().execute();
|
||||
} else {
|
||||
String id = theReq.getParameter("resource-create-id");
|
||||
String id = sanitizeUrlPart(theReq.getParameter("resource-create-id"));
|
||||
if ("update".equals(theMethod)) {
|
||||
outcomeDescription = "Update Resource";
|
||||
client.update(id, resource);
|
||||
|
@ -626,17 +629,17 @@ public class Controller extends BaseController {
|
|||
if ("history-type".equals(theMethod)) {
|
||||
RuntimeResourceDefinition def = getContext(theRequest).getResourceDefinition(theRequest.getResource());
|
||||
type = def.getImplementingClass();
|
||||
id = StringUtils.defaultString(theReq.getParameter("resource-history-id"));
|
||||
id = sanitizeUrlPart(defaultString(theReq.getParameter("resource-history-id")));
|
||||
}
|
||||
|
||||
DateTimeDt since = null;
|
||||
String sinceStr = theReq.getParameter("since");
|
||||
String sinceStr = sanitizeUrlPart(theReq.getParameter("since"));
|
||||
if (isNotBlank(sinceStr)) {
|
||||
since = new DateTimeDt(sinceStr);
|
||||
}
|
||||
|
||||
Integer limit = null;
|
||||
String limitStr = theReq.getParameter("limit");
|
||||
String limitStr = sanitizeUrlPart(theReq.getParameter("limit"));
|
||||
if (isNotBlank(limitStr)) {
|
||||
limit = Integer.parseInt(limitStr);
|
||||
}
|
||||
|
@ -811,17 +814,17 @@ public class Controller extends BaseController {
|
|||
}
|
||||
|
||||
private boolean handleSearchParam(String paramIdxString, HttpServletRequest theReq, IQuery theQuery, JsonWriter theClientCodeJsonWriter) throws IOException {
|
||||
String nextName = theReq.getParameter("param." + paramIdxString + ".name");
|
||||
String nextName = sanitizeUrlPart(theReq.getParameter("param." + paramIdxString + ".name"));
|
||||
if (isBlank(nextName)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
String nextQualifier = StringUtils.defaultString(theReq.getParameter("param." + paramIdxString + ".qualifier"));
|
||||
String nextType = theReq.getParameter("param." + paramIdxString + ".type");
|
||||
String nextQualifier = sanitizeUrlPart(defaultString(theReq.getParameter("param." + paramIdxString + ".qualifier")));
|
||||
String nextType = sanitizeUrlPart(theReq.getParameter("param." + paramIdxString + ".type"));
|
||||
|
||||
List<String> parts = new ArrayList<String>();
|
||||
for (int i = 0; i < 5; i++) {
|
||||
parts.add(defaultString(theReq.getParameter("param." + paramIdxString + "." + i)));
|
||||
parts.add(sanitizeUrlPart(defaultString(theReq.getParameter("param." + paramIdxString + "." + i))));
|
||||
}
|
||||
|
||||
List<String> values;
|
||||
|
|
|
@ -48,13 +48,13 @@ public class FhirTesterConfig {
|
|||
.addServer()
|
||||
.withId("hapi")
|
||||
.withFhirVersion(FhirVersionEnum.DSTU2)
|
||||
.withBaseUrl("http://fhirtest.uhn.ca/baseDstu2")
|
||||
.withBaseUrl("http://hapi.fhir.org/baseDstu2")
|
||||
.withName("Public HAPI Test Server")
|
||||
.allowsApiKey()
|
||||
.addServer()
|
||||
.withId("home3")
|
||||
.withFhirVersion(FhirVersionEnum.DSTU3)
|
||||
.withBaseUrl("http://fhirtest.uhn.ca/baseDstu3")
|
||||
.withBaseUrl("http://hapi.fhir.org/baseDstu3")
|
||||
.withName("Public HAPI Test Server (STU3)")
|
||||
.addServer()
|
||||
.withId("home")
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue