Merge branch 'master' into spring-boot-starter
This commit is contained in:
commit
85bc654a63
|
@ -22,4 +22,4 @@ before_script:
|
|||
script:
|
||||
# - mvn -e -B clean install && cd hapi-fhir-ra && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID clean test jacoco:report coveralls:report
|
||||
# - mvn -Dci=true -e -B -P ALLMODULES,NOPARALLEL,ERRORPRONE clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report
|
||||
- mvn -Dci=true -e -B -P ALLMODULES,MINPARALLEL clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report
|
||||
- mvn -Dci=true -e -B -P ALLMODULES,MINPARALLEL,ERRORPRONE clean install && cd hapi-fhir-jacoco && mvn -e -B -DTRAVIS_JOB_ID=$TRAVIS_JOB_ID jacoco:report coveralls:report
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package example;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -8,6 +9,7 @@ import ca.uhn.fhir.rest.api.EncodingEnum;
|
|||
import ca.uhn.fhir.rest.client.apache.GZipContentInterceptor;
|
||||
import ca.uhn.fhir.rest.client.api.*;
|
||||
import ca.uhn.fhir.rest.client.interceptor.*;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
|
||||
public class ClientExamples {
|
||||
|
||||
|
@ -52,6 +54,26 @@ public class ClientExamples {
|
|||
// END SNIPPET: processMessage
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void cacheControl() {
|
||||
FhirContext ctx = FhirContext.forDstu3();
|
||||
|
||||
// Create the client
|
||||
IGenericClient client = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
// ..populate the bundle..
|
||||
|
||||
// START SNIPPET: cacheControl
|
||||
Bundle response = client
|
||||
.search()
|
||||
.forResource(Patient.class)
|
||||
.returnBundle(Bundle.class)
|
||||
.cacheControl(new CacheControlDirective().setNoCache(true)) // <-- add a directive
|
||||
.execute();
|
||||
// END SNIPPET: cacheControl
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
public void createOkHttp() {
|
||||
// START SNIPPET: okhttp
|
||||
|
|
|
@ -63,6 +63,65 @@
|
|||
</dependency>
|
||||
</dependencies>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.basepom.maven</groupId>
|
||||
<artifactId>duplicate-finder-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>default</id>
|
||||
<phase>verify</phase>
|
||||
<goals>
|
||||
<goal>check</goal>
|
||||
</goals>
|
||||
<inherited>true</inherited>
|
||||
</execution>
|
||||
</executions>
|
||||
<configuration>
|
||||
<printEqualFiles>false</printEqualFiles>
|
||||
<failBuildInCaseOfDifferentContentConflict>true</failBuildInCaseOfDifferentContentConflict>
|
||||
<failBuildInCaseOfEqualContentConflict>true</failBuildInCaseOfEqualContentConflict>
|
||||
<failBuildInCaseOfConflict>true</failBuildInCaseOfConflict>
|
||||
<checkCompileClasspath>true</checkCompileClasspath>
|
||||
<checkRuntimeClasspath>false</checkRuntimeClasspath>
|
||||
<checkTestClasspath>false</checkTestClasspath>
|
||||
<skip>false</skip>
|
||||
<quiet>false</quiet>
|
||||
<preferLocal>true</preferLocal>
|
||||
<useResultFile>true</useResultFile>
|
||||
<resultFileMinClasspathCount>2</resultFileMinClasspathCount>
|
||||
<resultFile>${project.build.directory}/duplicate-finder-result.xml</resultFile>
|
||||
<ignoredDependencies>
|
||||
<dependency>
|
||||
<groupId>javax.el</groupId>
|
||||
<artifactId>javax.el-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>javax.mail-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.phloc</groupId>
|
||||
<artifactId>phloc-commons</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>commons-logging</groupId>
|
||||
<artifactId>commons-logging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.jscience</groupId>
|
||||
<artifactId>jscience</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-jcl</artifactId>
|
||||
</dependency>
|
||||
</ignoredDependencies>
|
||||
<ignoredResources>
|
||||
<ignoredResource>changelog.txt</ignoredResource>
|
||||
<ignoredResource>javac.bat</ignoredResource>
|
||||
</ignoredResources>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
|
|
|
@ -35,6 +35,25 @@
|
|||
<groupId>commons-codec</groupId>
|
||||
<artifactId>commons-codec</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.codehaus.woodstox</groupId>
|
||||
<artifactId>woodstox-core-asl</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-fhir-client</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpcore</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>org.apache.httpcomponents</groupId>
|
||||
<artifactId>httpclient</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
@ -92,52 +111,14 @@
|
|||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>dstu2_shade</id>
|
||||
<id>it</id>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Dstu2ShadeIT.java</include>
|
||||
<include>**/*IT.java</include>
|
||||
</includes>
|
||||
<classpathDependencyExcludes>
|
||||
<!--<classpathDependencyExclude>ca.uhn.hapi.fhir:*</classpathDependencyExclude>-->
|
||||
<classpathDependencyExclude>org.codehaus.woodstox:woodstox-core-asl</classpathDependencyExclude>
|
||||
<classpathDependencyExclude>org.codehaus.woodstox:stax2-api</classpathDependencyExclude>
|
||||
</classpathDependencyExcludes>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>dstu2</id>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Dstu2IT.java</include>
|
||||
</includes>
|
||||
<classpathDependencyExcludes>
|
||||
<classpathDependencyExclude>org.codehaus.woodstox:woodstox-core-asl</classpathDependencyExclude>
|
||||
<classpathDependencyExclude>org.codehaus.woodstox:stax2-api</classpathDependencyExclude>
|
||||
</classpathDependencyExcludes>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>dstu3</id>
|
||||
<goals>
|
||||
<goal>integration-test</goal>
|
||||
<goal>verify</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<includes>
|
||||
<include>**/*Dstu3IT.java</include>
|
||||
</includes>
|
||||
<classpathDependencyExcludes>
|
||||
<classpathDependencyExclude>org.codehaus.woodstox:woodstox-core-asl</classpathDependencyExclude>
|
||||
<classpathDependencyExclude>org.codehaus.woodstox:stax2-api</classpathDependencyExclude>
|
||||
</classpathDependencyExcludes>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
|
|
|
@ -9,6 +9,7 @@ import java.util.zip.ZipFile;
|
|||
|
||||
import org.apache.commons.io.FileUtils;
|
||||
import org.apache.commons.io.filefilter.WildcardFileFilter;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
|
@ -21,6 +22,7 @@ public class BuiltJarDstu2ShadeIT {
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BuiltJarDstu2ShadeIT.class);
|
||||
|
||||
@Test
|
||||
@Ignore
|
||||
public void testParserXml() throws Exception {
|
||||
|
||||
FhirContext ctx = FhirContext.forDstu2();
|
||||
|
|
|
@ -7,7 +7,6 @@ import static org.mockito.Mockito.when;
|
|||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.junit.*;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
|
@ -82,6 +81,7 @@ public class GenericClientDstu3IT {
|
|||
* TODO: narratives don't work without stax
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void testBinaryCreateWithFhirContentType() throws Exception {
|
||||
IParser p = ourCtx.newXmlParser();
|
||||
|
||||
|
@ -142,7 +142,7 @@ public class GenericClientDstu3IT {
|
|||
.returnBundle(Bundle.class)
|
||||
.execute();
|
||||
|
||||
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(idx).url().toString());
|
||||
assertEquals("http://example.com/fhir/Patient?_format=json", capt.getAllValues().get(idx).url().toString());
|
||||
idx++;
|
||||
|
||||
}
|
||||
|
@ -177,12 +177,12 @@ public class GenericClientDstu3IT {
|
|||
Request request = capt.getAllValues().get(0);
|
||||
ourLog.info(request.headers().toString());
|
||||
|
||||
assertEquals("http://example.com/fhir/Binary", request.url().toString());
|
||||
assertEquals("http://example.com/fhir/Binary?_format=json", request.url().toString());
|
||||
validateUserAgent(capt);
|
||||
|
||||
assertEquals(Constants.CT_FHIR_XML_NEW + ";charset=utf-8", request.body().contentType().toString().toLowerCase().replace(" ", ""));
|
||||
assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_NON_LEGACY, request.header("Accept"));
|
||||
assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent());
|
||||
assertEquals(Constants.CT_FHIR_JSON_NEW + ";charset=utf-8", request.body().contentType().toString().toLowerCase().replace(" ", ""));
|
||||
assertEquals(Constants.HEADER_ACCEPT_VALUE_JSON_NON_LEGACY, request.header("Accept"));
|
||||
assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newJsonParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent());
|
||||
|
||||
}
|
||||
|
||||
|
@ -257,11 +257,11 @@ public class GenericClientDstu3IT {
|
|||
assertNotNull(outcome.getResource());
|
||||
|
||||
assertEquals("<div xmlns=\"http://www.w3.org/1999/xhtml\">FINAL VALUE</div>", ((Patient) outcome.getResource()).getText().getDivAsString());
|
||||
assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).url().toString());
|
||||
assertEquals("http://example.com/fhir/Patient?_format=json", capt.getAllValues().get(0).url().toString());
|
||||
}
|
||||
|
||||
|
||||
private ArgumentCaptor<Request> prepareClientForSearchResponse() throws IOException, ClientProtocolException {
|
||||
private ArgumentCaptor<Request> prepareClientForSearchResponse() throws IOException {
|
||||
final String respString = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
|
||||
myHttpResponse = new Response.Builder()
|
||||
.request(myRequest)
|
||||
|
|
|
@ -28,6 +28,7 @@
|
|||
<dependency>
|
||||
<groupId>org.codehaus.woodstox</groupId>
|
||||
<artifactId>woodstox-core-asl</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!-- Only required for narrative generator support -->
|
||||
|
@ -124,6 +125,7 @@
|
|||
<argLine>${argLine} -Dfile.encoding=UTF-8 -Xmx712m</argLine>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<!--
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-source-plugin</artifactId>
|
||||
|
@ -136,6 +138,7 @@
|
|||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
-->
|
||||
</plugins>
|
||||
<resources>
|
||||
<resource>
|
||||
|
|
|
@ -78,6 +78,10 @@ public enum FhirVersionEnum {
|
|||
return myVersionImplementation;
|
||||
}
|
||||
|
||||
public boolean isEqualOrNewerThan(FhirVersionEnum theVersion) {
|
||||
return ordinal() >= theVersion.ordinal();
|
||||
}
|
||||
|
||||
public boolean isEquivalentTo(FhirVersionEnum theVersion) {
|
||||
if (this.equals(theVersion)) {
|
||||
return true;
|
||||
|
|
|
@ -152,7 +152,8 @@ public class RuntimeSearchParam {
|
|||
public enum RuntimeSearchParamStatusEnum {
|
||||
ACTIVE,
|
||||
DRAFT,
|
||||
RETIRED
|
||||
RETIRED,
|
||||
UNKNOWN
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,29 +20,29 @@ package ca.uhn.fhir.model.primitive;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.io.StringReader;
|
||||
import java.io.StringWriter;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import javax.xml.stream.FactoryConfigurationError;
|
||||
import javax.xml.stream.XMLEventReader;
|
||||
import javax.xml.stream.XMLEventWriter;
|
||||
import javax.xml.stream.XMLStreamException;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.model.api.BasePrimitive;
|
||||
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
|
||||
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.util.XmlDetectionUtil;
|
||||
import ca.uhn.fhir.util.XmlUtil;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
/**
|
||||
* Note that as of HAPI FHIR 3.1.0, this method no longer uses
|
||||
* the StAX XMLEvent type as the XML representation, and uses a
|
||||
* String instead. If you need to work with XML as StAX events, you
|
||||
* can use the {@link XmlUtil#parse(String)} and {@link XmlUtil#encode(List)}
|
||||
* methods to do so.
|
||||
*/
|
||||
@DatatypeDef(name = "xhtml")
|
||||
public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
|
||||
public class XhtmlDt extends BasePrimitive<String> {
|
||||
|
||||
private static final String DECL_XMLNS = " xmlns=\"http://www.w3.org/1999/xhtml\"";
|
||||
private static final String DIV_OPEN_FIRST = "<div" + DECL_XMLNS + ">";
|
||||
public static final String DIV_OPEN_FIRST = "<div" + DECL_XMLNS + ">";
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
|
@ -54,7 +54,7 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
|
|||
|
||||
/**
|
||||
* Constructor which accepts a string code
|
||||
*
|
||||
*
|
||||
* @see #setValueAsString(String) for a description of how this value is applied
|
||||
*/
|
||||
@SimpleSetter()
|
||||
|
@ -63,29 +63,12 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected String encode(List<XMLEvent> theValue) {
|
||||
try {
|
||||
StringWriter w = new StringWriter();
|
||||
XMLEventWriter ew = XmlUtil.createXmlFragmentWriter(w);
|
||||
|
||||
for (XMLEvent next : getValue()) {
|
||||
if (next.isCharacters()) {
|
||||
ew.add(next);
|
||||
} else {
|
||||
ew.add(next);
|
||||
}
|
||||
}
|
||||
ew.close();
|
||||
return w.toString();
|
||||
} catch (XMLStreamException e) {
|
||||
throw new DataFormatException("Problem with the contained XML events", e);
|
||||
} catch (FactoryConfigurationError e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
protected String encode(String theValue) {
|
||||
return theValue;
|
||||
}
|
||||
|
||||
public boolean hasContent() {
|
||||
return getValue() != null && getValue().size() > 0;
|
||||
return isNotBlank(getValue());
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -94,40 +77,37 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
|
|||
}
|
||||
|
||||
@Override
|
||||
protected List<XMLEvent> parse(String theValue) {
|
||||
String val = theValue.trim();
|
||||
if (!val.startsWith("<")) {
|
||||
val = DIV_OPEN_FIRST + val + "</div>";
|
||||
}
|
||||
boolean hasProcessingInstruction = val.startsWith("<?");
|
||||
if (hasProcessingInstruction && val.endsWith("?>")) {
|
||||
return null;
|
||||
protected String parse(String theValue) {
|
||||
if (XmlDetectionUtil.isStaxPresent()) {
|
||||
// for validation
|
||||
XmlUtil.parse(theValue);
|
||||
}
|
||||
return theValue;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
ArrayList<XMLEvent> value = new ArrayList<XMLEvent>();
|
||||
StringReader reader = new StringReader(val);
|
||||
XMLEventReader er = XmlUtil.createXmlReader(reader);
|
||||
boolean first = true;
|
||||
while (er.hasNext()) {
|
||||
XMLEvent next = er.nextEvent();
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (er.hasNext()) {
|
||||
// don't add the last event
|
||||
value.add(next);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
} catch (XMLStreamException e) {
|
||||
throw new DataFormatException("String does not appear to be valid XML/XHTML (error is \"" + e.getMessage() + "\"): " + theValue, e);
|
||||
} catch (FactoryConfigurationError e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
/**
|
||||
* Note that as of HAPI FHIR 3.1.0, this method no longer uses
|
||||
* the StAX XMLEvent type as the XML representation, and uses a
|
||||
* String instead. If you need to work with XML as StAX events, you
|
||||
* can use the {@link XmlUtil#parse(String)} and {@link XmlUtil#encode(List)}
|
||||
* methods to do so.
|
||||
*/
|
||||
@Override
|
||||
public String getValue() {
|
||||
return super.getValue();
|
||||
}
|
||||
|
||||
/**
|
||||
* Note that as of HAPI FHIR 3.1.0, this method no longer uses
|
||||
* the StAX XMLEvent type as the XML representation, and uses a
|
||||
* String instead. If you need to work with XML as StAX events, you
|
||||
* can use the {@link XmlUtil#parse(String)} and {@link XmlUtil#encode(List)}
|
||||
* methods to do so.
|
||||
*/
|
||||
@Override
|
||||
public BasePrimitive<String> setValue(String theValue) throws DataFormatException {
|
||||
return super.setValue(theValue);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -157,7 +137,7 @@ public class XhtmlDt extends BasePrimitive<List<XMLEvent>> {
|
|||
if (value.charAt(0) != '<') {
|
||||
value = DIV_OPEN_FIRST + value + "</div>";
|
||||
}
|
||||
|
||||
|
||||
boolean hasProcessingInstruction = value.startsWith("<?");
|
||||
int firstTagIndex = value.indexOf("<", hasProcessingInstruction ? 1 : 0);
|
||||
if (firstTagIndex != -1) {
|
||||
|
|
|
@ -43,7 +43,7 @@ public abstract class BaseParser implements IParser {
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseParser.class);
|
||||
|
||||
private ContainedResources myContainedResources;
|
||||
|
||||
private boolean myEncodeElementsAppliesToChildResourcesOnly;
|
||||
private FhirContext myContext;
|
||||
private Set<String> myDontEncodeElements;
|
||||
private boolean myDontEncodeElementsIncludesStars;
|
||||
|
@ -556,6 +556,16 @@ public abstract class BaseParser implements IParser {
|
|||
&& theIncludedResource == false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isEncodeElementsAppliesToChildResourcesOnly() {
|
||||
return myEncodeElementsAppliesToChildResourcesOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly) {
|
||||
myEncodeElementsAppliesToChildResourcesOnly = theEncodeElementsAppliesToChildResourcesOnly;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isOmitResourceId() {
|
||||
return myOmitResourceId;
|
||||
|
@ -1039,7 +1049,13 @@ public abstract class BaseParser implements IParser {
|
|||
}
|
||||
|
||||
private boolean checkIfParentShouldBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
|
||||
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, myEncodeElements, true);
|
||||
Set<String> encodeElements = myEncodeElements;
|
||||
if (encodeElements != null && encodeElements.isEmpty() == false) {
|
||||
if (isEncodeElementsAppliesToChildResourcesOnly() && !mySubResource) {
|
||||
encodeElements = null;
|
||||
}
|
||||
}
|
||||
return checkIfPathMatchesForEncoding(thePathBuilder, theStarPass, myEncodeElementsAppliesToResourceTypes, encodeElements, true);
|
||||
}
|
||||
|
||||
private boolean checkIfParentShouldNotBeEncodedAndBuildPath(StringBuilder thePathBuilder, boolean theStarPass) {
|
||||
|
@ -1058,6 +1074,9 @@ public abstract class BaseParser implements IParser {
|
|||
} else {
|
||||
thePathBuilder.append(myResDef.getName());
|
||||
}
|
||||
if (theElements == null) {
|
||||
return true;
|
||||
}
|
||||
if (theElements.contains(thePathBuilder.toString())) {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -206,6 +206,22 @@ public interface IParser {
|
|||
*/
|
||||
void setEncodeElements(Set<String> theEncodeElements);
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (default is false), the values supplied
|
||||
* to {@link #setEncodeElements(Set)} will not be applied to the root
|
||||
* resource (typically a Bundle), but will be applied to any sub-resources
|
||||
* contained within it (i.e. search result resources in that bundle)
|
||||
*/
|
||||
void setEncodeElementsAppliesToChildResourcesOnly(boolean theEncodeElementsAppliesToChildResourcesOnly);
|
||||
|
||||
/**
|
||||
* If set to <code>true</code> (default is false), the values supplied
|
||||
* to {@link #setEncodeElements(Set)} will not be applied to the root
|
||||
* resource (typically a Bundle), but will be applied to any sub-resources
|
||||
* contained within it (i.e. search result resources in that bundle)
|
||||
*/
|
||||
boolean isEncodeElementsAppliesToChildResourcesOnly();
|
||||
|
||||
/**
|
||||
* If provided, tells the parse which resource types to apply {@link #setEncodeElements(Set) encode elements} to. Any
|
||||
* resource types not specified here will be encoded completely, with no elements excluded.
|
||||
|
|
|
@ -19,19 +19,6 @@ package ca.uhn.fhir.parser;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.*;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.text.WordUtils;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum;
|
||||
|
@ -46,9 +33,27 @@ import ca.uhn.fhir.parser.json.*;
|
|||
import ca.uhn.fhir.parser.json.JsonLikeValue.ScalarType;
|
||||
import ca.uhn.fhir.parser.json.JsonLikeValue.ValueType;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.util.BinaryUtil;
|
||||
import ca.uhn.fhir.util.ElementUtil;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.GsonBuilder;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.text.WordUtils;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.Reader;
|
||||
import java.io.Writer;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.ID_DATATYPE;
|
||||
import static ca.uhn.fhir.context.BaseRuntimeElementDefinition.ChildTypeEnum.PRIMITIVE_DATATYPE;
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
/**
|
||||
* This class is the FHIR JSON parser/encoder. Users should not interact with this class directly, but should use
|
||||
|
@ -88,7 +93,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
private boolean addToHeldExtensions(int valueIdx, List<? extends IBaseExtension<?, ?>> ext, ArrayList<ArrayList<HeldExtension>> list, boolean theIsModifier, CompositeChildElement theChildElem,
|
||||
CompositeChildElement theParent) {
|
||||
CompositeChildElement theParent) {
|
||||
if (ext.size() > 0) {
|
||||
list.ensureCapacity(valueIdx);
|
||||
while (list.size() <= valueIdx) {
|
||||
|
@ -139,12 +144,6 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException {
|
||||
JsonLikeWriter eventWriter = createJsonWriter(theWriter);
|
||||
doEncodeResourceToJsonLikeWriter(theResource, eventWriter);
|
||||
}
|
||||
|
||||
public void doEncodeResourceToJsonLikeWriter(IBaseResource theResource, JsonLikeWriter theEventWriter) throws IOException {
|
||||
if (myPrettyPrint) {
|
||||
theEventWriter.setPrettyPrint(myPrettyPrint);
|
||||
|
@ -156,6 +155,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
theEventWriter.flush();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doEncodeResourceToWriter(IBaseResource theResource, Writer theWriter) throws IOException {
|
||||
JsonLikeWriter eventWriter = createJsonWriter(theWriter);
|
||||
doEncodeResourceToJsonLikeWriter(theResource, eventWriter);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T extends IBaseResource> T doParseResource(Class<T> theResourceType, Reader theReader) {
|
||||
JsonLikeStructure jsonStructure = new GsonStructure();
|
||||
|
@ -191,136 +196,136 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
private void encodeChildElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, IBase theNextValue,
|
||||
BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, boolean theSubResource, CompositeChildElement theChildElem,
|
||||
boolean theForceEmpty) throws IOException {
|
||||
BaseRuntimeElementDefinition<?> theChildDef, String theChildName, boolean theContainedResource, boolean theSubResource, CompositeChildElement theChildElem,
|
||||
boolean theForceEmpty) throws IOException {
|
||||
|
||||
switch (theChildDef.getChildType()) {
|
||||
case ID_DATATYPE: {
|
||||
IIdType value = (IIdType) theNextValue;
|
||||
String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue();
|
||||
if (isBlank(encodedValue)) {
|
||||
break;
|
||||
}
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, encodedValue);
|
||||
} else {
|
||||
theEventWriter.write(encodedValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PRIMITIVE_DATATYPE: {
|
||||
final IPrimitiveType<?> value = (IPrimitiveType<?>) theNextValue;
|
||||
if (isBlank(value.getValueAsString())) {
|
||||
if (theForceEmpty) {
|
||||
theEventWriter.writeNull();
|
||||
case ID_DATATYPE: {
|
||||
IIdType value = (IIdType) theNextValue;
|
||||
String encodedValue = "id".equals(theChildName) ? value.getIdPart() : value.getValue();
|
||||
if (isBlank(encodedValue)) {
|
||||
break;
|
||||
}
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, encodedValue);
|
||||
} else {
|
||||
theEventWriter.write(encodedValue);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
if (value instanceof IBaseIntegerDatatype) {
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, ((IBaseIntegerDatatype) value).getValue());
|
||||
} else {
|
||||
theEventWriter.write(((IBaseIntegerDatatype) value).getValue());
|
||||
}
|
||||
} else if (value instanceof IBaseDecimalDatatype) {
|
||||
BigDecimal decimalValue = ((IBaseDecimalDatatype) value).getValue();
|
||||
decimalValue = new BigDecimal(decimalValue.toString()) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value.getValueAsString();
|
||||
case PRIMITIVE_DATATYPE: {
|
||||
final IPrimitiveType<?> value = (IPrimitiveType<?>) theNextValue;
|
||||
if (isBlank(value.getValueAsString())) {
|
||||
if (theForceEmpty) {
|
||||
theEventWriter.writeNull();
|
||||
}
|
||||
};
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, decimalValue);
|
||||
} else {
|
||||
theEventWriter.write(decimalValue);
|
||||
break;
|
||||
}
|
||||
} else if (value instanceof IBaseBooleanDatatype) {
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, ((IBaseBooleanDatatype) value).getValue());
|
||||
|
||||
if (value instanceof IBaseIntegerDatatype) {
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, ((IBaseIntegerDatatype) value).getValue());
|
||||
} else {
|
||||
theEventWriter.write(((IBaseIntegerDatatype) value).getValue());
|
||||
}
|
||||
} else if (value instanceof IBaseDecimalDatatype) {
|
||||
BigDecimal decimalValue = ((IBaseDecimalDatatype) value).getValue();
|
||||
decimalValue = new BigDecimal(decimalValue.toString()) {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return value.getValueAsString();
|
||||
}
|
||||
};
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, decimalValue);
|
||||
} else {
|
||||
theEventWriter.write(decimalValue);
|
||||
}
|
||||
} else if (value instanceof IBaseBooleanDatatype) {
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, ((IBaseBooleanDatatype) value).getValue());
|
||||
} else {
|
||||
Boolean booleanValue = ((IBaseBooleanDatatype) value).getValue();
|
||||
if (booleanValue != null) {
|
||||
theEventWriter.write(booleanValue.booleanValue());
|
||||
}
|
||||
}
|
||||
} else {
|
||||
Boolean booleanValue = ((IBaseBooleanDatatype) value).getValue();
|
||||
if (booleanValue != null) {
|
||||
theEventWriter.write(booleanValue.booleanValue());
|
||||
String valueStr = value.getValueAsString();
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, valueStr);
|
||||
} else {
|
||||
theEventWriter.write(valueStr);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
String valueStr = value.getValueAsString();
|
||||
break;
|
||||
}
|
||||
case RESOURCE_BLOCK:
|
||||
case COMPOSITE_DATATYPE: {
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, valueStr);
|
||||
theEventWriter.beginObject(theChildName);
|
||||
} else {
|
||||
theEventWriter.write(valueStr);
|
||||
theEventWriter.beginObject();
|
||||
}
|
||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theChildElem);
|
||||
theEventWriter.endObject();
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RESOURCE_BLOCK:
|
||||
case COMPOSITE_DATATYPE: {
|
||||
if (theChildName != null) {
|
||||
theEventWriter.beginObject(theChildName);
|
||||
} else {
|
||||
theEventWriter.beginObject();
|
||||
}
|
||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theChildElem);
|
||||
theEventWriter.endObject();
|
||||
break;
|
||||
}
|
||||
case CONTAINED_RESOURCE_LIST:
|
||||
case CONTAINED_RESOURCES: {
|
||||
case CONTAINED_RESOURCE_LIST:
|
||||
case CONTAINED_RESOURCES: {
|
||||
/*
|
||||
* Disabled per #103 ContainedDt value = (ContainedDt) theNextValue; for (IResource next :
|
||||
* value.getContainedResources()) { if (getContainedResources().getResourceId(next) != null) { continue; }
|
||||
* encodeResourceToJsonStreamWriter(theResDef, next, theWriter, null, true,
|
||||
* fixContainedResourceId(next.getId().getValue())); }
|
||||
*/
|
||||
List<IBaseResource> containedResources = getContainedResources().getContainedResources();
|
||||
if (containedResources.size() > 0) {
|
||||
beginArray(theEventWriter, theChildName);
|
||||
List<IBaseResource> containedResources = getContainedResources().getContainedResources();
|
||||
if (containedResources.size() > 0) {
|
||||
beginArray(theEventWriter, theChildName);
|
||||
|
||||
for (IBaseResource next : containedResources) {
|
||||
IIdType resourceId = getContainedResources().getResourceId(next);
|
||||
encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, false, fixContainedResourceId(resourceId.getValue()));
|
||||
}
|
||||
for (IBaseResource next : containedResources) {
|
||||
IIdType resourceId = getContainedResources().getResourceId(next);
|
||||
encodeResourceToJsonStreamWriter(theResDef, next, theEventWriter, null, true, false, fixContainedResourceId(resourceId.getValue()));
|
||||
}
|
||||
|
||||
theEventWriter.endArray();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case PRIMITIVE_XHTML_HL7ORG:
|
||||
case PRIMITIVE_XHTML: {
|
||||
if (!isSuppressNarratives()) {
|
||||
IPrimitiveType<?> dt = (IPrimitiveType<?>) theNextValue;
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, dt.getValueAsString());
|
||||
} else {
|
||||
theEventWriter.write(dt.getValueAsString());
|
||||
}
|
||||
} else {
|
||||
if (theChildName != null) {
|
||||
// do nothing
|
||||
} else {
|
||||
theEventWriter.writeNull();
|
||||
theEventWriter.endArray();
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RESOURCE:
|
||||
IBaseResource resource = (IBaseResource) theNextValue;
|
||||
RuntimeResourceDefinition def = myContext.getResourceDefinition(resource);
|
||||
encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, false, true);
|
||||
break;
|
||||
case UNDECL_EXT:
|
||||
default:
|
||||
throw new IllegalStateException("Should not have this state here: " + theChildDef.getChildType().name());
|
||||
case PRIMITIVE_XHTML_HL7ORG:
|
||||
case PRIMITIVE_XHTML: {
|
||||
if (!isSuppressNarratives()) {
|
||||
IPrimitiveType<?> dt = (IPrimitiveType<?>) theNextValue;
|
||||
if (theChildName != null) {
|
||||
write(theEventWriter, theChildName, dt.getValueAsString());
|
||||
} else {
|
||||
theEventWriter.write(dt.getValueAsString());
|
||||
}
|
||||
} else {
|
||||
if (theChildName != null) {
|
||||
// do nothing
|
||||
} else {
|
||||
theEventWriter.writeNull();
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case RESOURCE:
|
||||
IBaseResource resource = (IBaseResource) theNextValue;
|
||||
RuntimeResourceDefinition def = myContext.getResourceDefinition(resource);
|
||||
encodeResourceToJsonStreamWriter(def, resource, theEventWriter, theChildName, false, true);
|
||||
break;
|
||||
case UNDECL_EXT:
|
||||
default:
|
||||
throw new IllegalStateException("Should not have this state here: " + theChildDef.getChildType().name());
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void encodeCompositeElementChildrenToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theElement, JsonLikeWriter theEventWriter,
|
||||
boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) throws IOException {
|
||||
boolean theContainedResource, boolean theSubResource, CompositeChildElement theParent) throws IOException {
|
||||
|
||||
{
|
||||
String elementId = getCompositeElementId(theElement);
|
||||
|
@ -335,7 +340,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
BaseRuntimeChildDefinition nextChild = nextChildElem.getDef();
|
||||
|
||||
if (nextChildElem.getDef().getElementName().equals("extension") || nextChildElem.getDef().getElementName().equals("modifierExtension")
|
||||
|| nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
|
||||
|| nextChild instanceof RuntimeChildDeclaredExtensionDefinition) {
|
||||
if (!haveWrittenExtensions) {
|
||||
extractAndWriteExtensionsAsDirectChild(theElement, theEventWriter, myContext.getElementDefinition(theElement.getClass()), theResDef, theResource, nextChildElem, theParent);
|
||||
haveWrittenExtensions = true;
|
||||
|
@ -451,15 +456,15 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
if (nextChild.getMax() > 1 || nextChild.getMax() == Child.MAX_UNLIMITED) {
|
||||
beginArray(theEventWriter, childName);
|
||||
inArray = true;
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource,nextChildElem, force);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource, nextChildElem, force);
|
||||
} else if (nextChild instanceof RuntimeChildNarrativeDefinition && theContainedResource) {
|
||||
// suppress narratives from contained resources
|
||||
} else {
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, theSubResource,nextChildElem, false);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, childName, theContainedResource, theSubResource, nextChildElem, false);
|
||||
}
|
||||
currentChildName = childName;
|
||||
} else {
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource,theSubResource, nextChildElem, force);
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, nextValue, childDef, null, theContainedResource, theSubResource, nextChildElem, force);
|
||||
}
|
||||
|
||||
valueIdx++;
|
||||
|
@ -541,7 +546,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
private void encodeCompositeElementToStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, IBase theNextValue, JsonLikeWriter theEventWriter, boolean theContainedResource, boolean theSubResource,
|
||||
CompositeChildElement theParent) throws IOException, DataFormatException {
|
||||
CompositeChildElement theParent) throws IOException, DataFormatException {
|
||||
|
||||
writeCommentsPreAndPost(theNextValue, theEventWriter);
|
||||
encodeCompositeElementChildrenToStreamWriter(theResDef, theResource, theNextValue, theEventWriter, theContainedResource, theSubResource, theParent);
|
||||
|
@ -554,14 +559,14 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
|
||||
if (theResource.getStructureFhirVersionEnum() != myContext.getVersion().getVersion()) {
|
||||
throw new IllegalArgumentException(
|
||||
"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
|
||||
"This parser is for FHIR version " + myContext.getVersion().getVersion() + " - Can not encode a structure for version " + theResource.getStructureFhirVersionEnum());
|
||||
}
|
||||
|
||||
doEncodeResourceToJsonLikeWriter(theResource, theJsonLikeWriter);
|
||||
}
|
||||
|
||||
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
|
||||
boolean theContainedResource, boolean theSubResource) throws IOException {
|
||||
boolean theContainedResource, boolean theSubResource) throws IOException {
|
||||
IIdType resourceId = null;
|
||||
// if (theResource instanceof IResource) {
|
||||
// IResource res = (IResource) theResource;
|
||||
|
@ -598,7 +603,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
private void encodeResourceToJsonStreamWriter(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter, String theObjectNameOrNull,
|
||||
boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws IOException {
|
||||
boolean theContainedResource, boolean theSubResource, IIdType theResourceId) throws IOException {
|
||||
if (!theContainedResource) {
|
||||
super.containResourcesForEncoding(theResource);
|
||||
}
|
||||
|
@ -612,28 +617,28 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
write(theEventWriter, "resourceType", resDef.getName());
|
||||
if (theResourceId != null && theResourceId.hasIdPart()) {
|
||||
write(theEventWriter, "id", theResourceId.getIdPart());
|
||||
final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
|
||||
final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
|
||||
// Undeclared extensions
|
||||
extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null);
|
||||
boolean haveExtension = false;
|
||||
if (!extensions.isEmpty()) {
|
||||
haveExtension = true;
|
||||
}
|
||||
if (theResourceId != null && theResourceId.hasIdPart()) {
|
||||
write(theEventWriter, "id", theResourceId.getIdPart());
|
||||
final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
|
||||
final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
|
||||
// Undeclared extensions
|
||||
extractUndeclaredExtensions(theResourceId, extensions, modifierExtensions, null, null);
|
||||
boolean haveExtension = false;
|
||||
if (!extensions.isEmpty()) {
|
||||
haveExtension = true;
|
||||
}
|
||||
|
||||
if (theResourceId.hasFormatComment() || haveExtension) {
|
||||
beginObject(theEventWriter, "_id");
|
||||
if (theResourceId.hasFormatComment()) {
|
||||
writeCommentsPreAndPost(theResourceId, theEventWriter);
|
||||
}
|
||||
if (haveExtension) {
|
||||
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
|
||||
}
|
||||
theEventWriter.endObject();
|
||||
}
|
||||
}
|
||||
if (theResourceId.hasFormatComment() || haveExtension) {
|
||||
beginObject(theEventWriter, "_id");
|
||||
if (theResourceId.hasFormatComment()) {
|
||||
writeCommentsPreAndPost(theResourceId, theEventWriter);
|
||||
}
|
||||
if (haveExtension) {
|
||||
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
|
||||
}
|
||||
theEventWriter.endObject();
|
||||
}
|
||||
}
|
||||
|
||||
if (theResource instanceof IResource) {
|
||||
IResource resource = (IResource) theResource;
|
||||
|
@ -695,19 +700,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
}
|
||||
|
||||
if (theResource instanceof IBaseBinary) {
|
||||
IBaseBinary bin = (IBaseBinary) theResource;
|
||||
String contentType = bin.getContentType();
|
||||
if (isNotBlank(contentType)) {
|
||||
write(theEventWriter, "contentType", contentType);
|
||||
}
|
||||
String contentAsBase64 = bin.getContentAsBase64();
|
||||
if (isNotBlank(contentAsBase64)) {
|
||||
write(theEventWriter, "content", contentAsBase64);
|
||||
}
|
||||
} else {
|
||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
|
||||
}
|
||||
encodeCompositeElementToStreamWriter(theResDef, theResource, theResource, theEventWriter, theContainedResource, theSubResource, new CompositeChildElement(resDef, theSubResource));
|
||||
|
||||
theEventWriter.endObject();
|
||||
}
|
||||
|
@ -715,12 +708,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
/**
|
||||
* This is useful only for the two cases where extensions are encoded as direct children (e.g. not in some object
|
||||
* called _name): resource extensions, and extension extensions
|
||||
*
|
||||
*
|
||||
* @param theChildElem
|
||||
* @param theParent
|
||||
*/
|
||||
private void extractAndWriteExtensionsAsDirectChild(IBase theElement, JsonLikeWriter theEventWriter, BaseRuntimeElementDefinition<?> theElementDef, RuntimeResourceDefinition theResDef,
|
||||
IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent) throws IOException {
|
||||
IBaseResource theResource, CompositeChildElement theChildElem, CompositeChildElement theParent) throws IOException {
|
||||
List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
|
||||
List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
|
||||
|
||||
|
@ -737,7 +730,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
private void extractDeclaredExtensions(IBase theResource, BaseRuntimeElementDefinition<?> resDef, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions,
|
||||
CompositeChildElement theChildElem) {
|
||||
CompositeChildElement theChildElem) {
|
||||
for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) {
|
||||
for (IBase nextValue : nextDef.getAccessor().getValues(theResource)) {
|
||||
if (nextValue != null) {
|
||||
|
@ -761,7 +754,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
private void extractUndeclaredExtensions(IBase theElement, List<HeldExtension> extensions, List<HeldExtension> modifierExtensions, CompositeChildElement theChildElem,
|
||||
CompositeChildElement theParent) {
|
||||
CompositeChildElement theParent) {
|
||||
if (theElement instanceof ISupportsUndeclaredExtensions) {
|
||||
ISupportsUndeclaredExtensions element = (ISupportsUndeclaredExtensions) theElement;
|
||||
List<ExtensionDt> ext = element.getUndeclaredExtensions();
|
||||
|
@ -1029,78 +1022,78 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
}
|
||||
|
||||
private void parseExtension(ParserState<?> theState, JsonLikeArray theValues, boolean theIsModifier) {
|
||||
int allUnderscoreNames = 0;
|
||||
int handledUnderscoreNames = 0;
|
||||
private void parseExtension(ParserState<?> theState, JsonLikeArray theValues, boolean theIsModifier) {
|
||||
int allUnderscoreNames = 0;
|
||||
int handledUnderscoreNames = 0;
|
||||
|
||||
for (int i = 0; i < theValues.size(); i++) {
|
||||
JsonLikeObject nextExtObj = JsonLikeValue.asObject(theValues.get(i));
|
||||
JsonLikeValue jsonElement = nextExtObj.get("url");
|
||||
String url;
|
||||
if (null == jsonElement || !(jsonElement.isScalar())) {
|
||||
String parentElementName;
|
||||
if (theIsModifier) {
|
||||
parentElementName = "modifierExtension";
|
||||
} else {
|
||||
parentElementName = "extension";
|
||||
}
|
||||
getErrorHandler().missingRequiredElement(new ParseLocation(parentElementName), "url");
|
||||
url = null;
|
||||
} else {
|
||||
url = getExtensionUrl(jsonElement.getAsString());
|
||||
}
|
||||
theState.enteringNewElementExtension(null, url, theIsModifier, getServerBaseUrl());
|
||||
for (String next : nextExtObj.keySet()) {
|
||||
if ("url".equals(next)) {
|
||||
continue;
|
||||
} else if ("extension".equals(next)) {
|
||||
JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next));
|
||||
parseExtension(theState, jsonVal, false);
|
||||
} else if ("modifierExtension".equals(next)) {
|
||||
JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next));
|
||||
parseExtension(theState, jsonVal, true);
|
||||
} else if (next.charAt(0) == '_') {
|
||||
allUnderscoreNames++;
|
||||
continue;
|
||||
} else {
|
||||
JsonLikeValue jsonVal = nextExtObj.get(next);
|
||||
String alternateName = '_' + next;
|
||||
JsonLikeValue alternateVal = nextExtObj.get(alternateName);
|
||||
if (alternateVal != null) {
|
||||
handledUnderscoreNames++;
|
||||
}
|
||||
parseChildren(theState, next, jsonVal, alternateVal, alternateName, false);
|
||||
}
|
||||
}
|
||||
|
||||
for (int i = 0; i < theValues.size(); i++) {
|
||||
JsonLikeObject nextExtObj = JsonLikeValue.asObject(theValues.get(i));
|
||||
JsonLikeValue jsonElement = nextExtObj.get("url");
|
||||
String url;
|
||||
if (null == jsonElement || !(jsonElement.isScalar())) {
|
||||
String parentElementName;
|
||||
if (theIsModifier) {
|
||||
parentElementName = "modifierExtension";
|
||||
} else {
|
||||
parentElementName = "extension";
|
||||
}
|
||||
getErrorHandler().missingRequiredElement(new ParseLocation(parentElementName), "url");
|
||||
url = null;
|
||||
} else {
|
||||
url = getExtensionUrl(jsonElement.getAsString());
|
||||
}
|
||||
theState.enteringNewElementExtension(null, url, theIsModifier, getServerBaseUrl());
|
||||
for (String next : nextExtObj.keySet()) {
|
||||
if ("url".equals(next)) {
|
||||
continue;
|
||||
} else if ("extension".equals(next)) {
|
||||
JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next));
|
||||
parseExtension(theState, jsonVal, false);
|
||||
} else if ("modifierExtension".equals(next)) {
|
||||
JsonLikeArray jsonVal = JsonLikeValue.asArray(nextExtObj.get(next));
|
||||
parseExtension(theState, jsonVal, true);
|
||||
} else if (next.charAt(0) == '_') {
|
||||
allUnderscoreNames++;
|
||||
continue;
|
||||
} else {
|
||||
JsonLikeValue jsonVal = nextExtObj.get(next);
|
||||
String alternateName = '_' + next;
|
||||
JsonLikeValue alternateVal = nextExtObj.get(alternateName);
|
||||
if (alternateVal != null) {
|
||||
handledUnderscoreNames++;
|
||||
}
|
||||
parseChildren(theState, next, jsonVal, alternateVal, alternateName, false);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* This happens if an element has an extension but no actual value. I.e.
|
||||
* This happens if an element has an extension but no actual value. I.e.
|
||||
* if a resource has a "_status" element but no corresponding "status"
|
||||
* element. This could be used to handle a null value with an extension
|
||||
* for example.
|
||||
*/
|
||||
if (allUnderscoreNames > handledUnderscoreNames) {
|
||||
for (String alternateName : nextExtObj.keySet()) {
|
||||
if (alternateName.startsWith("_") && alternateName.length() > 1) {
|
||||
JsonLikeValue nextValue = nextExtObj.get(alternateName);
|
||||
if (nextValue != null) {
|
||||
if (nextValue.isObject()) {
|
||||
String nextName = alternateName.substring(1);
|
||||
if (nextExtObj.get(nextName) == null) {
|
||||
theState.enteringNewElement(null, nextName);
|
||||
parseAlternates(nextValue, theState, alternateName, alternateName);
|
||||
theState.endingElement();
|
||||
}
|
||||
} else {
|
||||
getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
theState.endingElement();
|
||||
}
|
||||
}
|
||||
if (allUnderscoreNames > handledUnderscoreNames) {
|
||||
for (String alternateName : nextExtObj.keySet()) {
|
||||
if (alternateName.startsWith("_") && alternateName.length() > 1) {
|
||||
JsonLikeValue nextValue = nextExtObj.get(alternateName);
|
||||
if (nextValue != null) {
|
||||
if (nextValue.isObject()) {
|
||||
String nextName = alternateName.substring(1);
|
||||
if (nextExtObj.get(nextName) == null) {
|
||||
theState.enteringNewElement(null, nextName);
|
||||
parseAlternates(nextValue, theState, alternateName, alternateName);
|
||||
theState.endingElement();
|
||||
}
|
||||
} else {
|
||||
getErrorHandler().incorrectJsonType(null, alternateName, ValueType.OBJECT, null, nextValue.getJsonType(), null);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
theState.endingElement();
|
||||
}
|
||||
}
|
||||
|
||||
private void parseFhirComments(JsonLikeValue theObject, ParserState<?> theState) {
|
||||
if (theObject.isArray()) {
|
||||
|
@ -1254,7 +1247,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
}
|
||||
|
||||
private void writeExtensionsAsDirectChild(IBaseResource theResource, JsonLikeWriter theEventWriter, RuntimeResourceDefinition resDef, List<HeldExtension> extensions,
|
||||
List<HeldExtension> modifierExtensions) throws IOException {
|
||||
List<HeldExtension> modifierExtensions) throws IOException {
|
||||
if (extensions.isEmpty() == false) {
|
||||
beginArray(theEventWriter, "extension");
|
||||
for (HeldExtension next : extensions) {
|
||||
|
@ -1328,6 +1321,28 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
return url1.compareTo(url2);
|
||||
}
|
||||
|
||||
private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final JsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition<?> def, final String childName) throws IOException {
|
||||
if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) {
|
||||
final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
|
||||
final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
|
||||
// Undeclared extensions
|
||||
extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null);
|
||||
// Declared extensions
|
||||
if (def != null) {
|
||||
extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent);
|
||||
}
|
||||
boolean haveContent = false;
|
||||
if (!extensions.isEmpty() || !modifierExtensions.isEmpty()) {
|
||||
haveContent = true;
|
||||
}
|
||||
if (haveContent) {
|
||||
beginObject(theEventWriter, '_' + childName);
|
||||
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
|
||||
theEventWriter.endObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void write(RuntimeResourceDefinition theResDef, IBaseResource theResource, JsonLikeWriter theEventWriter) throws IOException {
|
||||
if (myUndeclaredExtension != null) {
|
||||
writeUndeclaredExtension(theResDef, theResource, theEventWriter, myUndeclaredExtension);
|
||||
|
@ -1341,7 +1356,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
/*
|
||||
* This makes sure that even if the extension contains a reference to a contained
|
||||
* resource which has a HAPI-assigned ID we'll still encode that ID.
|
||||
*
|
||||
*
|
||||
* See #327
|
||||
*/
|
||||
List<? extends IBase> preProcessedValue = preProcessValues(myDef, theResource, Collections.singletonList(myValue), myChildElem);
|
||||
|
@ -1367,7 +1382,7 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
} else {
|
||||
String childName = myDef.getChildNameByDatatype(myValue.getClass());
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, myValue, def, childName, false, false, myParent, false);
|
||||
managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName);
|
||||
managePrimitiveExtension(myValue, theResDef, theResource, theEventWriter, def, childName);
|
||||
}
|
||||
|
||||
theEventWriter.endObject();
|
||||
|
@ -1422,34 +1437,12 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
|
|||
throw new ConfigurationException("Unable to encode extension, unregognized child element type: " + value.getClass().getCanonicalName());
|
||||
}
|
||||
encodeChildElementToStreamWriter(theResDef, theResource, theEventWriter, value, childDef, childName, true, false, myParent, false);
|
||||
managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName);
|
||||
managePrimitiveExtension(value, theResDef, theResource, theEventWriter, childDef, childName);
|
||||
}
|
||||
|
||||
// theEventWriter.name(myUndeclaredExtension.get);
|
||||
|
||||
theEventWriter.endObject();
|
||||
}
|
||||
|
||||
private void managePrimitiveExtension(final IBase theValue, final RuntimeResourceDefinition theResDef, final IBaseResource theResource, final JsonLikeWriter theEventWriter, final BaseRuntimeElementDefinition<?> def, final String childName) throws IOException {
|
||||
if (def.getChildType().equals(ID_DATATYPE) || def.getChildType().equals(PRIMITIVE_DATATYPE)) {
|
||||
final List<HeldExtension> extensions = new ArrayList<HeldExtension>(0);
|
||||
final List<HeldExtension> modifierExtensions = new ArrayList<HeldExtension>(0);
|
||||
// Undeclared extensions
|
||||
extractUndeclaredExtensions(theValue, extensions, modifierExtensions, myParent, null);
|
||||
// Declared extensions
|
||||
if (def != null) {
|
||||
extractDeclaredExtensions(theValue, def, extensions, modifierExtensions, myParent);
|
||||
}
|
||||
boolean haveContent = false;
|
||||
if (!extensions.isEmpty() || !modifierExtensions.isEmpty()) {
|
||||
haveContent = true;
|
||||
}
|
||||
if (haveContent) {
|
||||
beginObject(theEventWriter, '_' + childName);
|
||||
writeExtensionsAsDirectChild(theResource, theEventWriter, theResDef, extensions, modifierExtensions);
|
||||
theEventWriter.endObject();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -606,7 +606,7 @@ class ParserState<T> {
|
|||
return;
|
||||
}
|
||||
case RESOURCE: {
|
||||
if (myInstance instanceof IAnyResource || myInstance instanceof IBaseBackboneElement) {
|
||||
if (myInstance instanceof IAnyResource || myInstance instanceof IBaseBackboneElement || myInstance instanceof IBaseElement) {
|
||||
ParserState<T>.PreResourceStateHl7Org state = new PreResourceStateHl7Org(myInstance, child.getMutator(), null);
|
||||
push(state);
|
||||
} else {
|
||||
|
@ -1559,7 +1559,8 @@ class ParserState<T> {
|
|||
|
||||
if (theEvent.isEndElement()) {
|
||||
if (myDepth == 0) {
|
||||
myDt.setValue(myEvents);
|
||||
String eventsAsString = XmlUtil.encode(myEvents);
|
||||
myDt.setValue(eventsAsString);
|
||||
doPop();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
import java.io.Writer;
|
||||
import java.util.*;
|
||||
|
||||
|
@ -605,13 +606,16 @@ public class XmlParser extends BaseParser /* implements IParser */ {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
private void encodeXhtml(XhtmlDt theDt, XMLStreamWriter theEventWriter) throws XMLStreamException {
|
||||
if (theDt == null || theDt.getValue() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
List<XMLEvent> events = XmlUtil.parse(theDt.getValue());
|
||||
boolean firstElement = true;
|
||||
for (XMLEvent event : theDt.getValue()) {
|
||||
|
||||
for (XMLEvent event : events) {
|
||||
switch (event.getEventType()) {
|
||||
case XMLStreamConstants.ATTRIBUTE:
|
||||
Attribute attr = (Attribute) event;
|
||||
|
|
|
@ -0,0 +1,128 @@
|
|||
package ca.uhn.fhir.rest.api;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.StringTokenizer;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.trim;
|
||||
|
||||
/**
|
||||
* Parses and stores the value(s) within HTTP Cache-Control headers
|
||||
*/
|
||||
public class CacheControlDirective {
|
||||
|
||||
private static final String MAX_RESULTS_EQUALS = Constants.CACHE_CONTROL_MAX_RESULTS + "=";
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(CacheControlDirective.class);
|
||||
private boolean myNoCache;
|
||||
private boolean myNoStore;
|
||||
private Integer myMaxResults;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public CacheControlDirective() {
|
||||
super();
|
||||
}
|
||||
|
||||
/**
|
||||
* If the {@link #isNoStore() no-store} directive is set, this HAPI FHIR extention
|
||||
* to the <code>Cache-Control</code> header called <code>max-results=123</code>
|
||||
* specified the maximum number of results which will be fetched from the
|
||||
* database before returning.
|
||||
*/
|
||||
public Integer getMaxResults() {
|
||||
return myMaxResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* If the {@link #isNoStore() no-store} directive is set, this HAPI FHIR extention
|
||||
* to the <code>Cache-Control</code> header called <code>max-results=123</code>
|
||||
* specified the maximum number of results which will be fetched from the
|
||||
* database before returning.
|
||||
*/
|
||||
public CacheControlDirective setMaxResults(Integer theMaxResults) {
|
||||
myMaxResults = theMaxResults;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* If <code>true<</code>, adds the <code>no-cache</code> directive to the
|
||||
* request. This directive indicates that the cache should not be used to
|
||||
* serve this request.
|
||||
*/
|
||||
public boolean isNoCache() {
|
||||
return myNoCache;
|
||||
}
|
||||
|
||||
/**
|
||||
* If <code>true<</code>, adds the <code>no-cache</code> directive to the
|
||||
* request. This directive indicates that the cache should not be used to
|
||||
* serve this request.
|
||||
*/
|
||||
public CacheControlDirective setNoCache(boolean theNoCache) {
|
||||
myNoCache = theNoCache;
|
||||
return this;
|
||||
}
|
||||
|
||||
public boolean isNoStore() {
|
||||
return myNoStore;
|
||||
}
|
||||
|
||||
public CacheControlDirective setNoStore(boolean theNoStore) {
|
||||
myNoStore = theNoStore;
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a list of <code>Cache-Control</code> header values
|
||||
*
|
||||
* @param theValues The <code>Cache-Control</code> header values
|
||||
*/
|
||||
public CacheControlDirective parse(List<String> theValues) {
|
||||
if (theValues != null) {
|
||||
for (String nextValue : theValues) {
|
||||
StringTokenizer tok = new StringTokenizer(nextValue, ",");
|
||||
while (tok.hasMoreTokens()) {
|
||||
String next = trim(tok.nextToken());
|
||||
if (Constants.CACHE_CONTROL_NO_CACHE.equals(next)) {
|
||||
myNoCache = true;
|
||||
} else if (Constants.CACHE_CONTROL_NO_STORE.equals(next)) {
|
||||
myNoStore = true;
|
||||
} else if (next.startsWith(MAX_RESULTS_EQUALS)) {
|
||||
String valueString = trim(next.substring(MAX_RESULTS_EQUALS.length()));
|
||||
try {
|
||||
myMaxResults = Integer.parseInt(valueString);
|
||||
} catch (NumberFormatException e) {
|
||||
ourLog.warn("Invalid {} value: {}", Constants.CACHE_CONTROL_MAX_RESULTS, valueString);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -25,9 +25,22 @@ import java.util.*;
|
|||
|
||||
public class Constants {
|
||||
|
||||
public static final String CACHE_CONTROL_MAX_RESULTS = "max-results";
|
||||
public static final String CACHE_CONTROL_NO_CACHE = "no-cache";
|
||||
public static final String CACHE_CONTROL_NO_STORE = "no-store";
|
||||
public static final String CHARSET_NAME_UTF8 = "UTF-8";
|
||||
public static final Charset CHARSET_UTF8;
|
||||
public static final String CHARSET_UTF8_CTSUFFIX = "; charset=" + CHARSET_NAME_UTF8;
|
||||
/**
|
||||
* Contains a standard set of headers which are used by FHIR / HAPI FHIR, and therefore
|
||||
* would make a useful set for CORS AllowedHeader declarations
|
||||
*/
|
||||
public static final Set<String> CORS_ALLOWED_HEADERS;
|
||||
/**
|
||||
* Contains a standard set of HTTP Methods which are used by FHIR / HAPI FHIR, and therefore
|
||||
* would make a useful set for CORS AllowedMethod declarations
|
||||
*/
|
||||
public static final Set<String> CORS_ALLWED_METHODS;
|
||||
public static final String CT_FHIR_JSON = "application/json+fhir";
|
||||
public static final String CT_FHIR_JSON_NEW = "application/fhir+json";
|
||||
public static final String CT_FHIR_XML = "application/xml+fhir";
|
||||
|
@ -67,6 +80,7 @@ public class Constants {
|
|||
public static final String HEADER_AUTHORIZATION = "Authorization";
|
||||
public static final String HEADER_AUTHORIZATION_VALPREFIX_BASIC = "Basic ";
|
||||
public static final String HEADER_AUTHORIZATION_VALPREFIX_BEARER = "Bearer ";
|
||||
public static final String HEADER_CACHE_CONTROL = "Cache-Control";
|
||||
public static final String HEADER_CONTENT_DISPOSITION = "Content-Disposition";
|
||||
public static final String HEADER_CONTENT_ENCODING = "Content-Encoding";
|
||||
public static final String HEADER_CONTENT_LOCATION = "Content-Location";
|
||||
|
@ -172,12 +186,13 @@ public class Constants {
|
|||
public static final String URL_TOKEN_METADATA = "metadata";
|
||||
public static final String OO_INFOSTATUS_PROCESSING = "processing";
|
||||
public static final String PARAM_GRAPHQL_QUERY = "query";
|
||||
public static final String HEADER_X_CACHE = "X-Cache";
|
||||
public static final String HEADER_X_SECURITY_CONTEXT = "X-Security-Context";
|
||||
|
||||
static {
|
||||
CHARSET_UTF8 = Charset.forName(CHARSET_NAME_UTF8);
|
||||
|
||||
HashMap<Integer, String> statusNames = new HashMap<Integer, String>();
|
||||
|
||||
HashMap<Integer, String> statusNames = new HashMap<>();
|
||||
statusNames.put(200, "OK");
|
||||
statusNames.put(201, "Created");
|
||||
statusNames.put(202, "Accepted");
|
||||
|
@ -242,11 +257,31 @@ public class Constants {
|
|||
statusNames.put(511, "Network Authentication Required");
|
||||
HTTP_STATUS_NAMES = Collections.unmodifiableMap(statusNames);
|
||||
|
||||
Set<String> formatsHtml = new HashSet<String>();
|
||||
Set<String> formatsHtml = new HashSet<>();
|
||||
formatsHtml.add(CT_HTML);
|
||||
formatsHtml.add(FORMAT_HTML);
|
||||
FORMATS_HTML = Collections.unmodifiableSet(formatsHtml);
|
||||
|
||||
|
||||
// *********************************************************
|
||||
// Update CorsInterceptor's constructor documentation if you change these:
|
||||
// *********************************************************
|
||||
HashSet<String> corsAllowedHeaders = new HashSet<>();
|
||||
corsAllowedHeaders.add("Accept");
|
||||
corsAllowedHeaders.add("Access-Control-Request-Headers");
|
||||
corsAllowedHeaders.add("Access-Control-Request-Method");
|
||||
corsAllowedHeaders.add("Cache-Control");
|
||||
corsAllowedHeaders.add("Content-Type");
|
||||
corsAllowedHeaders.add("Origin");
|
||||
corsAllowedHeaders.add("Prefer");
|
||||
corsAllowedHeaders.add("X-Requested-With");
|
||||
CORS_ALLOWED_HEADERS = Collections.unmodifiableSet(corsAllowedHeaders);
|
||||
|
||||
// *********************************************************
|
||||
// Update CorsInterceptor's constructor documentation if you change these:
|
||||
// *********************************************************
|
||||
HashSet<String> corsAllowedMethods = new HashSet<>();
|
||||
corsAllowedMethods.addAll(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"));
|
||||
CORS_ALLWED_METHODS = Collections.unmodifiableSet(corsAllowedMethods);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,7 +35,7 @@ public interface IVersionSpecificBundleFactory {
|
|||
|
||||
void addResourcesToBundle(List<IBaseResource> theResult, BundleTypeEnum theBundleType, String theServerBase, BundleInclusionRule theBundleInclusionRule, Set<Include> theIncludes);
|
||||
|
||||
void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theLinkSelf, String theLinkPrev, String theLinkNext, Integer theTotalResults, BundleTypeEnum theBundleType, IPrimitiveType<Date> theLastUpdated);
|
||||
void addRootPropertiesToBundle(String theId, String theServerBase, String theLinkSelf, String theLinkPrev, String theLinkNext, Integer theTotalResults, BundleTypeEnum theBundleType, IPrimitiveType<Date> theLastUpdated);
|
||||
|
||||
IBaseResource getResourceBundle();
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhir.rest.gclient;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -38,6 +39,12 @@ public interface IClientExecutable<T extends IClientExecutable<?,Y>, Y> {
|
|||
@Deprecated
|
||||
T andLogRequestAndResponse(boolean theLogRequestAndResponse);
|
||||
|
||||
/**
|
||||
* Sets the <code>Cache-Control</code> header value, which advises the server (or any cache in front of it)
|
||||
* how to behave in terms of cached requests
|
||||
*/
|
||||
T cacheControl(CacheControlDirective theCacheControlDirective);
|
||||
|
||||
/**
|
||||
* Request that the server return subsetted resources, containing only the elements specified in the given parameters.
|
||||
* For example: <code>subsetElements("name", "identifier")</code> requests that the server only return
|
||||
|
|
|
@ -0,0 +1,47 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
|
||||
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import org.hl7.fhir.instance.model.api.IBase;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBinary;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class BinaryUtil {
|
||||
|
||||
private BinaryUtil() {
|
||||
// non instantiable
|
||||
}
|
||||
|
||||
public static IBaseReference getSecurityContext(FhirContext theCtx, IBaseBinary theBinary) {
|
||||
RuntimeResourceDefinition def = theCtx.getResourceDefinition("Binary");
|
||||
BaseRuntimeChildDefinition child = def.getChildByName("securityContext");
|
||||
IBaseReference retVal = null;
|
||||
if (child != null) {
|
||||
List<IBase> values = child.getAccessor().getValues(theBinary);
|
||||
if (values.size() > 0) {
|
||||
retVal = (IBaseReference) values.get(0);
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static IBaseBinary newBinary(FhirContext theCtx) {
|
||||
return (IBaseBinary) theCtx.getResourceDefinition("Binary").newInstance();
|
||||
}
|
||||
|
||||
public static void setSecurityContext(FhirContext theCtx, IBaseBinary theBinary, String theSecurityContext) {
|
||||
RuntimeResourceDefinition def = theCtx.getResourceDefinition("Binary");
|
||||
BaseRuntimeChildDefinition child = def.getChildByName("securityContext");
|
||||
|
||||
BaseRuntimeElementDefinition<?> referenceDef = theCtx.getElementDefinition("reference");
|
||||
IBaseReference reference = (IBaseReference) referenceDef.newInstance();
|
||||
child.getMutator().addValue(theBinary, reference);
|
||||
|
||||
reference.setReference(theSecurityContext);
|
||||
}
|
||||
|
||||
}
|
|
@ -21,12 +21,14 @@ package ca.uhn.fhir.util;
|
|||
*/
|
||||
|
||||
import java.net.ServerSocket;
|
||||
import java.util.LinkedHashSet;
|
||||
|
||||
/**
|
||||
* Provides server ports
|
||||
*/
|
||||
@CoverageIgnore
|
||||
public class PortUtil {
|
||||
private static LinkedHashSet<Integer> ourPorts = new LinkedHashSet<>();
|
||||
|
||||
/*
|
||||
* Non instantiable
|
||||
|
@ -41,9 +43,13 @@ public class PortUtil {
|
|||
public static int findFreePort() {
|
||||
ServerSocket server;
|
||||
try {
|
||||
server = new ServerSocket(0);
|
||||
int port = server.getLocalPort();
|
||||
server.close();
|
||||
int port;
|
||||
do {
|
||||
server = new ServerSocket(0);
|
||||
port = server.getLocalPort();
|
||||
server.close();
|
||||
} while (!ourPorts.add(port));
|
||||
|
||||
Thread.sleep(500);
|
||||
return port;
|
||||
} catch (Exception e) {
|
||||
|
|
|
@ -20,33 +20,34 @@ package ca.uhn.fhir.util;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.Modifier;
|
||||
import java.util.Arrays;
|
||||
import java.util.Locale;
|
||||
import java.util.TimeZone;
|
||||
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||
import ch.qos.logback.classic.Level;
|
||||
import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.LoggerContext;
|
||||
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||
|
||||
public class TestUtil {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TestUtil.class);
|
||||
|
||||
/**
|
||||
* <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
|
||||
*
|
||||
* <p>
|
||||
* When we run the unit tests in cobertura, JUnit doesn't seem to clean up static fields which leads to
|
||||
* tons of memory being used by the end and the JVM crashes in Travis. Manually clearing all of the
|
||||
* static fields seems to solve this.
|
||||
*/
|
||||
public static void clearAllStaticFieldsForUnitTest() {
|
||||
HapiLocalizer.setOurFailOnMissingMessage(true);
|
||||
|
||||
|
||||
Class<?> theType;
|
||||
try {
|
||||
throw new Exception();
|
||||
|
@ -104,7 +105,7 @@ public class TestUtil {
|
|||
* environment
|
||||
*/
|
||||
public static void randomizeLocale() {
|
||||
Locale[] availableLocales = { Locale.CANADA, Locale.GERMANY, Locale.TAIWAN };
|
||||
Locale[] availableLocales = {Locale.CANADA, Locale.GERMANY, Locale.TAIWAN};
|
||||
Locale.setDefault(availableLocales[(int) (Math.random() * availableLocales.length)]);
|
||||
ourLog.info("Tests are running in locale: " + Locale.getDefault().getDisplayName());
|
||||
if (Math.random() < 0.5) {
|
||||
|
@ -116,10 +117,19 @@ public class TestUtil {
|
|||
System.setProperty("file.encoding", "UTF-8");
|
||||
System.setProperty("line.separator", "\n");
|
||||
}
|
||||
String availableTimeZones[] = { "GMT+08:00", "GMT-05:00", "GMT+00:00", "GMT+03:30" };
|
||||
String availableTimeZones[] = {"GMT+08:00", "GMT-05:00", "GMT+00:00", "GMT+03:30"};
|
||||
String timeZone = availableTimeZones[(int) (Math.random() * availableTimeZones.length)];
|
||||
TimeZone.setDefault(TimeZone.getTimeZone(timeZone));
|
||||
ourLog.info("Tests are using time zone: {}", TimeZone.getDefault().getID());
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>THIS IS FOR UNIT TESTS ONLY - DO NOT CALL THIS METHOD FROM USER CODE</b>
|
||||
* <p>
|
||||
* Strip \r chars from a string to account for line ending platform differences
|
||||
*/
|
||||
public static String stripReturns(String theString) {
|
||||
return defaultString(theString).replace("\r", "");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package ca.uhn.fhir.util;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import java.io.Reader;
|
||||
import java.io.StringReader;
|
||||
|
||||
public class XmlDetectionUtil {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(XmlDetectionUtil.class);
|
||||
private static Boolean ourStaxPresent;
|
||||
|
||||
/**
|
||||
* This method will return <code>true</code> if a StAX XML parsing library is present
|
||||
* on the classpath
|
||||
*/
|
||||
public static boolean isStaxPresent() {
|
||||
Boolean retVal = ourStaxPresent;
|
||||
if (retVal == null) {
|
||||
try {
|
||||
Class.forName("javax.xml.stream.events.XMLEvent");
|
||||
Class<?> xmlUtilClazz = Class.forName("ca.uhn.fhir.util.XmlUtil");
|
||||
xmlUtilClazz.getMethod("createXmlReader", Reader.class).invoke(xmlUtilClazz, new StringReader(""));
|
||||
ourStaxPresent = Boolean.TRUE;
|
||||
retVal = Boolean.TRUE;
|
||||
} catch (Throwable t) {
|
||||
ourLog.info("StAX not detected on classpath, XML processing will be disabled");
|
||||
ourStaxPresent = Boolean.FALSE;
|
||||
retVal = Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -20,12 +20,13 @@ package ca.uhn.fhir.util;
|
|||
* #L%
|
||||
*/
|
||||
import java.io.*;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.*;
|
||||
|
||||
import javax.xml.stream.*;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
|
||||
import ca.uhn.fhir.model.primitive.XhtmlDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import org.apache.commons.lang3.StringEscapeUtils;
|
||||
import org.codehaus.stax2.XMLOutputFactory2;
|
||||
import org.codehaus.stax2.io.EscapingWriterFactory;
|
||||
|
@ -37,21 +38,22 @@ import ca.uhn.fhir.context.ConfigurationException;
|
|||
import ca.uhn.fhir.util.jar.DependencyLogFactory;
|
||||
import ca.uhn.fhir.util.jar.IDependencyLog;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
/**
|
||||
* Utility methods for working with the StAX API.
|
||||
*
|
||||
* This class contains code adapted from the Apache Axiom project.
|
||||
*/
|
||||
public class XmlUtil {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlUtil.class);
|
||||
private static final Map<String, Integer> VALID_ENTITY_NAMES;
|
||||
private static final ExtendedEntityReplacingXmlResolver XML_RESOLVER = new ExtendedEntityReplacingXmlResolver();
|
||||
private static XMLOutputFactory ourFragmentOutputFactory;
|
||||
private static volatile boolean ourHaveLoggedStaxImplementation;
|
||||
private static volatile XMLInputFactory ourInputFactory;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(XmlUtil.class);
|
||||
private static Throwable ourNextException;
|
||||
private static volatile XMLOutputFactory ourOutputFactory;
|
||||
private static Boolean ourStaxPresent;
|
||||
private static final Map<String, Integer> VALID_ENTITY_NAMES;
|
||||
private static final ExtendedEntityReplacingXmlResolver XML_RESOLVER = new ExtendedEntityReplacingXmlResolver();
|
||||
|
||||
static {
|
||||
HashMap<String, Integer> validEntityNames = new HashMap<String, Integer>(1448);
|
||||
|
@ -1517,7 +1519,7 @@ public class XmlUtil {
|
|||
}
|
||||
|
||||
XMLOutputFactory outputFactory = newOutputFactory();
|
||||
|
||||
|
||||
if (!ourHaveLoggedStaxImplementation) {
|
||||
logStaxImplementation(outputFactory.getClass());
|
||||
}
|
||||
|
@ -1545,7 +1547,7 @@ public class XmlUtil {
|
|||
|
||||
public static XMLEventReader createXmlReader(Reader reader) throws FactoryConfigurationError, XMLStreamException {
|
||||
throwUnitTestExceptionIfConfiguredToDoSo();
|
||||
|
||||
|
||||
XMLInputFactory inputFactory = getOrCreateInputFactory();
|
||||
|
||||
// Now.. create the reader and return it
|
||||
|
@ -1555,7 +1557,7 @@ public class XmlUtil {
|
|||
|
||||
public static XMLStreamWriter createXmlStreamWriter(Writer theWriter) throws FactoryConfigurationError, XMLStreamException {
|
||||
throwUnitTestExceptionIfConfiguredToDoSo();
|
||||
|
||||
|
||||
XMLOutputFactory outputFactory = getOrCreateOutputFactory();
|
||||
XMLStreamWriter retVal = outputFactory.createXMLStreamWriter(theWriter);
|
||||
return retVal;
|
||||
|
@ -1567,6 +1569,30 @@ public class XmlUtil {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
* Encode a set of StAX events into a String
|
||||
*/
|
||||
public static String encode(List<XMLEvent> theEvents) {
|
||||
try {
|
||||
StringWriter w = new StringWriter();
|
||||
XMLEventWriter ew = XmlUtil.createXmlFragmentWriter(w);
|
||||
|
||||
for (XMLEvent next : theEvents) {
|
||||
if (next.isCharacters()) {
|
||||
ew.add(next);
|
||||
} else {
|
||||
ew.add(next);
|
||||
}
|
||||
}
|
||||
ew.close();
|
||||
return w.toString();
|
||||
} catch (XMLStreamException e) {
|
||||
throw new DataFormatException("Problem with the contained XML events", e);
|
||||
} catch (FactoryConfigurationError e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
private static XMLOutputFactory getOrCreateFragmentOutputFactory() throws FactoryConfigurationError {
|
||||
XMLOutputFactory retVal = ourFragmentOutputFactory;
|
||||
if (retVal == null) {
|
||||
|
@ -1588,7 +1614,7 @@ public class XmlUtil {
|
|||
} catch (ClassNotFoundException e) {
|
||||
// ok
|
||||
}
|
||||
|
||||
|
||||
XMLInputFactory inputFactory = newInputFactory();
|
||||
|
||||
if (!ourHaveLoggedStaxImplementation) {
|
||||
|
@ -1596,20 +1622,20 @@ public class XmlUtil {
|
|||
}
|
||||
|
||||
/*
|
||||
* These two properties disable external entity processing, which can
|
||||
* These two properties disable external entity processing, which can
|
||||
* be a security vulnerability.
|
||||
*
|
||||
*
|
||||
* See https://github.com/jamesagnew/hapi-fhir/issues/339
|
||||
* https://www.owasp.org/index.php/XML_External_Entity_%28XXE%29_Processing
|
||||
*/
|
||||
inputFactory.setProperty(XMLInputFactory.SUPPORT_DTD, false); // This disables DTDs entirely for that factory
|
||||
inputFactory.setProperty("javax.xml.stream.isSupportingExternalEntities", false); // disable external entities
|
||||
|
||||
|
||||
|
||||
/*
|
||||
* In the following few lines, you can uncomment the first and comment the second to disable automatic
|
||||
* parsing of extended entities, e.g. §
|
||||
*
|
||||
*
|
||||
* Note that these properties are Woodstox specific and they cause a crash in environments where SJSXP is
|
||||
* being used (e.g. glassfish) so we don't set them there.
|
||||
*/
|
||||
|
@ -1652,6 +1678,7 @@ public class XmlUtil {
|
|||
return ourOutputFactory;
|
||||
}
|
||||
|
||||
|
||||
private static void logStaxImplementation(Class<?> theClass) {
|
||||
IDependencyLog logger = DependencyLogFactory.createJarLogger();
|
||||
if (logger != null) {
|
||||
|
@ -1683,6 +1710,49 @@ public class XmlUtil {
|
|||
return outputFactory;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses an XML string into a set of StAX events
|
||||
*/
|
||||
public static List<XMLEvent> parse(String theValue) {
|
||||
if (isBlank(theValue)) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
String val = theValue.trim();
|
||||
if (!val.startsWith("<")) {
|
||||
val = XhtmlDt.DIV_OPEN_FIRST + val + "</div>";
|
||||
}
|
||||
boolean hasProcessingInstruction = val.startsWith("<?");
|
||||
if (hasProcessingInstruction && val.endsWith("?>")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
ArrayList<XMLEvent> value = new ArrayList<>();
|
||||
StringReader reader = new StringReader(val);
|
||||
XMLEventReader er = XmlUtil.createXmlReader(reader);
|
||||
boolean first = true;
|
||||
while (er.hasNext()) {
|
||||
XMLEvent next = er.nextEvent();
|
||||
if (first) {
|
||||
first = false;
|
||||
continue;
|
||||
}
|
||||
if (er.hasNext()) {
|
||||
// don't add the last event
|
||||
value.add(next);
|
||||
}
|
||||
}
|
||||
return value;
|
||||
|
||||
} catch (XMLStreamException e) {
|
||||
throw new DataFormatException("String does not appear to be valid XML/XHTML (error is \"" + e.getMessage() + "\"): " + theValue, e);
|
||||
} catch (FactoryConfigurationError e) {
|
||||
throw new ConfigurationException(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* FOR UNIT TESTS ONLY - Throw this exception for the next operation
|
||||
*/
|
||||
|
@ -1711,26 +1781,6 @@ public class XmlUtil {
|
|||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This method will return <code>true</code> if a StAX XML parsing library is present
|
||||
* on the classpath
|
||||
*/
|
||||
public static boolean isStaxPresent() {
|
||||
Boolean retVal = ourStaxPresent;
|
||||
if (retVal == null) {
|
||||
try {
|
||||
newInputFactory();
|
||||
ourStaxPresent = Boolean.TRUE;
|
||||
retVal = Boolean.TRUE;
|
||||
} catch (ConfigurationException e) {
|
||||
ourLog.info("StAX not detected on classpath, XML processing will be disabled");
|
||||
ourStaxPresent = Boolean.FALSE;
|
||||
retVal = Boolean.FALSE;
|
||||
}
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static class MyEscaper implements EscapingWriterFactory {
|
||||
|
||||
|
|
|
@ -1,44 +0,0 @@
|
|||
package org.hl7.fhir.exceptions;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 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%
|
||||
*/
|
||||
|
||||
|
||||
public class DefinitionException extends FHIRException {
|
||||
|
||||
private static final long serialVersionUID = 8175803009974854088L;
|
||||
|
||||
public DefinitionException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public DefinitionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public DefinitionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public DefinitionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package org.hl7.fhir.exceptions;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 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%
|
||||
*/
|
||||
|
||||
|
||||
public class FHIRException extends Exception {
|
||||
|
||||
// Note that the 4-argument constructor has been removed as it is not JDK6 compatible
|
||||
|
||||
private static final long serialVersionUID = -1793365096090608037L;
|
||||
|
||||
public FHIRException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public FHIRException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public FHIRException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FHIRException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package org.hl7.fhir.exceptions;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 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%
|
||||
*/
|
||||
|
||||
|
||||
public class FHIRFormatError extends FHIRException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public FHIRFormatError() {
|
||||
super();
|
||||
}
|
||||
|
||||
public FHIRFormatError(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public FHIRFormatError(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public FHIRFormatError(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package org.hl7.fhir.exceptions;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 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%
|
||||
*/
|
||||
|
||||
|
||||
public class NoTerminologyServiceException extends FHIRException {
|
||||
|
||||
private static final long serialVersionUID = 8196224236579018584L;
|
||||
|
||||
public NoTerminologyServiceException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public NoTerminologyServiceException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public NoTerminologyServiceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public NoTerminologyServiceException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package org.hl7.fhir.exceptions;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 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%
|
||||
*/
|
||||
|
||||
|
||||
public class PathEngineException extends FHIRException {
|
||||
|
||||
private static final long serialVersionUID = -8550234068917355391L;
|
||||
|
||||
public PathEngineException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public PathEngineException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public PathEngineException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public PathEngineException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package org.hl7.fhir.exceptions;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 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%
|
||||
*/
|
||||
|
||||
|
||||
public class TerminologyServiceException extends FHIRException {
|
||||
|
||||
private static final long serialVersionUID = -1547510329455394225L;
|
||||
|
||||
public TerminologyServiceException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public TerminologyServiceException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public TerminologyServiceException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TerminologyServiceException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -1,44 +0,0 @@
|
|||
package org.hl7.fhir.exceptions;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Core Library
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 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%
|
||||
*/
|
||||
|
||||
|
||||
public class UcumException extends FHIRException {
|
||||
|
||||
private static final long serialVersionUID = -8535757334881835619L;
|
||||
|
||||
public UcumException() {
|
||||
super();
|
||||
}
|
||||
|
||||
public UcumException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
public UcumException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public UcumException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,58 @@
|
|||
package ca.uhn.fhir.rest.api;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class CacheControlDirectiveTest {
|
||||
|
||||
@Test
|
||||
public void testParseNoCache() {
|
||||
List<String> values = Arrays.asList(Constants.CACHE_CONTROL_NO_CACHE);
|
||||
CacheControlDirective ccd = new CacheControlDirective();
|
||||
ccd.parse(values);
|
||||
assertTrue(ccd.isNoCache());
|
||||
assertFalse(ccd.isNoStore());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseNoCacheNoStore() {
|
||||
List<String> values = Arrays.asList(Constants.CACHE_CONTROL_NO_CACHE + " , " + Constants.CACHE_CONTROL_NO_STORE);
|
||||
CacheControlDirective ccd = new CacheControlDirective();
|
||||
ccd.parse(values);
|
||||
assertTrue(ccd.isNoCache());
|
||||
assertTrue(ccd.isNoStore());
|
||||
assertEquals(null, ccd.getMaxResults());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseNoCacheNoStoreMaxResults() {
|
||||
List<String> values = Arrays.asList(Constants.CACHE_CONTROL_NO_STORE + ", "+ Constants.CACHE_CONTROL_MAX_RESULTS + "=5");
|
||||
CacheControlDirective ccd = new CacheControlDirective();
|
||||
ccd.parse(values);
|
||||
assertFalse(ccd.isNoCache());
|
||||
assertTrue(ccd.isNoStore());
|
||||
assertEquals(5, ccd.getMaxResults().intValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseNoCacheNoStoreMaxResultsInvalid() {
|
||||
List<String> values = Arrays.asList(Constants.CACHE_CONTROL_NO_STORE + ", "+ Constants.CACHE_CONTROL_MAX_RESULTS + "=A");
|
||||
CacheControlDirective ccd = new CacheControlDirective();
|
||||
ccd.parse(values);
|
||||
assertFalse(ccd.isNoCache());
|
||||
assertTrue(ccd.isNoStore());
|
||||
assertEquals(null, ccd.getMaxResults());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testParseNull() {
|
||||
CacheControlDirective ccd = new CacheControlDirective();
|
||||
ccd.parse(null);
|
||||
assertFalse(ccd.isNoCache());
|
||||
assertFalse(ccd.isNoStore());
|
||||
}
|
||||
}
|
|
@ -73,6 +73,7 @@ public class IgPackUploader extends BaseCommand {
|
|||
.and(StructureDefinition.URL.matches().value(nextResourceUrl))
|
||||
.execute();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
throw new ParseException("This command does not support FHIR version " + ctx.getVersion().getVersion());
|
||||
}
|
||||
|
|
|
@ -1,28 +1,36 @@
|
|||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import java.util.*;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.*;
|
||||
import ca.uhn.fhir.jpa.provider.r4.*;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
|
||||
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
|
||||
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.*;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemo extends RestfulServer {
|
||||
|
||||
|
@ -134,19 +142,7 @@ public class JpaServerDemo extends RestfulServer {
|
|||
setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||
|
||||
// Register a CORS filter
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
CorsInterceptor corsInterceptor = new CorsInterceptor(config);
|
||||
config.addAllowedHeader("x-fhir-starter");
|
||||
config.addAllowedHeader("Origin");
|
||||
config.addAllowedHeader("Accept");
|
||||
config.addAllowedHeader("X-Requested-With");
|
||||
config.addAllowedHeader("Content-Type");
|
||||
config.addAllowedHeader("Access-Control-Request-Method");
|
||||
config.addAllowedHeader("Access-Control-Request-Headers");
|
||||
config.addAllowedOrigin("*");
|
||||
config.addExposedHeader("Location");
|
||||
config.addExposedHeader("Content-Location");
|
||||
config.setAllowedMethods(Arrays.asList("GET","POST","PUT","DELETE","OPTIONS"));
|
||||
CorsInterceptor corsInterceptor = new CorsInterceptor();
|
||||
registerInterceptor(corsInterceptor);
|
||||
|
||||
/*
|
||||
|
|
|
@ -59,6 +59,11 @@
|
|||
</dependency>
|
||||
|
||||
<!-- Unit test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.woodstox</groupId>
|
||||
<artifactId>woodstox-core-asl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
@ -125,4 +130,4 @@
|
|||
</plugins>
|
||||
</build>
|
||||
|
||||
</project>
|
||||
</project>
|
||||
|
|
|
@ -98,24 +98,23 @@ public class ApacheHttpResponse implements IHttpResponse {
|
|||
}
|
||||
if (charset == null) {
|
||||
if (Constants.STATUS_HTTP_204_NO_CONTENT != myResponse.getStatusLine().getStatusCode()) {
|
||||
ourLog.warn("Response did not specify a charset.");
|
||||
ourLog.debug("Response did not specify a charset, defaulting to utf-8");
|
||||
}
|
||||
charset = Charset.forName("UTF-8");
|
||||
}
|
||||
|
||||
Reader reader = new InputStreamReader(readEntity(), charset);
|
||||
return reader;
|
||||
return new InputStreamReader(readEntity(), charset);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, List<String>> getAllHeaders() {
|
||||
Map<String, List<String>> headers = new HashMap<String, List<String>>();
|
||||
Map<String, List<String>> headers = new HashMap<>();
|
||||
if (myResponse.getAllHeaders() != null) {
|
||||
for (Header next : myResponse.getAllHeaders()) {
|
||||
String name = next.getName().toLowerCase();
|
||||
List<String> list = headers.get(name);
|
||||
if (list == null) {
|
||||
list = new ArrayList<String>();
|
||||
list = new ArrayList<>();
|
||||
headers.put(name, list);
|
||||
}
|
||||
list.add(next.getValue());
|
||||
|
@ -131,7 +130,7 @@ public class ApacheHttpResponse implements IHttpResponse {
|
|||
if (headers == null) {
|
||||
headers = new Header[0];
|
||||
}
|
||||
List<String> retVal = new ArrayList<String>();
|
||||
List<String> retVal = new ArrayList<>();
|
||||
for (Header next : headers) {
|
||||
retVal.add(next.getValue());
|
||||
}
|
||||
|
|
|
@ -34,6 +34,8 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import ca.uhn.fhir.util.XmlDetectionUtil;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
@ -109,7 +111,7 @@ public abstract class BaseClient implements IRestfulClient {
|
|||
setKeepResponses(true);
|
||||
}
|
||||
|
||||
if (XmlUtil.isStaxPresent() == false) {
|
||||
if (XmlDetectionUtil.isStaxPresent() == false) {
|
||||
myEncoding = EncodingEnum.JSON;
|
||||
}
|
||||
|
||||
|
@ -135,7 +137,7 @@ public abstract class BaseClient implements IRestfulClient {
|
|||
public <T extends IBaseResource> T fetchResourceFromUrl(Class<T> theResourceType, String theUrl) {
|
||||
BaseHttpClientInvocation clientInvocation = new HttpGetClientInvocation(getFhirContext(), theUrl);
|
||||
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theResourceType);
|
||||
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null);
|
||||
return invokeClient(getFhirContext(), binding, clientInvocation, null, false, false, null, null, null);
|
||||
}
|
||||
|
||||
void forceConformanceCheck() {
|
||||
|
@ -198,11 +200,11 @@ public abstract class BaseClient implements IRestfulClient {
|
|||
}
|
||||
|
||||
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, boolean theLogRequestAndResponse) {
|
||||
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null);
|
||||
return invokeClient(theContext, binding, clientInvocation, null, null, theLogRequestAndResponse, null, null, null);
|
||||
}
|
||||
|
||||
<T> T invokeClient(FhirContext theContext, IClientResponseHandler<T> binding, BaseHttpClientInvocation clientInvocation, EncodingEnum theEncoding, Boolean thePrettyPrint,
|
||||
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements) {
|
||||
boolean theLogRequestAndResponse, SummaryEnum theSummaryMode, Set<String> theSubsetElements, CacheControlDirective theCacheControlDirective) {
|
||||
|
||||
if (!myDontValidateConformance) {
|
||||
myFactory.validateServerBaseIfConfiguredToDoSo(myUrlBase, myClient, this);
|
||||
|
@ -244,6 +246,18 @@ public abstract class BaseClient implements IRestfulClient {
|
|||
|
||||
httpRequest = clientInvocation.asHttpRequest(myUrlBase, params, encoding, thePrettyPrint);
|
||||
|
||||
if (theCacheControlDirective != null) {
|
||||
StringBuilder b = new StringBuilder();
|
||||
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_CACHE, theCacheControlDirective.isNoCache());
|
||||
addToCacheControlHeader(b, Constants.CACHE_CONTROL_NO_STORE, theCacheControlDirective.isNoStore());
|
||||
if (theCacheControlDirective.getMaxResults() != null) {
|
||||
addToCacheControlHeader(b, Constants.CACHE_CONTROL_MAX_RESULTS+"="+ Integer.toString(theCacheControlDirective.getMaxResults().intValue()), true);
|
||||
}
|
||||
if (b.length() > 0) {
|
||||
httpRequest.addHeader(Constants.HEADER_CACHE_CONTROL, b.toString());
|
||||
}
|
||||
}
|
||||
|
||||
if (theLogRequestAndResponse) {
|
||||
ourLog.info("Client invoking: {}", httpRequest);
|
||||
String body = httpRequest.getRequestBodyFromStream();
|
||||
|
@ -366,6 +380,15 @@ public abstract class BaseClient implements IRestfulClient {
|
|||
}
|
||||
}
|
||||
|
||||
private void addToCacheControlHeader(StringBuilder theBuilder, String theDirective, boolean theActive) {
|
||||
if (theActive) {
|
||||
if (theBuilder.length() > 0) {
|
||||
theBuilder.append(", ");
|
||||
}
|
||||
theBuilder.append(theDirective);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
|
||||
*/
|
||||
|
|
|
@ -120,10 +120,10 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
ResourceResponseHandler<T> binding = new ResourceResponseHandler<T>(theType, (Class<? extends IBaseResource>) null, id, allowHtmlResponse);
|
||||
|
||||
if (theNotModifiedHandler == null) {
|
||||
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements);
|
||||
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null);
|
||||
}
|
||||
try {
|
||||
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements);
|
||||
return invokeClient(myContext, binding, invocation, theEncoding, thePrettyPrint, myLogRequestAndResponse, theSummary, theSubsetElements, null);
|
||||
} catch (NotModifiedException e) {
|
||||
return theNotModifiedHandler.call();
|
||||
}
|
||||
|
@ -373,6 +373,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
private boolean myQueryLogRequestAndResponse;
|
||||
private HashSet<String> mySubsetElements;
|
||||
protected SummaryEnum mySummaryMode;
|
||||
protected CacheControlDirective myCacheControlDirective;
|
||||
|
||||
@Deprecated // override deprecated method
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -382,6 +383,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
return (T) this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T cacheControl(CacheControlDirective theCacheControlDirective) {
|
||||
myCacheControlDirective = theCacheControlDirective;
|
||||
return (T) this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public T elementsSubset(String... theElements) {
|
||||
|
@ -434,19 +441,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
|
|||
}
|
||||
|
||||
protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) {
|
||||
// if (myParamEncoding != null) {
|
||||
// theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType()));
|
||||
// }
|
||||
//
|
||||
// if (myPrettyPrint != null) {
|
||||
// theParams.put(Constants.PARAM_PRETTY, Collections.singletonList(myPrettyPrint.toString()));
|
||||
// }
|
||||
|
||||
if (isKeepResponses()) {
|
||||
myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding(), myPrettyPrint);
|
||||
}
|
||||
|
||||
Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements);
|
||||
Z resp = invokeClient(myContext, theHandler, theInvocation, myParamEncoding, myPrettyPrint, myQueryLogRequestAndResponse || myLogRequestAndResponse, mySummaryMode, mySubsetElements, myCacheControlDirective);
|
||||
return resp;
|
||||
}
|
||||
|
||||
|
|
|
@ -330,6 +330,8 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
|
|||
serverFhirVersionEnum = FhirVersionEnum.DSTU2_1;
|
||||
} else if (serverFhirVersionString.equals(FhirVersionEnum.DSTU3.getFhirVersionString())) {
|
||||
serverFhirVersionEnum = FhirVersionEnum.DSTU3;
|
||||
} else if (serverFhirVersionString.equals(FhirVersionEnum.R4.getFhirVersionString())) {
|
||||
serverFhirVersionEnum = FhirVersionEnum.R4;
|
||||
} else {
|
||||
// we'll be lenient and accept this
|
||||
ourLog.debug("Server conformance statement indicates unknown FHIR version: {}", serverFhirVersionString);
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -1346,7 +1346,7 @@ public class VersionConvertor_14_40 {
|
|||
org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent tgt = new org.hl7.fhir.r4.model.ElementDefinition.ElementDefinitionSlicingComponent();
|
||||
copyElement(src, tgt);
|
||||
for (org.hl7.fhir.dstu2016may.model.StringType t : src.getDiscriminator())
|
||||
tgt.addDiscriminator(ProfileUtilities.interpretR2Discriminator(t.getValue()));
|
||||
tgt.addDiscriminator(ProfileUtilities.interpretR2Discriminator(t.getValue(), true));
|
||||
if (src.hasDescription())
|
||||
tgt.setDescription(src.getDescription());
|
||||
if (src.hasOrdered())
|
||||
|
|
|
@ -1,83 +0,0 @@
|
|||
package org.hl7.fhir.utilities;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Converter
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 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%
|
||||
*/
|
||||
|
||||
|
||||
public class OIDUtils {
|
||||
|
||||
/*
|
||||
2.16.840.1.113883.3.72.5.2 - NIST owns this
|
||||
2.16.840.1.113883.4.6 - National Provider Identifier
|
||||
2.16.840.1.113883.6.21 - UB92
|
||||
2.16.840.1.113883.6.69 - NDC
|
||||
*/
|
||||
|
||||
public static String getUriForOid(String r) {
|
||||
if (r.equals("2.16.840.1.113883.6.96"))
|
||||
return "http://snomed.info/sct";
|
||||
if (r.equals("2.16.840.1.113883.6.1"))
|
||||
return "http://loinc.org";
|
||||
if (r.equals("2.16.840.1.113883.6.8"))
|
||||
return "http://unitsofmeasure.org";
|
||||
if (r.equals("2.16.840.1.113883.6.3"))
|
||||
return "http://hl7.org/fhir/sid/icd-10";
|
||||
if (r.equals("2.16.840.1.113883.6.42"))
|
||||
return "http://hl7.org/fhir/sid/icd-9";
|
||||
if (r.equals("2.16.840.1.113883.6.104"))
|
||||
return "http://hl7.org/fhir/sid/icd-9";
|
||||
if (r.equals("2.16.840.1.113883.6.103"))
|
||||
return "http://hl7.org/fhir/sid/icd-9"; //todo: confirm this
|
||||
if (r.equals("2.16.840.1.113883.6.73"))
|
||||
return "http://hl7.org/fhir/sid/atc";
|
||||
if (r.equals("2.16.840.1.113883.3.26.1.1"))
|
||||
return "http://ncimeta.nci.nih.gov";
|
||||
if (r.equals("2.16.840.1.113883.3.26.1.1.1"))
|
||||
return "http://ncimeta.nci.nih.gov";
|
||||
if (r.equals("2.16.840.1.113883.6.88"))
|
||||
return "http://www.nlm.nih.gov/research/umls/rxnorm"; // todo: confirm this
|
||||
|
||||
if (r.equals("2.16.840.1.113883.5.1008"))
|
||||
return "http://hl7.org/fhir/v3/NullFlavor";
|
||||
if (r.equals("2.16.840.1.113883.5.111"))
|
||||
return "http://hl7.org/fhir/v3/RoleCode";
|
||||
if (r.equals("2.16.840.1.113883.5.4"))
|
||||
return "http://hl7.org/fhir/v3/ActCode";
|
||||
if (r.equals("2.16.840.1.113883.5.8"))
|
||||
return "http://hl7.org/fhir/v3/ActReason";
|
||||
if (r.equals("2.16.840.1.113883.5.83"))
|
||||
return "http://hl7.org/fhir/v3/ObservationInterpretation";
|
||||
if (r.equals("2.16.840.1.113883.6.238"))
|
||||
return "http://hl7.org/fhir/v3/Race";
|
||||
|
||||
if (r.equals("2.16.840.1.113883.6.59"))
|
||||
return "http://hl7.org/fhir/sid/cvx";
|
||||
if (r.equals("2.16.840.1.113883.12.292"))
|
||||
return "http://hl7.org/fhir/sid/cvx";
|
||||
|
||||
if (r.equals("2.16.840.1.113883.6.12"))
|
||||
return "http://www.ama-assn.org/go/cpt";
|
||||
|
||||
if (r.startsWith("2.16.840.1.113883.12."))
|
||||
return "http://hl7.org/fhir/sid/v2-"+r.substring(21);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,193 +0,0 @@
|
|||
package org.hl7.fhir.utilities.ucum;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR - Converter
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2017 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%
|
||||
*/
|
||||
|
||||
/*******************************************************************************
|
||||
* Crown Copyright (c) 2006+, Copyright (c) 2006 - 2014 Kestral Computing & Health Intersections.
|
||||
* All rights reserved. This program and the accompanying materials
|
||||
* are made available under the terms of the Eclipse Public License v1.0
|
||||
* which accompanies this distribution, and is available at
|
||||
* http://www.eclipse.org/legal/epl-v10.html
|
||||
*
|
||||
* Contributors:
|
||||
* Kestral Computing P/L - initial implementation
|
||||
* Health Intersections - ongoing maintenance
|
||||
*******************************************************************************/
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.hl7.fhir.exceptions.UcumException;
|
||||
|
||||
|
||||
/**
|
||||
* General Ucum Service
|
||||
*
|
||||
* UCUM functionality that is useful for applications that make use of UCUM codes
|
||||
*
|
||||
* This is a tightly bound interface - consumers use the internal model classes
|
||||
*
|
||||
* @author Grahame Grieve
|
||||
*
|
||||
*/
|
||||
public interface UcumService {
|
||||
|
||||
public class UcumVersionDetails {
|
||||
private Date releaseDate;
|
||||
private String version;
|
||||
/**
|
||||
* @param releaseDate
|
||||
* @param version
|
||||
*/
|
||||
public UcumVersionDetails(Date releaseDate, String version) {
|
||||
super();
|
||||
this.releaseDate = releaseDate;
|
||||
this.version = version;
|
||||
}
|
||||
/**
|
||||
* @return the releaseDate
|
||||
*/
|
||||
public Date getReleaseDate() {
|
||||
return releaseDate;
|
||||
}
|
||||
/**
|
||||
* @return the version
|
||||
*/
|
||||
public String getVersion() {
|
||||
return version;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* return Ucum Identification details for the version in use
|
||||
*/
|
||||
public abstract UcumVersionDetails ucumIdentification();
|
||||
|
||||
|
||||
/**
|
||||
* Check UCUM. Note that this stands as a test of the service
|
||||
* more than UCUM itself (for version 1.7, there are no known
|
||||
* semantic errors in UCUM). But you should always run this test at least
|
||||
* once with the version of UCUM you are using to ensure that
|
||||
* the service implementation correctly understands the UCUM data
|
||||
* to which it is bound
|
||||
*
|
||||
* @return a list of internal errors in the UCUM spec.
|
||||
*
|
||||
*/
|
||||
public abstract List<String> validateUCUM();
|
||||
|
||||
|
||||
/**
|
||||
* return a list of the defined types of units in this UCUM version
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public abstract Set<String> getProperties();
|
||||
|
||||
/**
|
||||
* validate whether a unit code are valid UCUM units
|
||||
*
|
||||
* @param units - the unit code to check
|
||||
* @return nil if valid, or an error message describing the problem
|
||||
*/
|
||||
public abstract String validate(String unit);
|
||||
|
||||
/**
|
||||
* given a unit, return a formal description of what the units stand for using
|
||||
* full names
|
||||
* @param units the unit code
|
||||
* @return formal description
|
||||
* @throws UcumException
|
||||
* @throws OHFException
|
||||
*/
|
||||
public String analyse(String unit) throws UcumException ;
|
||||
|
||||
/**
|
||||
* validate whether a units are valid UCUM units and additionally require that the
|
||||
* units from a particular property
|
||||
*
|
||||
* @param units - the unit code to check
|
||||
* @return nil if valid, or an error message describing the problem
|
||||
*/
|
||||
public abstract String validateInProperty(String unit, String property);
|
||||
|
||||
/**
|
||||
* validate whether a units are valid UCUM units and additionally require that the
|
||||
* units match a particular base canonical unit
|
||||
*
|
||||
* @param units - the unit code to check
|
||||
* @return nil if valid, or an error message describing the problem
|
||||
*/
|
||||
public abstract String validateCanonicalUnits(String unit, String canonical);
|
||||
|
||||
/**
|
||||
* given a set of units, return their canonical form
|
||||
* @param unit
|
||||
* @return the canonical form
|
||||
* @throws UcumException
|
||||
* @throws OHFException
|
||||
*/
|
||||
public abstract String getCanonicalUnits(String unit) throws UcumException ;
|
||||
|
||||
/**
|
||||
* given two pairs of units, return true if they sahre the same canonical base
|
||||
*
|
||||
* @param units1
|
||||
* @param units2
|
||||
* @return
|
||||
* @throws UcumException
|
||||
* @
|
||||
*/
|
||||
public abstract boolean isComparable(String units1, String units2) throws UcumException ;
|
||||
|
||||
|
||||
/**
|
||||
* given a value and source unit, return the value in the given dest unit
|
||||
* an exception is thrown if the conversion is not possible
|
||||
*
|
||||
* @param value
|
||||
* @param sourceUnit
|
||||
* @param destUnit
|
||||
* @return the value if a conversion is possible
|
||||
* @throws UcumException
|
||||
* @throws OHFException
|
||||
*/
|
||||
public abstract Decimal convert(Decimal value, String sourceUnit, String destUnit) throws UcumException ;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* given a set of UCUM units, return a likely preferred human dense form
|
||||
*
|
||||
* SI units - as is.
|
||||
* Other units - improved by manual fixes, or the removal of []
|
||||
*
|
||||
* @param code
|
||||
* @return the preferred human display form
|
||||
*/
|
||||
public abstract String getCommonDisplay(String code);
|
||||
|
||||
|
||||
|
||||
}
|
|
@ -1,8 +1,13 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<!-- The parent of this project is the deployable POM. This project isn't deployable, but this keeps it before the root pom in the reactor order when building the site. I don't know why this works...
|
||||
Need to investigate this. -->
|
||||
<!--
|
||||
The parent of this project is the deployable POM. This project isn't
|
||||
deployable, but this keeps it before the root pom in the reactor
|
||||
order when building the site. I don't know why this works...
|
||||
Need to investigate this.
|
||||
-->
|
||||
<parent>
|
||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||
<artifactId>hapi-deployable-pom</artifactId>
|
||||
|
@ -139,7 +144,7 @@
|
|||
<artifactId>jetty-util</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<dependency>
|
||||
<groupId>net.sf.json-lib</groupId>
|
||||
<artifactId>json-lib</artifactId>
|
||||
|
@ -225,14 +230,14 @@
|
|||
<artifactId>spring-web</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
<!--
|
||||
For some reason JavaDoc crashed during site generation unless we have this dependency
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>javax.interceptor</groupId>
|
||||
<artifactId>javax.interceptor-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.interceptor</groupId>
|
||||
<artifactId>javax.interceptor-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
</dependencies>
|
||||
|
@ -253,6 +258,13 @@
|
|||
</plugins>
|
||||
</pluginManagement>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.basepom.maven</groupId>
|
||||
<artifactId>duplicate-finder-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<skip>true</skip>
|
||||
</configuration>
|
||||
</plugin>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
|
@ -285,19 +297,17 @@
|
|||
</fileSets>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>post-integration-test</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<dataFile>${project.build.directory}/jacoco.exec</dataFile>
|
||||
<outputDirectory>${project.reporting.outputDirectory}/jacoco-report</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
|
||||
|
||||
<execution>
|
||||
<id>post-integration-test</id>
|
||||
<phase>install</phase>
|
||||
<goals>
|
||||
<goal>report</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<dataFile>${project.build.directory}/jacoco.exec</dataFile>
|
||||
<outputDirectory>${project.reporting.outputDirectory}/jacoco-report</outputDirectory>
|
||||
</configuration>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
<plugin>
|
||||
|
|
|
@ -74,6 +74,11 @@
|
|||
</dependency>
|
||||
|
||||
<!-- Unit test dependencies -->
|
||||
<dependency>
|
||||
<groupId>org.codehaus.woodstox</groupId>
|
||||
<artifactId>woodstox-core-asl</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
|
|
|
@ -34,13 +34,15 @@ import ca.uhn.fhir.rest.client.impl.RestfulClientFactory;
|
|||
|
||||
/**
|
||||
* A Restful Client Factory, based on Jax Rs
|
||||
*
|
||||
* Default Jax-Rs client is NOT thread safe in static context, you should create a new factory every time or
|
||||
* use a specific Jax-Rs client implementation which managed connection pool.
|
||||
* @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
|
||||
*/
|
||||
public class JaxRsRestfulClientFactory extends RestfulClientFactory {
|
||||
|
||||
private Client myNativeClient;
|
||||
|
||||
private List<Class<?>> registeredComponents;
|
||||
|
||||
/**
|
||||
* Constructor. Note that you must set the {@link FhirContext} manually using {@link #setFhirContext(FhirContext)} if this constructor is used!
|
||||
*/
|
||||
|
@ -64,6 +66,12 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
|
|||
myNativeClient = builder.build();
|
||||
}
|
||||
|
||||
if (registeredComponents != null && !registeredComponents.isEmpty()) {
|
||||
for (Class<?> c : registeredComponents) {
|
||||
myNativeClient = myNativeClient.register(c);
|
||||
}
|
||||
}
|
||||
|
||||
return myNativeClient;
|
||||
}
|
||||
|
||||
|
@ -73,29 +81,46 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
|
|||
return new JaxRsHttpClient(client, url, theIfNoneExistParams, theIfNoneExistString, theRequestType, theHeaders);
|
||||
}
|
||||
|
||||
/***
|
||||
* Not supported with default Jax-Rs client implementation
|
||||
* @param theHost
|
||||
* The host (or null to disable proxying, as is the default)
|
||||
* @param thePort
|
||||
*/
|
||||
@Override
|
||||
public void setProxy(String theHost, Integer thePort) {
|
||||
throw new UnsupportedOperationException("Proxies are not supported yet in JAX-RS client");
|
||||
}
|
||||
|
||||
/**
|
||||
* Only accept clients of type javax.ws.rs.client.Client
|
||||
*
|
||||
* @param theHttpClient
|
||||
*/
|
||||
|
||||
/**
|
||||
* Only accept clients of type javax.ws.rs.client.Client
|
||||
* Can be used to set a specific Client implementation
|
||||
* @param theHttpClient
|
||||
*/
|
||||
@Override
|
||||
public synchronized void setHttpClient(Object theHttpClient) {
|
||||
this.myNativeClient = (Client) theHttpClient;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a list of Jax-Rs component (provider, filter...)
|
||||
* @param components list of Jax-Rs components to register
|
||||
*/
|
||||
public void register(List<Class<?>> components) {
|
||||
registeredComponents = components;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected JaxRsHttpClient getHttpClient(String theServerBase) {
|
||||
return new JaxRsHttpClient(getNativeClientClient(), new StringBuilder(theServerBase), null, null, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetHttpClient() {
|
||||
this.myNativeClient = null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void resetHttpClient() {
|
||||
if (myNativeClient != null)
|
||||
myNativeClient.close(); // close client to avoid memory leak
|
||||
myNativeClient = null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
package ca.uhn.fhir.jaxrs.client;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import javax.ws.rs.client.Client;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
/**
|
||||
* Created by Sebastien Riviere on 31/07/2017.
|
||||
*/
|
||||
public class JaxRsRestfulClientFactoryTest {
|
||||
|
||||
private final FhirContext context = FhirContext.forDstu2();
|
||||
private JaxRsRestfulClientFactory factory;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
factory = new JaxRsRestfulClientFactory(context);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void emptyConstructorTest() {
|
||||
assertNotNull(new JaxRsRestfulClientFactory());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getDefaultNativeClientTest() {
|
||||
assertNotNull(factory.getNativeClientClient());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNativeClientEmptyRegisteredComponentListTest() {
|
||||
factory.register(new ArrayList<Class<?>>());
|
||||
final Client result = factory.getNativeClientClient();
|
||||
assertNotNull(result);
|
||||
assertTrue(result.getConfiguration().getClasses().isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getNativeClientRegisteredComponentListTest() {
|
||||
factory.register(Arrays.asList(MyFilter.class, String.class));
|
||||
final Client result = factory.getNativeClientClient();
|
||||
assertNotNull(result);
|
||||
assertEquals(1, result.getConfiguration().getClasses().size());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,18 @@
|
|||
package ca.uhn.fhir.jaxrs.client;
|
||||
|
||||
import javax.ws.rs.client.ClientRequestContext;
|
||||
import javax.ws.rs.client.ClientResponseContext;
|
||||
import javax.ws.rs.client.ClientResponseFilter;
|
||||
import javax.ws.rs.ext.Provider;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Created by Sebastien Riviere on 31/07/2017.
|
||||
*/
|
||||
@Provider
|
||||
public class MyFilter implements ClientResponseFilter {
|
||||
@Override
|
||||
public void filter(final ClientRequestContext requestContext, final ClientResponseContext responseContext) throws IOException {
|
||||
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
|
@ -32,6 +32,12 @@
|
|||
<version>3.5</version>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.codehaus.woodstox</groupId>
|
||||
<artifactId>woodstox-core-asl</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.sf.saxon</groupId>
|
||||
<artifactId>Saxon-HE</artifactId>
|
||||
|
@ -105,10 +111,12 @@
|
|||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
</dependency>
|
||||
-->
|
||||
|
||||
<dependency>
|
||||
<groupId>ch.qos.logback</groupId>
|
||||
|
@ -163,7 +171,7 @@
|
|||
<artifactId>thymeleaf-spring4</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- For UCUM -->
|
||||
<!-- For UCUM: TODO we should replace this with org.fhir UCUM -->
|
||||
<dependency>
|
||||
<groupId>org.jscience</groupId>
|
||||
<artifactId>jscience</artifactId>
|
||||
|
@ -233,10 +241,12 @@
|
|||
<!-- <dependency> <groupId>org.hsqldb</groupId> <artifactId>hsqldb</artifactId> <version>2.3.2</version> </dependency> -->
|
||||
|
||||
<!-- Spring -->
|
||||
<!--
|
||||
<dependency>
|
||||
<groupId>aopalliance</groupId>
|
||||
<artifactId>aopalliance</artifactId>
|
||||
</dependency>
|
||||
-->
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
|
@ -358,6 +368,7 @@
|
|||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>javax.mail-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.sun.mail</groupId>
|
||||
|
|
|
@ -24,6 +24,7 @@ import ca.uhn.fhir.jpa.graphql.JpaStorageServices;
|
|||
import ca.uhn.fhir.jpa.search.*;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.sp.SearchParamPresenceSvcImpl;
|
||||
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import org.hl7.fhir.utilities.graphql.IGraphQLStorageServices;
|
||||
|
@ -93,15 +94,6 @@ public class BaseConfig implements SchedulingConfigurer {
|
|||
return new StaleSearchDeletingSvcImpl();
|
||||
}
|
||||
|
||||
// @PostConstruct
|
||||
// public void wireResourceDaos() {
|
||||
// Map<String, IDao> daoBeans = myAppCtx.getBeansOfType(IDao.class);
|
||||
// List bean = myAppCtx.getBean("myResourceProvidersDstu2", List.class);
|
||||
// for (IDao next : daoBeans.values()) {
|
||||
// next.setResourceDaos(bean);
|
||||
// }
|
||||
// }
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public SubscriptionRestHookInterceptor subscriptionRestHookInterceptor() {
|
||||
|
@ -114,15 +106,23 @@ public class BaseConfig implements SchedulingConfigurer {
|
|||
return new SubscriptionWebsocketInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: If you're going to use this, you need to provide a bean
|
||||
* of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender}
|
||||
* in your own Spring config
|
||||
*/
|
||||
@Bean
|
||||
@Lazy
|
||||
public SubscriptionEmailInterceptor subscriptionEmailInterceptor() {
|
||||
return new SubscriptionEmailInterceptor();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public TaskScheduler taskScheduler() {
|
||||
ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler();
|
||||
retVal.setConcurrentExecutor(scheduledExecutorService().getObject());
|
||||
retVal.setScheduledExecutor(scheduledExecutorService().getObject());
|
||||
return retVal;
|
||||
// ThreadPoolTaskScheduler retVal = new ThreadPoolTaskScheduler();
|
||||
// retVal.setPoolSize(5);
|
||||
// return retVal;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -29,7 +29,6 @@ import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
|||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.jpa.util.DeleteConflict;
|
||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||
import ca.uhn.fhir.model.api.*;
|
||||
import ca.uhn.fhir.model.base.composite.BaseCodingDt;
|
||||
import ca.uhn.fhir.model.base.composite.BaseResourceReferenceDt;
|
||||
|
@ -51,10 +50,7 @@ import ca.uhn.fhir.rest.server.exceptions.*;
|
|||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import ca.uhn.fhir.util.CoverageIgnore;
|
||||
import ca.uhn.fhir.util.FhirTerser;
|
||||
import ca.uhn.fhir.util.OperationOutcomeUtil;
|
||||
import ca.uhn.fhir.util.UrlUtil;
|
||||
import ca.uhn.fhir.util.*;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Sets;
|
||||
|
@ -1011,14 +1007,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
changed = true;
|
||||
}
|
||||
|
||||
if (theResource instanceof IResource) {
|
||||
String title = ResourceMetadataKeyEnum.TITLE.get((IResource) theResource);
|
||||
if (title != null && title.length() > BaseHasResource.MAX_TITLE_LENGTH) {
|
||||
title = title.substring(0, BaseHasResource.MAX_TITLE_LENGTH);
|
||||
}
|
||||
theEntity.setTitle(title);
|
||||
}
|
||||
|
||||
return changed;
|
||||
}
|
||||
|
||||
|
@ -1052,10 +1040,6 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
ResourceMetadataKeyEnum.UPDATED.put(res, theEntity.getUpdated());
|
||||
IDao.RESOURCE_PID.put(res, theEntity.getId());
|
||||
|
||||
if (theEntity.getTitle() != null) {
|
||||
ResourceMetadataKeyEnum.TITLE.put(res, theEntity.getTitle());
|
||||
}
|
||||
|
||||
Collection<? extends BaseTag> tags = theEntity.getTags();
|
||||
if (theEntity.isHasTags()) {
|
||||
TagList tagList = new TagList();
|
||||
|
@ -2056,7 +2040,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
StringBuilder b = new StringBuilder();
|
||||
if (theResource instanceof IResource) {
|
||||
IResource resource = (IResource) theResource;
|
||||
List<XMLEvent> xmlEvents = resource.getText().getDiv().getValue();
|
||||
List<XMLEvent> xmlEvents = XmlUtil.parse(resource.getText().getDiv().getValue());
|
||||
if (xmlEvents != null) {
|
||||
for (XMLEvent next : xmlEvents) {
|
||||
if (next.isCharacters()) {
|
||||
|
@ -2069,8 +2053,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao {
|
|||
IDomainResource resource = (IDomainResource) theResource;
|
||||
try {
|
||||
String divAsString = resource.getText().getDivAsString();
|
||||
XhtmlDt xhtml = new XhtmlDt(divAsString);
|
||||
List<XMLEvent> xmlEvents = xhtml.getValue();
|
||||
List<XMLEvent> xmlEvents = XmlUtil.parse(divAsString);
|
||||
if (xmlEvents != null) {
|
||||
for (XMLEvent next : xmlEvents) {
|
||||
if (next.isCharacters()) {
|
||||
|
|
|
@ -30,16 +30,14 @@ import ca.uhn.fhir.jpa.dao.data.IResourceTableDao;
|
|||
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||
import ca.uhn.fhir.jpa.util.DeleteConflict;
|
||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||
import ca.uhn.fhir.jpa.util.jsonpatch.JsonPatchUtils;
|
||||
import ca.uhn.fhir.jpa.util.xmlpatch.XmlPatchUtils;
|
||||
import ca.uhn.fhir.model.api.*;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.api.PatchTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.QualifiedParamList;
|
||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.*;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
|
@ -64,6 +62,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import javax.annotation.PostConstruct;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.*;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
@ -897,6 +896,12 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
@Override
|
||||
public IBundleProvider search(final SearchParameterMap theParams, RequestDetails theRequestDetails) {
|
||||
return search(theParams, theRequestDetails, null);
|
||||
}
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
@Override
|
||||
public IBundleProvider search(final SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse) {
|
||||
|
||||
if (myDaoConfig.getIndexMissingFields() == DaoConfig.IndexEnabledEnum.DISABLED) {
|
||||
for (List<List<? extends IQueryParameterType>> nextAnds : theParams.values()) {
|
||||
|
@ -928,7 +933,24 @@ public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends B
|
|||
}
|
||||
}
|
||||
|
||||
return mySearchCoordinatorSvc.registerSearch(this, theParams, getResourceName());
|
||||
CacheControlDirective cacheControlDirective = new CacheControlDirective();
|
||||
if (theRequestDetails != null) {
|
||||
cacheControlDirective.parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL));
|
||||
}
|
||||
|
||||
IBundleProvider retVal = mySearchCoordinatorSvc.registerSearch(this, theParams, getResourceName(), cacheControlDirective);
|
||||
|
||||
if (retVal instanceof PersistedJpaBundleProvider) {
|
||||
PersistedJpaBundleProvider provider = (PersistedJpaBundleProvider) retVal;
|
||||
if (provider.isCacheHit()) {
|
||||
if (theServletResponse != null && theRequestDetails != null) {
|
||||
String value = "HIT from " + theRequestDetails.getFhirServerBase();
|
||||
theServletResponse.addHeader(Constants.HEADER_X_CACHE, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -107,6 +107,8 @@ public class DaoConfig {
|
|||
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<String>();
|
||||
private Set<String> myTreatReferencesAsLogical = new HashSet<String>(DEFAULT_LOGICAL_BASE_URLS);
|
||||
private boolean myAutoCreatePlaceholderReferenceTargets;
|
||||
private Integer myCacheControlNoStoreMaxResultsUpperLimit = 1000;
|
||||
private Integer myCountSearchResultsUpTo = null;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
|
@ -131,6 +133,76 @@ public class DaoConfig {
|
|||
myTreatReferencesAsLogical.add(theTreatReferencesAsLogical);
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the highest number that a client is permitted to use in a
|
||||
* <code>Cache-Control: nostore, max-results=NNN</code>
|
||||
* directive. If the client tries to exceed this limit, the
|
||||
* request will be denied. Defaults to 1000.
|
||||
*/
|
||||
public Integer getCacheControlNoStoreMaxResultsUpperLimit() {
|
||||
return myCacheControlNoStoreMaxResultsUpperLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the highest number that a client is permitted to use in a
|
||||
* <code>Cache-Control: nostore, max-results=NNN</code>
|
||||
* directive. If the client tries to exceed this limit, the
|
||||
* request will be denied. Defaults to 1000.
|
||||
*/
|
||||
public void setCacheControlNoStoreMaxResultsUpperLimit(Integer theCacheControlNoStoreMaxResults) {
|
||||
myCacheControlNoStoreMaxResultsUpperLimit = theCacheControlNoStoreMaxResults;
|
||||
}
|
||||
|
||||
/**
|
||||
* When searching, if set to a non-null value (default is <code>null</code>) the
|
||||
* search coordinator will attempt to find at least this many results
|
||||
* before returning a response to the client. This parameter mainly affects
|
||||
* whether a "total count" is included in the response bundle for searches that
|
||||
* return large amounts of data.
|
||||
* <p>
|
||||
* For a search that returns 10000 results, if this value is set to
|
||||
* 10000 the search coordinator will find all 10000 results
|
||||
* prior to returning, so the initial response bundle will have the
|
||||
* total set to 10000. If this value is null (or less than 10000)
|
||||
* the response bundle will likely return slightly faster, but will
|
||||
* not include the total. Subsequent page requests will likely
|
||||
* include the total however, if they are performed after the
|
||||
* search coordinator has found all results.
|
||||
* </p>
|
||||
* <p>
|
||||
* Set this value to <code>0</code> to always load all
|
||||
* results before returning.
|
||||
* </p>
|
||||
*/
|
||||
public Integer getCountSearchResultsUpTo() {
|
||||
return myCountSearchResultsUpTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* When searching, if set to a non-null value (default is <code>null</code>) the
|
||||
* search coordinator will attempt to find at least this many results
|
||||
* before returning a response to the client. This parameter mainly affects
|
||||
* whether a "total count" is included in the response bundle for searches that
|
||||
* return large amounts of data.
|
||||
* <p>
|
||||
* For a search that returns 10000 results, if this value is set to
|
||||
* 10000 the search coordinator will find all 10000 results
|
||||
* prior to returning, so the initial response bundle will have the
|
||||
* total set to 10000. If this value is null (or less than 10000)
|
||||
* the response bundle will likely return slightly faster, but will
|
||||
* not include the total. Subsequent page requests will likely
|
||||
* include the total however, if they are performed after the
|
||||
* search coordinator has found all results.
|
||||
* </p>
|
||||
* <p>
|
||||
* Set this value to <code>0</code> to always load all
|
||||
* results before returning.
|
||||
* </p>
|
||||
*/
|
||||
public void setCountSearchResultsUpTo(Integer theCountSearchResultsUpTo) {
|
||||
myCountSearchResultsUpTo = theCountSearchResultsUpTo;
|
||||
}
|
||||
|
||||
/**
|
||||
* When a code system is added that contains more than this number of codes,
|
||||
* the code system will be indexed later in an incremental process in order to
|
||||
|
@ -434,6 +506,11 @@ public class DaoConfig {
|
|||
* This approach can improve performance, especially under heavy load, but can also mean that
|
||||
* searches may potentially return slightly out-of-date results.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that if this is set to a non-null value, clients may override this setting by using
|
||||
* the <code>Cache-Control</code> header. If this is set to <code>null</code>, the Cache-Control
|
||||
* header will be ignored.
|
||||
* </p>
|
||||
*/
|
||||
public Long getReuseCachedSearchResultsForMillis() {
|
||||
return myReuseCachedSearchResultsForMillis;
|
||||
|
@ -449,6 +526,11 @@ public class DaoConfig {
|
|||
* This approach can improve performance, especially under heavy load, but can also mean that
|
||||
* searches may potentially return slightly out-of-date results.
|
||||
* </p>
|
||||
* <p>
|
||||
* Note that if this is set to a non-null value, clients may override this setting by using
|
||||
* the <code>Cache-Control</code> header. If this is set to <code>null</code>, the Cache-Control
|
||||
* header will be ignored.
|
||||
* </p>
|
||||
*/
|
||||
public void setReuseCachedSearchResultsForMillis(Long theReuseCachedSearchResultsForMillis) {
|
||||
myReuseCachedSearchResultsForMillis = theReuseCachedSearchResultsForMillis;
|
||||
|
|
|
@ -65,7 +65,7 @@ public class FhirResourceDaoPatientDstu2 extends FhirResourceDaoDstu2<Patient>im
|
|||
paramMap.setLoadSynchronous(true);
|
||||
}
|
||||
|
||||
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName());
|
||||
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -33,6 +33,10 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
|||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import org.springframework.transaction.annotation.Propagation;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
|
||||
|
||||
|
@ -179,6 +183,9 @@ public interface IFhirResourceDao<T extends IBaseResource> extends IDao {
|
|||
|
||||
IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails);
|
||||
|
||||
@Transactional(propagation = Propagation.SUPPORTS)
|
||||
IBundleProvider search(SearchParameterMap theParams, RequestDetails theRequestDetails, HttpServletResponse theServletResponse);
|
||||
|
||||
Set<Long> searchForIds(SearchParameterMap theParams);
|
||||
|
||||
/**
|
||||
|
|
|
@ -1259,7 +1259,7 @@ public class SearchBuilder implements ISearchBuilder {
|
|||
* Check if there is a unique key associated with the set
|
||||
* of parameters passed in
|
||||
*/
|
||||
ourLog.info("Checking for unique index for query: {}", theParams.toNormalizedQueryString(myContext));
|
||||
ourLog.debug("Checking for unique index for query: {}", theParams.toNormalizedQueryString(myContext));
|
||||
if (myCallingDao.getConfig().isUniqueIndexesEnabled()) {
|
||||
if (myParams.getIncludes().isEmpty()) {
|
||||
if (myParams.getRevIncludes().isEmpty()) {
|
||||
|
|
|
@ -274,14 +274,6 @@ public class SearchParameterMap extends LinkedHashMap<String, List<List<? extend
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated As of HAPI FHIR 2.4 this method no longer does anything
|
||||
*/
|
||||
@Deprecated
|
||||
public void setPersistResults(boolean thePersistResults) {
|
||||
// does nothing as of HAPI FHIR 2.4
|
||||
}
|
||||
|
||||
public void setRevIncludes(Set<Include> theRevIncludes) {
|
||||
myRevIncludes = theRevIncludes;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Collections;
|
|||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -66,7 +67,7 @@ public class FhirResourceDaoPatientDstu3 extends FhirResourceDaoDstu3<Patient>im
|
|||
paramMap.setLoadSynchronous(true);
|
||||
}
|
||||
|
||||
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName());
|
||||
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -29,6 +29,7 @@ import java.util.Map.Entry;
|
|||
import javax.persistence.TypedQuery;
|
||||
|
||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.hibernate.Session;
|
||||
|
@ -438,7 +439,11 @@ public class FhirSystemDaoDstu3 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
|||
DaoMethodOutcome outcome;
|
||||
UrlParts parts = UrlUtil.parseUrl(url);
|
||||
if (isNotBlank(parts.getResourceId())) {
|
||||
res.setId(new IdType(parts.getResourceType(), parts.getResourceId()));
|
||||
String version = null;
|
||||
if (isNotBlank(nextReqEntry.getRequest().getIfMatch())) {
|
||||
version = ParameterUtil.parseETagValue(nextReqEntry.getRequest().getIfMatch());
|
||||
}
|
||||
res.setId(new IdType(parts.getResourceType(), parts.getResourceId(), version));
|
||||
outcome = resourceDao.update(res, null, false, theRequestDetails);
|
||||
} else {
|
||||
res.setId((String) null);
|
||||
|
|
|
@ -215,6 +215,9 @@ public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry {
|
|||
case RETIRED:
|
||||
status = RuntimeSearchParamStatusEnum.RETIRED;
|
||||
break;
|
||||
case UNKNOWN:
|
||||
status = RuntimeSearchParamStatusEnum.UNKNOWN;
|
||||
break;
|
||||
case NULL:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -24,6 +24,7 @@ import java.util.Collections;
|
|||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import org.hl7.fhir.r4.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
|
@ -66,7 +67,7 @@ public class FhirResourceDaoPatientR4 extends FhirResourceDaoR4<Patient>implemen
|
|||
paramMap.setLoadSynchronous(true);
|
||||
}
|
||||
|
||||
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName());
|
||||
return mySearchCoordinatorSvc.registerSearch(this, paramMap, getResourceName(), new CacheControlDirective().parse(theRequestDetails.getHeaders(Constants.HEADER_CACHE_CONTROL)));
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -28,6 +28,7 @@ import java.util.Map.Entry;
|
|||
|
||||
import javax.persistence.TypedQuery;
|
||||
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.http.NameValuePair;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
|
@ -435,7 +436,11 @@ public class FhirSystemDaoR4 extends BaseHapiFhirSystemDao<Bundle, Meta> {
|
|||
DaoMethodOutcome outcome;
|
||||
UrlParts parts = UrlUtil.parseUrl(url);
|
||||
if (isNotBlank(parts.getResourceId())) {
|
||||
res.setId(new IdType(parts.getResourceType(), parts.getResourceId()));
|
||||
String version = null;
|
||||
if (isNotBlank(nextReqEntry.getRequest().getIfMatch())) {
|
||||
version = ParameterUtil.parseETagValue(nextReqEntry.getRequest().getIfMatch());
|
||||
}
|
||||
res.setId(new IdType(parts.getResourceType(), parts.getResourceId(), version));
|
||||
outcome = resourceDao.update(res, null, false, theRequestDetails);
|
||||
} else {
|
||||
res.setId((String) null);
|
||||
|
|
|
@ -216,6 +216,9 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry {
|
|||
case RETIRED:
|
||||
status = RuntimeSearchParamStatusEnum.RETIRED;
|
||||
break;
|
||||
case UNKNOWN:
|
||||
status = RuntimeSearchParamStatusEnum.UNKNOWN;
|
||||
break;
|
||||
case NULL:
|
||||
break;
|
||||
}
|
||||
|
|
|
@ -20,52 +20,54 @@ package ca.uhn.fhir.jpa.entity;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
import javax.persistence.*;
|
||||
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import org.hibernate.annotations.OptimisticLock;
|
||||
|
||||
import javax.persistence.*;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
|
||||
@MappedSuperclass
|
||||
public abstract class BaseHasResource {
|
||||
|
||||
public static final int MAX_TITLE_LENGTH = 100;
|
||||
|
||||
@Column(name = "RES_DELETED_AT", nullable = true)
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
private Date myDeleted;
|
||||
|
||||
@Column(name = "RES_ENCODING", nullable = false, length = 5)
|
||||
@Enumerated(EnumType.STRING)
|
||||
@OptimisticLock(excluded = true)
|
||||
private ResourceEncodingEnum myEncoding;
|
||||
|
||||
@Column(name = "RES_VERSION", nullable = true, length = 7)
|
||||
@Enumerated(EnumType.STRING)
|
||||
@OptimisticLock(excluded = true)
|
||||
private FhirVersionEnum myFhirVersion;
|
||||
|
||||
@OneToOne(optional = true, fetch = FetchType.EAGER, cascade = {}, orphanRemoval = false)
|
||||
@JoinColumn(name = "FORCED_ID_PID")
|
||||
@OptimisticLock(excluded = true)
|
||||
private ForcedId myForcedId;
|
||||
|
||||
@Column(name = "HAS_TAGS", nullable = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private boolean myHasTags;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "RES_PUBLISHED", nullable = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Date myPublished;
|
||||
|
||||
@Column(name = "RES_TEXT", length = Integer.MAX_VALUE - 1, nullable = false)
|
||||
@Lob()
|
||||
@OptimisticLock(excluded = true)
|
||||
private byte[] myResource;
|
||||
|
||||
@Column(name = "RES_TITLE", nullable = true, length = MAX_TITLE_LENGTH)
|
||||
private String myTitle;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name = "RES_UPDATED", nullable = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Date myUpdated;
|
||||
|
||||
public abstract BaseTag addTag(TagDefinition theDef);
|
||||
|
@ -74,18 +76,36 @@ public abstract class BaseHasResource {
|
|||
return myDeleted;
|
||||
}
|
||||
|
||||
public void setDeleted(Date theDate) {
|
||||
myDeleted = theDate;
|
||||
}
|
||||
|
||||
public ResourceEncodingEnum getEncoding() {
|
||||
return myEncoding;
|
||||
}
|
||||
|
||||
public void setEncoding(ResourceEncodingEnum theEncoding) {
|
||||
myEncoding = theEncoding;
|
||||
}
|
||||
|
||||
public FhirVersionEnum getFhirVersion() {
|
||||
return myFhirVersion;
|
||||
}
|
||||
|
||||
public void setFhirVersion(FhirVersionEnum theFhirVersion) {
|
||||
myFhirVersion = theFhirVersion;
|
||||
}
|
||||
|
||||
public ForcedId getForcedId() {
|
||||
return myForcedId;
|
||||
}
|
||||
|
||||
public void setForcedId(ForcedId theForcedId) {
|
||||
myForcedId = theForcedId;
|
||||
}
|
||||
|
||||
public abstract Long getId();
|
||||
|
||||
public abstract IdDt getIdDt();
|
||||
|
||||
public InstantDt getPublished() {
|
||||
|
@ -96,22 +116,30 @@ public abstract class BaseHasResource {
|
|||
}
|
||||
}
|
||||
|
||||
public void setPublished(InstantDt thePublished) {
|
||||
myPublished = thePublished.getValue();
|
||||
}
|
||||
|
||||
public byte[] getResource() {
|
||||
return myResource;
|
||||
}
|
||||
|
||||
public void setResource(byte[] theResource) {
|
||||
myResource = theResource;
|
||||
}
|
||||
|
||||
public abstract String getResourceType();
|
||||
|
||||
public abstract Collection<? extends BaseTag> getTags();
|
||||
|
||||
public String getTitle() {
|
||||
return myTitle;
|
||||
}
|
||||
|
||||
public InstantDt getUpdated() {
|
||||
return new InstantDt(myUpdated);
|
||||
}
|
||||
|
||||
public void setUpdated(InstantDt theUpdated) {
|
||||
myUpdated = theUpdated.getValue();
|
||||
}
|
||||
|
||||
public Date getUpdatedDate() {
|
||||
return myUpdated;
|
||||
}
|
||||
|
@ -122,24 +150,6 @@ public abstract class BaseHasResource {
|
|||
return myHasTags;
|
||||
}
|
||||
|
||||
public void setDeleted(Date theDate) {
|
||||
myDeleted = theDate;
|
||||
}
|
||||
|
||||
public abstract Long getId();
|
||||
|
||||
public void setEncoding(ResourceEncodingEnum theEncoding) {
|
||||
myEncoding = theEncoding;
|
||||
}
|
||||
|
||||
public void setFhirVersion(FhirVersionEnum theFhirVersion) {
|
||||
myFhirVersion = theFhirVersion;
|
||||
}
|
||||
|
||||
public void setForcedId(ForcedId theForcedId) {
|
||||
myForcedId = theForcedId;
|
||||
}
|
||||
|
||||
public void setHasTags(boolean theHasTags) {
|
||||
myHasTags = theHasTags;
|
||||
}
|
||||
|
@ -148,24 +158,8 @@ public abstract class BaseHasResource {
|
|||
myPublished = thePublished;
|
||||
}
|
||||
|
||||
public void setPublished(InstantDt thePublished) {
|
||||
myPublished = thePublished.getValue();
|
||||
}
|
||||
|
||||
public void setResource(byte[] theResource) {
|
||||
myResource = theResource;
|
||||
}
|
||||
|
||||
public void setTitle(String theTitle) {
|
||||
myTitle = theTitle;
|
||||
}
|
||||
|
||||
public void setUpdated(Date theUpdated) {
|
||||
myUpdated = theUpdated;
|
||||
}
|
||||
|
||||
public void setUpdated(InstantDt theUpdated) {
|
||||
myUpdated = theUpdated.getValue();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,6 +37,7 @@ import org.apache.lucene.analysis.snowball.SnowballPorterFilterFactory;
|
|||
import org.apache.lucene.analysis.standard.StandardFilterFactory;
|
||||
import org.apache.lucene.analysis.standard.StandardTokenizerFactory;
|
||||
import org.hibernate.annotations.ColumnDefault;
|
||||
import org.hibernate.annotations.OptimisticLock;
|
||||
import org.hibernate.search.annotations.*;
|
||||
import org.hibernate.search.annotations.Parameter;
|
||||
|
||||
|
@ -124,12 +125,15 @@ public class ResourceTable extends BaseHasResource implements Serializable {
|
|||
@Field(name = "myContentTextNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")),
|
||||
@Field(name = "myContentTextPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
|
||||
})
|
||||
@OptimisticLock(excluded = true)
|
||||
private String myContentText;
|
||||
|
||||
@Column(name = "HASH_SHA256", length = 64, nullable = true)
|
||||
@OptimisticLock(excluded = true)
|
||||
private String myHashSha256;
|
||||
|
||||
@Column(name = "SP_HAS_LINKS")
|
||||
@OptimisticLock(excluded = true)
|
||||
private boolean myHasLinks;
|
||||
|
||||
@Id
|
||||
|
@ -139,12 +143,15 @@ public class ResourceTable extends BaseHasResource implements Serializable {
|
|||
private Long myId;
|
||||
|
||||
@OneToMany(mappedBy = "myTargetResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<ResourceLink> myIncomingResourceLinks;
|
||||
|
||||
@Column(name = "SP_INDEX_STATUS", nullable = true)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Long myIndexStatus;
|
||||
|
||||
@Column(name = "RES_LANGUAGE", length = MAX_LANGUAGE_LENGTH, nullable = true)
|
||||
@OptimisticLock(excluded = true)
|
||||
private String myLanguage;
|
||||
|
||||
/**
|
||||
|
@ -157,69 +164,100 @@ public class ResourceTable extends BaseHasResource implements Serializable {
|
|||
@Field(name = "myNarrativeTextNGram", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompleteNGramAnalyzer")),
|
||||
@Field(name = "myNarrativeTextPhonetic", index = org.hibernate.search.annotations.Index.YES, store = Store.NO, analyze = Analyze.YES, analyzer = @Analyzer(definition = "autocompletePhoneticAnalyzer"))
|
||||
})
|
||||
@OptimisticLock(excluded = true)
|
||||
private String myNarrativeText;
|
||||
|
||||
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<ResourceIndexedSearchParamCoords> myParamsCoords;
|
||||
|
||||
@Column(name = "SP_COORDS_PRESENT")
|
||||
@OptimisticLock(excluded = true)
|
||||
private boolean myParamsCoordsPopulated;
|
||||
|
||||
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<ResourceIndexedSearchParamDate> myParamsDate;
|
||||
|
||||
@Column(name = "SP_DATE_PRESENT")
|
||||
@OptimisticLock(excluded = true)
|
||||
private boolean myParamsDatePopulated;
|
||||
|
||||
@OptimisticLock(excluded = true)
|
||||
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
private Collection<ResourceIndexedSearchParamNumber> myParamsNumber;
|
||||
|
||||
@Column(name = "SP_NUMBER_PRESENT")
|
||||
@OptimisticLock(excluded = true)
|
||||
private boolean myParamsNumberPopulated;
|
||||
|
||||
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<ResourceIndexedSearchParamQuantity> myParamsQuantity;
|
||||
|
||||
@Column(name = "SP_QUANTITY_PRESENT")
|
||||
@OptimisticLock(excluded = true)
|
||||
private boolean myParamsQuantityPopulated;
|
||||
|
||||
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<ResourceIndexedSearchParamString> myParamsString;
|
||||
|
||||
@Column(name = "SP_STRING_PRESENT")
|
||||
@OptimisticLock(excluded = true)
|
||||
private boolean myParamsStringPopulated;
|
||||
|
||||
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<ResourceIndexedSearchParamToken> myParamsToken;
|
||||
|
||||
@Column(name = "SP_TOKEN_PRESENT")
|
||||
@OptimisticLock(excluded = true)
|
||||
private boolean myParamsTokenPopulated;
|
||||
|
||||
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<ResourceIndexedSearchParamUri> myParamsUri;
|
||||
|
||||
@Column(name = "SP_URI_PRESENT")
|
||||
@OptimisticLock(excluded = true)
|
||||
private boolean myParamsUriPopulated;
|
||||
|
||||
@Column(name = "RES_PROFILE", length = MAX_PROFILE_LENGTH, nullable = true)
|
||||
@OptimisticLock(excluded = true)
|
||||
private String myProfile;
|
||||
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
private Collection<ResourceIndexedCompositeStringUnique> myParamsCompositeStringUnique;
|
||||
|
||||
// Added in 3.0.0 - Should make this a primitive Boolean at some point
|
||||
@OptimisticLock(excluded = true)
|
||||
@Column(name = "SP_CMPSTR_UNIQ_PRESENT")
|
||||
private Boolean myParamsCompositeStringUniquePresent = false;
|
||||
|
||||
@OneToMany(mappedBy = "myResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<ResourceIndexedCompositeStringUnique> myParamsCompositeStringUnique;
|
||||
|
||||
@OneToMany(mappedBy = "mySourceResource", cascade = {}, fetch = FetchType.LAZY, orphanRemoval = false)
|
||||
@IndexedEmbedded()
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<ResourceLink> myResourceLinks;
|
||||
|
||||
@Column(name = "RES_TYPE", length = RESTYPE_LEN)
|
||||
@Field
|
||||
@OptimisticLock(excluded = true)
|
||||
private String myResourceType;
|
||||
|
||||
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Collection<SearchParamPresent> mySearchParamPresents;
|
||||
|
||||
@OneToMany(mappedBy = "myResource", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
|
||||
@OptimisticLock(excluded = true)
|
||||
private Set<ResourceTag> myTags;
|
||||
|
||||
@Transient
|
||||
private transient boolean myUnchangedInCurrentOperation;
|
||||
|
||||
@Version
|
||||
@Column(name = "RES_VER")
|
||||
private long myVersion;
|
||||
|
||||
|
@ -555,7 +593,6 @@ public class ResourceTable extends BaseHasResource implements Serializable {
|
|||
retVal.setResourceType(myResourceType);
|
||||
retVal.setVersion(myVersion);
|
||||
|
||||
retVal.setTitle(getTitle());
|
||||
retVal.setPublished(getPublished());
|
||||
retVal.setUpdated(getUpdated());
|
||||
retVal.setEncoding(getEncoding());
|
||||
|
|
|
@ -50,6 +50,7 @@ public class Search implements Serializable {
|
|||
private static final int FAILURE_MESSAGE_LENGTH = 500;
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
public static final int MAX_SEARCH_QUERY_STRING = 10000;
|
||||
|
||||
@Temporal(TemporalType.TIMESTAMP)
|
||||
@Column(name="CREATED", nullable=false, updatable=false)
|
||||
|
@ -100,8 +101,8 @@ public class Search implements Serializable {
|
|||
private Date mySearchLastReturned;
|
||||
|
||||
@Lob()
|
||||
@Basic(fetch=FetchType.LAZY)
|
||||
@Column(name="SEARCH_QUERY_STRING", nullable=true, updatable=false)
|
||||
@Basic(fetch=FetchType.LAZY)
|
||||
@Column(name="SEARCH_QUERY_STRING", nullable=true, updatable=false, length = MAX_SEARCH_QUERY_STRING)
|
||||
private String mySearchQueryString;
|
||||
|
||||
@Column(name="SEARCH_QUERY_STRING_HASH", nullable=true, updatable=false)
|
||||
|
@ -256,7 +257,11 @@ public class Search implements Serializable {
|
|||
}
|
||||
|
||||
public void setSearchQueryString(String theSearchQueryString) {
|
||||
mySearchQueryString = theSearchQueryString;
|
||||
if (theSearchQueryString != null && theSearchQueryString.length() > MAX_SEARCH_QUERY_STRING) {
|
||||
mySearchQueryString = null;
|
||||
} else {
|
||||
mySearchQueryString = theSearchQueryString;
|
||||
}
|
||||
}
|
||||
|
||||
public void setSearchQueryStringHash(Integer theSearchQueryStringHash) {
|
||||
|
|
|
@ -115,6 +115,8 @@ public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implement
|
|||
break;
|
||||
case URI:
|
||||
break;
|
||||
case HAS:
|
||||
break;
|
||||
}
|
||||
|
||||
params.add(nextArgument.getName(), param);
|
||||
|
|
|
@ -24,6 +24,7 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
|
@ -47,7 +48,7 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
|
|||
@Qualifier("mySystemDaoDstu2")
|
||||
private IFhirSystemDao<Bundle, MetaDt> mySystemDao;
|
||||
|
||||
@Autowired
|
||||
@Autowired(required = false)
|
||||
private IFulltextSearchSvc mySearchDao;
|
||||
|
||||
//@formatter:off
|
||||
|
@ -178,7 +179,8 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
|
|||
@OperationParam(name="searchParam", min=1, max=1) String theSearchParam,
|
||||
@OperationParam(name="text", min=1, max=1) String theText
|
||||
) {
|
||||
|
||||
JpaSystemProviderDstu3.validateFulltextSearchEnabled(mySearchDao);
|
||||
|
||||
if (isBlank(theContext)) {
|
||||
throw new InvalidRequestException("Parameter 'context' must be provided");
|
||||
}
|
||||
|
@ -188,16 +190,14 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
|
|||
if (isBlank(theText)) {
|
||||
throw new InvalidRequestException("Parameter 'text' must be provided");
|
||||
}
|
||||
|
||||
|
||||
List<Suggestion> keywords = mySearchDao.suggestKeywords(theContext, theSearchParam, theText);
|
||||
|
||||
Parameters retVal = new Parameters();
|
||||
for (Suggestion next : keywords) {
|
||||
//@formatter:off
|
||||
retVal.addParameter()
|
||||
.addPart(new Parameter().setName("keyword").setValue(new StringDt(next.getTerm())))
|
||||
.addPart(new Parameter().setName("score").setValue(new DecimalDt(next.getScore())));
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
return retVal;
|
||||
|
|
|
@ -20,35 +20,42 @@ package ca.uhn.fhir.jpa.provider.dstu3;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDstu3<ValueSet> {
|
||||
|
||||
//@formatter:off
|
||||
@Operation(name = "$expand", idempotent = true)
|
||||
public ValueSet expand(
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional=true) IdType theId,
|
||||
@OperationParam(name="valueSet", min=0, max=1) ValueSet theValueSet,
|
||||
@OperationParam(name="identifier", min=0, max=1) UriType theIdentifier,
|
||||
@OperationParam(name = "filter", min=0, max=1) StringType theFilter,
|
||||
RequestDetails theRequestDetails) {
|
||||
//@formatter:on
|
||||
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional = true) IdType theId,
|
||||
@OperationParam(name = "valueSet", min = 0, max = 1) ValueSet theValueSet,
|
||||
// Note: url is correct and identifier is not, but identifier was only added as
|
||||
// of 3.1.0 so we'll leave url for now. See: https://groups.google.com/d/msgid/hapi-fhir/CAN2Cfy8kW%2BAOkgC6VjPsU3gRCpExCNZBmJdi-k5R_TWeyWH4tA%40mail.gmail.com?utm_medium=email&utm_source=footer
|
||||
@OperationParam(name = "url", min = 0, max = 1) UriType theUrl,
|
||||
@OperationParam(name = "identifier", min = 0, max = 1) UriType theIdentifier,
|
||||
@OperationParam(name = "filter", min = 0, max = 1) StringType theFilter,
|
||||
RequestDetails theRequestDetails) {
|
||||
|
||||
boolean haveId = theId != null && theId.hasIdPart();
|
||||
boolean haveIdentifier = theIdentifier != null && isNotBlank(theIdentifier.getValue());
|
||||
UriType url = theIdentifier;
|
||||
if (theUrl != null && isNotBlank(theUrl.getValue())) {
|
||||
url = theUrl;
|
||||
}
|
||||
|
||||
boolean haveIdentifier = url != null && isNotBlank(url.getValue());
|
||||
boolean haveValueSet = theValueSet != null && theValueSet.isEmpty() == false;
|
||||
|
||||
|
||||
if (!haveId && !haveIdentifier && !haveValueSet) {
|
||||
throw new InvalidRequestException("$expand operation at the type level (no ID specified) requires an identifier or a valueSet as a part of the request");
|
||||
}
|
||||
|
@ -56,18 +63,18 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst
|
|||
if (moreThanOneTrue(haveId, haveIdentifier, haveValueSet)) {
|
||||
throw new InvalidRequestException("$expand must EITHER be invoked at the instance level, or have an identifier specified, or have a ValueSet specified. Can not combine these options.");
|
||||
}
|
||||
|
||||
|
||||
startRequest(theServletRequest);
|
||||
try {
|
||||
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
|
||||
if (haveId) {
|
||||
return dao.expand(theId, toFilterString(theFilter), theRequestDetails);
|
||||
} else if (haveIdentifier) {
|
||||
return dao.expandByIdentifier(theIdentifier.getValue(), toFilterString(theFilter));
|
||||
return dao.expandByIdentifier(url.getValue(), toFilterString(theFilter));
|
||||
} else {
|
||||
return dao.expand(theValueSet, toFilterString(theFilter));
|
||||
}
|
||||
|
||||
|
||||
} finally {
|
||||
endRequest(theServletRequest);
|
||||
}
|
||||
|
@ -79,30 +86,34 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst
|
|||
}
|
||||
|
||||
|
||||
//@formatter:off
|
||||
@SuppressWarnings("unchecked")
|
||||
@Operation(name = "$validate-code", idempotent = true, returnParameters= {
|
||||
@OperationParam(name="result", type=BooleanType.class, min=1),
|
||||
@OperationParam(name="message", type=StringType.class),
|
||||
@OperationParam(name="display", type=StringType.class)
|
||||
@Operation(name = "$validate-code", idempotent = true, returnParameters = {
|
||||
@OperationParam(name = "result", type = BooleanType.class, min = 1),
|
||||
@OperationParam(name = "message", type = StringType.class),
|
||||
@OperationParam(name = "display", type = StringType.class)
|
||||
})
|
||||
public Parameters validateCode(
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional=true) IdType theId,
|
||||
@OperationParam(name="identifier", min=0, max=1) UriType theValueSetIdentifier,
|
||||
@OperationParam(name="code", min=0, max=1) CodeType theCode,
|
||||
@OperationParam(name="system", min=0, max=1) UriType theSystem,
|
||||
@OperationParam(name="display", min=0, max=1) StringType theDisplay,
|
||||
@OperationParam(name="coding", min=0, max=1) Coding theCoding,
|
||||
@OperationParam(name="codeableConcept", min=0, max=1) CodeableConcept theCodeableConcept,
|
||||
RequestDetails theRequestDetails
|
||||
) {
|
||||
//@formatter:on
|
||||
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional = true) IdType theId,
|
||||
@OperationParam(name = "identifier", min = 0, max = 1) UriType theValueSetIdentifier,
|
||||
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
|
||||
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
|
||||
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
|
||||
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
|
||||
@OperationParam(name = "coding", min = 0, max = 1) Coding theCoding,
|
||||
@OperationParam(name = "codeableConcept", min = 0, max = 1) CodeableConcept theCodeableConcept,
|
||||
RequestDetails theRequestDetails
|
||||
) {
|
||||
|
||||
UriType url = theValueSetIdentifier;
|
||||
if (theValueSetUrl != null && isNotBlank(theValueSetUrl.getValue())) {
|
||||
url = theValueSetUrl;
|
||||
}
|
||||
|
||||
startRequest(theServletRequest);
|
||||
try {
|
||||
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
|
||||
ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
ValidateCodeResult result = dao.validateCode(url, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
Parameters retVal = new Parameters();
|
||||
retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult()));
|
||||
if (isNotBlank(result.getMessage())) {
|
||||
|
@ -117,8 +128,7 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static boolean moreThanOneTrue(boolean... theBooleans) {
|
||||
boolean haveOne = false;
|
||||
for (boolean next : theBooleans) {
|
||||
|
@ -133,5 +143,5 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,27 @@
|
|||
package ca.uhn.fhir.jpa.provider.dstu3;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
|
||||
import ca.uhn.fhir.model.api.annotation.Description;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.annotation.Transaction;
|
||||
import ca.uhn.fhir.rest.annotation.TransactionParam;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
/*
|
||||
|
@ -21,23 +43,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.dstu3.model.Parameters.ParametersParameterComponent;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.FulltextSearchSvcImpl.Suggestion;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
import ca.uhn.fhir.jpa.dao.IFulltextSearchSvc;
|
||||
import ca.uhn.fhir.jpa.provider.BaseJpaSystemProviderDstu2Plus;
|
||||
import ca.uhn.fhir.model.api.annotation.Description;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||
|
||||
public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundle, Meta> {
|
||||
|
||||
|
@ -45,122 +50,122 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
|
|||
@Qualifier("mySystemDaoDstu3")
|
||||
private IFhirSystemDao<Bundle, Meta> mySystemDao;
|
||||
|
||||
@Autowired
|
||||
@Autowired(required = false)
|
||||
private IFulltextSearchSvc mySearchDao;
|
||||
|
||||
|
||||
//@formatter:off
|
||||
// This is generated by hand:
|
||||
// ls hapi-fhir-structures-dstu2/target/generated-sources/tinder/ca/uhn/fhir/model/dstu2/resource/ | sort | sed "s/.java//" | sed "s/^/@OperationParam(name=\"/" | sed "s/$/\", type=IntegerType.class, min=0, max=1),/"
|
||||
@Operation(name="$get-resource-counts", idempotent=true, returnParameters= {
|
||||
@OperationParam(name="AllergyIntolerance", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Appointment", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="AppointmentResponse", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="AuditEvent", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Basic", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Binary", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="BodySite", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Bundle", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="CarePlan", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="CarePlan2", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Claim", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ClaimResponse", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ClinicalImpression", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Communication", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="CommunicationRequest", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Composition", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ConceptMap", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Condition", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Conformance", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Contract", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Contraindication", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Coverage", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="DataElement", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Device", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="DeviceComponent", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="DeviceMetric", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="DeviceUseRequest", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="DeviceUseStatement", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="DiagnosticOrder", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="DiagnosticReport", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="DocumentManifest", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="DocumentReference", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="EligibilityRequest", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="EligibilityResponse", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Encounter", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="EnrollmentRequest", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="EnrollmentResponse", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="EpisodeOfCare", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ExplanationOfBenefit", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="FamilyMemberHistory", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Flag", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Goal", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Group", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="HealthcareService", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ImagingObjectSelection", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ImagingStudy", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Immunization", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ImmunizationRecommendation", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ListResource", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Location", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Media", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Medication", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="MedicationAdministration", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="MedicationDispense", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="MedicationPrescription", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="MedicationStatement", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="MessageHeader", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="NamingSystem", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="NutritionOrder", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Observation", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="OperationDefinition", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="OperationOutcome", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Order", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="OrderResponse", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Organization", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Parameters", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Patient", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="PaymentNotice", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="PaymentReconciliation", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Person", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Practitioner", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Procedure", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ProcedureRequest", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ProcessRequest", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ProcessResponse", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Provenance", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Questionnaire", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="QuestionnaireAnswers", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ReferralRequest", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="RelatedPerson", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="RiskAssessment", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Schedule", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="SearchParameter", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Slot", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Specimen", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="StructureDefinition", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Subscription", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Substance", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="Supply", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="ValueSet", type=IntegerType.class, min=0, max=1),
|
||||
@OperationParam(name="VisionPrescription", type=IntegerType.class, min=0, max=1)
|
||||
@Operation(name = "$get-resource-counts", idempotent = true, returnParameters = {
|
||||
@OperationParam(name = "AllergyIntolerance", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Appointment", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "AppointmentResponse", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "AuditEvent", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Basic", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Binary", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "BodySite", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Bundle", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "CarePlan", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "CarePlan2", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Claim", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ClaimResponse", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ClinicalImpression", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Communication", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "CommunicationRequest", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Composition", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ConceptMap", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Condition", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Conformance", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Contract", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Contraindication", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Coverage", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "DataElement", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Device", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "DeviceComponent", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "DeviceMetric", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "DeviceUseRequest", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "DeviceUseStatement", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "DiagnosticOrder", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "DiagnosticReport", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "DocumentManifest", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "DocumentReference", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "EligibilityRequest", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "EligibilityResponse", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Encounter", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "EnrollmentRequest", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "EnrollmentResponse", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "EpisodeOfCare", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ExplanationOfBenefit", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "FamilyMemberHistory", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Flag", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Goal", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Group", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "HealthcareService", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ImagingObjectSelection", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ImagingStudy", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Immunization", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ImmunizationRecommendation", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ListResource", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Location", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Media", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Medication", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "MedicationAdministration", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "MedicationDispense", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "MedicationPrescription", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "MedicationStatement", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "MessageHeader", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "NamingSystem", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "NutritionOrder", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Observation", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "OperationDefinition", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "OperationOutcome", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Order", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "OrderResponse", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Organization", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Parameters", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Patient", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "PaymentNotice", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "PaymentReconciliation", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Person", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Practitioner", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Procedure", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ProcedureRequest", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ProcessRequest", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ProcessResponse", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Provenance", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Questionnaire", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "QuestionnaireAnswers", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ReferralRequest", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "RelatedPerson", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "RiskAssessment", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Schedule", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "SearchParameter", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Slot", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Specimen", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "StructureDefinition", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Subscription", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Substance", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "Supply", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "ValueSet", type = IntegerType.class, min = 0, max = 1),
|
||||
@OperationParam(name = "VisionPrescription", type = IntegerType.class, min = 0, max = 1)
|
||||
})
|
||||
@Description(shortDefinition="Provides the number of resources currently stored on the server, broken down by resource type")
|
||||
@Description(shortDefinition = "Provides the number of resources currently stored on the server, broken down by resource type")
|
||||
//@formatter:on
|
||||
public Parameters getResourceCounts() {
|
||||
Parameters retVal = new Parameters();
|
||||
|
||||
|
||||
Map<String, Long> counts = mySystemDao.getResourceCounts();
|
||||
counts = new TreeMap<String, Long>(counts);
|
||||
for (Entry<String, Long> nextEntry : counts.entrySet()) {
|
||||
retVal.addParameter().setName((nextEntry.getKey())).setValue(new IntegerType(nextEntry.getValue().intValue()));
|
||||
}
|
||||
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
//@formatter:off
|
||||
@Operation(name="$meta", idempotent=true, returnParameters= {
|
||||
@OperationParam(name="return", type=Meta.class)
|
||||
@Operation(name = "$meta", idempotent = true, returnParameters = {
|
||||
@OperationParam(name = "return", type = Meta.class)
|
||||
})
|
||||
//@formatter:on
|
||||
public Parameters meta(RequestDetails theRequestDetails) {
|
||||
|
@ -168,14 +173,15 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
|
|||
parameters.addParameter().setName("return").setValue(getDao().metaGetOperation(theRequestDetails));
|
||||
return parameters;
|
||||
}
|
||||
|
||||
@Operation(name="$suggest-keywords", idempotent=true)
|
||||
|
||||
@Operation(name = "$suggest-keywords", idempotent = true)
|
||||
public Parameters suggestKeywords(
|
||||
@OperationParam(name="context", min=1, max=1) String theContext,
|
||||
@OperationParam(name="searchParam", min=1, max=1) String theSearchParam,
|
||||
@OperationParam(name="text", min=1, max=1) String theText
|
||||
) {
|
||||
|
||||
@OperationParam(name = "context", min = 1, max = 1) String theContext,
|
||||
@OperationParam(name = "searchParam", min = 1, max = 1) String theSearchParam,
|
||||
@OperationParam(name = "text", min = 1, max = 1) String theText
|
||||
) {
|
||||
|
||||
|
||||
if (isBlank(theContext)) {
|
||||
throw new InvalidRequestException("Parameter 'context' must be provided");
|
||||
}
|
||||
|
@ -185,21 +191,21 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
|
|||
if (isBlank(theText)) {
|
||||
throw new InvalidRequestException("Parameter 'text' must be provided");
|
||||
}
|
||||
|
||||
|
||||
List<Suggestion> keywords = mySearchDao.suggestKeywords(theContext, theSearchParam, theText);
|
||||
|
||||
|
||||
Parameters retVal = new Parameters();
|
||||
for (Suggestion next : keywords) {
|
||||
//@formatter:off
|
||||
retVal.addParameter()
|
||||
.addPart(new ParametersParameterComponent().setName("keyword").setValue(new StringType(next.getTerm())))
|
||||
.addPart(new ParametersParameterComponent().setName("score").setValue(new DecimalType(next.getScore())));
|
||||
.addPart(new ParametersParameterComponent().setName("keyword").setValue(new StringType(next.getTerm())))
|
||||
.addPart(new ParametersParameterComponent().setName("score").setValue(new DecimalType(next.getScore())));
|
||||
//@formatter:on
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
||||
@Transaction
|
||||
public Bundle transaction(RequestDetails theRequestDetails, @TransactionParam Bundle theResources) {
|
||||
startRequest(((ServletRequestDetails) theRequestDetails).getServletRequest());
|
||||
|
@ -210,4 +216,10 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
|
|||
}
|
||||
}
|
||||
|
||||
public static void validateFulltextSearchEnabled(IFulltextSearchSvc theSearchDao) {
|
||||
if (theSearchDao == null || theSearchDao.isDisabled()) {
|
||||
throw new InvalidRequestException("Fulltext searching is disabled on this server");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,54 +20,53 @@ package ca.uhn.fhir.jpa.provider.r4;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Operation;
|
||||
import ca.uhn.fhir.rest.annotation.OperationParam;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDaoValueSet.ValidateCodeResult;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
|
||||
public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<ValueSet> {
|
||||
|
||||
//@formatter:off
|
||||
@Operation(name = "$expand", idempotent = true)
|
||||
public ValueSet expand(
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional=true) IdType theId,
|
||||
@OperationParam(name="valueSet", min=0, max=1) ValueSet theValueSet,
|
||||
@OperationParam(name="identifier", min=0, max=1) UriType theIdentifier,
|
||||
@OperationParam(name = "filter", min=0, max=1) StringType theFilter,
|
||||
RequestDetails theRequestDetails) {
|
||||
//@formatter:on
|
||||
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional = true) IdType theId,
|
||||
@OperationParam(name = "valueSet", min = 0, max = 1) ValueSet theValueSet,
|
||||
@OperationParam(name = "url", min = 0, max = 1) UriType theUrl,
|
||||
@OperationParam(name = "filter", min = 0, max = 1) StringType theFilter,
|
||||
RequestDetails theRequestDetails) {
|
||||
|
||||
boolean haveId = theId != null && theId.hasIdPart();
|
||||
boolean haveIdentifier = theIdentifier != null && isNotBlank(theIdentifier.getValue());
|
||||
boolean haveIdentifier = theUrl != null && isNotBlank(theUrl.getValue());
|
||||
boolean haveValueSet = theValueSet != null && theValueSet.isEmpty() == false;
|
||||
|
||||
|
||||
if (!haveId && !haveIdentifier && !haveValueSet) {
|
||||
throw new InvalidRequestException("$expand operation at the type level (no ID specified) requires an identifier or a valueSet as a part of the request");
|
||||
throw new InvalidRequestException("$expand operation at the type level (no ID specified) requires a url or a valueSet as a part of the request");
|
||||
}
|
||||
|
||||
if (moreThanOneTrue(haveId, haveIdentifier, haveValueSet)) {
|
||||
throw new InvalidRequestException("$expand must EITHER be invoked at the instance level, or have an identifier specified, or have a ValueSet specified. Can not combine these options.");
|
||||
throw new InvalidRequestException("$expand must EITHER be invoked at the instance level, or have a url specified, or have a ValueSet specified. Can not combine these options.");
|
||||
}
|
||||
|
||||
|
||||
startRequest(theServletRequest);
|
||||
try {
|
||||
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
|
||||
if (haveId) {
|
||||
return dao.expand(theId, toFilterString(theFilter), theRequestDetails);
|
||||
} else if (haveIdentifier) {
|
||||
return dao.expandByIdentifier(theIdentifier.getValue(), toFilterString(theFilter));
|
||||
return dao.expandByIdentifier(theUrl.getValue(), toFilterString(theFilter));
|
||||
} else {
|
||||
return dao.expand(theValueSet, toFilterString(theFilter));
|
||||
}
|
||||
|
||||
|
||||
} finally {
|
||||
endRequest(theServletRequest);
|
||||
}
|
||||
|
@ -79,30 +78,28 @@ public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<Val
|
|||
}
|
||||
|
||||
|
||||
//@formatter:off
|
||||
@SuppressWarnings("unchecked")
|
||||
@Operation(name = "$validate-code", idempotent = true, returnParameters= {
|
||||
@OperationParam(name="result", type=BooleanType.class, min=1),
|
||||
@OperationParam(name="message", type=StringType.class),
|
||||
@OperationParam(name="display", type=StringType.class)
|
||||
@Operation(name = "$validate-code", idempotent = true, returnParameters = {
|
||||
@OperationParam(name = "result", type = BooleanType.class, min = 1),
|
||||
@OperationParam(name = "message", type = StringType.class),
|
||||
@OperationParam(name = "display", type = StringType.class)
|
||||
})
|
||||
public Parameters validateCode(
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional=true) IdType theId,
|
||||
@OperationParam(name="identifier", min=0, max=1) UriType theValueSetIdentifier,
|
||||
@OperationParam(name="code", min=0, max=1) CodeType theCode,
|
||||
@OperationParam(name="system", min=0, max=1) UriType theSystem,
|
||||
@OperationParam(name="display", min=0, max=1) StringType theDisplay,
|
||||
@OperationParam(name="coding", min=0, max=1) Coding theCoding,
|
||||
@OperationParam(name="codeableConcept", min=0, max=1) CodeableConcept theCodeableConcept,
|
||||
RequestDetails theRequestDetails
|
||||
) {
|
||||
//@formatter:on
|
||||
|
||||
HttpServletRequest theServletRequest,
|
||||
@IdParam(optional = true) IdType theId,
|
||||
@OperationParam(name = "url", min = 0, max = 1) UriType theValueSetUrl,
|
||||
@OperationParam(name = "code", min = 0, max = 1) CodeType theCode,
|
||||
@OperationParam(name = "system", min = 0, max = 1) UriType theSystem,
|
||||
@OperationParam(name = "display", min = 0, max = 1) StringType theDisplay,
|
||||
@OperationParam(name = "coding", min = 0, max = 1) Coding theCoding,
|
||||
@OperationParam(name = "codeableConcept", min = 0, max = 1) CodeableConcept theCodeableConcept,
|
||||
RequestDetails theRequestDetails
|
||||
) {
|
||||
|
||||
startRequest(theServletRequest);
|
||||
try {
|
||||
IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept> dao = (IFhirResourceDaoValueSet<ValueSet, Coding, CodeableConcept>) getDao();
|
||||
ValidateCodeResult result = dao.validateCode(theValueSetIdentifier, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
ValidateCodeResult result = dao.validateCode(theValueSetUrl, theId, theCode, theSystem, theDisplay, theCoding, theCodeableConcept, theRequestDetails);
|
||||
Parameters retVal = new Parameters();
|
||||
retVal.addParameter().setName("result").setValue(new BooleanType(result.isResult()));
|
||||
if (isNotBlank(result.getMessage())) {
|
||||
|
@ -117,8 +114,7 @@ public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<Val
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
private static boolean moreThanOneTrue(boolean... theBooleans) {
|
||||
boolean haveOne = false;
|
||||
for (boolean next : theBooleans) {
|
||||
|
@ -133,5 +129,5 @@ public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<Val
|
|||
return false;
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -45,7 +45,7 @@ public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus<Bundle,
|
|||
@Qualifier("mySystemDaoR4")
|
||||
private IFhirSystemDao<Bundle, Meta> mySystemDao;
|
||||
|
||||
@Autowired
|
||||
@Autowired(required = false)
|
||||
private IFulltextSearchSvc mySearchDao;
|
||||
|
||||
//@formatter:off
|
||||
|
@ -175,6 +175,7 @@ public class JpaSystemProviderR4 extends BaseJpaSystemProviderDstu2Plus<Bundle,
|
|||
@OperationParam(name="searchParam", min=1, max=1) String theSearchParam,
|
||||
@OperationParam(name="text", min=1, max=1) String theText
|
||||
) {
|
||||
ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3.validateFulltextSearchEnabled(mySearchDao);
|
||||
|
||||
if (isBlank(theContext)) {
|
||||
throw new InvalidRequestException("Parameter 'context' must be provided");
|
||||
|
|
|
@ -20,18 +20,19 @@ package ca.uhn.fhir.jpa.search;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.IDao;
|
||||
import ca.uhn.fhir.jpa.dao.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public interface ISearchCoordinatorSvc {
|
||||
|
||||
List<Long> getResources(String theUuid, int theFrom, int theTo);
|
||||
|
||||
IBundleProvider registerSearch(IDao theCallingDao, SearchParameterMap theParams, String theResourceType);
|
||||
|
||||
void cancelAllActiveSearches();
|
||||
|
||||
List<Long> getResources(String theUuid, int theFrom, int theTo);
|
||||
|
||||
IBundleProvider registerSearch(IDao theCallingDao, SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective);
|
||||
|
||||
}
|
||||
|
|
|
@ -19,23 +19,32 @@ package ca.uhn.fhir.jpa.search;
|
|||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
import java.util.*;
|
||||
|
||||
import javax.persistence.*;
|
||||
import javax.persistence.criteria.*;
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.*;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.IDao;
|
||||
import ca.uhn.fhir.jpa.dao.ISearchBuilder;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.jpa.entity.BaseHasResource;
|
||||
import ca.uhn.fhir.jpa.entity.ResourceHistoryTable;
|
||||
import ca.uhn.fhir.jpa.entity.Search;
|
||||
import ca.uhn.fhir.jpa.entity.SearchTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.NoResultException;
|
||||
import javax.persistence.TypedQuery;
|
||||
import javax.persistence.criteria.CriteriaBuilder;
|
||||
import javax.persistence.criteria.CriteriaQuery;
|
||||
import javax.persistence.criteria.Predicate;
|
||||
import javax.persistence.criteria.Root;
|
||||
import java.util.*;
|
||||
|
||||
public class PersistedJpaBundleProvider implements IBundleProvider {
|
||||
|
||||
|
@ -47,6 +56,7 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||
private ISearchDao mySearchDao;
|
||||
private Search mySearchEntity;
|
||||
private String myUuid;
|
||||
private boolean myCacheHit;
|
||||
|
||||
public PersistedJpaBundleProvider(String theSearchUuid, IDao theDao) {
|
||||
myUuid = theSearchUuid;
|
||||
|
@ -179,17 +189,17 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||
});
|
||||
|
||||
switch (mySearchEntity.getSearchType()) {
|
||||
case HISTORY:
|
||||
return template.execute(new TransactionCallback<List<IBaseResource>>() {
|
||||
@Override
|
||||
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
|
||||
return doHistoryInTransaction(theFromIndex, theToIndex);
|
||||
}
|
||||
});
|
||||
case SEARCH:
|
||||
case EVERYTHING:
|
||||
default:
|
||||
return doSearchOrEverything(theFromIndex, theToIndex);
|
||||
case HISTORY:
|
||||
return template.execute(new TransactionCallback<List<IBaseResource>>() {
|
||||
@Override
|
||||
public List<IBaseResource> doInTransaction(TransactionStatus theStatus) {
|
||||
return doHistoryInTransaction(theFromIndex, theToIndex);
|
||||
}
|
||||
});
|
||||
case SEARCH:
|
||||
case EVERYTHING:
|
||||
default:
|
||||
return doSearchOrEverything(theFromIndex, theToIndex);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,6 +207,14 @@ public class PersistedJpaBundleProvider implements IBundleProvider {
|
|||
return myUuid;
|
||||
}
|
||||
|
||||
public boolean isCacheHit() {
|
||||
return myCacheHit;
|
||||
}
|
||||
|
||||
public void setCacheHit(boolean theCacheHit) {
|
||||
myCacheHit = theCacheHit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer preferredPageSize() {
|
||||
ensureSearchEntityLoaded();
|
||||
|
|
|
@ -67,8 +67,11 @@ public class PersistedJpaSearchFirstPageBundleProvider extends PersistedJpaBundl
|
|||
|
||||
@Override
|
||||
public Integer size() {
|
||||
mySearchTask.awaitInitialSync();
|
||||
Integer size = mySearchTask.awaitInitialSync();
|
||||
SearchCoordinatorSvcImpl.verifySearchHasntFailedOrThrowInternalErrorException(mySearch);
|
||||
if (size != null) {
|
||||
return size;
|
||||
}
|
||||
return super.size();
|
||||
}
|
||||
|
||||
|
|
|
@ -17,13 +17,15 @@ package ca.uhn.fhir.jpa.search;
|
|||
* 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%
|
||||
* #L%family
|
||||
*/
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
|
@ -55,7 +57,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
public static final int DEFAULT_SYNC_SIZE = 250;
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchCoordinatorSvcImpl.class);
|
||||
|
||||
private final ConcurrentHashMap<String, SearchTask> myIdToSearchTask = new ConcurrentHashMap<String, SearchTask>();
|
||||
@Autowired
|
||||
private FhirContext myContext;
|
||||
@Autowired
|
||||
|
@ -63,7 +65,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
@Autowired
|
||||
private EntityManager myEntityManager;
|
||||
private ExecutorService myExecutor;
|
||||
private final ConcurrentHashMap<String, SearchTask> myIdToSearchTask = new ConcurrentHashMap<String, SearchTask>();
|
||||
private Integer myLoadingThrottleForUnitTests = null;
|
||||
private long myMaxMillisToWaitForRemoteResults = DateUtils.MILLIS_PER_MINUTE;
|
||||
private boolean myNeverUseLocalSearchForUnitTests;
|
||||
|
@ -78,13 +79,6 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
|
||||
private int mySyncSize = DEFAULT_SYNC_SIZE;
|
||||
|
||||
// @Autowired
|
||||
// private DataSource myDataSource;
|
||||
// @PostConstruct
|
||||
// public void start() {
|
||||
// JpaTransactionManager txManager = (JpaTransactionManager) myManagedTxManager;
|
||||
// }
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -186,15 +180,33 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
}
|
||||
|
||||
@Override
|
||||
public IBundleProvider registerSearch(final IDao theCallingDao, final SearchParameterMap theParams, String theResourceType) {
|
||||
public IBundleProvider registerSearch(final IDao theCallingDao, final SearchParameterMap theParams, String theResourceType, CacheControlDirective theCacheControlDirective) {
|
||||
StopWatch w = new StopWatch();
|
||||
final String searchUuid = UUID.randomUUID().toString();
|
||||
|
||||
ourLog.debug("Registering new search {}", searchUuid);
|
||||
|
||||
Class<? extends IBaseResource> resourceTypeClass = myContext.getResourceDefinition(theResourceType).getImplementingClass();
|
||||
final ISearchBuilder sb = theCallingDao.newSearchBuilder();
|
||||
sb.setType(resourceTypeClass, theResourceType);
|
||||
|
||||
if (theParams.isLoadSynchronous()) {
|
||||
final Integer loadSynchronousUpTo;
|
||||
if (theCacheControlDirective != null && theCacheControlDirective.isNoStore()) {
|
||||
if (theCacheControlDirective.getMaxResults() != null) {
|
||||
loadSynchronousUpTo = theCacheControlDirective.getMaxResults();
|
||||
if (loadSynchronousUpTo > myDaoConfig.getCacheControlNoStoreMaxResultsUpperLimit()) {
|
||||
throw new InvalidRequestException(Constants.HEADER_CACHE_CONTROL + " header " + Constants.CACHE_CONTROL_MAX_RESULTS + " value must not exceed " + myDaoConfig.getCacheControlNoStoreMaxResultsUpperLimit());
|
||||
}
|
||||
} else {
|
||||
loadSynchronousUpTo = 100;
|
||||
}
|
||||
} else {
|
||||
loadSynchronousUpTo = null;
|
||||
}
|
||||
|
||||
if (theParams.isLoadSynchronous() || loadSynchronousUpTo != null) {
|
||||
|
||||
ourLog.debug("Search {} is loading in synchronous mode", searchUuid);
|
||||
|
||||
// Execute the query and make sure we return distinct results
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myManagedTxManager);
|
||||
|
@ -209,6 +221,9 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
Iterator<Long> resultIter = sb.createQuery(theParams, searchUuid);
|
||||
while (resultIter.hasNext()) {
|
||||
pids.add(resultIter.next());
|
||||
if (loadSynchronousUpTo != null && pids.size() >= loadSynchronousUpTo) {
|
||||
break;
|
||||
}
|
||||
if (theParams.getLoadSynchronousUpTo() != null && pids.size() >= theParams.getLoadSynchronousUpTo()) {
|
||||
break;
|
||||
}
|
||||
|
@ -238,9 +253,13 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
* See if there are any cached searches whose results we can return
|
||||
* instead
|
||||
*/
|
||||
boolean useCache = true;
|
||||
if (theCacheControlDirective != null && theCacheControlDirective.isNoCache() == true) {
|
||||
useCache = false;
|
||||
}
|
||||
final String queryString = theParams.toNormalizedQueryString(myContext);
|
||||
if (theParams.getEverythingMode() == null) {
|
||||
if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null) {
|
||||
if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null && useCache) {
|
||||
|
||||
final Date createdCutoff = new Date(System.currentTimeMillis() - myDaoConfig.getReuseCachedSearchResultsForMillis());
|
||||
final String resourceType = theResourceType;
|
||||
|
@ -266,6 +285,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
mySearchDao.updateSearchLastReturned(searchToUse.getId(), new Date());
|
||||
|
||||
retVal = new PersistedJpaBundleProvider(searchToUse.getUuid(), theCallingDao);
|
||||
retVal.setCacheHit(true);
|
||||
|
||||
populateBundleProvider(retVal);
|
||||
}
|
||||
|
||||
|
@ -401,16 +422,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
|
||||
public class SearchTask implements Callable<Void> {
|
||||
|
||||
private boolean myAbortRequested;
|
||||
private final IDao myCallingDao;
|
||||
private final CountDownLatch myCompletionLatch;
|
||||
private int myCountSaved = 0;
|
||||
private final CountDownLatch myInitialCollectionLatch = new CountDownLatch(1);
|
||||
private final SearchParameterMap myParams;
|
||||
private final String myResourceType;
|
||||
private final Search mySearch;
|
||||
private final ArrayList<Long> mySyncedPids = new ArrayList<Long>();
|
||||
private final ArrayList<Long> myUnsyncedPids = new ArrayList<Long>();
|
||||
private boolean myAbortRequested;
|
||||
private int myCountSaved = 0;
|
||||
private String mySearchUuid;
|
||||
|
||||
public SearchTask(Search theSearch, IDao theCallingDao, SearchParameterMap theParams, String theResourceType, String theSearchUuid) {
|
||||
|
@ -422,7 +443,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
mySearchUuid = theSearchUuid;
|
||||
}
|
||||
|
||||
public void awaitInitialSync() {
|
||||
public Integer awaitInitialSync() {
|
||||
ourLog.trace("Awaiting initial sync");
|
||||
do {
|
||||
try {
|
||||
|
@ -434,6 +455,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
}
|
||||
} while (mySearch.getStatus() == SearchStatusEnum.LOADING);
|
||||
ourLog.trace("Initial sync completed");
|
||||
|
||||
return mySearch.getTotalCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -492,6 +515,7 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
}
|
||||
|
||||
myIdToSearchTask.remove(mySearch.getUuid());
|
||||
myInitialCollectionLatch.countDown();
|
||||
myCompletionLatch.countDown();
|
||||
return null;
|
||||
}
|
||||
|
@ -537,27 +561,27 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
public List<Long> getResourcePids(int theFromIndex, int theToIndex) {
|
||||
ourLog.info("Requesting search PIDs from {}-{}", theFromIndex, theToIndex);
|
||||
|
||||
CountDownLatch latch = null;
|
||||
synchronized (mySyncedPids) {
|
||||
if (mySyncedPids.size() < theToIndex && mySearch.getStatus() == SearchStatusEnum.LOADING) {
|
||||
int latchSize = theToIndex - mySyncedPids.size();
|
||||
ourLog.trace("Registering latch to await {} results (want {} total)", latchSize, theToIndex);
|
||||
latch = new CountDownLatch(latchSize);
|
||||
}
|
||||
}
|
||||
|
||||
if (latch != null) {
|
||||
while (latch.getCount() > 0 && mySearch.getStatus() == SearchStatusEnum.LOADING) {
|
||||
try {
|
||||
ourLog.trace("Awaiting latch with {}", latch.getCount());
|
||||
latch.await(500, TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
// ok
|
||||
boolean keepWaiting;
|
||||
do {
|
||||
synchronized (mySyncedPids) {
|
||||
keepWaiting = false;
|
||||
if (mySyncedPids.size() < theToIndex && mySearch.getStatus() == SearchStatusEnum.LOADING) {
|
||||
keepWaiting = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (keepWaiting) {
|
||||
ourLog.info("Waiting, as we only have {} results", mySyncedPids.size());
|
||||
try {
|
||||
Thread.sleep(500);
|
||||
} catch (InterruptedException theE) {
|
||||
// ignore
|
||||
}
|
||||
}
|
||||
} while (keepWaiting);
|
||||
|
||||
ArrayList<Long> retVal = new ArrayList<Long>();
|
||||
ourLog.info("Proceeding, as we have {} results", mySyncedPids.size());
|
||||
|
||||
ArrayList<Long> retVal = new ArrayList<>();
|
||||
synchronized (mySyncedPids) {
|
||||
verifySearchHasntFailedOrThrowInternalErrorException(mySearch);
|
||||
|
||||
|
@ -570,6 +594,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
}
|
||||
}
|
||||
|
||||
ourLog.info("Done syncing results", mySyncedPids.size());
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
|
@ -618,8 +644,8 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
myUnsyncedPids.clear();
|
||||
|
||||
if (theResultIter.hasNext() == false) {
|
||||
mySearch.setStatus(SearchStatusEnum.FINISHED);
|
||||
mySearch.setTotalCount(myCountSaved);
|
||||
mySearch.setStatus(SearchStatusEnum.FINISHED);
|
||||
}
|
||||
}
|
||||
mySearch.setNumFound(myCountSaved);
|
||||
|
@ -628,7 +654,16 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
|
|||
}
|
||||
});
|
||||
|
||||
myInitialCollectionLatch.countDown();
|
||||
int numSynced;
|
||||
synchronized (mySyncedPids) {
|
||||
numSynced = mySyncedPids.size();
|
||||
}
|
||||
|
||||
if (myDaoConfig.getCountSearchResultsUpTo() == null ||
|
||||
myDaoConfig.getCountSearchResultsUpTo() <= 0 ||
|
||||
myDaoConfig.getCountSearchResultsUpTo() <= numSynced) {
|
||||
myInitialCollectionLatch.countDown();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,8 +20,12 @@ package ca.uhn.fhir.jpa.search;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchIncludeDao;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchResultDao;
|
||||
import ca.uhn.fhir.jpa.entity.Search;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.data.domain.PageRequest;
|
||||
|
@ -34,11 +38,7 @@ import org.springframework.transaction.annotation.Transactional;
|
|||
import org.springframework.transaction.support.TransactionCallback;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.data.*;
|
||||
import ca.uhn.fhir.jpa.entity.Search;
|
||||
import java.util.Date;
|
||||
|
||||
/**
|
||||
* Deletes old searches
|
||||
|
@ -46,35 +46,32 @@ import ca.uhn.fhir.jpa.entity.Search;
|
|||
public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
|
||||
public static final long DEFAULT_CUTOFF_SLACK = 10 * DateUtils.MILLIS_PER_SECOND;
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(StaleSearchDeletingSvcImpl.class);
|
||||
|
||||
private static Long ourNowForUnitTests;
|
||||
/*
|
||||
* We give a bit of extra leeway just to avoid race conditions where a query result
|
||||
* is being reused (because a new client request came in with the same params) right before
|
||||
* the result is to be deleted
|
||||
*/
|
||||
private long myCutoffSlack = DEFAULT_CUTOFF_SLACK;
|
||||
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
|
||||
@Autowired
|
||||
private ISearchDao mySearchDao;
|
||||
|
||||
@Autowired
|
||||
private ISearchIncludeDao mySearchIncludeDao;
|
||||
|
||||
@Autowired
|
||||
private ISearchResultDao mySearchResultDao;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myTransactionManager;
|
||||
|
||||
private void deleteSearch(final Long theSearchPid) {
|
||||
Search searchToDelete = mySearchDao.findOne(theSearchPid);
|
||||
ourLog.info("Deleting search {}/{} - Created[{}] -- Last returned[{}]", searchToDelete.getId(), searchToDelete.getUuid(), searchToDelete.getCreated(), searchToDelete.getSearchLastReturned());
|
||||
mySearchIncludeDao.deleteForSearch(searchToDelete.getId());
|
||||
mySearchResultDao.deleteForSearch(searchToDelete.getId());
|
||||
mySearchDao.delete(searchToDelete);
|
||||
if (searchToDelete != null) {
|
||||
ourLog.info("Deleting search {}/{} - Created[{}] -- Last returned[{}]", searchToDelete.getId(), searchToDelete.getUuid(), searchToDelete.getCreated(), searchToDelete.getSearchLastReturned());
|
||||
mySearchIncludeDao.deleteForSearch(searchToDelete.getId());
|
||||
mySearchResultDao.deleteForSearch(searchToDelete.getId());
|
||||
mySearchDao.delete(searchToDelete);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -85,7 +82,7 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
|
|||
if (myDaoConfig.getReuseCachedSearchResultsForMillis() != null) {
|
||||
cutoffMillis = Math.max(cutoffMillis, myDaoConfig.getReuseCachedSearchResultsForMillis());
|
||||
}
|
||||
final Date cutoff = new Date((System.currentTimeMillis() - cutoffMillis) - myCutoffSlack);
|
||||
final Date cutoff = new Date((now() - cutoffMillis) - myCutoffSlack);
|
||||
|
||||
ourLog.debug("Searching for searches which are before {}", cutoff);
|
||||
|
||||
|
@ -129,4 +126,19 @@ public class StaleSearchDeletingSvcImpl implements IStaleSearchDeletingSvc {
|
|||
myCutoffSlack = theCutoffSlack;
|
||||
}
|
||||
|
||||
private static long now() {
|
||||
if (ourNowForUnitTests != null) {
|
||||
return ourNowForUnitTests;
|
||||
}
|
||||
return System.currentTimeMillis();
|
||||
}
|
||||
|
||||
/**
|
||||
* This is for unit tests only, do not call otherwise
|
||||
*/
|
||||
@VisibleForTesting
|
||||
public static void setNowForUnitTests(Long theNowForUnitTests) {
|
||||
ourNowForUnitTests = theNowForUnitTests;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -89,7 +89,7 @@ public class SearchParamPresenceSvcImpl implements ISearchParamPresenceSvc {
|
|||
searchParam = new SearchParam();
|
||||
searchParam.setResourceName(resourceType);
|
||||
searchParam.setParamName(paramName);
|
||||
searchParam = mySearchParamDao.saveAndFlush(searchParam);
|
||||
searchParam = mySearchParamDao.save(searchParam);
|
||||
ourLog.info("Added search param {} with pid {}", paramName, searchParam.getId());
|
||||
// Don't add the newly saved entity to the map in case the save fails
|
||||
}
|
||||
|
|
|
@ -36,6 +36,7 @@ import ca.uhn.fhir.rest.param.TokenParam;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
@ -51,8 +52,12 @@ import org.springframework.messaging.MessageHandler;
|
|||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
@ -87,6 +92,9 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
@Autowired(required = false)
|
||||
@Qualifier("myEventDefinitionDaoR4")
|
||||
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDaoR4;
|
||||
@Autowired
|
||||
private PlatformTransactionManager myTxManager;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
|
@ -148,13 +156,11 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
try {
|
||||
from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
||||
subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
||||
bodyTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_BODY_TEMPLATE);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getEmailDetails().setFrom(from);
|
||||
retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
|
||||
retVal.getEmailDetails().setBodyTemplate(bodyTemplate);
|
||||
}
|
||||
|
||||
} catch (FHIRException theE) {
|
||||
|
@ -183,13 +189,11 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
try {
|
||||
from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
||||
subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
||||
bodyTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_BODY_TEMPLATE);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getEmailDetails().setFrom(from);
|
||||
retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
|
||||
retVal.getEmailDetails().setBodyTemplate(bodyTemplate);
|
||||
}
|
||||
|
||||
List<org.hl7.fhir.r4.model.Extension> topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
|
||||
|
@ -368,6 +372,11 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
myResourceDaos = theResourceDaos;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setTxManager(PlatformTransactionManager theTxManager) {
|
||||
myTxManager = theTxManager;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
for (IFhirResourceDao<?> next : myResourceDaos) {
|
||||
|
@ -446,13 +455,19 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
|
|||
}
|
||||
|
||||
if (mySubscriptionActivatingSubscriber == null) {
|
||||
mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(getSubscriptionDao(), getChannelType(), this);
|
||||
mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager);
|
||||
}
|
||||
|
||||
registerSubscriptionCheckingSubscriber();
|
||||
registerDeliverySubscriber();
|
||||
|
||||
initSubscriptions();
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(myTxManager);
|
||||
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
initSubscriptions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected void submitResourceModified(final ResourceModifiedMessage theMsg) {
|
||||
|
|
|
@ -131,10 +131,12 @@ public class CanonicalSubscription implements Serializable {
|
|||
return myHeaders;
|
||||
}
|
||||
|
||||
public void setHeaders(String theHeaders) {
|
||||
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
|
||||
myHeaders = new ArrayList<>();
|
||||
if (isNotBlank(theHeaders)) {
|
||||
myHeaders.add(theHeaders);
|
||||
for (IPrimitiveType<String> next : theHeader) {
|
||||
if (isNotBlank(next.getValueAsString())) {
|
||||
myHeaders.add(next.getValueAsString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -189,12 +191,10 @@ public class CanonicalSubscription implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
public void setHeaders(List<? extends IPrimitiveType<String>> theHeader) {
|
||||
public void setHeaders(String theHeaders) {
|
||||
myHeaders = new ArrayList<>();
|
||||
for (IPrimitiveType<String> next : theHeader) {
|
||||
if (isNotBlank(next.getValueAsString())) {
|
||||
myHeaders.add(next.getValueAsString());
|
||||
}
|
||||
if (isNotBlank(theHeaders)) {
|
||||
myHeaders.add(theHeaders);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -212,16 +212,6 @@ public class CanonicalSubscription implements Serializable {
|
|||
private String myFrom;
|
||||
@JsonProperty("subjectTemplate")
|
||||
private String mySubjectTemplate;
|
||||
@JsonProperty("bodyTemplate")
|
||||
private String myBodyTemplate;
|
||||
|
||||
public String getBodyTemplate() {
|
||||
return myBodyTemplate;
|
||||
}
|
||||
|
||||
public void setBodyTemplate(String theBodyTemplate) {
|
||||
myBodyTemplate = theBodyTemplate;
|
||||
}
|
||||
|
||||
public String getFrom() {
|
||||
return myFrom;
|
||||
|
|
|
@ -27,18 +27,21 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.hl7.fhir.utilities.ucum.Canonical;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public class SubscriptionActivatingSubscriber {
|
||||
private final IFhirResourceDao mySubscriptionDao;
|
||||
private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
|
||||
private final PlatformTransactionManager myTransactionManager;
|
||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingSubscriber.class);
|
||||
private FhirContext myCtx;
|
||||
private Subscription.SubscriptionChannelType myChannelType;
|
||||
|
@ -46,29 +49,36 @@ public class SubscriptionActivatingSubscriber {
|
|||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public SubscriptionActivatingSubscriber(IFhirResourceDao<? extends IBaseResource> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
||||
public SubscriptionActivatingSubscriber(IFhirResourceDao<? extends IBaseResource> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, PlatformTransactionManager theTransactionManager) {
|
||||
mySubscriptionDao = theSubscriptionDao;
|
||||
mySubscriptionInterceptor = theSubscriptionInterceptor;
|
||||
myChannelType = theChannelType;
|
||||
myCtx = theSubscriptionDao.getContext();
|
||||
myTransactionManager = theTransactionManager;
|
||||
}
|
||||
|
||||
public void activateAndRegisterSubscriptionIfRequired(IBaseResource theSubscription) {
|
||||
public void activateAndRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
|
||||
boolean subscriptionTypeApplies = BaseSubscriptionSubscriber.subscriptionTypeApplies(myCtx, theSubscription, myChannelType);
|
||||
if (subscriptionTypeApplies == false) {
|
||||
return;
|
||||
}
|
||||
|
||||
IPrimitiveType<?> status = myCtx.newTerser().getSingleValueOrNull(theSubscription, BaseSubscriptionInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class);
|
||||
final IPrimitiveType<?> status = myCtx.newTerser().getSingleValueOrNull(theSubscription, BaseSubscriptionInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class);
|
||||
String statusString = status.getValueAsString();
|
||||
|
||||
String requestedStatus = Subscription.SubscriptionStatus.REQUESTED.toCode();
|
||||
String activeStatus = Subscription.SubscriptionStatus.ACTIVE.toCode();
|
||||
final String requestedStatus = Subscription.SubscriptionStatus.REQUESTED.toCode();
|
||||
final String activeStatus = Subscription.SubscriptionStatus.ACTIVE.toCode();
|
||||
if (requestedStatus.equals(statusString)) {
|
||||
status.setValueAsString(activeStatus);
|
||||
ourLog.info("Activating and registering subscription {} from status {} to {}", theSubscription.getIdElement().toUnqualified().getValue(), requestedStatus, activeStatus);
|
||||
mySubscriptionDao.update(theSubscription);
|
||||
mySubscriptionInterceptor.registerSubscription(theSubscription.getIdElement(), theSubscription);
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
activateSubscription(status, activeStatus, theSubscription, requestedStatus);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
activateSubscription(status, activeStatus, theSubscription, requestedStatus);
|
||||
}
|
||||
} else if (activeStatus.equals(statusString)) {
|
||||
if (!mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement())) {
|
||||
ourLog.info("Registering active subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
|
||||
|
@ -82,8 +92,15 @@ public class SubscriptionActivatingSubscriber {
|
|||
}
|
||||
}
|
||||
|
||||
private void activateSubscription(IPrimitiveType<?> theStatus, String theActiveStatus, IBaseResource theSubscription, String theRequestedStatus) {
|
||||
theStatus.setValueAsString(theActiveStatus);
|
||||
ourLog.info("Activating and registering subscription {} from status {} to {}", theSubscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus);
|
||||
mySubscriptionDao.update(theSubscription);
|
||||
mySubscriptionInterceptor.registerSubscription(theSubscription.getIdElement(), theSubscription);
|
||||
}
|
||||
|
||||
public void handleMessage(RestOperationTypeEnum theOperationType, IIdType theId, IBaseResource theSubscription) throws MessagingException {
|
||||
|
||||
public void handleMessage(RestOperationTypeEnum theOperationType, IIdType theId, final IBaseResource theSubscription) throws MessagingException {
|
||||
|
||||
switch (theOperationType) {
|
||||
case DELETE:
|
||||
|
@ -94,7 +111,15 @@ public class SubscriptionActivatingSubscriber {
|
|||
if (!theId.getResourceType().equals("Subscription")) {
|
||||
return;
|
||||
}
|
||||
activateAndRegisterSubscriptionIfRequired(theSubscription);
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
activateAndRegisterSubscriptionIfRequired(theSubscription);
|
||||
}
|
||||
});
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.jpa.subscription.email;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class EmailDetails {
|
||||
|
@ -27,6 +29,7 @@ public class EmailDetails {
|
|||
private String myBodyTemplate;
|
||||
private List<String> myTo;
|
||||
private String myFrom;
|
||||
private IIdType mySubscription;
|
||||
|
||||
public String getBodyTemplate() {
|
||||
return myBodyTemplate;
|
||||
|
@ -52,6 +55,14 @@ public class EmailDetails {
|
|||
mySubjectTemplate = theSubjectTemplate;
|
||||
}
|
||||
|
||||
public IIdType getSubscription() {
|
||||
return mySubscription;
|
||||
}
|
||||
|
||||
public void setSubscription(IIdType theSubscription) {
|
||||
mySubscription = theSubscription;
|
||||
}
|
||||
|
||||
public List<String> getTo() {
|
||||
return myTo;
|
||||
}
|
||||
|
|
|
@ -22,11 +22,11 @@ package ca.uhn.fhir.jpa.subscription.email;
|
|||
|
||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.mail.SimpleMailMessage;
|
||||
import org.springframework.mail.javamail.JavaMailSender;
|
||||
import org.springframework.mail.javamail.JavaMailSenderImpl;
|
||||
import org.thymeleaf.context.Context;
|
||||
import org.thymeleaf.spring4.SpringTemplateEngine;
|
||||
|
@ -35,6 +35,9 @@ import org.thymeleaf.templatemode.TemplateMode;
|
|||
import org.thymeleaf.templateresolver.StringTemplateResolver;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.mail.Message;
|
||||
import javax.mail.MessagingException;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
@ -42,26 +45,57 @@ import java.util.List;
|
|||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.trim;
|
||||
|
||||
public class EmailSender implements IEmailSender {
|
||||
public class JavaMailEmailSender implements IEmailSender {
|
||||
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(EmailSender.class);
|
||||
private String mySmtpServerHost;
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(JavaMailEmailSender.class);
|
||||
private String mySmtpServerHostname;
|
||||
private int mySmtpServerPort = 25;
|
||||
private JavaMailSenderImpl mySender;
|
||||
private String mySmtpServerUsername;
|
||||
private String mySmtpServerPassword;
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
Validate.notBlank(mySmtpServerHost, "No SMTP host defined");
|
||||
public String getSmtpServerHostname() {
|
||||
return mySmtpServerHostname;
|
||||
}
|
||||
|
||||
mySender = new JavaMailSenderImpl();
|
||||
mySender.setHost(mySmtpServerHost);
|
||||
mySender.setPort(mySmtpServerPort);
|
||||
mySender.setDefaultEncoding(Constants.CHARSET_UTF8.name());
|
||||
/**
|
||||
* Set the SMTP server host to use for outbound mail
|
||||
*/
|
||||
public void setSmtpServerHostname(String theSmtpServerHostname) {
|
||||
mySmtpServerHostname = theSmtpServerHostname;
|
||||
}
|
||||
|
||||
public String getSmtpServerPassword() {
|
||||
return mySmtpServerPassword;
|
||||
}
|
||||
|
||||
public void setSmtpServerPassword(String theSmtpServerPassword) {
|
||||
mySmtpServerPassword = theSmtpServerPassword;
|
||||
}
|
||||
|
||||
public int getSmtpServerPort() {
|
||||
return mySmtpServerPort;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SMTP server port to use for outbound mail
|
||||
*/
|
||||
public void setSmtpServerPort(int theSmtpServerPort) {
|
||||
mySmtpServerPort = theSmtpServerPort;
|
||||
}
|
||||
|
||||
public String getSmtpServerUsername() {
|
||||
return mySmtpServerUsername;
|
||||
}
|
||||
|
||||
public void setSmtpServerUsername(String theSmtpServerUsername) {
|
||||
mySmtpServerUsername = theSmtpServerUsername;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void send(EmailDetails theDetails) {
|
||||
ourLog.info("Sending email to recipients: {}", theDetails.getTo());
|
||||
String subscriptionId = theDetails.getSubscription().toUnqualifiedVersionless().getValue();
|
||||
ourLog.info("Sending email for subscription {} to recipients: {}", subscriptionId, theDetails.getTo());
|
||||
StopWatch sw = new StopWatch();
|
||||
|
||||
StringTemplateResolver templateResolver = new StringTemplateResolver();
|
||||
|
@ -80,39 +114,44 @@ public class EmailSender implements IEmailSender {
|
|||
String body = engine.process(theDetails.getBodyTemplate(), context);
|
||||
String subject = engine.process(theDetails.getSubjectTemplate(), context);
|
||||
|
||||
SimpleMailMessage email = new SimpleMailMessage();
|
||||
email.setFrom(trim(theDetails.getFrom()));
|
||||
email.setTo(toTrimmedStringArray(theDetails.getTo()));
|
||||
email.setSubject(subject);
|
||||
email.setText(body);
|
||||
email.setSentDate(new Date());
|
||||
MimeMessage email = mySender.createMimeMessage();
|
||||
|
||||
try {
|
||||
email.setFrom(trim(theDetails.getFrom()));
|
||||
email.setRecipients(Message.RecipientType.TO, toTrimmedCommaSeparatedString(theDetails.getTo()));
|
||||
email.setSubject(subject);
|
||||
email.setText(body);
|
||||
email.setSentDate(new Date());
|
||||
email.addHeader("X-FHIR-Subscription", subscriptionId);
|
||||
} catch (MessagingException e) {
|
||||
throw new InternalErrorException("Failed to create email messaage", e);
|
||||
}
|
||||
|
||||
mySender.send(email);
|
||||
|
||||
ourLog.info("Done sending email (took {}ms)", sw.getMillis());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SMTP server host to use for outbound mail
|
||||
*/
|
||||
public void setSmtpServerHost(String theSmtpServerHost) {
|
||||
mySmtpServerHost = theSmtpServerHost;
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
Validate.notBlank(mySmtpServerHostname, "No SMTP host defined");
|
||||
|
||||
mySender = new JavaMailSenderImpl();
|
||||
mySender.setHost(getSmtpServerHostname());
|
||||
mySender.setPort(getSmtpServerPort());
|
||||
mySender.setUsername(getSmtpServerUsername());
|
||||
mySender.setPassword(getSmtpServerPassword());
|
||||
mySender.setDefaultEncoding(Constants.CHARSET_UTF8.name());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the SMTP server port to use for outbound mail
|
||||
*/
|
||||
public void setSmtpServerPort(int theSmtpServerPort) {
|
||||
mySmtpServerPort = theSmtpServerPort;
|
||||
}
|
||||
|
||||
private static String[] toTrimmedStringArray(List<String> theTo) {
|
||||
private static String toTrimmedCommaSeparatedString(List<String> theTo) {
|
||||
List<String> to = new ArrayList<>();
|
||||
for (String next : theTo) {
|
||||
if (isNotBlank(next)) {
|
||||
to.add(next);
|
||||
}
|
||||
}
|
||||
return to.toArray(new String[to.size()]);
|
||||
|
||||
return StringUtils.join(to, ",");
|
||||
}
|
||||
}
|
|
@ -54,32 +54,29 @@ public class SubscriptionDeliveringEmailSubscriber extends BaseSubscriptionDeliv
|
|||
List<String> destinationAddresses = new ArrayList<>();
|
||||
String[] destinationAddressStrings = StringUtils.split(endpointUrl, ",");
|
||||
for (String next : destinationAddressStrings) {
|
||||
next = trim(defaultString(next));
|
||||
if (next.startsWith("mailto:")) {
|
||||
next = next.substring("mailto:".length());
|
||||
}
|
||||
if (isNotBlank(next)) {
|
||||
destinationAddresses.add(trim(next));
|
||||
destinationAddresses.add(next);
|
||||
}
|
||||
}
|
||||
|
||||
String from = defaultString(subscription.getEmailDetails().getFrom(), provideDefaultFrom());
|
||||
String from = defaultString(subscription.getEmailDetails().getFrom(), mySubscriptionEmailInterceptor.getDefaultFromAddress());
|
||||
String subjectTemplate = defaultString(subscription.getEmailDetails().getSubjectTemplate(), provideDefaultSubjectTemplate());
|
||||
String bodyTemplate = defaultString(subscription.getEmailDetails().getBodyTemplate(), provideDefaultBodyTemplate());
|
||||
|
||||
EmailDetails details = new EmailDetails();
|
||||
details.setTo(destinationAddresses);
|
||||
details.setFrom(from);
|
||||
details.setBodyTemplate(bodyTemplate);
|
||||
details.setBodyTemplate(subscription.getPayloadString());
|
||||
details.setSubjectTemplate(subjectTemplate);
|
||||
details.setSubscription(subscription.getIdElement(getContext()));
|
||||
|
||||
IEmailSender emailSender = mySubscriptionEmailInterceptor.getEmailSender();
|
||||
emailSender.send(details);
|
||||
}
|
||||
|
||||
private String provideDefaultBodyTemplate() {
|
||||
return "A subscription update has been received";
|
||||
}
|
||||
|
||||
private String provideDefaultFrom() {
|
||||
return "unknown@sender.com";
|
||||
}
|
||||
|
||||
private String provideDefaultSubjectTemplate() {
|
||||
return "HAPI FHIR Subscriptions";
|
||||
|
|
|
@ -20,28 +20,51 @@ package ca.uhn.fhir.jpa.subscription.email;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.springframework.beans.factory.annotation.Required;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.List;
|
||||
|
||||
public class SubscriptionEmailInterceptor extends BaseSubscriptionInterceptor {
|
||||
private SubscriptionDeliveringEmailSubscriber mySubscriptionDeliverySubscriber;
|
||||
|
||||
/**
|
||||
* This is set to autowired=false just so that implementors can supply this
|
||||
* with a mechanism other than autowiring if they want
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
private IEmailSender myEmailSender;
|
||||
private String myDefaultFromAddress = "noreply@unknown.com";
|
||||
|
||||
@Override
|
||||
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
|
||||
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.EMAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "from" address to use for any sent emails that to not explicitly specity a from address
|
||||
*/
|
||||
public String getDefaultFromAddress() {
|
||||
return myDefaultFromAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "from" address to use for any sent emails that to not explicitly specity a from address
|
||||
*/
|
||||
public void setDefaultFromAddress(String theDefaultFromAddress) {
|
||||
Validate.notBlank(theDefaultFromAddress, "theDefaultFromAddress must not be null or blank");
|
||||
myDefaultFromAddress = theDefaultFromAddress;
|
||||
}
|
||||
|
||||
public IEmailSender getEmailSender() {
|
||||
return myEmailSender;
|
||||
}
|
||||
|
||||
@Required
|
||||
/**
|
||||
* Set the email sender (this method does not need to be explicitly called if you
|
||||
* are using autowiring to supply the sender)
|
||||
*/
|
||||
public void setEmailSender(IEmailSender theEmailSender) {
|
||||
myEmailSender = theEmailSender;
|
||||
}
|
||||
|
@ -54,12 +77,12 @@ public class SubscriptionEmailInterceptor extends BaseSubscriptionInterceptor {
|
|||
getDeliveryChannel().subscribe(mySubscriptionDeliverySubscriber);
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
Validate.notNull(myEmailSender, "emailSender has not been configured");
|
||||
|
||||
super.start();
|
||||
}
|
||||
// @PostConstruct
|
||||
// public void start() {
|
||||
// Validate.notNull(myEmailSender, "emailSender has not been configured");
|
||||
//
|
||||
// super.start();
|
||||
// }
|
||||
|
||||
@Override
|
||||
protected void unregisterDeliverySubscriber() {
|
||||
|
|
|
@ -24,8 +24,20 @@ public class JpaConstants {
|
|||
|
||||
public static final String EXT_SP_UNIQUE = "http://hapifhir.io/fhir/StructureDefinition/sp-unique";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This extension should be of type <code>string</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This extension should be of type <code>string</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template";
|
||||
public static final String EXT_SUBSCRIPTION_BODY_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-body-template";
|
||||
|
||||
}
|
||||
|
|
|
@ -42,7 +42,7 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
public class SubscriptionsRequireManualActivationInterceptorDstu2 extends ServerOperationInterceptorAdapter {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("mySubscriptionDaoR4")
|
||||
@Qualifier("mySubscriptionDaoDstu2")
|
||||
private IFhirResourceDao<Subscription> myDao;
|
||||
|
||||
@Override
|
||||
|
|
|
@ -130,4 +130,19 @@ public class TestUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static void sleepAtLeast(int theMillis) {
|
||||
long start = System.currentTimeMillis();
|
||||
while (System.currentTimeMillis() <= start + theMillis) {
|
||||
try {
|
||||
long timeSinceStarted = System.currentTimeMillis() - start;
|
||||
long timeToSleep = Math.max(0, theMillis - timeSinceStarted);
|
||||
ourLog.info("Sleeping for {}ms", timeToSleep);
|
||||
Thread.sleep(timeToSleep);
|
||||
} catch (InterruptedException theE) {
|
||||
theE.printStackTrace();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -97,7 +97,7 @@ public class ConnectionWrapper implements Connection {
|
|||
|
||||
@Override
|
||||
public String getClientInfo(String theName) throws SQLException {
|
||||
return getClientInfo(theName);
|
||||
return myWrap.getClientInfo(theName);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -1,26 +1,29 @@
|
|||
package ca.uhn.fhir.jpa.config;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.email.IEmailSender;
|
||||
import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender;
|
||||
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
@Configuration
|
||||
@EnableTransactionManagement()
|
||||
|
@ -30,12 +33,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
|||
private Exception myLastStackTrace;
|
||||
|
||||
@Bean()
|
||||
public DaoConfig daoConfig() {
|
||||
return new DaoConfig();
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public DataSource dataSource() {
|
||||
public BasicDataSource basicDataSource() {
|
||||
BasicDataSource retVal = new BasicDataSource() {
|
||||
|
||||
|
||||
|
@ -48,36 +46,36 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
|||
ourLog.error("Exceeded maximum wait for connection", e);
|
||||
logGetConnectionStackTrace();
|
||||
// if ("true".equals(System.getProperty("ci"))) {
|
||||
fail("Exceeded maximum wait for connection: "+ e.toString());
|
||||
fail("Exceeded maximum wait for connection: " + e.toString());
|
||||
// }
|
||||
// System.exit(1);
|
||||
retVal = null;
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
throw new Exception();
|
||||
} catch (Exception e) {
|
||||
myLastStackTrace = e;
|
||||
}
|
||||
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private void logGetConnectionStackTrace() {
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("Last connection request stack trace:");
|
||||
for (StackTraceElement next : myLastStackTrace.getStackTrace()) {
|
||||
b.append("\n ");
|
||||
b.append(next.getClassName());
|
||||
b.append(".");
|
||||
b.append(next.getMethodName());
|
||||
b.append("(");
|
||||
b.append(next.getFileName());
|
||||
b.append(":");
|
||||
b.append(next.getLineNumber());
|
||||
b.append(")");
|
||||
}
|
||||
ourLog.info(b.toString());
|
||||
StringBuilder b = new StringBuilder();
|
||||
b.append("Last connection request stack trace:");
|
||||
for (StackTraceElement next : myLastStackTrace.getStackTrace()) {
|
||||
b.append("\n ");
|
||||
b.append(next.getClassName());
|
||||
b.append(".");
|
||||
b.append(next.getMethodName());
|
||||
b.append("(");
|
||||
b.append(next.getFileName());
|
||||
b.append(":");
|
||||
b.append(next.getLineNumber());
|
||||
b.append(")");
|
||||
}
|
||||
ourLog.info(b.toString());
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -92,19 +90,39 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
|
|||
* and catch any potential deadlocks caused by database connection
|
||||
* starvation
|
||||
*/
|
||||
int maxThreads = (int) (Math.random() * 6) + 1;
|
||||
int maxThreads = (int) (Math.random() * 6.0) + 1;
|
||||
retVal.setMaxTotal(maxThreads);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public DaoConfig daoConfig() {
|
||||
return new DaoConfig();
|
||||
}
|
||||
|
||||
@Bean()
|
||||
@Primary()
|
||||
public DataSource dataSource() {
|
||||
|
||||
DataSource dataSource = ProxyDataSourceBuilder
|
||||
.create(retVal)
|
||||
// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
||||
.logSlowQueryBySlf4j(100, TimeUnit.MILLISECONDS)
|
||||
.countQuery()
|
||||
.build();
|
||||
.create(basicDataSource())
|
||||
// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
||||
.logSlowQueryBySlf4j(1000, TimeUnit.MILLISECONDS)
|
||||
.countQuery()
|
||||
.build();
|
||||
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public IEmailSender emailSender() {
|
||||
JavaMailEmailSender retVal = new JavaMailEmailSender();
|
||||
retVal.setSmtpServerHostname("localhost");
|
||||
retVal.setSmtpServerPort(3025);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
@Bean()
|
||||
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
|
||||
LocalContainerEntityManagerFactoryBean retVal = new LocalContainerEntityManagerFactoryBean();
|
||||
|
|
|
@ -94,12 +94,12 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
|||
* and catch any potential deadlocks caused by database connection
|
||||
* starvation
|
||||
*/
|
||||
int maxThreads = (int) (Math.random() * 6) + 1;
|
||||
int maxThreads = (int) (Math.random() * 6.0) + 1;
|
||||
retVal.setMaxTotal(maxThreads);
|
||||
|
||||
DataSource dataSource = ProxyDataSourceBuilder
|
||||
.create(retVal)
|
||||
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
||||
// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
||||
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||
.countQuery(new ThreadQueryCountHolder())
|
||||
.build();
|
||||
|
|
|
@ -118,8 +118,13 @@ public abstract class BaseJpaTest {
|
|||
|
||||
protected List<String> toUnqualifiedVersionlessIdValues(IBundleProvider theFound) {
|
||||
List<String> retVal = new ArrayList<String>();
|
||||
int size = theFound.size();
|
||||
Integer size = theFound.size();
|
||||
ourLog.info("Found {} results", size);
|
||||
|
||||
if (size == null) {
|
||||
size = 99999;
|
||||
}
|
||||
|
||||
List<IBaseResource> resources = theFound.getResources(0, size);
|
||||
for (IBaseResource next : resources) {
|
||||
retVal.add(next.getIdElement().toUnqualifiedVersionless().getValue());
|
||||
|
@ -298,7 +303,7 @@ public abstract class BaseJpaTest {
|
|||
|
||||
public static void waitForSize(int theTarget, List<?> theList) {
|
||||
StopWatch sw = new StopWatch();
|
||||
while (theList.size() != theTarget && sw.getMillis() < 10000) {
|
||||
while (theList.size() != theTarget && sw.getMillis() <= 15000) {
|
||||
try {
|
||||
Thread.sleep(50);
|
||||
} catch (InterruptedException theE) {
|
||||
|
|
|
@ -120,7 +120,7 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
protected Object myResourceProviders;
|
||||
@Autowired
|
||||
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||
@Autowired
|
||||
@Autowired(required = false)
|
||||
protected IFulltextSearchSvc mySearchDao;
|
||||
@Autowired
|
||||
protected ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
||||
|
|
|
@ -921,7 +921,6 @@ public class FhirResourceDaoDstu2SearchNoFtTest extends BaseJpaDstu2Test {
|
|||
List<Patient> patients = toList(myPatientDao.search(params));
|
||||
assertEquals(1, patients.size());
|
||||
assertEquals(id1.getIdPart(), patients.get(0).getId().getIdPart());
|
||||
assertEquals("P1TITLE", ResourceMetadataKeyEnum.TITLE.get(patients.get(0)));
|
||||
|
||||
// Given name shouldn't return for family param
|
||||
params = new SearchParameterMap();
|
||||
|
|
|
@ -56,6 +56,30 @@ public class FhirResourceDaoDstu2Test extends BaseJpaDstu2Test {
|
|||
myDaoConfig.setTreatReferencesAsLogical(new DaoConfig().getTreatReferencesAsLogical());
|
||||
}
|
||||
|
||||
/**
|
||||
* See #773
|
||||
*/
|
||||
@Test
|
||||
public void testDeleteResourceWithOutboundDeletedResources() {
|
||||
myDaoConfig.setEnforceReferentialIntegrityOnDelete(false);
|
||||
|
||||
Organization org = new Organization();
|
||||
org.setId("ORG");
|
||||
org.setName("ORG");
|
||||
myOrganizationDao.update(org);
|
||||
|
||||
Patient pat = new Patient();
|
||||
pat.setId("PAT");
|
||||
pat.setActive(true);
|
||||
pat.setManagingOrganization(new ResourceReferenceDt("Organization/ORG"));
|
||||
myPatientDao.update(pat);
|
||||
|
||||
myOrganizationDao.delete(new IdDt("Organization/ORG"));
|
||||
|
||||
myPatientDao.delete(new IdDt("Patient/PAT"));
|
||||
}
|
||||
|
||||
|
||||
private void assertGone(IIdType theId) {
|
||||
try {
|
||||
assertNotGone(theId);
|
||||
|
|
|
@ -102,6 +102,7 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
|
|||
} catch (PreconditionFailedException e) {
|
||||
return (OperationOutcome) e.getOperationOutcome();
|
||||
}
|
||||
break;
|
||||
case XML:
|
||||
encoded = myFhirCtx.newXmlParser().encodeResourceToString(input);
|
||||
try {
|
||||
|
@ -110,6 +111,7 @@ public class FhirResourceDaoDstu2ValidateTest extends BaseJpaDstu2Test {
|
|||
} catch (PreconditionFailedException e) {
|
||||
return (OperationOutcome) e.getOperationOutcome();
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
throw new IllegalStateException(); // shouldn't get here
|
||||
|
|
|
@ -172,7 +172,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
|
|||
protected IResourceTagDao myResourceTagDao;
|
||||
@Autowired
|
||||
protected ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||
@Autowired
|
||||
@Autowired(required = false)
|
||||
protected IFulltextSearchSvc mySearchDao;
|
||||
@Autowired
|
||||
protected ISearchDao mySearchEntityDao;
|
||||
|
|
|
@ -0,0 +1,153 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import com.phloc.commons.compare.ReverseComparator;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Bundle.BundleType;
|
||||
import org.hl7.fhir.dstu3.model.Bundle.HTTPVerb;
|
||||
import org.hl7.fhir.dstu3.model.IdType;
|
||||
import org.hl7.fhir.dstu3.model.Organization;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.util.comparator.ComparableComparator;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
public class FhirDaoConcurrencyDstu3Test extends BaseJpaDstu3SystemTest {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(FhirDaoConcurrencyDstu3Test.class);
|
||||
|
||||
@Autowired
|
||||
public BasicDataSource myBasicDataSource;
|
||||
private int myMaxTotal;
|
||||
|
||||
@After
|
||||
public void afterResetConnectionPool() {
|
||||
myBasicDataSource.setMaxTotal(myMaxTotal);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeSetUpConnectionPool() {
|
||||
myMaxTotal = myBasicDataSource.getMaxTotal();
|
||||
myBasicDataSource.setMaxTotal(5);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testMultipleConcurrentWritesToSameResource() throws InterruptedException {
|
||||
|
||||
ThreadPoolExecutor exec = new ThreadPoolExecutor(10, 10,
|
||||
0L, TimeUnit.MILLISECONDS,
|
||||
new LinkedBlockingQueue<Runnable>());
|
||||
|
||||
final AtomicInteger errors = new AtomicInteger();
|
||||
|
||||
List<Future> futures = new ArrayList<>();
|
||||
for (int i = 0; i < 50; i++) {
|
||||
final Patient p = new Patient();
|
||||
p.setId("PID");
|
||||
p.setActive(true);
|
||||
p.setBirthDate(new Date());
|
||||
p.addIdentifier().setSystem("foo1");
|
||||
p.addIdentifier().setSystem("foo2");
|
||||
p.addIdentifier().setSystem("foo3");
|
||||
p.addIdentifier().setSystem("foo4");
|
||||
p.addName().setFamily("FOO" + i);
|
||||
p.addName().addGiven("AAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBB1");
|
||||
p.addName().addGiven("AAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBB2");
|
||||
p.addName().addGiven("AAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBB3");
|
||||
p.addName().addGiven("AAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBB4");
|
||||
p.addName().addGiven("AAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBB5");
|
||||
p.addName().addGiven("AAAAABBBBBBBBBBBBBBBBBBBBBBBBBBBB6");
|
||||
|
||||
Organization o = new Organization();
|
||||
o.setName("ORG" + i);
|
||||
|
||||
final Bundle t = new Bundle();
|
||||
t.setType(BundleType.TRANSACTION);
|
||||
t.addEntry()
|
||||
.setResource(p)
|
||||
.getRequest()
|
||||
.setUrl("Patient/PID")
|
||||
.setMethod(HTTPVerb.PUT);
|
||||
t.addEntry()
|
||||
.setResource(o)
|
||||
.getRequest()
|
||||
.setUrl("Organization")
|
||||
.setMethod(HTTPVerb.POST);
|
||||
|
||||
if (i == 0) {
|
||||
mySystemDao.transaction(mySrd, t);
|
||||
}
|
||||
futures.add(exec.submit(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
mySystemDao.transaction(mySrd, t);
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failed to update", e);
|
||||
errors.incrementAndGet();
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
ourLog.info("Shutting down excutor");
|
||||
StopWatch sw = new StopWatch();
|
||||
for (Future next : futures) {
|
||||
while (!next.isDone()) {
|
||||
Thread.sleep(20);
|
||||
}
|
||||
}
|
||||
exec.shutdown();
|
||||
ourLog.info("Shut down excutor in {}ms", sw.getMillis());
|
||||
ourLog.info("Had {} errors", errors.get());
|
||||
|
||||
Patient currentPatient = myPatientDao.read(new IdType("Patient/PID"));
|
||||
Long currentVersion = currentPatient.getIdElement().getVersionIdPartAsLong();
|
||||
ourLog.info("Current version: {}", currentVersion);
|
||||
|
||||
IBundleProvider historyBundle = myPatientDao.history(new IdType("Patient/PID"),null,null,mySrd);
|
||||
List<IBaseResource> resources = historyBundle.getResources(0, 1000);
|
||||
List<Long> versions = new ArrayList<>();
|
||||
for (IBaseResource next : resources) {
|
||||
versions.add(next.getIdElement().getVersionIdPartAsLong());
|
||||
}
|
||||
|
||||
String message = "Current version is " + currentVersion + " - History is: " + versions;
|
||||
ourLog.info(message);
|
||||
|
||||
Collections.sort(versions, new ReverseComparator<>(new ComparableComparator<Long>()));
|
||||
Long lastVersion = versions.get(0);
|
||||
ourLog.info("Last version: {}", lastVersion);
|
||||
|
||||
//assertEquals(message, currentVersion.intValue(), versions.size());
|
||||
assertEquals(message, currentVersion, lastVersion);
|
||||
|
||||
}
|
||||
|
||||
|
||||
@AfterClass
|
||||
public static void afterClassClearContext() {
|
||||
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,16 @@
|
|||
package ca.uhn.fhir.jpa.dao.dstu3;
|
||||
|
||||
import static ca.uhn.fhir.jpa.util.TestUtil.sleepAtLeast;
|
||||
import static org.hamcrest.Matchers.containsInAnyOrder;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import ca.uhn.fhir.jpa.util.StopWatch;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.dstu3.model.Patient;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.test.util.AopTestUtils;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
|
@ -18,15 +22,13 @@ import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
|||
import ca.uhn.fhir.rest.param.StringParam;
|
||||
|
||||
public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(FhirResourceDaoDstu3SearchPageExpiryTest.class);
|
||||
|
||||
@After()
|
||||
public void after() {
|
||||
StaleSearchDeletingSvcImpl staleSearchDeletingSvc = AopTestUtils.getTargetObject(myStaleSearchDeletingSvc);
|
||||
staleSearchDeletingSvc.setCutoffSlackForUnitTest(StaleSearchDeletingSvcImpl.DEFAULT_CUTOFF_SLACK);
|
||||
StaleSearchDeletingSvcImpl.setNowForUnitTests(null);
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -35,48 +37,9 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
|
|||
staleSearchDeletingSvc.setCutoffSlackForUnitTest(0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpirePagesAfterSingleUse() throws Exception {
|
||||
IIdType pid1;
|
||||
IIdType pid2;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.addName().setFamily("EXPIRE");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
Thread.sleep(10);
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.addName().setFamily("EXPIRE");
|
||||
pid2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
Thread.sleep(10);
|
||||
|
||||
SearchParameterMap params;
|
||||
params = new SearchParameterMap();
|
||||
params.add(Patient.SP_FAMILY, new StringParam("EXPIRE"));
|
||||
final IBundleProvider bundleProvider = myPatientDao.search(params);
|
||||
assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2));
|
||||
assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2));
|
||||
|
||||
myDaoConfig.setExpireSearchResultsAfterMillis(500);
|
||||
myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem();
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
assertNotNull(mySearchEntityDao.findByUuid(bundleProvider.getUuid()));
|
||||
}
|
||||
});
|
||||
|
||||
Thread.sleep(750);
|
||||
myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem();
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
assertNull(mySearchEntityDao.findByUuid(bundleProvider.getUuid()));
|
||||
}
|
||||
});
|
||||
@Before
|
||||
public void beforeDisableResultReuse() {
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(null);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -98,6 +61,7 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
|
|||
|
||||
myDaoConfig.setExpireSearchResultsAfterMillis(1000L);
|
||||
myDaoConfig.setReuseCachedSearchResultsForMillis(500L);
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
final String searchUuid1;
|
||||
{
|
||||
|
@ -109,7 +73,7 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
|
|||
Validate.notBlank(searchUuid1);
|
||||
}
|
||||
|
||||
Thread.sleep(250);
|
||||
sleepAtLeast(250);
|
||||
|
||||
String searchUuid2;
|
||||
{
|
||||
|
@ -122,7 +86,7 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
|
|||
}
|
||||
assertEquals(searchUuid1, searchUuid2);
|
||||
|
||||
Thread.sleep(500);
|
||||
sleepAtLeast(500);
|
||||
|
||||
// We're now past 500ms so we shouldn't reuse the search
|
||||
|
||||
|
@ -139,18 +103,31 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
|
|||
|
||||
// Search just got used so it shouldn't be deleted
|
||||
|
||||
Thread.sleep(750);
|
||||
myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem();
|
||||
newTxTemplate().execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
assertNotNull(mySearchEntityDao.findByUuid(searchUuid3));
|
||||
}
|
||||
});
|
||||
|
||||
StaleSearchDeletingSvcImpl.setNowForUnitTests(start + 1400);
|
||||
|
||||
myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem();
|
||||
newTxTemplate().execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
assertNull(mySearchEntityDao.findByUuid(searchUuid1));
|
||||
assertNotNull(mySearchEntityDao.findByUuid(searchUuid3));
|
||||
}
|
||||
});
|
||||
newTxTemplate().execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
assertNull(mySearchEntityDao.findByUuid(searchUuid1));
|
||||
}
|
||||
});
|
||||
|
||||
Thread.sleep(300);
|
||||
StaleSearchDeletingSvcImpl.setNowForUnitTests(start + 2200);
|
||||
|
||||
myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem();
|
||||
newTxTemplate().execute(new TransactionCallbackWithoutResult() {
|
||||
|
@ -162,4 +139,63 @@ public class FhirResourceDaoDstu3SearchPageExpiryTest extends BaseJpaDstu3Test {
|
|||
});
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testExpirePagesAfterSingleUse() throws Exception {
|
||||
IIdType pid1;
|
||||
IIdType pid2;
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.addName().setFamily("EXPIRE");
|
||||
pid1 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
Thread.sleep(10);
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.addName().setFamily("EXPIRE");
|
||||
pid2 = myPatientDao.create(patient, mySrd).getId().toUnqualifiedVersionless();
|
||||
}
|
||||
Thread.sleep(10);
|
||||
|
||||
final StopWatch sw = new StopWatch();
|
||||
|
||||
long start = System.currentTimeMillis();
|
||||
|
||||
SearchParameterMap params;
|
||||
params = new SearchParameterMap();
|
||||
params.add(Patient.SP_FAMILY, new StringParam("EXPIRE"));
|
||||
final IBundleProvider bundleProvider = myPatientDao.search(params);
|
||||
assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2));
|
||||
assertThat(toUnqualifiedVersionlessIds(bundleProvider), containsInAnyOrder(pid1, pid2));
|
||||
|
||||
myDaoConfig.setExpireSearchResultsAfterMillis(500);
|
||||
StaleSearchDeletingSvcImpl.setNowForUnitTests(start);
|
||||
|
||||
myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem();
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTxManager);
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
assertNotNull("Failed after " + sw.toString(), mySearchEntityDao.findByUuid(bundleProvider.getUuid()));
|
||||
}
|
||||
});
|
||||
|
||||
StaleSearchDeletingSvcImpl.setNowForUnitTests(start + 499);
|
||||
myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem();
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
assertNotNull(mySearchEntityDao.findByUuid(bundleProvider.getUuid()));
|
||||
}
|
||||
});
|
||||
|
||||
StaleSearchDeletingSvcImpl.setNowForUnitTests(start + 600);
|
||||
myStaleSearchDeletingSvc.pollForStaleSearchesAndDeleteThem();
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus theArg0) {
|
||||
assertNull(mySearchEntityDao.findByUuid(bundleProvider.getUuid()));
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue