mirror of
https://github.com/hapifhir/hapi-fhir.git
synced 2025-02-16 18:05:19 +00:00
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
|
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)
|
[![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)
|
[![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)
|
[![License](https://img.shields.io/badge/license-apache%202.0-60C060.svg)](https://hapifhir.io/hapi-fhir/license.html)
|
||||||
|
@ -79,7 +79,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.guava</groupId>
|
<groupId>com.google.guava</groupId>
|
||||||
<artifactId>guava</artifactId>
|
<artifactId>guava</artifactId>
|
||||||
<version>21.0</version>
|
<version>24.1.1</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.google.inject.extensions</groupId>
|
<groupId>com.google.inject.extensions</groupId>
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ import ca.uhn.fhir.rest.param.ParamPrefixEnum;
|
|||||||
*/
|
*/
|
||||||
public class NumberClientParam extends BaseClientParam implements IParam {
|
public class NumberClientParam extends BaseClientParam implements IParam {
|
||||||
|
|
||||||
private String myParamName;
|
private final String myParamName;
|
||||||
|
|
||||||
public NumberClientParam(String theParamName) {
|
public NumberClientParam(String theParamName) {
|
||||||
myParamName = theParamName;
|
myParamName = theParamName;
|
||||||
@ -37,12 +37,12 @@ public class NumberClientParam extends BaseClientParam implements IParam {
|
|||||||
return new IMatches<ICriterion<NumberClientParam>>() {
|
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||||
@Override
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
public ICriterion<NumberClientParam> number(long theNumber) {
|
||||||
return new StringCriterion<NumberClientParam>(getParamName(), Long.toString(theNumber));
|
return new StringCriterion<>(getParamName(), Long.toString(theNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
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>>() {
|
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||||
@Override
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
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
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
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>>() {
|
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||||
@Override
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
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
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
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>>() {
|
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||||
@Override
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
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
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
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>>() {
|
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||||
@Override
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
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
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
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>>() {
|
return new IMatches<ICriterion<NumberClientParam>>() {
|
||||||
@Override
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(long theNumber) {
|
public ICriterion<NumberClientParam> number(long theNumber) {
|
||||||
return new StringCriterion<NumberClientParam>(getParamName(), thePrefix, Long.toString(theNumber));
|
return new StringCriterion<>(getParamName(), thePrefix, Long.toString(theNumber));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ICriterion<NumberClientParam> number(String theNumber) {
|
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
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param theValue
|
* @param theValue
|
||||||
* A string value, e.g. ">5.0"
|
* A string value, e.g. "gt5.0"
|
||||||
*/
|
*/
|
||||||
public NumberParam(String theValue) {
|
public NumberParam(String theValue) {
|
||||||
setValueAsQueryToken(null, null, null, theValue);
|
setValueAsQueryToken(null, null, null, theValue);
|
||||||
|
@ -16,10 +16,16 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLDecoder;
|
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.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
|
* #%L
|
||||||
@ -370,7 +376,7 @@ public class UrlUtil {
|
|||||||
/**
|
/**
|
||||||
* This method specifically HTML-encodes the " and
|
* This method specifically HTML-encodes the " and
|
||||||
* < characters in order to prevent injection attacks.
|
* < characters in order to prevent injection attacks.
|
||||||
*
|
* <p>
|
||||||
* The following characters are escaped:
|
* The following characters are escaped:
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>'</li>
|
* <li>'</li>
|
||||||
@ -379,7 +385,6 @@ public class UrlUtil {
|
|||||||
* <li>></li>
|
* <li>></li>
|
||||||
* <li>\n (newline)</li>
|
* <li>\n (newline)</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
|
||||||
*/
|
*/
|
||||||
public static String sanitizeUrlPart(CharSequence theString) {
|
public static String sanitizeUrlPart(CharSequence theString) {
|
||||||
if (theString == null) {
|
if (theString == null) {
|
||||||
@ -432,6 +437,21 @@ public class UrlUtil {
|
|||||||
return theString.toString();
|
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) {
|
private static Map<String, String[]> toQueryStringMap(HashMap<String, List<String>> map) {
|
||||||
HashMap<String, String[]> retVal = new HashMap<>();
|
HashMap<String, String[]> retVal = new HashMap<>();
|
||||||
for (Entry<String, List<String>> nextEntry : map.entrySet()) {
|
for (Entry<String, List<String>> nextEntry : map.entrySet()) {
|
||||||
|
@ -64,7 +64,8 @@ public enum VersionEnum {
|
|||||||
V5_0_0,
|
V5_0_0,
|
||||||
V5_0_1,
|
V5_0_1,
|
||||||
V5_0_2,
|
V5_0_2,
|
||||||
V5_1_0;
|
V5_1_0,
|
||||||
|
V5_2_0;
|
||||||
|
|
||||||
public static VersionEnum latestVersion() {
|
public static VersionEnum latestVersion() {
|
||||||
VersionEnum[] values = VersionEnum.values();
|
VersionEnum[] values = VersionEnum.values();
|
||||||
|
@ -3,14 +3,14 @@
|
|||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-bom</artifactId>
|
<artifactId>hapi-fhir-bom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<packaging>pom</packaging>
|
<packaging>pom</packaging>
|
||||||
<name>HAPI FHIR BOM</name>
|
<name>HAPI FHIR BOM</name>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-cli</artifactId>
|
<artifactId>hapi-fhir-cli</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../../hapi-deployable-pom</relativePath>
|
<relativePath>../../hapi-deployable-pom</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Qualifier;
|
|||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
@ -106,8 +107,9 @@ public class FhirServerConfig extends BaseJavaConfigDstu2 {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Primary
|
||||||
@Bean
|
@Bean
|
||||||
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
|
public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {
|
||||||
JpaTransactionManager retVal = new JpaTransactionManager();
|
JpaTransactionManager retVal = new JpaTransactionManager();
|
||||||
retVal.setEntityManagerFactory(entityManagerFactory);
|
retVal.setEntityManagerFactory(entityManagerFactory);
|
||||||
return retVal;
|
return retVal;
|
||||||
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.demo;
|
|||||||
* #L%
|
* #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.config.BaseJavaConfigDstu3;
|
||||||
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
import ca.uhn.fhir.jpa.util.SubscriptionsRequireManualActivationInterceptorDstu3;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
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.Bean;
|
||||||
import org.springframework.context.annotation.Configuration;
|
import org.springframework.context.annotation.Configuration;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
|
import org.springframework.context.annotation.Primary;
|
||||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||||
@ -97,10 +100,12 @@ public class FhirServerConfigDstu3 extends BaseJavaConfigDstu3 {
|
|||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Primary
|
||||||
@Bean
|
@Bean
|
||||||
public JpaTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
|
public JpaTransactionManager hapiTransactionManager(EntityManagerFactory entityManagerFactory) {
|
||||||
JpaTransactionManager retVal = new JpaTransactionManager();
|
JpaTransactionManager retVal = new JpaTransactionManager();
|
||||||
retVal.setEntityManagerFactory(entityManagerFactory);
|
retVal.setEntityManagerFactory(entityManagerFactory);
|
||||||
return retVal;
|
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.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
||||||
import ca.uhn.fhir.jpa.api.dao.IFhirSystemDao;
|
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.config.BaseConfig;
|
||||||
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
import ca.uhn.fhir.jpa.interceptor.CascadingDeleteInterceptor;
|
||||||
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
|
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
|
||||||
@ -180,6 +182,8 @@ public class JpaServerDemo extends RestfulServer {
|
|||||||
|
|
||||||
getInterceptorService().registerInterceptor(new ResponseHighlighterInterceptor());
|
getInterceptorService().registerInterceptor(new ResponseHighlighterInterceptor());
|
||||||
|
|
||||||
|
registerProvider(myAppCtx.getBean(BinaryAccessProvider.class));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@ -73,13 +73,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-structures-dstu2</artifactId>
|
<artifactId>hapi-fhir-structures-dstu2</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-jpaserver-subscription</artifactId>
|
<artifactId>hapi-fhir-jpaserver-subscription</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<scope>compile</scope>
|
<scope>compile</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
@ -96,7 +96,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-testpage-overlay</artifactId>
|
<artifactId>hapi-fhir-testpage-overlay</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<classifier>classes</classifier>
|
<classifier>classes</classifier>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
---
|
---
|
||||||
type: fix
|
type: fix
|
||||||
issue: 1856
|
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
|
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."
|
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
|
type: change
|
||||||
issue: 237
|
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)`
|
`getExtensionByUrl(String)`, `removeExtension(String)`, `getExtensionsByUrl(String)`
|
||||||
`hasExtension(String)`, and `getExtensionString(String)` have been enhanced so that they
|
`hasExtension(String)`, and `getExtensionString(String)` have been enhanced so that they
|
||||||
now return modifier extensions as well as the non-modifier extensions they previously
|
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
|
# Getting the Sources
|
||||||
|
|
||||||
<p style="float:right;">
|
<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>
|
</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.
|
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
|
# 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.
|
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
|
# 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.
|
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 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.
|
The following example shows a simple request counter interceptor.
|
||||||
|
|
||||||
```java
|
```java
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
<modelVersion>4.0.0</modelVersion>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
@ -11,7 +11,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -34,8 +34,9 @@ import java.util.Collection;
|
|||||||
* time to time, even within minor point releases.
|
* time to time, even within minor point releases.
|
||||||
*/
|
*/
|
||||||
public interface IDao {
|
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");
|
MetadataKeyCurrentlyReindexing CURRENTLY_REINDEXING = new MetadataKeyCurrentlyReindexing("CURRENTLY_REINDEXING");
|
||||||
|
|
||||||
|
@ -34,6 +34,7 @@ import java.util.function.Predicate;
|
|||||||
public class DeleteConflictList implements Iterable<DeleteConflict> {
|
public class DeleteConflictList implements Iterable<DeleteConflict> {
|
||||||
private final List<DeleteConflict> myList = new ArrayList<>();
|
private final List<DeleteConflict> myList = new ArrayList<>();
|
||||||
private final Set<String> myResourceIdsMarkedForDeletion;
|
private final Set<String> myResourceIdsMarkedForDeletion;
|
||||||
|
private final Set<String> myResourceIdsToIgnoreConflict;
|
||||||
private int myRemoveModCount;
|
private int myRemoveModCount;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -41,6 +42,7 @@ public class DeleteConflictList implements Iterable<DeleteConflict> {
|
|||||||
*/
|
*/
|
||||||
public DeleteConflictList() {
|
public DeleteConflictList() {
|
||||||
myResourceIdsMarkedForDeletion = new HashSet<>();
|
myResourceIdsMarkedForDeletion = new HashSet<>();
|
||||||
|
myResourceIdsToIgnoreConflict = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -49,6 +51,7 @@ public class DeleteConflictList implements Iterable<DeleteConflict> {
|
|||||||
*/
|
*/
|
||||||
public DeleteConflictList(DeleteConflictList theParentList) {
|
public DeleteConflictList(DeleteConflictList theParentList) {
|
||||||
myResourceIdsMarkedForDeletion = theParentList.myResourceIdsMarkedForDeletion;
|
myResourceIdsMarkedForDeletion = theParentList.myResourceIdsMarkedForDeletion;
|
||||||
|
myResourceIdsToIgnoreConflict = theParentList.myResourceIdsToIgnoreConflict;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -64,6 +67,18 @@ public class DeleteConflictList implements Iterable<DeleteConflict> {
|
|||||||
myResourceIdsMarkedForDeletion.add(theIdType.toUnqualifiedVersionless().getValue());
|
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) {
|
public void add(DeleteConflict theDeleteConflict) {
|
||||||
myList.add(theDeleteConflict);
|
myList.add(theDeleteConflict);
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.config;
|
|||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
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.api.IInterceptorService;
|
||||||
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
import ca.uhn.fhir.interceptor.executor.InterceptorService;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
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.dao.tx.HapiTransactionService;
|
||||||
import ca.uhn.fhir.jpa.entity.Search;
|
import ca.uhn.fhir.jpa.entity.Search;
|
||||||
import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
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.JpaConsentContextServices;
|
||||||
|
import ca.uhn.fhir.jpa.interceptor.OverridePathBasedReferentialIntegrityForDeletesInterceptor;
|
||||||
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
||||||
import ca.uhn.fhir.jpa.packages.IHapiPackageCacheManager;
|
import ca.uhn.fhir.jpa.packages.IHapiPackageCacheManager;
|
||||||
import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc;
|
import ca.uhn.fhir.jpa.packages.IPackageInstallerSvc;
|
||||||
@ -167,6 +170,12 @@ public abstract class BaseConfig {
|
|||||||
return new BatchJobSubmitterImpl();
|
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
|
* This method should be overridden to provide an actual completed
|
||||||
* bean, but it provides a partially completed entity manager
|
* bean, but it provides a partially completed entity manager
|
||||||
@ -295,6 +304,12 @@ public abstract class BaseConfig {
|
|||||||
return new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer());
|
return new HapiFhirHibernateJpaDialect(fhirContext().getLocalizer());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
@Lazy
|
||||||
|
public OverridePathBasedReferentialIntegrityForDeletesInterceptor overridePathBasedReferentialIntegrityForDeletesInterceptor() {
|
||||||
|
return new OverridePathBasedReferentialIntegrityForDeletesInterceptor();
|
||||||
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public IRequestPartitionHelperSvc requestPartitionHelperService() {
|
public IRequestPartitionHelperSvc requestPartitionHelperService() {
|
||||||
return new RequestPartitionHelperSvc();
|
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.api.server.storage.TransactionDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
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.exceptions.UnprocessableEntityException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
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.context.ApplicationContextAware;
|
||||||
import org.springframework.stereotype.Repository;
|
import org.springframework.stereotype.Repository;
|
||||||
import org.springframework.transaction.PlatformTransactionManager;
|
import org.springframework.transaction.PlatformTransactionManager;
|
||||||
import org.springframework.transaction.support.TransactionCallback;
|
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||||
import org.springframework.transaction.support.TransactionTemplate;
|
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import javax.persistence.EntityManager;
|
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.VERSION.put(res, Long.toString(theEntity.getVersion()));
|
||||||
ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
|
ResourceMetadataKeyEnum.PUBLISHED.put(res, theEntity.getPublished());
|
||||||
ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
|
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;
|
Collection<? extends BaseTag> tags = theTagList;
|
||||||
if (theEntity.isHasTags()) {
|
if (theEntity.isHasTags()) {
|
||||||
@ -708,7 +705,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> extends BaseStora
|
|||||||
res.setId(res.getIdElement().withVersion(theVersion.toString()));
|
res.setId(res.getIdElement().withVersion(theVersion.toString()));
|
||||||
|
|
||||||
res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
|
res.getMeta().setLastUpdated(theEntity.getUpdatedDate());
|
||||||
IDao.RESOURCE_PID.put(res, theEntity.getId());
|
IDao.RESOURCE_PID.put(res, theEntity.getResourceId());
|
||||||
|
|
||||||
Collection<? extends BaseTag> tags = theTagList;
|
Collection<? extends BaseTag> tags = theTagList;
|
||||||
|
|
||||||
|
@ -994,22 +994,25 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public T read(IIdType theId) {
|
public T read(IIdType theId) {
|
||||||
return read(theId, null);
|
return read(theId, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public T read(IIdType theId, RequestDetails theRequestDetails) {
|
public T read(IIdType theId, RequestDetails theRequestDetails) {
|
||||||
return read(theId, theRequestDetails, false);
|
return read(theId, theRequestDetails, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public T read(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) {
|
public T read(IIdType theId, RequestDetails theRequest, boolean theDeletedOk) {
|
||||||
validateResourceTypeAndThrowInvalidRequestException(theId);
|
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
|
// Notify interceptors
|
||||||
if (theRequest != null) {
|
if (theRequest != null) {
|
||||||
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getResourceName(), theId);
|
ActionRequestDetails requestDetails = new ActionRequestDetails(theRequest, getResourceName(), theId);
|
||||||
|
@ -51,6 +51,7 @@ public class FhirResourceDaoSearchParameterDstu2 extends BaseHapiFhirResourceDao
|
|||||||
protected void postPersist(ResourceTable theEntity, SearchParameter theResource) {
|
protected void postPersist(ResourceTable theEntity, SearchParameter theResource) {
|
||||||
super.postPersist(theEntity, theResource);
|
super.postPersist(theEntity, theResource);
|
||||||
markAffectedResources(theResource);
|
markAffectedResources(theResource);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@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.ResourceHistoryTable;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||||
import ca.uhn.fhir.jpa.util.JpaInterceptorBroadcaster;
|
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.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
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.Slice;
|
||||||
import org.springframework.data.domain.SliceImpl;
|
import org.springframework.data.domain.SliceImpl;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.TransactionManager;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
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.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -108,6 +113,8 @@ public class ResourceExpungeService implements IResourceExpungeService {
|
|||||||
private ISearchParamPresentDao mySearchParamPresentDao;
|
private ISearchParamPresentDao mySearchParamPresentDao;
|
||||||
@Autowired
|
@Autowired
|
||||||
private DaoConfig myDaoConfig;
|
private DaoConfig myDaoConfig;
|
||||||
|
@Autowired
|
||||||
|
private MemoryCacheService myMemoryCacheService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@ -158,6 +165,20 @@ public class ResourceExpungeService implements IResourceExpungeService {
|
|||||||
return;
|
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) {
|
private void expungeHistoricalVersion(RequestDetails theRequestDetails, Long theNextVersionId, AtomicInteger theRemainingCount) {
|
||||||
|
@ -100,6 +100,8 @@ public class IdHelperService {
|
|||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
@Autowired
|
@Autowired
|
||||||
private FhirContext myFhirCtx;
|
private FhirContext myFhirCtx;
|
||||||
|
@Autowired
|
||||||
|
private MemoryCacheService myMemoryCacheService;
|
||||||
|
|
||||||
public void delete(ForcedId forcedId) {
|
public void delete(ForcedId forcedId) {
|
||||||
myForcedIdDao.deleteByPid(forcedId.getId());
|
myForcedIdDao.deleteByPid(forcedId.getId());
|
||||||
@ -123,9 +125,6 @@ public class IdHelperService {
|
|||||||
return matches.iterator().next();
|
return matches.iterator().next();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private MemoryCacheService myMemoryCacheService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Given a resource type and ID, determines the internal persistent ID for the resource.
|
* Given a resource type and ID, determines the internal persistent ID for the resource.
|
||||||
*
|
*
|
||||||
@ -389,7 +388,7 @@ public class IdHelperService {
|
|||||||
lookup
|
lookup
|
||||||
.stream()
|
.stream()
|
||||||
.map(t -> new ResourceLookup((String) t[0], (Long) t[1], (Date) t[2]))
|
.map(t -> new ResourceLookup((String) t[0], (Long) t[1], (Date) t[2]))
|
||||||
.forEach(t->{
|
.forEach(t -> {
|
||||||
theTarget.add(t);
|
theTarget.add(t);
|
||||||
if (!myDaoConfig.isDeleteEnabled()) {
|
if (!myDaoConfig.isDeleteEnabled()) {
|
||||||
String nextKey = Long.toString(t.getResourceId());
|
String nextKey = Long.toString(t.getResourceId());
|
||||||
@ -432,19 +431,6 @@ public class IdHelperService {
|
|||||||
return retVal;
|
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
|
@Nullable
|
||||||
public Long getPidOrNull(IBaseResource theResource) {
|
public Long getPidOrNull(IBaseResource theResource) {
|
||||||
IAnyResource anyResource = (IAnyResource) theResource;
|
IAnyResource anyResource = (IAnyResource) theResource;
|
||||||
@ -488,4 +474,17 @@ public class IdHelperService {
|
|||||||
}
|
}
|
||||||
return optionalResource.get().getIdDt().toVersionless();
|
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.slf4j.LoggerFactory;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@Service
|
@Service
|
||||||
public class DeleteConflictService {
|
public class DeleteConflictService {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(DeleteConflictService.class);
|
|
||||||
public static final int FIRST_QUERY_RESULT_COUNT = 1;
|
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 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.";
|
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
|
@Autowired
|
||||||
DeleteConflictFinderService myDeleteConflictFinderService;
|
DeleteConflictFinderService myDeleteConflictFinderService;
|
||||||
@Autowired
|
@Autowired
|
||||||
DaoConfig myDaoConfig;
|
DaoConfig myDaoConfig;
|
||||||
@Autowired
|
@Autowired
|
||||||
protected IResourceLinkDao myResourceLinkDao;
|
|
||||||
@Autowired
|
|
||||||
private FhirContext myFhirContext;
|
private FhirContext myFhirContext;
|
||||||
@Autowired
|
|
||||||
protected IInterceptorBroadcaster myInterceptorBroadcaster;
|
|
||||||
|
|
||||||
public int validateOkToDelete(DeleteConflictList theDeleteConflicts, ResourceTable theEntity, boolean theForValidate, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
public int validateOkToDelete(DeleteConflictList theDeleteConflicts, ResourceTable theEntity, boolean theForValidate, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||||
|
|
||||||
@ -87,9 +85,9 @@ public class DeleteConflictService {
|
|||||||
++retryCount;
|
++retryCount;
|
||||||
}
|
}
|
||||||
theDeleteConflicts.addAll(newConflicts);
|
theDeleteConflicts.addAll(newConflicts);
|
||||||
if(retryCount >= MAX_RETRY_ATTEMPTS && !theDeleteConflicts.isEmpty()) {
|
if (retryCount >= MAX_RETRY_ATTEMPTS && !theDeleteConflicts.isEmpty()) {
|
||||||
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(myFhirContext);
|
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);
|
throw new ResourceVersionConflictException(MAX_RETRY_ATTEMPTS_EXCEEDED_MSG, oo);
|
||||||
}
|
}
|
||||||
return retryCount;
|
return retryCount;
|
||||||
@ -123,7 +121,7 @@ public class DeleteConflictService {
|
|||||||
.add(RequestDetails.class, theRequest)
|
.add(RequestDetails.class, theRequest)
|
||||||
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
.addIfMatchesType(ServletRequestDetails.class, theRequest)
|
||||||
.add(TransactionDetails.class, theTransactionDetails);
|
.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) {
|
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) {
|
public static void validateDeleteConflictsEmptyOrThrowException(FhirContext theFhirContext, DeleteConflictList theDeleteConflicts) {
|
||||||
if (theDeleteConflicts.isEmpty()) {
|
IBaseOperationOutcome oo = null;
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(theFhirContext);
|
|
||||||
String firstMsg = null;
|
String firstMsg = null;
|
||||||
|
|
||||||
for (DeleteConflict next : theDeleteConflicts) {
|
for (DeleteConflict next : theDeleteConflicts) {
|
||||||
|
|
||||||
|
if (theDeleteConflicts.isResourceIdToIgnoreConflict(next.getTargetId())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
String msg = "Unable to delete " +
|
String msg = "Unable to delete " +
|
||||||
next.getTargetId().toUnqualifiedVersionless().getValue() +
|
next.getTargetId().toUnqualifiedVersionless().getValue() +
|
||||||
" because at least one resource has a reference to this resource. First reference found was resource " +
|
" because at least one resource has a reference to this resource. First reference found was resource " +
|
||||||
next.getSourceId().toUnqualifiedVersionless().getValue() +
|
next.getSourceId().toUnqualifiedVersionless().getValue() +
|
||||||
" in path " +
|
" in path " +
|
||||||
next.getSourcePath();
|
next.getSourcePath();
|
||||||
|
|
||||||
if (firstMsg == null) {
|
if (firstMsg == null) {
|
||||||
firstMsg = msg;
|
firstMsg = msg;
|
||||||
|
oo = OperationOutcomeUtil.newInstance(theFhirContext);
|
||||||
}
|
}
|
||||||
OperationOutcomeUtil.addIssue(theFhirContext, oo, BaseHapiFhirDao.OO_SEVERITY_ERROR, msg, null, "processing");
|
OperationOutcomeUtil.addIssue(theFhirContext, oo, BaseHapiFhirDao.OO_SEVERITY_ERROR, msg, null, "processing");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (firstMsg == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
throw new ResourceVersionConflictException(firstMsg, oo);
|
throw new ResourceVersionConflictException(firstMsg, oo);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +71,13 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||||||
@Interceptor
|
@Interceptor
|
||||||
public class CascadingDeleteInterceptor {
|
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 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_KEY = CascadingDeleteInterceptor.class.getName() + "_CASCADED_DELETES_KEY";
|
||||||
private static final String CASCADED_DELETES_FAILED_KEY = CascadingDeleteInterceptor.class.getName() + "_CASCADED_DELETES_FAILED_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;
|
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) {
|
public DeleteConflictOutcome handleDeleteConflicts(DeleteConflictList theConflictList, RequestDetails theRequest, TransactionDetails theTransactionDetails) {
|
||||||
ourLog.debug("Have delete conflicts: {}", theConflictList);
|
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;
|
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.api.config.DaoConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
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.IntegerDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
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.DateParam;
|
||||||
import ca.uhn.fhir.rest.param.NumberParam;
|
import ca.uhn.fhir.rest.param.NumberParam;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceParam;
|
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
|
@Test
|
||||||
public void testIncludeExtensionReferenceAsRecurse() {
|
public void testIncludeExtensionReferenceAsRecurse() {
|
||||||
SearchParameter attendingSp = new SearchParameter();
|
SearchParameter attendingSp = new SearchParameter();
|
||||||
|
@ -144,7 +144,7 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
|
|||||||
} catch (PreconditionFailedException e) {
|
} catch (PreconditionFailedException e) {
|
||||||
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome());
|
String ooString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome());
|
||||||
ourLog.info(ooString);
|
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();
|
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
|
||||||
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
|
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
|
||||||
ourLog.info(outputString);
|
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
|
@Test
|
||||||
public void testDeleteCircularReferenceInTransaction() throws IOException {
|
public void testDeleteCircularReferenceInTransaction() {
|
||||||
|
|
||||||
// Create two resources with a circular reference
|
// Create two resources with a circular reference
|
||||||
Organization org1 = new Organization();
|
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;
|
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.HookParams;
|
||||||
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
|
import ca.uhn.fhir.interceptor.api.IAnonymousInterceptor;
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
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.model.search.StorageProcessingMessage;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.model.api.Include;
|
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.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.DateParam;
|
||||||
import ca.uhn.fhir.rest.param.NumberParam;
|
import ca.uhn.fhir.rest.param.NumberParam;
|
||||||
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
|
import ca.uhn.fhir.rest.param.ReferenceOrListParam;
|
||||||
@ -112,6 +117,46 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
|||||||
mySearchParamRegistry.forceRefresh();
|
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
|
* 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();
|
outcome = (OperationOutcome) e.getOperationOutcome();
|
||||||
String outcomeStr = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome);
|
String outcomeStr = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(outcome);
|
||||||
ourLog.info("Validation outcome: {}", outcomeStr);
|
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"));
|
obs.setSubject(new Reference("Group/123"));
|
||||||
OperationOutcome oo = validateAndReturnOutcome(obs);
|
OperationOutcome oo = validateAndReturnOutcome(obs);
|
||||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
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
|
// Target of wrong type
|
||||||
obs.setSubject(new Reference("Group/ABC"));
|
obs.setSubject(new Reference("Group/ABC"));
|
||||||
@ -530,7 +530,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||||||
obs.setSubject(new Reference("Group/123"));
|
obs.setSubject(new Reference("Group/123"));
|
||||||
OperationOutcome oo = validateAndReturnOutcome(obs);
|
OperationOutcome oo = validateAndReturnOutcome(obs);
|
||||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
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
|
// Target of wrong type
|
||||||
obs.setSubject(new Reference("Group/ABC"));
|
obs.setSubject(new Reference("Group/ABC"));
|
||||||
@ -595,7 +595,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||||||
obs.setSubject(new Reference("Group/123"));
|
obs.setSubject(new Reference("Group/123"));
|
||||||
OperationOutcome oo = validateAndReturnOutcome(obs);
|
OperationOutcome oo = validateAndReturnOutcome(obs);
|
||||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo));
|
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
|
// Target of wrong type
|
||||||
obs.setSubject(new Reference("Group/ABC"));
|
obs.setSubject(new Reference("Group/ABC"));
|
||||||
@ -625,7 +625,7 @@ public class FhirResourceDaoR4ValidateTest extends BaseJpaR4Test {
|
|||||||
" },\n" +
|
" },\n" +
|
||||||
" \"text\": {\n" +
|
" \"text\": {\n" +
|
||||||
" \"status\": \"generated\",\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" +
|
" },\n" +
|
||||||
" \"url\": \"https://foo/bb\",\n" +
|
" \"url\": \"https://foo/bb\",\n" +
|
||||||
" \"name\": \"BBBehaviourType\",\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();
|
org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome();
|
||||||
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
|
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
|
||||||
ourLog.info(outputString);
|
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();
|
org.hl7.fhir.r4.model.OperationOutcome oo = (org.hl7.fhir.r4.model.OperationOutcome) e.getOperationOutcome();
|
||||||
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
|
String outputString = myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(oo);
|
||||||
ourLog.info(outputString);
|
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);
|
fail("Didn't fail- response was " + encode);
|
||||||
} catch (PreconditionFailedException e) {
|
} catch (PreconditionFailedException e) {
|
||||||
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
|
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);
|
fail("Didn't fail- response was " + encode);
|
||||||
} catch (PreconditionFailedException e) {
|
} catch (PreconditionFailedException e) {
|
||||||
OperationOutcome oo = (OperationOutcome) e.getOperationOutcome();
|
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 {
|
try {
|
||||||
MethodOutcome validationOutcome = myQuestionnaireResponseDao.validate(qa, null, null, null, null, null, null);
|
MethodOutcome validationOutcome = myQuestionnaireResponseDao.validate(qa, null, null, null, null, null, null);
|
||||||
OperationOutcome oo = (OperationOutcome) validationOutcome.getOperationOutcome();
|
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) {
|
} catch (PreconditionFailedException e) {
|
||||||
fail(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(e.getOperationOutcome()));
|
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.interceptor.api.IInterceptorBroadcaster;
|
||||||
import ca.uhn.fhir.jpa.api.dao.DaoRegistry;
|
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.api.Constants;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
|
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.Patient;
|
||||||
import org.hl7.fhir.r4.model.Reference;
|
import org.hl7.fhir.r4.model.Reference;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
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.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.fail;
|
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;
|
private IIdType myDiagnosticReportId;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@ -42,18 +41,13 @@ public class CascadingDeleteInterceptorR4Test extends BaseResourceProviderR4Test
|
|||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
||||||
|
|
||||||
private IIdType myPatientId;
|
private IIdType myPatientId;
|
||||||
|
@Autowired
|
||||||
private CascadingDeleteInterceptor myDeleteInterceptor;
|
private CascadingDeleteInterceptor myDeleteInterceptor;
|
||||||
private IIdType myObservationId;
|
private IIdType myObservationId;
|
||||||
private IIdType myConditionId;
|
private IIdType myConditionId;
|
||||||
private IIdType myEncounterId;
|
private IIdType myEncounterId;
|
||||||
|
@Autowired
|
||||||
@Override
|
private OverridePathBasedReferentialIntegrityForDeletesInterceptor myOverridePathBasedReferentialIntegrityForDeletesInterceptor;
|
||||||
@BeforeEach
|
|
||||||
public void before() throws Exception {
|
|
||||||
super.before();
|
|
||||||
|
|
||||||
myDeleteInterceptor = new CascadingDeleteInterceptor(myFhirCtx, myDaoRegistry, myInterceptorBroadcaster);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@AfterEach
|
@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
|
@Test
|
||||||
public void testDeleteCascadingWithCircularReference() throws IOException {
|
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.List;
|
||||||
import java.util.Set;
|
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.io.IOUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
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
|
* See #484
|
||||||
*/
|
*/
|
||||||
|
@ -413,6 +413,8 @@ public class BinaryAccessProviderR4Test extends BaseResourceProviderR4Test {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stores a binary large enough that it should live in binary storage
|
* 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) {
|
private IIdType createDocumentReference(boolean theSetData) {
|
||||||
DocumentReference documentReference = new DocumentReference();
|
DocumentReference documentReference = new DocumentReference();
|
||||||
Attachment attachment = 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.search.PersistedJpaSearchFirstPageBundleProvider;
|
||||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
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.PreconditionFailedException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.util.HapiExtensions;
|
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.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.hl7.fhir.r4.model.BooleanType;
|
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.Patient;
|
||||||
import org.hl7.fhir.r4.model.SearchParameter;
|
import org.hl7.fhir.r4.model.SearchParameter;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
@ -36,7 +36,9 @@ import static org.awaitility.Awaitility.await;
|
|||||||
import static org.hamcrest.MatcherAssert.assertThat;
|
import static org.hamcrest.MatcherAssert.assertThat;
|
||||||
import static org.hamcrest.Matchers.empty;
|
import static org.hamcrest.Matchers.empty;
|
||||||
import static org.hamcrest.Matchers.not;
|
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 {
|
public class ExpungeR4Test extends BaseResourceProviderR4Test {
|
||||||
|
|
||||||
@ -410,16 +412,16 @@ public class ExpungeR4Test extends BaseResourceProviderR4Test {
|
|||||||
public void testExpungeEverythingWhereResourceInSearchResults() {
|
public void testExpungeEverythingWhereResourceInSearchResults() {
|
||||||
createStandardPatients();
|
createStandardPatients();
|
||||||
|
|
||||||
await().until(()-> runInTransaction(() -> mySearchEntityDao.count() == 0));
|
await().until(() -> runInTransaction(() -> mySearchEntityDao.count() == 0));
|
||||||
await().until(()-> runInTransaction(() -> mySearchResultDao.count() == 0));
|
await().until(() -> runInTransaction(() -> mySearchResultDao.count() == 0));
|
||||||
|
|
||||||
PersistedJpaSearchFirstPageBundleProvider search = (PersistedJpaSearchFirstPageBundleProvider) myPatientDao.search(new SearchParameterMap());
|
PersistedJpaSearchFirstPageBundleProvider search = (PersistedJpaSearchFirstPageBundleProvider) myPatientDao.search(new SearchParameterMap());
|
||||||
assertEquals(PersistedJpaSearchFirstPageBundleProvider.class, search.getClass());
|
assertEquals(PersistedJpaSearchFirstPageBundleProvider.class, search.getClass());
|
||||||
assertEquals(2, search.size().intValue());
|
assertEquals(2, search.size().intValue());
|
||||||
assertEquals(2, search.getResources(0, 2).size());
|
assertEquals(2, search.getResources(0, 2).size());
|
||||||
|
|
||||||
await().until(()-> runInTransaction(() -> mySearchEntityDao.count() == 1));
|
await().until(() -> runInTransaction(() -> mySearchEntityDao.count() == 1));
|
||||||
await().until(()-> runInTransaction(() -> mySearchResultDao.count() == 2));
|
await().until(() -> runInTransaction(() -> mySearchResultDao.count() == 2));
|
||||||
|
|
||||||
mySystemDao.expunge(new ExpungeOptions()
|
mySystemDao.expunge(new ExpungeOptions()
|
||||||
.setExpungeEverything(true), null);
|
.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;
|
package ca.uhn.fhir.jpa.provider.r4;
|
||||||
|
|
||||||
import ca.uhn.fhir.interceptor.api.Pointcut;
|
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 ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import org.hl7.fhir.instance.model.api.IAnyResource;
|
import org.hl7.fhir.instance.model.api.IAnyResource;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.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.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.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 java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
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;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
|
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
|
@BeforeEach
|
||||||
// @AfterEach
|
public void before() throws Exception {
|
||||||
// public void after( ) throws Exception {
|
super.before();
|
||||||
// super.after();
|
|
||||||
//
|
myDaoConfig.setExpungeEnabled(true);
|
||||||
// myInterceptorRegistry.unregisterAllInterceptors();
|
}
|
||||||
// }
|
|
||||||
|
@AfterEach
|
||||||
|
public void after() throws Exception {
|
||||||
|
myDaoConfig.setExpungeEnabled(new DaoConfig().isExpungeEnabled());
|
||||||
|
|
||||||
|
super.after();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOP_PRESTORAGE_RESOURCE_CREATED_ModifyResource() {
|
public void testOP_PRESTORAGE_RESOURCE_CREATED_ModifyResource() {
|
||||||
@ -69,7 +89,7 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
|
|||||||
AtomicLong pid = new AtomicLong();
|
AtomicLong pid = new AtomicLong();
|
||||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, (thePointcut, t) -> {
|
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_CREATED, (thePointcut, t) -> {
|
||||||
IAnyResource resource = (IAnyResource) t.get(IBaseResource.class, 0);
|
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.");
|
assertNotNull(resourcePid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||||
pid.set(resourcePid);
|
pid.set(resourcePid);
|
||||||
});
|
});
|
||||||
@ -77,6 +97,72 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
|
|||||||
assertTrue(pid.get() > 0);
|
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
|
@Test
|
||||||
public void testSTORAGE_PRECOMMIT_RESOURCE_UPDATED_hasPid() {
|
public void testSTORAGE_PRECOMMIT_RESOURCE_UPDATED_hasPid() {
|
||||||
AtomicLong oldPid = new AtomicLong();
|
AtomicLong oldPid = new AtomicLong();
|
||||||
@ -84,12 +170,12 @@ public class HookInterceptorR4Test extends BaseResourceProviderR4Test {
|
|||||||
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, (thePointcut, t) -> {
|
myInterceptorRegistry.registerAnonymousInterceptor(Pointcut.STORAGE_PRECOMMIT_RESOURCE_UPDATED, (thePointcut, t) -> {
|
||||||
|
|
||||||
IAnyResource oldResource = (IAnyResource) t.get(IBaseResource.class, 0);
|
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.");
|
assertNotNull(oldResourcePid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||||
oldPid.set(oldResourcePid);
|
oldPid.set(oldResourcePid);
|
||||||
|
|
||||||
IAnyResource newResource = (IAnyResource) t.get(IBaseResource.class, 1);
|
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.");
|
assertNotNull(newResourcePid, "Expecting RESOURCE_PID to be set on resource user data.");
|
||||||
newPid.set(newResourcePid);
|
newPid.set(newResourcePid);
|
||||||
});
|
});
|
||||||
|
@ -2359,7 +2359,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
|||||||
String respString = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
|
String respString = IOUtils.toString(resp.getEntity().getContent(), Charsets.UTF_8);
|
||||||
ourLog.info(respString);
|
ourLog.info(respString);
|
||||||
assertEquals(412, resp.getStatusLine().getStatusCode());
|
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>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@ -55,13 +55,13 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-test-utilities</artifactId>
|
<artifactId>hapi-fhir-test-utilities</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-jpaserver-test-utilities</artifactId>
|
<artifactId>hapi-fhir-jpaserver-test-utilities</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<scope>test</scope>
|
<scope>test</scope>
|
||||||
</dependency>
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -1,5 +1,25 @@
|
|||||||
package ca.uhn.fhir.jpa.empi.svc;
|
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.context.FhirContext;
|
||||||
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
import ca.uhn.fhir.empi.api.IEmpiSettings;
|
||||||
import ca.uhn.fhir.empi.log.Logs;
|
import ca.uhn.fhir.empi.log.Logs;
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -24,8 +24,10 @@ import ca.uhn.fhir.context.FhirContext;
|
|||||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.context.phonetic.IPhoneticEncoder;
|
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.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.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.sched.HapiJob;
|
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.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
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.SearchParameterUtil;
|
||||||
import ca.uhn.fhir.util.StopWatch;
|
import ca.uhn.fhir.util.StopWatch;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
@ -51,6 +51,7 @@ import org.slf4j.LoggerFactory;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
|
import javax.annotation.PreDestroy;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -89,7 +90,8 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||||||
private volatile long myLastRefresh;
|
private volatile long myLastRefresh;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorService myInterceptorBroadcaster;
|
||||||
|
private RefreshSearchParameterCacheOnUpdate myInterceptor;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
|
public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) {
|
||||||
@ -236,8 +238,16 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void postConstruct() {
|
public void start() {
|
||||||
myBuiltInSearchParams = createBuiltInSearchParamMap(myFhirContext);
|
myBuiltInSearchParams = createBuiltInSearchParamMap(myFhirContext);
|
||||||
|
|
||||||
|
myInterceptor = new RefreshSearchParameterCacheOnUpdate();
|
||||||
|
myInterceptorBroadcaster.registerInterceptor(myInterceptor);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PreDestroy
|
||||||
|
public void stop() {
|
||||||
|
myInterceptorBroadcaster.unregisterInterceptor(myInterceptor);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int doRefresh(long theRefreshInterval) {
|
public int doRefresh(long theRefreshInterval) {
|
||||||
@ -376,16 +386,6 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||||||
mySchedulerService.scheduleLocalJob(10 * DateUtils.MILLIS_PER_SECOND, jobDetail);
|
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
|
@Override
|
||||||
public boolean refreshCacheIfNecessary() {
|
public boolean refreshCacheIfNecessary() {
|
||||||
if (myActiveSearchParams == null || System.currentTimeMillis() - REFRESH_INTERVAL > myLastRefresh) {
|
if (myActiveSearchParams == null || System.currentTimeMillis() - REFRESH_INTERVAL > myLastRefresh) {
|
||||||
@ -402,30 +402,12 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||||||
return Collections.unmodifiableMap(myActiveSearchParams);
|
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.
|
* All SearchParameters with the name "phonetic" encode the normalized index value using this phonetic encoder.
|
||||||
*
|
*
|
||||||
* @since 5.1.0
|
* @since 5.1.0
|
||||||
*/
|
*/
|
||||||
|
@Override
|
||||||
public void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) {
|
public void setPhoneticEncoder(IPhoneticEncoder thePhoneticEncoder) {
|
||||||
myPhoneticEncoder = thePhoneticEncoder;
|
myPhoneticEncoder = thePhoneticEncoder;
|
||||||
|
|
||||||
@ -446,4 +428,58 @@ public class SearchParamRegistryImpl implements ISearchParamRegistry {
|
|||||||
searchParam.setPhoneticEncoder(myPhoneticEncoder);
|
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();
|
String path = theNextSp.getXpath();
|
||||||
RestSearchParameterTypeEnum paramType = null;
|
RestSearchParameterTypeEnum paramType = null;
|
||||||
RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null;
|
RuntimeSearchParam.RuntimeSearchParamStatusEnum status = null;
|
||||||
|
if (theNextSp.getTypeElement().getValueAsEnum() != null) {
|
||||||
switch (theNextSp.getTypeElement().getValueAsEnum()) {
|
switch (theNextSp.getTypeElement().getValueAsEnum()) {
|
||||||
case COMPOSITE:
|
case COMPOSITE:
|
||||||
paramType = RestSearchParameterTypeEnum.COMPOSITE;
|
paramType = RestSearchParameterTypeEnum.COMPOSITE;
|
||||||
@ -122,6 +123,7 @@ public class SearchParameterCanonicalizer {
|
|||||||
paramType = RestSearchParameterTypeEnum.URI;
|
paramType = RestSearchParameterTypeEnum.URI;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
if (theNextSp.getStatus() != null) {
|
if (theNextSp.getStatus() != null) {
|
||||||
switch (theNextSp.getStatusElement().getValueAsEnum()) {
|
switch (theNextSp.getStatusElement().getValueAsEnum()) {
|
||||||
case ACTIVE:
|
case ACTIVE:
|
||||||
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.searchparam.registry;
|
|||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||||
import ca.uhn.fhir.interceptor.api.IInterceptorBroadcaster;
|
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.entity.ModelConfig;
|
||||||
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
import ca.uhn.fhir.jpa.model.sched.ISchedulerService;
|
||||||
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
||||||
@ -46,7 +47,7 @@ public class SearchParamRegistryImplTest {
|
|||||||
@MockBean
|
@MockBean
|
||||||
private ModelConfig myModelConfig;
|
private ModelConfig myModelConfig;
|
||||||
@MockBean
|
@MockBean
|
||||||
private IInterceptorBroadcaster myInterceptorBroadcaster;
|
private IInterceptorService myInterceptorBroadcaster;
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
static class SpringConfig {
|
static class SpringConfig {
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
@ -157,7 +157,7 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-converter</artifactId>
|
<artifactId>hapi-fhir-converter</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
@ -7,7 +7,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
<artifactId>hapi-fhir-spring-boot-sample-client-apache</artifactId>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>
|
<artifactId>hapi-fhir-spring-boot-sample-client-okhttp</artifactId>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>
|
<artifactId>hapi-fhir-spring-boot-sample-server-jersey</artifactId>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hapi-fhir-spring-boot-sample-server-jpa</artifactId>
|
<artifactId>hapi-fhir-spring-boot-sample-server-jpa</artifactId>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-spring-boot</artifactId>
|
<artifactId>hapi-fhir-spring-boot</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
<artifactId>hapi-fhir-spring-boot-samples</artifactId>
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -1,12 +1,14 @@
|
|||||||
package ca.uhn.fhir.rest.server;
|
package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
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.Create;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
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.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
import ca.uhn.fhir.test.utilities.JettyUtil;
|
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.dstu3.model.Patient;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.junit.jupiter.api.AfterAll;
|
import org.junit.jupiter.api.AfterAll;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
@ -44,11 +48,17 @@ import static org.junit.jupiter.api.Assertions.assertEquals;
|
|||||||
public class ServerExceptionDstu3Test {
|
public class ServerExceptionDstu3Test {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerExceptionDstu3Test.class);
|
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 CloseableHttpClient ourClient;
|
||||||
private static FhirContext ourCtx = FhirContext.forDstu3();
|
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||||
private static int ourPort;
|
private static int ourPort;
|
||||||
private static Server ourServer;
|
private static Server ourServer;
|
||||||
|
private static RestfulServer ourServlet;
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
public void after() {
|
||||||
|
ourException = null;
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testAddHeadersNotFound() throws Exception {
|
public void testAddHeadersNotFound() throws Exception {
|
||||||
@ -56,8 +66,8 @@ public class ServerExceptionDstu3Test {
|
|||||||
OperationOutcome operationOutcome = new OperationOutcome();
|
OperationOutcome operationOutcome = new OperationOutcome();
|
||||||
operationOutcome.addIssue().setCode(IssueType.BUSINESSRULE);
|
operationOutcome.addIssue().setCode(IssueType.BUSINESSRULE);
|
||||||
|
|
||||||
ourException = new ResourceNotFoundException("SOME MESSAGE");
|
ourException = new ResourceNotFoundException("SOME MESSAGE")
|
||||||
ourException.addResponseHeader("X-Foo", "BAR BAR");
|
.addResponseHeader("X-Foo", "BAR BAR");
|
||||||
|
|
||||||
|
|
||||||
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient");
|
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
|
@Test
|
||||||
public void testPostWithNoBody() throws IOException {
|
public void testPostWithNoBody() throws IOException {
|
||||||
|
|
||||||
@ -143,7 +212,10 @@ public class ServerExceptionDstu3Test {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Search()
|
@Search()
|
||||||
public List<Patient> search() {
|
public List<Patient> search() throws Exception {
|
||||||
|
if (ourException == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
throw ourException;
|
throw ourException;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -168,11 +240,11 @@ public class ServerExceptionDstu3Test {
|
|||||||
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||||
|
|
||||||
ServletHandler proxyHandler = new ServletHandler();
|
ServletHandler proxyHandler = new ServletHandler();
|
||||||
RestfulServer servlet = new RestfulServer(ourCtx);
|
ourServlet = new RestfulServer(ourCtx);
|
||||||
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
ourServlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||||
|
|
||||||
servlet.setResourceProviders(patientProvider);
|
ourServlet.setResourceProviders(patientProvider);
|
||||||
ServletHolder servletHolder = new ServletHolder(servlet);
|
ServletHolder servletHolder = new ServletHolder(ourServlet);
|
||||||
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||||
ourServer.setHandler(proxyHandler);
|
ourServer.setHandler(proxyHandler);
|
||||||
JettyUtil.startServer(ourServer);
|
JettyUtil.startServer(ourServer);
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</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.terminologies.ValueSetExpander;
|
||||||
import org.hl7.fhir.r5.utils.IResourceValidator;
|
import org.hl7.fhir.r5.utils.IResourceValidator;
|
||||||
import org.hl7.fhir.utilities.TranslationServices;
|
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.cache.NpmPackage;
|
||||||
import org.hl7.fhir.utilities.i18n.I18nBase;
|
import org.hl7.fhir.utilities.i18n.I18nBase;
|
||||||
import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity;
|
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);
|
return validateCode(theOptions, system, code, display, theVs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void validateCodeBatch(ValidationOptions options, List<? extends CodingValidationRequest> codes, ValueSet vs) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public ValidationResult validateCode(ValidationOptions theOptions, String theSystem, String theCode, String theDisplay) {
|
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);
|
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
|
@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();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-deployable-pom</artifactId>
|
<artifactId>hapi-deployable-pom</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
<relativePath>../hapi-deployable-pom/pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -4,7 +4,7 @@
|
|||||||
<parent>
|
<parent>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir</artifactId>
|
<artifactId>hapi-fhir</artifactId>
|
||||||
<version>5.1.0-SNAPSHOT</version>
|
<version>5.2.0-SNAPSHOT</version>
|
||||||
<relativePath>../pom.xml</relativePath>
|
<relativePath>../pom.xml</relativePath>
|
||||||
</parent>
|
</parent>
|
||||||
|
|
||||||
|
@ -41,6 +41,7 @@ import java.io.UnsupportedEncodingException;
|
|||||||
import java.net.URLDecoder;
|
import java.net.URLDecoder;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
|
import static ca.uhn.fhir.util.UrlUtil.sanitizeUrlPart;
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
|
|
||||||
public class BaseController {
|
public class BaseController {
|
||||||
@ -49,7 +50,7 @@ public class BaseController {
|
|||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseController.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseController.class);
|
||||||
@Autowired
|
@Autowired
|
||||||
protected TesterConfig myConfig;
|
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;
|
private List<String> myFilterHeaders;
|
||||||
@Autowired
|
@Autowired
|
||||||
private ITemplateEngine myTemplateEngine;
|
private ITemplateEngine myTemplateEngine;
|
||||||
@ -78,19 +79,6 @@ public class BaseController {
|
|||||||
return loadAndAddConf(theServletRequest, theRequest, theModel);
|
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) {
|
private Header[] applyHeaderFilters(Map<String, List<String>> theAllHeaders) {
|
||||||
ArrayList<Header> retVal = new ArrayList<Header>();
|
ArrayList<Header> retVal = new ArrayList<Header>();
|
||||||
for (String nextKey : theAllHeaders.keySet()) {
|
for (String nextKey : theAllHeaders.keySet()) {
|
||||||
@ -274,7 +262,7 @@ public class BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected RuntimeResourceDefinition getResourceType(HomeRequest theRequest, HttpServletRequest theReq) throws ServletException {
|
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);
|
RuntimeResourceDefinition def = getContext(theRequest).getResourceDefinition(resourceName);
|
||||||
if (def == null) {
|
if (def == null) {
|
||||||
throw new ServletException("Invalid resourceName: " + resourceName);
|
throw new ServletException("Invalid resourceName: " + resourceName);
|
||||||
@ -317,7 +305,7 @@ public class BaseController {
|
|||||||
|
|
||||||
ca.uhn.fhir.model.dstu2.resource.Conformance conformance;
|
ca.uhn.fhir.model.dstu2.resource.Conformance conformance;
|
||||||
try {
|
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) {
|
} catch (Exception e) {
|
||||||
ourLog.warn("Failed to load conformance statement, error was: {}", e.toString());
|
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));
|
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));
|
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;
|
long total = 0;
|
||||||
for (ca.uhn.fhir.model.dstu2.resource.Conformance.Rest nextRest : conformance.getRest()) {
|
for (ca.uhn.fhir.model.dstu2.resource.Conformance.Rest nextRest : conformance.getRest()) {
|
||||||
for (ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource nextResource : nextRest.getResource()) {
|
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));
|
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;
|
long total = 0;
|
||||||
|
|
||||||
for (CapabilityStatementRestComponent nextRest : capabilityStatement.getRest()) {
|
for (CapabilityStatementRestComponent nextRest : capabilityStatement.getRest()) {
|
||||||
@ -446,7 +434,7 @@ public class BaseController {
|
|||||||
|
|
||||||
theModel.put("jsonEncodedConf", getContext(theRequest).newJsonParser().encodeResourceToString(capabilityStatement));
|
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;
|
long total = 0;
|
||||||
|
|
||||||
for (org.hl7.fhir.r4.model.CapabilityStatement.CapabilityStatementRestComponent nextRest : capabilityStatement.getRest()) {
|
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));
|
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;
|
long total = 0;
|
||||||
|
|
||||||
for (org.hl7.fhir.r5.model.CapabilityStatement.CapabilityStatementRestComponent nextRest : capabilityStatement.getRest()) {
|
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,
|
protected void processAndAddLastClientInvocation(GenericClient theClient, ResultType theResultType, ModelMap theModelMap, long theLatency, String outcomeDescription,
|
||||||
CaptureInterceptor theInterceptor, HomeRequest theRequest) {
|
CaptureInterceptor theInterceptor, HomeRequest theRequest) {
|
||||||
try {
|
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();
|
IHttpRequest lastRequest = theInterceptor.getLastRequest();
|
||||||
IHttpResponse lastResponse = theInterceptor.getLastResponse();
|
IHttpResponse lastResponse = theInterceptor.getLastResponse();
|
||||||
String requestBody = null;
|
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.HomeRequest;
|
||||||
import ca.uhn.fhir.to.model.ResourceRequest;
|
import ca.uhn.fhir.to.model.ResourceRequest;
|
||||||
import ca.uhn.fhir.to.model.TransactionRequest;
|
import ca.uhn.fhir.to.model.TransactionRequest;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
import com.google.gson.stream.JsonWriter;
|
import com.google.gson.stream.JsonWriter;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.dstu3.model.CapabilityStatement;
|
import org.hl7.fhir.dstu3.model.CapabilityStatement;
|
||||||
@ -49,6 +50,7 @@ import java.util.Collections;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.TreeSet;
|
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.defaultIfEmpty;
|
||||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
@ -128,7 +130,7 @@ public class Controller extends BaseController {
|
|||||||
return "resource";
|
return "resource";
|
||||||
}
|
}
|
||||||
|
|
||||||
String id = StringUtils.defaultString(theServletRequest.getParameter("resource-delete-id"));
|
String id = sanitizeUrlPart(defaultString(theServletRequest.getParameter("resource-delete-id")));
|
||||||
if (StringUtils.isBlank(id)) {
|
if (StringUtils.isBlank(id)) {
|
||||||
populateModelForResource(theServletRequest, theRequest, theModel);
|
populateModelForResource(theServletRequest, theRequest, theModel);
|
||||||
theModel.put("errorMsg", toDisplayError("No ID specified", null));
|
theModel.put("errorMsg", toDisplayError("No ID specified", null));
|
||||||
@ -184,7 +186,7 @@ public class Controller extends BaseController {
|
|||||||
FhirContext context = getContext(theRequest);
|
FhirContext context = getContext(theRequest);
|
||||||
GenericClient client = theRequest.newClient(theReq, context, myConfig, interceptor);
|
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 (myConfig.isRefuseToFetchThirdPartyUrls()) {
|
||||||
if (!url.startsWith(theModel.get("base").toString())) {
|
if (!url.startsWith(theModel.get("base").toString())) {
|
||||||
ourLog.warn(logPrefix(theModel) + "Refusing to load page URL: {}", url);
|
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));
|
theModel.put("errorMsg", toDisplayError(e.toString(), e));
|
||||||
return "resource";
|
return "resource";
|
||||||
}
|
}
|
||||||
String id = StringUtils.defaultString(theServletRequest.getParameter("id"));
|
String id = sanitizeUrlPart(defaultString(theServletRequest.getParameter("id")));
|
||||||
if (StringUtils.isBlank(id)) {
|
if (StringUtils.isBlank(id)) {
|
||||||
populateModelForResource(theServletRequest, theRequest, theModel);
|
populateModelForResource(theServletRequest, theRequest, theModel);
|
||||||
theModel.put("errorMsg", toDisplayError("No ID specified", null));
|
theModel.put("errorMsg", toDisplayError("No ID specified", null));
|
||||||
@ -238,7 +240,7 @@ public class Controller extends BaseController {
|
|||||||
}
|
}
|
||||||
ResultType returnsResource = ResultType.RESOURCE;
|
ResultType returnsResource = ResultType.RESOURCE;
|
||||||
|
|
||||||
String versionId = StringUtils.defaultString(theServletRequest.getParameter("vid"));
|
String versionId = sanitizeUrlPart(defaultString(theServletRequest.getParameter("vid")));
|
||||||
String outcomeDescription;
|
String outcomeDescription;
|
||||||
if (StringUtils.isBlank(versionId)) {
|
if (StringUtils.isBlank(versionId)) {
|
||||||
versionId = null;
|
versionId = null;
|
||||||
@ -353,7 +355,7 @@ public class Controller extends BaseController {
|
|||||||
return "resource";
|
return "resource";
|
||||||
}
|
}
|
||||||
clientCodeJsonWriter.name("resource");
|
clientCodeJsonWriter.name("resource");
|
||||||
clientCodeJsonWriter.value(theServletRequest.getParameter("resource"));
|
clientCodeJsonWriter.value(sanitizeUrlPart(theServletRequest.getParameter("resource")));
|
||||||
} else {
|
} else {
|
||||||
query = search.forAllResources();
|
query = search.forAllResources();
|
||||||
clientCodeJsonWriter.name("resource");
|
clientCodeJsonWriter.name("resource");
|
||||||
@ -394,7 +396,7 @@ public class Controller extends BaseController {
|
|||||||
|
|
||||||
clientCodeJsonWriter.name("includes");
|
clientCodeJsonWriter.name("includes");
|
||||||
clientCodeJsonWriter.beginArray();
|
clientCodeJsonWriter.beginArray();
|
||||||
String[] incValues = theServletRequest.getParameterValues(Constants.PARAM_INCLUDE);
|
String[] incValues = sanitizeUrlPart(theServletRequest.getParameterValues(Constants.PARAM_INCLUDE));
|
||||||
if (incValues != null) {
|
if (incValues != null) {
|
||||||
for (String next : incValues) {
|
for (String next : incValues) {
|
||||||
if (isNotBlank(next)) {
|
if (isNotBlank(next)) {
|
||||||
@ -407,7 +409,7 @@ public class Controller extends BaseController {
|
|||||||
|
|
||||||
clientCodeJsonWriter.name("revincludes");
|
clientCodeJsonWriter.name("revincludes");
|
||||||
clientCodeJsonWriter.beginArray();
|
clientCodeJsonWriter.beginArray();
|
||||||
String[] revIncValues = theServletRequest.getParameterValues(Constants.PARAM_REVINCLUDE);
|
String[] revIncValues = sanitizeUrlPart(theServletRequest.getParameterValues(Constants.PARAM_REVINCLUDE));
|
||||||
if (revIncValues != null) {
|
if (revIncValues != null) {
|
||||||
for (String next : revIncValues) {
|
for (String next : revIncValues) {
|
||||||
if (isNotBlank(next)) {
|
if (isNotBlank(next)) {
|
||||||
@ -418,7 +420,7 @@ public class Controller extends BaseController {
|
|||||||
}
|
}
|
||||||
clientCodeJsonWriter.endArray();
|
clientCodeJsonWriter.endArray();
|
||||||
|
|
||||||
String limit = theServletRequest.getParameter("resource-search-limit");
|
String limit = sanitizeUrlPart(theServletRequest.getParameter("resource-search-limit"));
|
||||||
if (isNotBlank(limit)) {
|
if (isNotBlank(limit)) {
|
||||||
if (!limit.matches("[0-9]+")) {
|
if (!limit.matches("[0-9]+")) {
|
||||||
populateModelForResource(theServletRequest, theRequest, theModel);
|
populateModelForResource(theServletRequest, theRequest, theModel);
|
||||||
@ -434,13 +436,13 @@ public class Controller extends BaseController {
|
|||||||
clientCodeJsonWriter.nullValue();
|
clientCodeJsonWriter.nullValue();
|
||||||
}
|
}
|
||||||
|
|
||||||
String[] sort = theServletRequest.getParameterValues("sort_by");
|
String[] sort = sanitizeUrlPart(theServletRequest.getParameterValues("sort_by"));
|
||||||
if (sort != null) {
|
if (sort != null) {
|
||||||
for (String next : sort) {
|
for (String next : sort) {
|
||||||
if (isBlank(next)) {
|
if (isBlank(next)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
String direction = theServletRequest.getParameter("sort_direction");
|
String direction = sanitizeUrlPart(theServletRequest.getParameter("sort_direction"));
|
||||||
if ("asc".equals(direction)) {
|
if ("asc".equals(direction)) {
|
||||||
query.sort().ascending(new StringClientParam(next));
|
query.sort().ascending(new StringClientParam(next));
|
||||||
} else if ("desc".equals(direction)) {
|
} else if ("desc".equals(direction)) {
|
||||||
@ -545,6 +547,7 @@ public class Controller extends BaseController {
|
|||||||
type = def.getImplementingClass();
|
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");
|
String body = validate ? theReq.getParameter("resource-validate-body") : theReq.getParameter("resource-create-body");
|
||||||
if (isBlank(body)) {
|
if (isBlank(body)) {
|
||||||
theModel.put("errorMsg", toDisplayError("No message body specified", null));
|
theModel.put("errorMsg", toDisplayError("No message body specified", null));
|
||||||
@ -583,7 +586,7 @@ public class Controller extends BaseController {
|
|||||||
outcomeDescription = "Validate Resource";
|
outcomeDescription = "Validate Resource";
|
||||||
client.validate().resource(resource).prettyPrint().execute();
|
client.validate().resource(resource).prettyPrint().execute();
|
||||||
} else {
|
} else {
|
||||||
String id = theReq.getParameter("resource-create-id");
|
String id = sanitizeUrlPart(theReq.getParameter("resource-create-id"));
|
||||||
if ("update".equals(theMethod)) {
|
if ("update".equals(theMethod)) {
|
||||||
outcomeDescription = "Update Resource";
|
outcomeDescription = "Update Resource";
|
||||||
client.update(id, resource);
|
client.update(id, resource);
|
||||||
@ -626,17 +629,17 @@ public class Controller extends BaseController {
|
|||||||
if ("history-type".equals(theMethod)) {
|
if ("history-type".equals(theMethod)) {
|
||||||
RuntimeResourceDefinition def = getContext(theRequest).getResourceDefinition(theRequest.getResource());
|
RuntimeResourceDefinition def = getContext(theRequest).getResourceDefinition(theRequest.getResource());
|
||||||
type = def.getImplementingClass();
|
type = def.getImplementingClass();
|
||||||
id = StringUtils.defaultString(theReq.getParameter("resource-history-id"));
|
id = sanitizeUrlPart(defaultString(theReq.getParameter("resource-history-id")));
|
||||||
}
|
}
|
||||||
|
|
||||||
DateTimeDt since = null;
|
DateTimeDt since = null;
|
||||||
String sinceStr = theReq.getParameter("since");
|
String sinceStr = sanitizeUrlPart(theReq.getParameter("since"));
|
||||||
if (isNotBlank(sinceStr)) {
|
if (isNotBlank(sinceStr)) {
|
||||||
since = new DateTimeDt(sinceStr);
|
since = new DateTimeDt(sinceStr);
|
||||||
}
|
}
|
||||||
|
|
||||||
Integer limit = null;
|
Integer limit = null;
|
||||||
String limitStr = theReq.getParameter("limit");
|
String limitStr = sanitizeUrlPart(theReq.getParameter("limit"));
|
||||||
if (isNotBlank(limitStr)) {
|
if (isNotBlank(limitStr)) {
|
||||||
limit = Integer.parseInt(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 {
|
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)) {
|
if (isBlank(nextName)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
String nextQualifier = StringUtils.defaultString(theReq.getParameter("param." + paramIdxString + ".qualifier"));
|
String nextQualifier = sanitizeUrlPart(defaultString(theReq.getParameter("param." + paramIdxString + ".qualifier")));
|
||||||
String nextType = theReq.getParameter("param." + paramIdxString + ".type");
|
String nextType = sanitizeUrlPart(theReq.getParameter("param." + paramIdxString + ".type"));
|
||||||
|
|
||||||
List<String> parts = new ArrayList<String>();
|
List<String> parts = new ArrayList<String>();
|
||||||
for (int i = 0; i < 5; i++) {
|
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;
|
List<String> values;
|
||||||
|
@ -48,13 +48,13 @@ public class FhirTesterConfig {
|
|||||||
.addServer()
|
.addServer()
|
||||||
.withId("hapi")
|
.withId("hapi")
|
||||||
.withFhirVersion(FhirVersionEnum.DSTU2)
|
.withFhirVersion(FhirVersionEnum.DSTU2)
|
||||||
.withBaseUrl("http://fhirtest.uhn.ca/baseDstu2")
|
.withBaseUrl("http://hapi.fhir.org/baseDstu2")
|
||||||
.withName("Public HAPI Test Server")
|
.withName("Public HAPI Test Server")
|
||||||
.allowsApiKey()
|
.allowsApiKey()
|
||||||
.addServer()
|
.addServer()
|
||||||
.withId("home3")
|
.withId("home3")
|
||||||
.withFhirVersion(FhirVersionEnum.DSTU3)
|
.withFhirVersion(FhirVersionEnum.DSTU3)
|
||||||
.withBaseUrl("http://fhirtest.uhn.ca/baseDstu3")
|
.withBaseUrl("http://hapi.fhir.org/baseDstu3")
|
||||||
.withName("Public HAPI Test Server (STU3)")
|
.withName("Public HAPI Test Server (STU3)")
|
||||||
.addServer()
|
.addServer()
|
||||||
.withId("home")
|
.withId("home")
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user