Merge branch 'master' into spring-boot-starter

This commit is contained in:
James Agnew 2017-11-13 13:20:24 -05:00 committed by GitHub
commit 85bc654a63
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
1393 changed files with 258571 additions and 40268 deletions

View File

@ -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

View File

@ -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

View File

@ -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>

View File

@ -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>

View File

@ -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();

View File

@ -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)

View File

@ -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>

View File

@ -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;

View File

@ -152,7 +152,8 @@ public class RuntimeSearchParam {
public enum RuntimeSearchParamStatusEnum {
ACTIVE,
DRAFT,
RETIRED
RETIRED,
UNKNOWN
}
}

View File

@ -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;
/**
@ -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>";
protected String parse(String theValue) {
if (XmlDetectionUtil.isStaxPresent()) {
// for validation
XmlUtil.parse(theValue);
}
boolean hasProcessingInstruction = val.startsWith("<?");
if (hasProcessingInstruction && val.endsWith("?>")) {
return null;
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;
/**
* 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();
}
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 BasePrimitive<String> setValue(String theValue) throws DataFormatException {
return super.setValue(theValue);
}
/**

View File

@ -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;
}

View File

@ -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.

View File

@ -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
@ -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();
@ -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++;
@ -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));
}
theEventWriter.endObject();
}
@ -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);
@ -1429,27 +1444,5 @@ public class JsonParser extends BaseParser implements IJsonLikeParser {
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();
}
}
}
}
}

View File

@ -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();
}
}

View File

@ -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;

View File

@ -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;
}
}

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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

View File

@ -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);
}
}

View File

@ -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 {
int port;
do {
server = new ServerSocket(0);
int port = server.getLocalPort();
port = server.getLocalPort();
server.close();
} while (!ourPorts.add(port));
Thread.sleep(500);
return port;
} catch (Exception e) {

View File

@ -20,26 +20,27 @@ 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.
@ -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", "");
}
}

View File

@ -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;
}
}

View File

@ -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);
@ -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) {
@ -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
*/
@ -1712,26 +1782,6 @@ public class XmlUtil {
}
}
/**
* 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 {
@Override

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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());
}
}

View File

@ -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());
}

View File

@ -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);
/*

View File

@ -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>

View File

@ -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());
}

View File

@ -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!
*/

View File

@ -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;
}

View File

@ -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);

View File

@ -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())

View File

@ -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;
}
}

View File

@ -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);
}

View File

@ -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>
@ -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,7 +297,7 @@
</fileSets>
</configuration>
</execution>
<execution>
<execution>
<id>post-integration-test</id>
<phase>install</phase>
<goals>
@ -296,8 +308,6 @@
<outputDirectory>${project.reporting.outputDirectory}/jacoco-report</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<plugin>

View File

@ -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>

View File

@ -34,12 +34,14 @@ 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,6 +81,12 @@ 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");
@ -80,7 +94,7 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
/**
* Only accept clients of type javax.ws.rs.client.Client
*
* Can be used to set a specific Client implementation
* @param theHttpClient
*/
@Override
@ -88,6 +102,15 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
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);
@ -95,7 +118,9 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
@Override
protected void resetHttpClient() {
this.myNativeClient = null;
if (myNativeClient != null)
myNativeClient.close(); // close client to avoid memory leak
myNativeClient = null;
}
}

View File

@ -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());
}
}

View File

@ -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 {
}
}

View File

@ -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>

View File

@ -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;
}
/**

View File

@ -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()) {

View File

@ -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

View File

@ -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;

View File

@ -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

View File

@ -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);
/**

View File

@ -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()) {

View File

@ -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;
}

View File

@ -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

View File

@ -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);

View File

@ -215,6 +215,9 @@ public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry {
case RETIRED:
status = RuntimeSearchParamStatusEnum.RETIRED;
break;
case UNKNOWN:
status = RuntimeSearchParamStatusEnum.UNKNOWN;
break;
case NULL:
break;
}

View File

@ -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

View File

@ -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);

View File

@ -216,6 +216,9 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry {
case RETIRED:
status = RuntimeSearchParamStatusEnum.RETIRED;
break;
case UNKNOWN:
status = RuntimeSearchParamStatusEnum.UNKNOWN;
break;
case NULL:
break;
}

View File

@ -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();
}
}

View File

@ -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());

View File

@ -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)
@ -101,7 +102,7 @@ public class Search implements Serializable {
@Lob()
@Basic(fetch=FetchType.LAZY)
@Column(name="SEARCH_QUERY_STRING", nullable=true, updatable=false)
@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,8 +257,12 @@ public class Search implements Serializable {
}
public void setSearchQueryString(String theSearchQueryString) {
if (theSearchQueryString != null && theSearchQueryString.length() > MAX_SEARCH_QUERY_STRING) {
mySearchQueryString = null;
} else {
mySearchQueryString = theSearchQueryString;
}
}
public void setSearchQueryStringHash(Integer theSearchQueryStringHash) {
mySearchQueryStringHash = theSearchQueryStringHash;

View File

@ -115,6 +115,8 @@ public class JpaStorageServices extends BaseHapiFhirDao<IBaseResource> implement
break;
case URI:
break;
case HAS:
break;
}
params.add(nextArgument.getName(), param);

View File

@ -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,6 +179,7 @@ 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");
@ -193,11 +195,9 @@ public class JpaSystemProviderDstu2 extends BaseJpaSystemProviderDstu2Plus<Bundl
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;

View File

@ -20,33 +20,40 @@ 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,
@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) {
//@formatter:on
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) {
@ -63,7 +70,7 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst
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));
}
@ -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,
@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
) {
//@formatter:on
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())) {
@ -118,7 +129,6 @@ public class BaseJpaResourceProviderValueSetDstu3 extends JpaResourceProviderDst
}
private static boolean moreThanOneTrue(boolean... theBooleans) {
boolean haveOne = false;
for (boolean next : theBooleans) {

View File

@ -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,106 +50,106 @@ 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();
@ -159,8 +164,8 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
}
//@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) {
@ -169,13 +174,14 @@ public class JpaSystemProviderDstu3 extends BaseJpaSystemProviderDstu2Plus<Bundl
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");
}
@ -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");
}
}
}

View File

@ -20,41 +20,40 @@ 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,
@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) {
//@formatter:on
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);
@ -63,7 +62,7 @@ public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<Val
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));
}
@ -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,
@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
) {
//@formatter:on
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())) {
@ -118,7 +115,6 @@ public class BaseJpaResourceProviderValueSetR4 extends JpaResourceProviderR4<Val
}
private static boolean moreThanOneTrue(boolean... theBooleans) {
boolean haveOne = false;
for (boolean next : theBooleans) {

View File

@ -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");

View File

@ -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);
}

View File

@ -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;
@ -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();

View File

@ -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();
}

View File

@ -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;
boolean keepWaiting;
do {
synchronized (mySyncedPids) {
keepWaiting = false;
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);
keepWaiting = true;
}
}
if (latch != null) {
while (latch.getCount() > 0 && mySearch.getStatus() == SearchStatusEnum.LOADING) {
if (keepWaiting) {
ourLog.info("Waiting, as we only have {} results", mySyncedPids.size());
try {
ourLog.trace("Awaiting latch with {}", latch.getCount());
latch.await(500, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
// ok
}
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,8 +654,17 @@ public class SearchCoordinatorSvcImpl implements ISearchCoordinatorSvc {
}
});
int numSynced;
synchronized (mySyncedPids) {
numSynced = mySyncedPids.size();
}
if (myDaoConfig.getCountSearchResultsUpTo() == null ||
myDaoConfig.getCountSearchResultsUpTo() <= 0 ||
myDaoConfig.getCountSearchResultsUpTo() <= numSynced) {
myInitialCollectionLatch.countDown();
}
}
}

View File

@ -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,36 +46,33 @@ 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);
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
@Transactional(propagation = Propagation.NOT_SUPPORTED)
@ -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;
}
}

View File

@ -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
}

View File

@ -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,14 +455,20 @@ 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();
TransactionTemplate transactionTemplate = new TransactionTemplate(myTxManager);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
initSubscriptions();
}
});
}
protected void submitResourceModified(final ResourceModifiedMessage theMsg) {
mySubscriptionActivatingSubscriber.handleMessage(theMsg.getOperationType(), theMsg.getId(myCtx), theMsg.getNewPayload(myCtx));

View File

@ -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;

View File

@ -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;
}
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
txTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
activateAndRegisterSubscriptionIfRequired(theSubscription);
}
});
break;
default:
break;
}

View File

@ -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;
}

View File

@ -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();
MimeMessage email = mySender.createMimeMessage();
try {
email.setFrom(trim(theDetails.getFrom()));
email.setTo(toTrimmedStringArray(theDetails.getTo()));
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, ",");
}
}

View File

@ -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";

View File

@ -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() {

View File

@ -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";
}

View File

@ -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

View File

@ -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();
}
}
}
}

View File

@ -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

View File

@ -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,7 +46,7 @@ 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;
@ -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)
.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();

View File

@ -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();

View File

@ -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) {

View File

@ -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;

View File

@ -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();

View File

@ -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);

View File

@ -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

View File

@ -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;

View File

@ -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();
}
}

View File

@ -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