Add new interceptor method which enabled interceptors to modify response
This commit is contained in:
parent
0677f35847
commit
f976b7bf7e
|
@ -0,0 +1,121 @@
|
||||||
|
package example;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
|
import ca.uhn.fhir.util.ResourceReferenceInfo;
|
||||||
|
import org.hl7.fhir.dstu3.model.Bundle;
|
||||||
|
import org.hl7.fhir.dstu3.model.Resource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
import org.slf4j.Logger;
|
||||||
|
import org.slf4j.LoggerFactory;
|
||||||
|
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
@SuppressWarnings("unused")
|
||||||
|
public class Copier {
|
||||||
|
private static final Logger ourLog = LoggerFactory.getLogger(Copier.class);
|
||||||
|
|
||||||
|
public static void main(String[] args) {
|
||||||
|
FhirContext ctx = FhirContext.forDstu3();
|
||||||
|
IGenericClient source = ctx.newRestfulGenericClient("http://localhost:8080/baseDstu3");
|
||||||
|
IGenericClient target = ctx.newRestfulGenericClient("https://try.smilecdr.com:8000");
|
||||||
|
|
||||||
|
List<String> resType = Arrays.asList(
|
||||||
|
"Patient", "Organization", "Encounter", "Procedure",
|
||||||
|
"Observation", "ResearchSubject", "Specimen",
|
||||||
|
"ResearchStudy", "Location", "Practitioner"
|
||||||
|
);
|
||||||
|
|
||||||
|
List<IBaseResource> queued = new ArrayList<>();
|
||||||
|
Set<String> sent = new HashSet<>();
|
||||||
|
for (String next : resType) {
|
||||||
|
copy(ctx, source, target, next, queued, sent);
|
||||||
|
}
|
||||||
|
|
||||||
|
while (queued.size() > 0) {
|
||||||
|
ourLog.info("Have {} queued resources to deliver", queued.size());
|
||||||
|
|
||||||
|
for (IBaseResource nextQueued : new ArrayList<>(queued)) {
|
||||||
|
|
||||||
|
String missingRef = null;
|
||||||
|
for (ResourceReferenceInfo nextRefInfo : ctx.newTerser().getAllResourceReferences(nextQueued)) {
|
||||||
|
String nextRef = nextRefInfo.getResourceReference().getReferenceElement().getValue();
|
||||||
|
if (isNotBlank(nextRef) && !sent.contains(nextRef)) {
|
||||||
|
missingRef = nextRef;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (missingRef != null) {
|
||||||
|
ourLog.info("Can't send {} because of missing ref {}", nextQueued.getIdElement().getIdPart(), missingRef);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIdType newId = target
|
||||||
|
.update()
|
||||||
|
.resource(nextQueued)
|
||||||
|
.execute()
|
||||||
|
.getId();
|
||||||
|
|
||||||
|
ourLog.info("Copied resource {} and got ID {}", nextQueued.getIdElement().getValue(), newId);
|
||||||
|
sent.add(nextQueued.getIdElement().toUnqualifiedVersionless().getValue());
|
||||||
|
queued.remove(nextQueued);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void copy(FhirContext theCtx, IGenericClient theSource, IGenericClient theTarget, String theResType, List<IBaseResource> theQueued, Set<String> theSent) {
|
||||||
|
Bundle received = theSource
|
||||||
|
.search()
|
||||||
|
.forResource(theResType)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.execute();
|
||||||
|
copy(theCtx, theTarget, theResType, theQueued, theSent, received);
|
||||||
|
|
||||||
|
while (received.getLink("next") != null) {
|
||||||
|
ourLog.info("Fetching next page...");
|
||||||
|
received = theSource.loadPage().next(received).execute();
|
||||||
|
copy(theCtx, theTarget, theResType, theQueued, theSent, received);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void copy(FhirContext theCtx, IGenericClient theTarget, String theResType, List<IBaseResource> theQueued, Set<String> theSent, Bundle theReceived) {
|
||||||
|
for (Bundle.BundleEntryComponent nextEntry : theReceived.getEntry()) {
|
||||||
|
Resource nextResource = nextEntry.getResource();
|
||||||
|
nextResource.setId(theResType + "/" + "CR-" + nextResource.getIdElement().getIdPart());
|
||||||
|
|
||||||
|
boolean haveUnsentReference = false;
|
||||||
|
for (ResourceReferenceInfo nextRefInfo : theCtx.newTerser().getAllResourceReferences(nextResource)) {
|
||||||
|
IIdType nextRef = nextRefInfo.getResourceReference().getReferenceElement();
|
||||||
|
if (nextRef.hasIdPart()) {
|
||||||
|
String newRef = nextRef.getResourceType() + "/" + "CR-" + nextRef.getIdPart();
|
||||||
|
ourLog.info("Changing reference {} to {}", nextRef.getValue(), newRef);
|
||||||
|
nextRefInfo.getResourceReference().setReference(newRef);
|
||||||
|
if (!theSent.contains(newRef)) {
|
||||||
|
haveUnsentReference = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (haveUnsentReference) {
|
||||||
|
ourLog.info("Queueing {} for delivery after", nextResource.getId());
|
||||||
|
theQueued.add(nextResource);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
IIdType newId = theTarget
|
||||||
|
.update()
|
||||||
|
.resource(nextResource)
|
||||||
|
.execute()
|
||||||
|
.getId();
|
||||||
|
|
||||||
|
ourLog.info("Copied resource {} and got ID {}", nextResource.getId(), newId);
|
||||||
|
theSent.add(nextResource.getIdElement().toUnqualifiedVersionless().getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -82,6 +82,21 @@ public enum FhirVersionEnum {
|
||||||
return ordinal() >= theVersion.ordinal();
|
return ordinal() >= theVersion.ordinal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link FhirVersionEnum} which corresponds to a specific version of
|
||||||
|
* FHIR. Partial version strings (e.g. "3.0") are acceptable.
|
||||||
|
*
|
||||||
|
* @return Returns null if no version exists matching the given string
|
||||||
|
*/
|
||||||
|
public static FhirVersionEnum forVersionString(String theVersionString) {
|
||||||
|
for (FhirVersionEnum next : values()) {
|
||||||
|
if (next.getFhirVersionString().startsWith(theVersionString)) {
|
||||||
|
return next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean isEquivalentTo(FhirVersionEnum theVersion) {
|
public boolean isEquivalentTo(FhirVersionEnum theVersion) {
|
||||||
if (this.equals(theVersion)) {
|
if (this.equals(theVersion)) {
|
||||||
return true;
|
return true;
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
<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>
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
<parent>
|
<parent>
|
||||||
|
@ -11,23 +12,26 @@
|
||||||
<artifactId>hapi-fhir-converter</artifactId>
|
<artifactId>hapi-fhir-converter</artifactId>
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
<name>HAPI FHIR - Converter</name>
|
|
||||||
|
|
||||||
<build>
|
|
||||||
<resources>
|
|
||||||
<resource>
|
|
||||||
<directory>src/main/resources</directory>
|
|
||||||
<filtering>false</filtering>
|
|
||||||
</resource>
|
|
||||||
</resources>
|
|
||||||
</build>
|
|
||||||
|
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-base</artifactId>
|
<artifactId>hapi-fhir-base</artifactId>
|
||||||
<version>3.3.0-SNAPSHOT</version>
|
<version>3.3.0-SNAPSHOT</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Server -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
|
<artifactId>hapi-fhir-server</artifactId>
|
||||||
|
<version>3.3.0-SNAPSHOT</version>
|
||||||
|
<optional>true</optional>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>ca.uhn.hapi.fhir</groupId>
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
|
<artifactId>hapi-fhir-structures-hl7org-dstu2</artifactId>
|
||||||
|
@ -64,5 +68,60 @@
|
||||||
<version>3.3.0-SNAPSHOT</version>
|
<version>3.3.0-SNAPSHOT</version>
|
||||||
<optional>true</optional>
|
<optional>true</optional>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- Testing -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ca.uhn.hapi.fhir</groupId>
|
||||||
|
<artifactId>hapi-fhir-client</artifactId>
|
||||||
|
<version>${project.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.xmlunit</groupId>
|
||||||
|
<artifactId>xmlunit-core</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-servlets</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-servlet</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-server</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-util</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-webapp</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.eclipse.jetty</groupId>
|
||||||
|
<artifactId>jetty-http</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
|
<name>HAPI FHIR - Converter</name>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<resources>
|
||||||
|
<resource>
|
||||||
|
<directory>src/main/resources</directory>
|
||||||
|
<filtering>false</filtering>
|
||||||
|
</resource>
|
||||||
|
</resources>
|
||||||
|
</build>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
@ -0,0 +1,72 @@
|
||||||
|
package ca.uhn.hapi.converters.server;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.api.SummaryEnum;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.InterceptorAdapter;
|
||||||
|
import org.hl7.fhir.convertors.VersionConvertor_30_40;
|
||||||
|
import org.hl7.fhir.exceptions.FHIRException;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.StringTokenizer;
|
||||||
|
|
||||||
|
import static org.apache.commons.lang3.StringUtils.defaultString;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
|
public class VersionedApiConverterInterceptor extends InterceptorAdapter {
|
||||||
|
private VersionConvertor_30_40 myVersionConvertor_30_40 = new VersionConvertor_30_40();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
|
||||||
|
String accept = defaultString(theServletRequest.getHeader(Constants.HEADER_ACCEPT));
|
||||||
|
StringTokenizer tok = new StringTokenizer(accept, ";");
|
||||||
|
String wantVersionString = null;
|
||||||
|
while (tok.hasMoreTokens()) {
|
||||||
|
String next = tok.nextToken().trim();
|
||||||
|
if (next.startsWith("fhir-version=")) {
|
||||||
|
wantVersionString = next.substring("fhir-version=".length()).trim();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
FhirVersionEnum wantVersion = null;
|
||||||
|
if (isNotBlank(wantVersionString)) {
|
||||||
|
wantVersion = FhirVersionEnum.forVersionString(wantVersionString);
|
||||||
|
}
|
||||||
|
FhirVersionEnum haveVersion = theResponseObject.getStructureFhirVersionEnum();
|
||||||
|
|
||||||
|
IBaseResource converted = null;
|
||||||
|
try {
|
||||||
|
if (wantVersion == FhirVersionEnum.R4 && haveVersion == FhirVersionEnum.DSTU3) {
|
||||||
|
converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.dstu3.model.Resource) theResponseObject);
|
||||||
|
} else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.R4) {
|
||||||
|
converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.r4.model.Resource) theResponseObject);
|
||||||
|
} else if (wantVersion == FhirVersionEnum.DSTU3 && haveVersion == FhirVersionEnum.R4) {
|
||||||
|
converted = myVersionConvertor_30_40.convertResource((org.hl7.fhir.r4.model.Resource) theResponseObject);
|
||||||
|
}
|
||||||
|
} catch (FHIRException e) {
|
||||||
|
throw new InternalErrorException(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (converted != null) {
|
||||||
|
Set<SummaryEnum> objects = Collections.emptySet();
|
||||||
|
try {
|
||||||
|
RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), converted, objects, 200, "OK", false, false, theRequestDetails, null, null);
|
||||||
|
return false;
|
||||||
|
} catch (IOException e) {
|
||||||
|
throw new InternalErrorException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,276 @@
|
||||||
|
package ca.uhn.hapi.converters.server;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
|
import ca.uhn.fhir.rest.api.SearchStyleEnum;
|
||||||
|
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
|
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.gclient.StringClientParam;
|
||||||
|
import ca.uhn.fhir.rest.param.TokenAndListParam;
|
||||||
|
import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.util.PortUtil;
|
||||||
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
|
import org.apache.commons.io.IOUtils;
|
||||||
|
import org.apache.http.client.ClientProtocolException;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClientBuilder;
|
||||||
|
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
|
import org.eclipse.jetty.server.Server;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHandler;
|
||||||
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
|
import org.hl7.fhir.dstu3.model.Bundle;
|
||||||
|
import org.hl7.fhir.dstu3.model.HumanName;
|
||||||
|
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||||
|
import org.hl7.fhir.dstu3.model.Patient;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.junit.AfterClass;
|
||||||
|
import org.junit.Before;
|
||||||
|
import org.junit.BeforeClass;
|
||||||
|
import org.junit.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.containsString;
|
||||||
|
import static org.hamcrest.Matchers.not;
|
||||||
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
public class SearchDstu3Test {
|
||||||
|
|
||||||
|
private static CloseableHttpClient ourClient;
|
||||||
|
private static FhirContext ourCtx = FhirContext.forDstu3();
|
||||||
|
private static TokenAndListParam ourIdentifiers;
|
||||||
|
private static String ourLastMethod;
|
||||||
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchDstu3Test.class);
|
||||||
|
private static int ourPort;
|
||||||
|
|
||||||
|
private static Server ourServer;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void before() {
|
||||||
|
ourLastMethod = null;
|
||||||
|
ourIdentifiers = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchNormal() throws Exception {
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar");
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||||
|
try {
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info(responseContent);
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
|
||||||
|
assertEquals("search", ourLastMethod);
|
||||||
|
|
||||||
|
assertEquals("foo", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getSystem());
|
||||||
|
assertEquals("bar", ourIdentifiers.getValuesAsQueryTokens().get(0).getValuesAsQueryTokens().get(0).getValue());
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithInvalidChain() throws Exception {
|
||||||
|
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier.chain=foo%7Cbar");
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||||
|
try {
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info(responseContent);
|
||||||
|
assertEquals(400, status.getStatusLine().getStatusCode());
|
||||||
|
|
||||||
|
OperationOutcome oo = (OperationOutcome) ourCtx.newJsonParser().parseResource(responseContent);
|
||||||
|
assertEquals(
|
||||||
|
"Invalid search parameter \"identifier.chain\". Parameter contains a chain (.chain) and chains are not supported for this parameter (chaining is only allowed on reference parameters)",
|
||||||
|
oo.getIssueFirstRep().getDiagnostics());
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPagingPreservesEncodingJson() throws Exception {
|
||||||
|
HttpGet httpGet;
|
||||||
|
String linkNext;
|
||||||
|
Bundle bundle;
|
||||||
|
|
||||||
|
// Initial search
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=json");
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=json"));
|
||||||
|
|
||||||
|
// Fetch the next page
|
||||||
|
httpGet = new HttpGet(linkNext);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=json"));
|
||||||
|
|
||||||
|
// Fetch the next page
|
||||||
|
httpGet = new HttpGet(linkNext);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=json"));
|
||||||
|
|
||||||
|
// Fetch the next page
|
||||||
|
httpGet = new HttpGet(linkNext);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=json"));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testPagingPreservesEncodingApplicationJsonFhir() throws Exception {
|
||||||
|
HttpGet httpGet;
|
||||||
|
String linkNext;
|
||||||
|
Bundle bundle;
|
||||||
|
|
||||||
|
// Initial search
|
||||||
|
httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?identifier=foo%7Cbar&_format=" + Constants.CT_FHIR_JSON_NEW);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
|
||||||
|
|
||||||
|
// Fetch the next page
|
||||||
|
httpGet = new HttpGet(linkNext);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
|
||||||
|
|
||||||
|
// Fetch the next page
|
||||||
|
httpGet = new HttpGet(linkNext);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
|
||||||
|
|
||||||
|
// Fetch the next page
|
||||||
|
httpGet = new HttpGet(linkNext);
|
||||||
|
bundle = executeAndReturnLinkNext(httpGet, EncodingEnum.JSON);
|
||||||
|
linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertThat(linkNext, containsString("_format=" + UrlUtil.escapeUrlParam(Constants.CT_FHIR_JSON_NEW)));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private Bundle executeAndReturnLinkNext(HttpGet httpGet, EncodingEnum theExpectEncoding) throws IOException {
|
||||||
|
CloseableHttpResponse status = ourClient.execute(httpGet);
|
||||||
|
Bundle bundle;
|
||||||
|
try {
|
||||||
|
String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8);
|
||||||
|
ourLog.info(responseContent);
|
||||||
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
|
EncodingEnum ct = EncodingEnum.forContentType(status.getEntity().getContentType().getValue().replaceAll(";.*", "").trim());
|
||||||
|
assertEquals(theExpectEncoding, ct);
|
||||||
|
bundle = ct.newParser(ourCtx).parseResource(Bundle.class, responseContent);
|
||||||
|
assertEquals(10, bundle.getEntry().size());
|
||||||
|
String linkNext = bundle.getLink(Constants.LINK_NEXT).getUrl();
|
||||||
|
assertNotNull(linkNext);
|
||||||
|
} finally {
|
||||||
|
IOUtils.closeQuietly(status.getEntity().getContent());
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchWithPostAndInvalidParameters() {
|
||||||
|
IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
|
||||||
|
LoggingInterceptor interceptor = new LoggingInterceptor();
|
||||||
|
interceptor.setLogRequestSummary(true);
|
||||||
|
interceptor.setLogRequestBody(true);
|
||||||
|
interceptor.setLogRequestHeaders(false);
|
||||||
|
interceptor.setLogResponseBody(false);
|
||||||
|
interceptor.setLogResponseHeaders(false);
|
||||||
|
interceptor.setLogResponseSummary(false);
|
||||||
|
client.registerInterceptor(interceptor);
|
||||||
|
try {
|
||||||
|
client
|
||||||
|
.search()
|
||||||
|
.forResource(Patient.class)
|
||||||
|
.where(new StringClientParam("foo").matches().value("bar"))
|
||||||
|
.prettyPrint()
|
||||||
|
.usingStyle(SearchStyleEnum.POST)
|
||||||
|
.returnBundle(Bundle.class)
|
||||||
|
.encodedJson()
|
||||||
|
.execute();
|
||||||
|
fail();
|
||||||
|
} catch (InvalidRequestException e) {
|
||||||
|
assertThat(e.getMessage(), containsString("Invalid request: The FHIR endpoint on this server does not know how to handle POST operation[Patient/_search] with parameters [[_pretty, foo]]"));
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@AfterClass
|
||||||
|
public static void afterClassClearContext() throws Exception {
|
||||||
|
ourServer.stop();
|
||||||
|
TestUtil.clearAllStaticFieldsForUnitTest();
|
||||||
|
}
|
||||||
|
|
||||||
|
@BeforeClass
|
||||||
|
public static void beforeClass() throws Exception {
|
||||||
|
ourPort = PortUtil.findFreePort();
|
||||||
|
ourServer = new Server(ourPort);
|
||||||
|
|
||||||
|
DummyPatientResourceProvider patientProvider = new DummyPatientResourceProvider();
|
||||||
|
|
||||||
|
ServletHandler proxyHandler = new ServletHandler();
|
||||||
|
RestfulServer servlet = new RestfulServer(ourCtx);
|
||||||
|
servlet.setDefaultResponseEncoding(EncodingEnum.JSON);
|
||||||
|
servlet.setPagingProvider(new FifoMemoryPagingProvider(10));
|
||||||
|
|
||||||
|
servlet.setResourceProviders(patientProvider);
|
||||||
|
ServletHolder servletHolder = new ServletHolder(servlet);
|
||||||
|
proxyHandler.addServletWithMapping(servletHolder, "/*");
|
||||||
|
ourServer.setHandler(proxyHandler);
|
||||||
|
ourServer.start();
|
||||||
|
|
||||||
|
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||||
|
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||||
|
builder.setConnectionManager(connectionManager);
|
||||||
|
ourClient = builder.build();
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Class<? extends IBaseResource> getResourceType() {
|
||||||
|
return Patient.class;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
|
@Search()
|
||||||
|
public List search(
|
||||||
|
@RequiredParam(name = Patient.SP_IDENTIFIER) TokenAndListParam theIdentifiers) {
|
||||||
|
ourLastMethod = "search";
|
||||||
|
ourIdentifiers = theIdentifiers;
|
||||||
|
ArrayList<Patient> retVal = new ArrayList<Patient>();
|
||||||
|
|
||||||
|
for (int i = 0; i < 200; i++) {
|
||||||
|
Patient patient = new Patient();
|
||||||
|
patient.addName(new HumanName().setFamily("FAMILY"));
|
||||||
|
patient.getIdElement().setValue("Patient/" + i);
|
||||||
|
retVal.add(patient);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -19,6 +19,7 @@ import ca.uhn.fhir.util.TestUtil;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
import org.junit.AfterClass;
|
import org.junit.AfterClass;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
|
import org.junit.Ignore;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.internal.util.collections.ListUtil;
|
import org.mockito.internal.util.collections.ListUtil;
|
||||||
import org.thymeleaf.util.ListUtils;
|
import org.thymeleaf.util.ListUtils;
|
||||||
|
@ -53,6 +54,9 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testOverrideAndDisableBuiltInSearchParametersWithOverridingEnabled() {
|
public void testOverrideAndDisableBuiltInSearchParametersWithOverridingEnabled() {
|
||||||
myDaoConfig.setDefaultSearchParamsCanBeOverridden(true);
|
myDaoConfig.setDefaultSearchParamsCanBeOverridden(true);
|
||||||
|
@ -883,6 +887,53 @@ public class FhirResourceDaoDstu2SearchCustomSearchParamTest extends BaseJpaDstu
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@Ignore
|
||||||
|
public void testSearchForStringOnIdentifierWithSpecificSystem() {
|
||||||
|
|
||||||
|
SearchParameter fooSp = new SearchParameter();
|
||||||
|
fooSp.setBase(ResourceTypeEnum.PATIENT);
|
||||||
|
fooSp.setCode("foo");
|
||||||
|
fooSp.setType(SearchParamTypeEnum.STRING);
|
||||||
|
fooSp.setXpath("Patient.identifier.where(system = 'http://AAA').value");
|
||||||
|
fooSp.setXpathUsage(XPathUsageTypeEnum.NORMAL);
|
||||||
|
fooSp.setStatus(ConformanceResourceStatusEnum.ACTIVE);
|
||||||
|
IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
|
||||||
|
mySearchParamRegsitry.forceRefresh();
|
||||||
|
|
||||||
|
Patient pat = new Patient();
|
||||||
|
pat.addIdentifier().setSystem("http://AAA").setValue("BAR678");
|
||||||
|
pat.setGender(AdministrativeGenderEnum.MALE);
|
||||||
|
IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
Patient pat2 = new Patient();
|
||||||
|
pat2.addIdentifier().setSystem("http://BBB").setValue("BAR678");
|
||||||
|
pat2.setGender(AdministrativeGenderEnum.FEMALE);
|
||||||
|
myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
SearchParameterMap map;
|
||||||
|
IBundleProvider results;
|
||||||
|
List<String> foundResources;
|
||||||
|
|
||||||
|
// Partial match
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.add("foo", new StringParam("bar"));
|
||||||
|
results = myPatientDao.search(map);
|
||||||
|
foundResources = toUnqualifiedVersionlessIdValues(results);
|
||||||
|
assertThat(foundResources, contains(patId.getValue()));
|
||||||
|
|
||||||
|
// Non match
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.add("foo", new StringParam("zzz"));
|
||||||
|
results = myPatientDao.search(map);
|
||||||
|
foundResources = toUnqualifiedVersionlessIdValues(results);
|
||||||
|
assertThat(foundResources, empty());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchWithCustomParam() {
|
public void testSearchWithCustomParam() {
|
||||||
|
|
||||||
|
|
|
@ -913,6 +913,51 @@ public class FhirResourceDaoR4SearchCustomSearchParamTest extends BaseJpaR4Test
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSearchForStringOnIdentifierWithSpecificSystem() {
|
||||||
|
|
||||||
|
SearchParameter fooSp = new SearchParameter();
|
||||||
|
fooSp.addBase("Patient");
|
||||||
|
fooSp.setCode("foo");
|
||||||
|
fooSp.setType(org.hl7.fhir.r4.model.Enumerations.SearchParamType.STRING);
|
||||||
|
fooSp.setTitle("FOO SP");
|
||||||
|
fooSp.setExpression("Patient.identifier.where(system = 'http://AAA').value");
|
||||||
|
fooSp.setXpathUsage(org.hl7.fhir.r4.model.SearchParameter.XPathUsageType.NORMAL);
|
||||||
|
fooSp.setStatus(org.hl7.fhir.r4.model.Enumerations.PublicationStatus.ACTIVE);
|
||||||
|
IIdType spId = mySearchParameterDao.create(fooSp, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
mySearchParamRegsitry.forceRefresh();
|
||||||
|
|
||||||
|
Patient pat = new Patient();
|
||||||
|
pat.addIdentifier().setSystem("http://AAA").setValue("BAR678");
|
||||||
|
pat.setGender(AdministrativeGender.MALE);
|
||||||
|
IIdType patId = myPatientDao.create(pat, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
Patient pat2 = new Patient();
|
||||||
|
pat2.addIdentifier().setSystem("http://BBB").setValue("BAR678");
|
||||||
|
pat2.setGender(AdministrativeGender.FEMALE);
|
||||||
|
myPatientDao.create(pat2, mySrd).getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
SearchParameterMap map;
|
||||||
|
IBundleProvider results;
|
||||||
|
List<String> foundResources;
|
||||||
|
|
||||||
|
// Partial match
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.add("foo", new StringParam("bar"));
|
||||||
|
results = myPatientDao.search(map);
|
||||||
|
foundResources = toUnqualifiedVersionlessIdValues(results);
|
||||||
|
assertThat(foundResources, contains(patId.getValue()));
|
||||||
|
|
||||||
|
// Non match
|
||||||
|
map = new SearchParameterMap();
|
||||||
|
map.add("foo", new StringParam("zzz"));
|
||||||
|
results = myPatientDao.search(map);
|
||||||
|
foundResources = toUnqualifiedVersionlessIdValues(results);
|
||||||
|
assertThat(foundResources, empty());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchWithCustomParam() {
|
public void testSearchWithCustomParam() {
|
||||||
|
|
||||||
|
|
|
@ -9,6 +9,7 @@ import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
|
@ -67,12 +68,14 @@ public class ResourceProviderInterceptorDstu2Test extends BaseResourceProviderDs
|
||||||
when(myServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true);
|
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true);
|
||||||
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
|
||||||
when(myJpaServerInterceptor.handleException(any(RequestDetails.class), any(BaseServerResponseException.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myJpaServerInterceptor.handleException(any(RequestDetails.class), any(BaseServerResponseException.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myJpaServerInterceptor.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myJpaServerInterceptor.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myJpaServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myJpaServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myJpaServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true);
|
when(myJpaServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true);
|
||||||
when(myJpaServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myJpaServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
when(myJpaServerInterceptor.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
|
||||||
myDaoConfig.getInterceptors().add(myDaoInterceptor);
|
myDaoConfig.getInterceptors().add(myDaoInterceptor);
|
||||||
ourRestServer.registerInterceptor(myServerInterceptor);
|
ourRestServer.registerInterceptor(myServerInterceptor);
|
||||||
|
|
|
@ -5,6 +5,7 @@ import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
|
@ -89,6 +90,7 @@ public class ResourceProviderInterceptorDstu3Test extends BaseResourceProviderDs
|
||||||
when(myServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true);
|
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true);
|
||||||
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -4,6 +4,7 @@ import ca.uhn.fhir.parser.IParser;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
|
@ -84,6 +85,7 @@ public class ResourceProviderInterceptorR4Test extends BaseResourceProviderR4Tes
|
||||||
when(myServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myServerInterceptor.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true);
|
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class))).thenReturn(true);
|
||||||
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
when(myServerInterceptor.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
|
@ -38,6 +38,7 @@ import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.entity.StringEntity;
|
import org.apache.http.entity.StringEntity;
|
||||||
import org.apache.http.message.BasicNameValuePair;
|
import org.apache.http.message.BasicNameValuePair;
|
||||||
import org.hamcrest.Matchers;
|
import org.hamcrest.Matchers;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
@ -131,6 +132,68 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test {
|
||||||
return retVal;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelfReferentialInclude() {
|
||||||
|
Location loc1 = new Location();
|
||||||
|
loc1.setName("loc1");
|
||||||
|
IIdType loc1id = myClient.create().resource(loc1).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
Location loc2 = new Location();
|
||||||
|
loc2.setName("loc2");
|
||||||
|
IIdType loc2id = myClient.create().resource(loc2).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
loc1 = new Location();
|
||||||
|
loc1.setId(loc1id);
|
||||||
|
loc1.setName("loc1");
|
||||||
|
loc1.getPartOf().setReference(loc2id.getValue());
|
||||||
|
myClient.update().resource(loc1).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
loc2 = new Location();
|
||||||
|
loc2.setId(loc2id);
|
||||||
|
loc2.setName("loc2");
|
||||||
|
loc2.getPartOf().setReference(loc1id.getValue());
|
||||||
|
myClient.update().resource(loc2).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
IBaseBundle result = myClient
|
||||||
|
.search()
|
||||||
|
.forResource(Location.class)
|
||||||
|
.where(Location.NAME.matches().value("loc1"))
|
||||||
|
.include(Location.INCLUDE_PARTOF.asRecursive())
|
||||||
|
.execute();
|
||||||
|
assertThat(toUnqualifiedVersionlessIdValues(result), contains(loc1id.getValue(), loc2id.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testSelfReferentialRevInclude() {
|
||||||
|
Location loc1 = new Location();
|
||||||
|
loc1.setName("loc1");
|
||||||
|
IIdType loc1id = myClient.create().resource(loc1).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
Location loc2 = new Location();
|
||||||
|
loc2.setName("loc2");
|
||||||
|
IIdType loc2id = myClient.create().resource(loc2).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
loc1 = new Location();
|
||||||
|
loc1.setId(loc1id);
|
||||||
|
loc1.setName("loc1");
|
||||||
|
loc1.getPartOf().setReference(loc2id.getValue());
|
||||||
|
myClient.update().resource(loc1).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
loc2 = new Location();
|
||||||
|
loc2.setId(loc2id);
|
||||||
|
loc2.setName("loc2");
|
||||||
|
loc2.getPartOf().setReference(loc1id.getValue());
|
||||||
|
myClient.update().resource(loc2).execute().getId().toUnqualifiedVersionless();
|
||||||
|
|
||||||
|
IBaseBundle result = myClient
|
||||||
|
.search()
|
||||||
|
.forResource(Location.class)
|
||||||
|
.where(Location.NAME.matches().value("loc1"))
|
||||||
|
.revInclude(Location.INCLUDE_PARTOF.asRecursive())
|
||||||
|
.execute();
|
||||||
|
assertThat(toUnqualifiedVersionlessIdValues(result), contains(loc1id.getValue(), loc2id.getValue()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* See #484
|
* See #484
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,49 @@
|
||||||
|
package ca.uhn.fhir.rest.api.server;
|
||||||
|
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see ca.uhn.fhir.rest.server.interceptor.IServerInterceptor
|
||||||
|
*/
|
||||||
|
public class ResponseDetails {
|
||||||
|
|
||||||
|
private IBaseResource myResponseResource;
|
||||||
|
private int myResponseCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public ResponseDetails() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public ResponseDetails(IBaseResource theResponseResource) {
|
||||||
|
setResponseResource(theResponseResource);
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getResponseCode() {
|
||||||
|
return myResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setResponseCode(int theResponseCode) {
|
||||||
|
myResponseCode = theResponseCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the resource which will be returned to the client
|
||||||
|
*/
|
||||||
|
public IBaseResource getResponseResource() {
|
||||||
|
return myResponseResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the resource which will be returned to the client
|
||||||
|
*/
|
||||||
|
public void setResponseResource(IBaseResource theResponseResource) {
|
||||||
|
myResponseResource = theResponseResource;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -581,7 +581,7 @@ public class RestfulServerUtils {
|
||||||
return streamResponseAsResource(theServer, theResource, theSummaryMode, stausCode, null, theAddContentLocationHeader, respondGzip, theRequestDetails, null, null);
|
return streamResponseAsResource(theServer, theResource, theSummaryMode, stausCode, null, theAddContentLocationHeader, respondGzip, theRequestDetails, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int theStausCode, String theStatusMessage,
|
public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, Set<SummaryEnum> theSummaryMode, int theStatusCode, String theStatusMessage,
|
||||||
boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType<Date> theOperationResourceLastUpdated)
|
boolean theAddContentLocationHeader, boolean respondGzip, RequestDetails theRequestDetails, IIdType theOperationResourceId, IPrimitiveType<Date> theOperationResourceLastUpdated)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
IRestfulResponse response = theRequestDetails.getResponse();
|
IRestfulResponse response = theRequestDetails.getResponse();
|
||||||
|
@ -636,7 +636,7 @@ public class RestfulServerUtils {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return response.sendAttachmentResponse(bin, theStausCode, contentType);
|
return response.sendAttachmentResponse(bin, theStatusCode, contentType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok, we're not serving a binary resource, so apply default encoding
|
// Ok, we're not serving a binary resource, so apply default encoding
|
||||||
|
@ -683,7 +683,7 @@ public class RestfulServerUtils {
|
||||||
}
|
}
|
||||||
String charset = Constants.CHARSET_NAME_UTF8;
|
String charset = Constants.CHARSET_NAME_UTF8;
|
||||||
|
|
||||||
Writer writer = response.getResponseWriter(theStausCode, theStatusMessage, contentType, charset, respondGzip);
|
Writer writer = response.getResponseWriter(theStatusCode, theStatusMessage, contentType, charset, respondGzip);
|
||||||
if (theResource == null) {
|
if (theResource == null) {
|
||||||
// No response is being returned
|
// No response is being returned
|
||||||
} else if (encodingDomainResourceAsText && theResource instanceof IResource) {
|
} else if (encodingDomainResourceAsText && theResource instanceof IResource) {
|
||||||
|
@ -693,7 +693,7 @@ public class RestfulServerUtils {
|
||||||
parser.encodeResourceToWriter(theResource, writer);
|
parser.encodeResourceToWriter(theResource, writer);
|
||||||
}
|
}
|
||||||
//FIXME resource leak
|
//FIXME resource leak
|
||||||
return response.sendWriterResponse(theStausCode, contentType, charset, writer);
|
return response.sendWriterResponse(theStatusCode, contentType, charset, writer);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String theParamName) {
|
public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String theParamName) {
|
||||||
|
|
|
@ -20,26 +20,29 @@ package ca.uhn.fhir.rest.server.interceptor;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import javax.servlet.ServletException;
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
|
||||||
import javax.servlet.http.HttpServletResponse;
|
|
||||||
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.TagList;
|
import ca.uhn.fhir.model.api.TagList;
|
||||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
||||||
import ca.uhn.fhir.rest.annotation.*;
|
import ca.uhn.fhir.rest.annotation.Read;
|
||||||
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
|
||||||
|
@ -65,27 +68,21 @@ public interface IServerInterceptor {
|
||||||
* should return <code>false</code>, to indicate that they have handled the request and processing should stop.
|
* should return <code>false</code>, to indicate that they have handled the request and processing should stop.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
*
|
* @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the
|
||||||
* @param theRequestDetails
|
|
||||||
* A bean containing details about the request that is about to be processed, including details such as the
|
|
||||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||||
* pulled out of the {@link javax.servlet.http.HttpServletRequest servlet request}. Note that the bean
|
* pulled out of the {@link javax.servlet.http.HttpServletRequest servlet request}. Note that the bean
|
||||||
* properties are not all guaranteed to be populated, depending on how early during processing the
|
* properties are not all guaranteed to be populated, depending on how early during processing the
|
||||||
* exception occurred.
|
* exception occurred.
|
||||||
* @param theServletRequest
|
* @param theServletRequest The incoming request
|
||||||
* The incoming request
|
* @param theServletResponse The response. Note that interceptors may choose to provide a response (i.e. by calling
|
||||||
* @param theServletResponse
|
|
||||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling
|
|
||||||
* {@link javax.servlet.http.HttpServletResponse#getWriter()}) but in that case it is important to return
|
* {@link javax.servlet.http.HttpServletResponse#getWriter()}) but in that case it is important to return
|
||||||
* <code>false</code> to indicate that the server itself should not also provide a response.
|
* <code>false</code> to indicate that the server itself should not also provide a response.
|
||||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
|
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
|
||||||
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
|
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
|
||||||
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
|
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
|
||||||
* will be called.
|
* will be called.
|
||||||
* @throws ServletException
|
* @throws ServletException If this exception is thrown, it will be re-thrown up to the container for handling.
|
||||||
* If this exception is thrown, it will be re-thrown up to the container for handling.
|
* @throws IOException If this exception is thrown, it will be re-thrown up to the container for handling.
|
||||||
* @throws IOException
|
|
||||||
* If this exception is thrown, it will be re-thrown up to the container for handling.
|
|
||||||
*/
|
*/
|
||||||
boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
|
boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
|
||||||
throws ServletException, IOException;
|
throws ServletException, IOException;
|
||||||
|
@ -93,22 +90,18 @@ public interface IServerInterceptor {
|
||||||
/**
|
/**
|
||||||
* This method is called just before the actual implementing server method is invoked.
|
* This method is called just before the actual implementing server method is invoked.
|
||||||
*
|
*
|
||||||
* @param theRequestDetails
|
* @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the
|
||||||
* A bean containing details about the request that is about to be processed, including details such as the
|
|
||||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||||
* pulled out of the {@link HttpServletRequest servlet request}.
|
* pulled out of the {@link HttpServletRequest servlet request}.
|
||||||
* @param theRequest
|
* @param theRequest The incoming request
|
||||||
* The incoming request
|
* @param theResponse The response. Note that interceptors may choose to provide a response (i.e. by calling
|
||||||
* @param theResponse
|
|
||||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling
|
|
||||||
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
||||||
* to indicate that the server itself should not also provide a response.
|
* to indicate that the server itself should not also provide a response.
|
||||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
|
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
|
||||||
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
|
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
|
||||||
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
|
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
|
||||||
* will be called.
|
* will be called.
|
||||||
* @throws AuthenticationException
|
* @throws AuthenticationException This exception may be thrown to indicate that the interceptor has detected an unauthorized access
|
||||||
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
|
|
||||||
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
|
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
|
||||||
*/
|
*/
|
||||||
boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException;
|
boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException;
|
||||||
|
@ -125,10 +118,8 @@ public interface IServerInterceptor {
|
||||||
* will be aborted with an appropriate error returned to the client.
|
* will be aborted with an appropriate error returned to the client.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theOperation
|
* @param theOperation The type of operation that the FHIR server has determined that the client is trying to invoke
|
||||||
* The type of operation that the FHIR server has determined that the client is trying to invoke
|
* @param theProcessedRequest An object which will be populated with the details which were extracted from the raw request by the
|
||||||
* @param theProcessedRequest
|
|
||||||
* An object which will be populated with the details which were extracted from the raw request by the
|
|
||||||
* server, e.g. the FHIR operation type and the parsed resource body (if any).
|
* server, e.g. the FHIR operation type and the parsed resource body (if any).
|
||||||
*/
|
*/
|
||||||
void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest);
|
void incomingRequestPreHandled(RestOperationTypeEnum theOperation, ActionRequestDetails theProcessedRequest);
|
||||||
|
@ -140,10 +131,8 @@ public interface IServerInterceptor {
|
||||||
* Note that any exceptions thrown by this method will not be trapped by HAPI (they will be passed up to the server)
|
* Note that any exceptions thrown by this method will not be trapped by HAPI (they will be passed up to the server)
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theRequest
|
* @param theRequest The incoming request
|
||||||
* The incoming request
|
* @param theResponse The response. Note that interceptors may choose to provide a response (i.e. by calling
|
||||||
* @param theResponse
|
|
||||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling
|
|
||||||
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
||||||
* to indicate that the server itself should not also provide a response.
|
* to indicate that the server itself should not also provide a response.
|
||||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
|
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
|
||||||
|
@ -181,30 +170,52 @@ public interface IServerInterceptor {
|
||||||
* This method is called after the server implementation method has been called, but before any attempt to stream the
|
* This method is called after the server implementation method has been called, but before any attempt to stream the
|
||||||
* response back to the client.
|
* response back to the client.
|
||||||
*
|
*
|
||||||
* @param theRequestDetails
|
* @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the
|
||||||
* A bean containing details about the request that is about to be processed, including details such as the
|
|
||||||
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||||
* pulled out of the {@link HttpServletRequest servlet request}.
|
* pulled out of the {@link HttpServletRequest servlet request}.
|
||||||
* @param theResponseObject
|
* @param theResponseObject The actual object which is being streamed to the client as a response. This may be
|
||||||
* The actual object which is being streamed to the client as a response. This may be
|
|
||||||
* <code>null</code> if the response does not include a resource.
|
* <code>null</code> if the response does not include a resource.
|
||||||
* @param theServletRequest
|
* @param theServletRequest The incoming request
|
||||||
* The incoming request
|
* @param theServletResponse The response. Note that interceptors may choose to provide a response (i.e. by calling
|
||||||
* @param theServletResponse
|
|
||||||
* The response. Note that interceptors may choose to provide a response (i.e. by calling
|
|
||||||
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
||||||
* to indicate that the server itself should not also provide a response.
|
* to indicate that the server itself should not also provide a response.
|
||||||
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
|
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
|
||||||
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
|
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
|
||||||
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
|
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
|
||||||
* will be called.
|
* will be called.
|
||||||
* @throws AuthenticationException
|
* @throws AuthenticationException This exception may be thrown to indicate that the interceptor has detected an unauthorized access
|
||||||
* This exception may be thrown to indicate that the interceptor has detected an unauthorized access
|
|
||||||
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
|
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
|
||||||
|
* @deprecated As of HAPI FHIR 3.3.0, this method has been deprecated in
|
||||||
|
* favour of {@link #outgoingResponse(RequestDetails, ResponseDetails, HttpServletRequest, HttpServletResponse)}
|
||||||
|
* and will be removed in a future version of HAPI FHIR.
|
||||||
*/
|
*/
|
||||||
boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
|
boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
|
||||||
throws AuthenticationException;
|
throws AuthenticationException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is called after the server implementation method has been called, but before any attempt to stream the
|
||||||
|
* response back to the client.
|
||||||
|
*
|
||||||
|
* @param theRequestDetails A bean containing details about the request that is about to be processed, including details such as the
|
||||||
|
* resource type and logical ID (if any) and other FHIR-specific aspects of the request which have been
|
||||||
|
* pulled out of the {@link HttpServletRequest servlet request}.
|
||||||
|
* @param theResponseDetails This object contains details about the response, including
|
||||||
|
* the actual payload that will be returned
|
||||||
|
* @param theServletRequest The incoming request
|
||||||
|
* @param theServletResponse The response. Note that interceptors may choose to provide a response (i.e. by calling
|
||||||
|
* {@link HttpServletResponse#getWriter()}) but in that case it is important to return <code>false</code>
|
||||||
|
* to indicate that the server itself should not also provide a response.
|
||||||
|
* @return Return <code>true</code> if processing should continue normally. This is generally the right thing to do.
|
||||||
|
* If your interceptor is providing a response rather than letting HAPI handle the response normally, you
|
||||||
|
* must return <code>false</code>. In this case, no further processing will occur and no further interceptors
|
||||||
|
* will be called.
|
||||||
|
* @throws AuthenticationException This exception may be thrown to indicate that the interceptor has detected an unauthorized access
|
||||||
|
* attempt. If thrown, processing will stop and an HTTP 401 will be returned to the client.
|
||||||
|
*/
|
||||||
|
boolean outgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
|
||||||
|
throws AuthenticationException;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Use {@link #outgoingResponse(RequestDetails, IBaseResource, HttpServletRequest, HttpServletResponse)} instead
|
* Use {@link #outgoingResponse(RequestDetails, IBaseResource, HttpServletRequest, HttpServletResponse)} instead
|
||||||
*
|
*
|
||||||
|
@ -253,8 +264,7 @@ public interface IServerInterceptor {
|
||||||
* which the interceptors were registered with the server.
|
* which the interceptors were registered with the server.
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @param theRequestDetails
|
* @param theRequestDetails The request itself
|
||||||
* The request itself
|
|
||||||
*/
|
*/
|
||||||
void processingCompletedNormally(ServletRequestDetails theRequestDetails);
|
void processingCompletedNormally(ServletRequestDetails theRequestDetails);
|
||||||
|
|
||||||
|
@ -301,10 +311,8 @@ public interface IServerInterceptor {
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*
|
*
|
||||||
* @param theRequestDetails
|
* @param theRequestDetails The request details to wrap
|
||||||
* The request details to wrap
|
* @param theId The ID of the resource being created (note that the ID should have the resource type populated)
|
||||||
* @param theId
|
|
||||||
* The ID of the resource being created (note that the ID should have the resource type populated)
|
|
||||||
*/
|
*/
|
||||||
public ActionRequestDetails(RequestDetails theRequestDetails, IIdType theId) {
|
public ActionRequestDetails(RequestDetails theRequestDetails, IIdType theId) {
|
||||||
this(theRequestDetails, theId.getResourceType(), theId);
|
this(theRequestDetails, theId.getResourceType(), theId);
|
||||||
|
|
|
@ -26,6 +26,7 @@ import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.TagList;
|
import ca.uhn.fhir.model.api.TagList;
|
||||||
|
@ -85,6 +86,11 @@ public class InterceptorAdapter implements IServerInterceptor {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean outgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject) {
|
public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject) {
|
||||||
ServletRequestDetails details = (ServletRequestDetails) theRequestDetails;
|
ServletRequestDetails details = (ServletRequestDetails) theRequestDetails;
|
||||||
|
|
|
@ -5,6 +5,7 @@ import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
|
import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
|
||||||
|
@ -301,7 +302,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
|
public boolean outgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse)
|
||||||
throws AuthenticationException {
|
throws AuthenticationException {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
@ -365,7 +366,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter {
|
||||||
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
|
return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
streamResponse(theRequestDetails, theServletResponse, theResponseObject, theServletRequest, 200);
|
streamResponse(theRequestDetails, theServletResponse, theResponseObject.getResponseResource(), theServletRequest, 200);
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,38 +20,30 @@ package ca.uhn.fhir.rest.server.method;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import java.io.IOException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import java.io.Writer;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import java.lang.reflect.Method;
|
import ca.uhn.fhir.rest.api.*;
|
||||||
import java.util.Collections;
|
import ca.uhn.fhir.rest.api.server.IRestfulResponse;
|
||||||
import java.util.EnumSet;
|
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
||||||
import java.util.Set;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
import org.hl7.fhir.instance.model.api.IIdType;
|
import org.hl7.fhir.instance.model.api.IIdType;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import ca.uhn.fhir.parser.IParser;
|
import java.io.IOException;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import java.lang.reflect.Method;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import java.util.Collections;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import java.util.EnumSet;
|
||||||
import ca.uhn.fhir.rest.api.PreferReturnEnum;
|
import java.util.Set;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.SummaryEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.server.IRestfulResponse;
|
|
||||||
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
|
||||||
|
|
||||||
abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<MethodOutcome> {
|
abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<MethodOutcome> {
|
||||||
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
|
static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
|
||||||
|
@ -181,6 +173,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
||||||
|
|
||||||
return returnResponse(theServer, theRequest, response, outcome, resource);
|
return returnResponse(theServer, theRequest, response, outcome, resource);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean isReturnVoid() {
|
public boolean isReturnVoid() {
|
||||||
return myReturnVoid;
|
return myReturnVoid;
|
||||||
}
|
}
|
||||||
|
@ -206,12 +199,28 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ResponseDetails responseDetails = new ResponseDetails();
|
||||||
|
responseDetails.setResponseResource(outcome);
|
||||||
|
responseDetails.setResponseCode(operationStatus);
|
||||||
|
|
||||||
|
HttpServletRequest servletRequest = null;
|
||||||
|
HttpServletResponse servletResponse = null;
|
||||||
|
if (theRequest instanceof ServletRequestDetails) {
|
||||||
|
servletRequest = ((ServletRequestDetails) theRequest).getServletRequest();
|
||||||
|
servletResponse = ((ServletRequestDetails) theRequest).getServletResponse();
|
||||||
|
}
|
||||||
|
|
||||||
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
|
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
|
||||||
IServerInterceptor next = theServer.getInterceptors().get(i);
|
IServerInterceptor next = theServer.getInterceptors().get(i);
|
||||||
boolean continueProcessing = next.outgoingResponse(theRequest, outcome);
|
boolean continueProcessing = next.outgoingResponse(theRequest, outcome);
|
||||||
if (!continueProcessing) {
|
if (!continueProcessing) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
continueProcessing = next.outgoingResponse(theRequest, responseDetails, servletRequest, servletResponse);
|
||||||
|
if (!continueProcessing) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
IRestfulResponse restfulResponse = theRequest.getResponse();
|
IRestfulResponse restfulResponse = theRequest.getResponse();
|
||||||
|
@ -236,35 +245,9 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding<Metho
|
||||||
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
|
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
|
||||||
Set<SummaryEnum> summaryMode = Collections.emptySet();
|
Set<SummaryEnum> summaryMode = Collections.emptySet();
|
||||||
|
|
||||||
return restfulResponse.streamResponseAsResource(outcome, prettyPrint, summaryMode, operationStatus, null, theRequest.isRespondGzip(), true);
|
return restfulResponse.streamResponseAsResource(responseDetails.getResponseResource(), prettyPrint, summaryMode, responseDetails.getResponseCode(), null, theRequest.isRespondGzip(), true);
|
||||||
// return theRequest.getResponse().returnResponse(ParseAction.create(outcome), operationStatus, allowPrefer, response, getResourceName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingEnum theEncodingNotNull, HttpServletResponse theResponse, RequestDetails theRequest) throws IOException {
|
|
||||||
theResponse.setStatus(theE.getStatusCode());
|
|
||||||
|
|
||||||
theServer.addHeadersToResponse(theResponse);
|
|
||||||
|
|
||||||
if (theE.getOperationOutcome() != null) {
|
|
||||||
theResponse.setContentType(theEncodingNotNull.getResourceContentType());
|
|
||||||
IParser parser = theEncodingNotNull.newParser(theServer.getFhirContext());
|
|
||||||
parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theServer, theRequest));
|
|
||||||
Writer writer = theResponse.getWriter();
|
|
||||||
try {
|
|
||||||
parser.encodeResourceToWriter(theE.getOperationOutcome(), writer);
|
|
||||||
} finally {
|
|
||||||
writer.close();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
theResponse.setContentType(Constants.CT_TEXT);
|
|
||||||
Writer writer = theResponse.getWriter();
|
|
||||||
try {
|
|
||||||
writer.append(theE.getMessage());
|
|
||||||
} finally {
|
|
||||||
writer.close();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected static void parseContentLocation(FhirContext theContext, MethodOutcome theOutcomeToPopulate, String theLocationHeader) {
|
protected static void parseContentLocation(FhirContext theContext, MethodOutcome theOutcomeToPopulate, String theLocationHeader) {
|
||||||
if (StringUtils.isBlank(theLocationHeader)) {
|
if (StringUtils.isBlank(theLocationHeader)) {
|
||||||
|
|
|
@ -1,6 +1,41 @@
|
||||||
package ca.uhn.fhir.rest.server.method;
|
package ca.uhn.fhir.rest.server.method;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
|
import ca.uhn.fhir.model.api.Include;
|
||||||
|
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
||||||
|
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
||||||
|
import ca.uhn.fhir.rest.api.*;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import ca.uhn.fhir.rest.api.server.IRestfulServer;
|
||||||
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
|
import ca.uhn.fhir.rest.server.IPagingProvider;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
||||||
|
import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||||
|
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
||||||
|
import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
|
||||||
|
import ca.uhn.fhir.util.ReflectionUtil;
|
||||||
|
import ca.uhn.fhir.util.UrlUtil;
|
||||||
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.lang.reflect.Modifier;
|
||||||
|
import java.util.*;
|
||||||
|
|
||||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
|
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR - Server Framework
|
* HAPI FHIR - Server Framework
|
||||||
|
@ -20,33 +55,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.lang.reflect.Method;
|
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.*;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.util.CollectionUtil;
|
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
|
||||||
import org.hl7.fhir.instance.model.api.IPrimitiveType;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
|
||||||
import ca.uhn.fhir.model.api.Include;
|
|
||||||
import ca.uhn.fhir.model.base.resource.BaseOperationOutcome;
|
|
||||||
import ca.uhn.fhir.model.valueset.BundleTypeEnum;
|
|
||||||
import ca.uhn.fhir.rest.api.*;
|
|
||||||
import ca.uhn.fhir.rest.api.server.*;
|
|
||||||
import ca.uhn.fhir.rest.server.IPagingProvider;
|
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils;
|
|
||||||
import ca.uhn.fhir.rest.server.RestfulServerUtils.ResponseEncoding;
|
|
||||||
import ca.uhn.fhir.rest.server.exceptions.*;
|
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
|
||||||
import ca.uhn.fhir.rest.server.interceptor.ResponseHighlighterInterceptor;
|
|
||||||
import ca.uhn.fhir.util.ReflectionUtil;
|
|
||||||
import ca.uhn.fhir.util.UrlUtil;
|
|
||||||
|
|
||||||
public abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Object> {
|
public abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding<Object> {
|
||||||
protected static final Set<String> ALLOWED_PARAMS;
|
protected static final Set<String> ALLOWED_PARAMS;
|
||||||
|
@ -115,40 +123,111 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public MethodReturnTypeEnum getMethodReturnType() {
|
protected IBaseResource createBundleFromBundleProvider(IRestfulServer<?> theServer, RequestDetails theRequest, Integer theLimit, String theLinkSelf, Set<Include> theIncludes,
|
||||||
return myMethodReturnType;
|
IBundleProvider theResult, int theOffset, BundleTypeEnum theBundleType, EncodingEnum theLinkEncoding, String theSearchId) {
|
||||||
|
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
|
||||||
|
|
||||||
|
int numToReturn;
|
||||||
|
String searchId = null;
|
||||||
|
List<IBaseResource> resourceList;
|
||||||
|
Integer numTotalResults = theResult.size();
|
||||||
|
if (theServer.getPagingProvider() == null) {
|
||||||
|
numToReturn = numTotalResults;
|
||||||
|
if (numToReturn > 0) {
|
||||||
|
resourceList = theResult.getResources(0, numToReturn);
|
||||||
|
} else {
|
||||||
|
resourceList = Collections.emptyList();
|
||||||
|
}
|
||||||
|
RestfulServerUtils.validateResourceListNotNull(resourceList);
|
||||||
|
|
||||||
|
} else {
|
||||||
|
IPagingProvider pagingProvider = theServer.getPagingProvider();
|
||||||
|
if (theLimit == null || theLimit.equals(Integer.valueOf(0))) {
|
||||||
|
numToReturn = pagingProvider.getDefaultPageSize();
|
||||||
|
} else {
|
||||||
|
numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
if (numTotalResults != null) {
|
||||||
public String getResourceName() {
|
numToReturn = Math.min(numToReturn, numTotalResults - theOffset);
|
||||||
return myResourceName;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
if (numToReturn > 0) {
|
||||||
* If the response is a bundle, this type will be placed in the root of the bundle (can be null)
|
resourceList = theResult.getResources(theOffset, numToReturn + theOffset);
|
||||||
|
} else {
|
||||||
|
resourceList = Collections.emptyList();
|
||||||
|
}
|
||||||
|
RestfulServerUtils.validateResourceListNotNull(resourceList);
|
||||||
|
|
||||||
|
if (theSearchId != null) {
|
||||||
|
searchId = theSearchId;
|
||||||
|
} else {
|
||||||
|
if (numTotalResults == null || numTotalResults > numToReturn) {
|
||||||
|
searchId = pagingProvider.storeResultList(theResult);
|
||||||
|
if (isBlank(searchId)) {
|
||||||
|
ourLog.info("Found {} results but paging provider did not provide an ID to use for paging", numTotalResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Remove any null entries in the list - This generally shouldn't happen but can if
|
||||||
|
* data has been manually purged from the JPA database
|
||||||
*/
|
*/
|
||||||
protected abstract BundleTypeEnum getResponseBundleType();
|
boolean hasNull = false;
|
||||||
|
for (IBaseResource next : resourceList) {
|
||||||
public abstract ReturnTypeEnum getReturnType();
|
if (next == null) {
|
||||||
|
hasNull = true;
|
||||||
@Override
|
break;
|
||||||
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
|
}
|
||||||
|
}
|
||||||
IBaseResource response = doInvokeServer(theServer, theRequest);
|
if (hasNull) {
|
||||||
|
for (Iterator<IBaseResource> iter = resourceList.iterator(); iter.hasNext(); ) {
|
||||||
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequest);
|
if (iter.next() == null) {
|
||||||
|
iter.remove();
|
||||||
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
|
}
|
||||||
IServerInterceptor next = theServer.getInterceptors().get(i);
|
|
||||||
boolean continueProcessing = next.outgoingResponse(theRequest, response);
|
|
||||||
if (!continueProcessing) {
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Make sure all returned resources have an ID (if not, this is a bug
|
||||||
|
* in the user server code)
|
||||||
|
*/
|
||||||
|
for (IBaseResource next : resourceList) {
|
||||||
|
if (next.getIdElement() == null || next.getIdElement().isEmpty()) {
|
||||||
|
if (!(next instanceof BaseOperationOutcome)) {
|
||||||
|
throw new InternalErrorException("Server method returned resource of type[" + next.getClass().getSimpleName() + "] with no ID specified (IResource#setId(IdDt) must be called)");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String serverBase = theRequest.getFhirServerBase();
|
||||||
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
|
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
|
||||||
|
|
||||||
return theRequest.getResponse().streamResponseAsResource(response, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, null, theRequest.isRespondGzip(), isAddContentLocationHeader());
|
String linkPrev = null;
|
||||||
|
String linkNext = null;
|
||||||
|
if (searchId != null) {
|
||||||
|
if (numTotalResults == null || theOffset + numToReturn < numTotalResults) {
|
||||||
|
linkNext = (RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, theOffset + numToReturn, numToReturn, theRequest.getParameters(), prettyPrint, theBundleType));
|
||||||
|
}
|
||||||
|
if (theOffset > 0) {
|
||||||
|
int start = Math.max(0, theOffset - theLimit);
|
||||||
|
linkPrev = RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, start, theLimit, theRequest.getParameters(), prettyPrint, theBundleType);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bundleFactory.addRootPropertiesToBundle(theResult.getUuid(), serverBase, theLinkSelf, linkPrev, linkNext, theResult.size(), theBundleType, theResult.getPublished());
|
||||||
|
bundleFactory.addResourcesToBundle(new ArrayList<>(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes);
|
||||||
|
|
||||||
|
if (theServer.getPagingProvider() != null) {
|
||||||
|
int limit;
|
||||||
|
limit = theLimit != null ? theLimit : theServer.getPagingProvider().getDefaultPageSize();
|
||||||
|
limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize());
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return bundleFactory.getResourceBundle();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -261,111 +340,60 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
||||||
return responseObject;
|
return responseObject;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected IBaseResource createBundleFromBundleProvider(IRestfulServer<?> theServer, RequestDetails theRequest, Integer theLimit, String theLinkSelf, Set<Include> theIncludes,
|
public MethodReturnTypeEnum getMethodReturnType() {
|
||||||
IBundleProvider theResult, int theOffset, BundleTypeEnum theBundleType, EncodingEnum theLinkEncoding, String theSearchId) {
|
return myMethodReturnType;
|
||||||
IVersionSpecificBundleFactory bundleFactory = theServer.getFhirContext().newBundleFactory();
|
|
||||||
|
|
||||||
int numToReturn;
|
|
||||||
String searchId = null;
|
|
||||||
List<IBaseResource> resourceList;
|
|
||||||
Integer numTotalResults = theResult.size();
|
|
||||||
if (theServer.getPagingProvider() == null) {
|
|
||||||
numToReturn = numTotalResults;
|
|
||||||
if (numToReturn > 0) {
|
|
||||||
resourceList = theResult.getResources(0, numToReturn);
|
|
||||||
} else {
|
|
||||||
resourceList = Collections.emptyList();
|
|
||||||
}
|
|
||||||
RestfulServerUtils.validateResourceListNotNull(resourceList);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
IPagingProvider pagingProvider = theServer.getPagingProvider();
|
|
||||||
if (theLimit == null || theLimit.equals(Integer.valueOf(0))) {
|
|
||||||
numToReturn = pagingProvider.getDefaultPageSize();
|
|
||||||
} else {
|
|
||||||
numToReturn = Math.min(pagingProvider.getMaximumPageSize(), theLimit);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numTotalResults != null) {
|
@Override
|
||||||
numToReturn = Math.min(numToReturn, numTotalResults - theOffset);
|
public String getResourceName() {
|
||||||
|
return myResourceName;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (numToReturn > 0) {
|
protected void setResourceName(String theResourceName) {
|
||||||
resourceList = theResult.getResources(theOffset, numToReturn + theOffset);
|
myResourceName = theResourceName;
|
||||||
} else {
|
|
||||||
resourceList = Collections.emptyList();
|
|
||||||
}
|
|
||||||
RestfulServerUtils.validateResourceListNotNull(resourceList);
|
|
||||||
|
|
||||||
if (theSearchId != null) {
|
|
||||||
searchId = theSearchId;
|
|
||||||
} else {
|
|
||||||
if (numTotalResults == null || numTotalResults > numToReturn) {
|
|
||||||
searchId = pagingProvider.storeResultList(theResult);
|
|
||||||
if (isBlank(searchId)) {
|
|
||||||
ourLog.info("Found {} results but paging provider did not provide an ID to use for paging", numTotalResults);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/**
|
||||||
* Remove any null entries in the list - This generally shouldn't happen but can if
|
* If the response is a bundle, this type will be placed in the root of the bundle (can be null)
|
||||||
* data has been manually purged from the JPA database
|
|
||||||
*/
|
*/
|
||||||
boolean hasNull = false;
|
protected abstract BundleTypeEnum getResponseBundleType();
|
||||||
for (IBaseResource next : resourceList) {
|
|
||||||
if (next == null) {
|
public abstract ReturnTypeEnum getReturnType();
|
||||||
hasNull = true;
|
|
||||||
break;
|
@Override
|
||||||
|
public Object invokeServer(IRestfulServer<?> theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException {
|
||||||
|
|
||||||
|
IBaseResource response = doInvokeServer(theServer, theRequest);
|
||||||
|
|
||||||
|
Set<SummaryEnum> summaryMode = RestfulServerUtils.determineSummaryMode(theRequest);
|
||||||
|
|
||||||
|
ResponseDetails responseDetails = new ResponseDetails();
|
||||||
|
responseDetails.setResponseResource(response);
|
||||||
|
responseDetails.setResponseCode(Constants.STATUS_HTTP_200_OK);
|
||||||
|
|
||||||
|
HttpServletRequest servletRequest = null;
|
||||||
|
HttpServletResponse servletResponse = null;
|
||||||
|
if (theRequest instanceof ServletRequestDetails) {
|
||||||
|
servletRequest = ((ServletRequestDetails) theRequest).getServletRequest();
|
||||||
|
servletResponse = ((ServletRequestDetails) theRequest).getServletResponse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) {
|
||||||
|
IServerInterceptor next = theServer.getInterceptors().get(i);
|
||||||
|
boolean continueProcessing = next.outgoingResponse(theRequest, response);
|
||||||
|
if (!continueProcessing) {
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
if (hasNull) {
|
|
||||||
for (Iterator<IBaseResource> iter = resourceList.iterator(); iter.hasNext(); ) {
|
continueProcessing = next.outgoingResponse(theRequest, responseDetails, servletRequest, servletResponse);
|
||||||
if (iter.next() == null) {
|
if (!continueProcessing) {
|
||||||
iter.remove();
|
return null;
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Make sure all returned resources have an ID (if not, this is a bug
|
|
||||||
* in the user server code)
|
|
||||||
*/
|
|
||||||
for (IBaseResource next : resourceList) {
|
|
||||||
if (next.getIdElement() == null || next.getIdElement().isEmpty()) {
|
|
||||||
if (!(next instanceof BaseOperationOutcome)) {
|
|
||||||
throw new InternalErrorException("Server method returned resource of type[" + next.getClass().getSimpleName() + "] with no ID specified (IResource#setId(IdDt) must be called)");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String serverBase = theRequest.getFhirServerBase();
|
|
||||||
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
|
boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest);
|
||||||
|
|
||||||
String linkPrev = null;
|
return theRequest.getResponse().streamResponseAsResource(responseDetails.getResponseResource(), prettyPrint, summaryMode, responseDetails.getResponseCode(), null, theRequest.isRespondGzip(), isAddContentLocationHeader());
|
||||||
String linkNext = null;
|
|
||||||
if (searchId != null) {
|
|
||||||
if (numTotalResults == null || theOffset + numToReturn < numTotalResults) {
|
|
||||||
linkNext = (RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, theOffset + numToReturn, numToReturn, theRequest.getParameters(), prettyPrint, theBundleType));
|
|
||||||
}
|
|
||||||
if (theOffset > 0) {
|
|
||||||
int start = Math.max(0, theOffset - theLimit);
|
|
||||||
linkPrev = RestfulServerUtils.createPagingLink(theIncludes, serverBase, searchId, start, theLimit, theRequest.getParameters(), prettyPrint, theBundleType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bundleFactory.addRootPropertiesToBundle(theResult.getUuid(), serverBase, theLinkSelf, linkPrev, linkNext, theResult.size(), theBundleType, theResult.getPublished());
|
|
||||||
bundleFactory.addResourcesToBundle(new ArrayList<>(resourceList), theBundleType, serverBase, theServer.getBundleInclusionRule(), theIncludes);
|
|
||||||
|
|
||||||
if (theServer.getPagingProvider() != null) {
|
|
||||||
int limit;
|
|
||||||
limit = theLimit != null ? theLimit : theServer.getPagingProvider().getDefaultPageSize();
|
|
||||||
limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize());
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
return bundleFactory.getResourceBundle();
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -378,10 +406,6 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void setResourceName(String theResourceName) {
|
|
||||||
myResourceName = theResourceName;
|
|
||||||
}
|
|
||||||
|
|
||||||
public enum MethodReturnTypeEnum {
|
public enum MethodReturnTypeEnum {
|
||||||
BUNDLE,
|
BUNDLE,
|
||||||
BUNDLE_PROVIDER,
|
BUNDLE_PROVIDER,
|
||||||
|
|
|
@ -15,6 +15,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
@ -66,9 +67,11 @@ public class InterceptorDstu2_1Test {
|
||||||
when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
||||||
|
when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor2.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor2.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor2.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor2.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
||||||
|
when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
|
||||||
//@formatter:off
|
//@formatter:off
|
||||||
String input =
|
String input =
|
||||||
|
@ -107,7 +110,9 @@ public class InterceptorDstu2_1Test {
|
||||||
order.verify(myInterceptor1, times(1)).incomingRequestPreHandled(opTypeCapt.capture(), arTypeCapt.capture());
|
order.verify(myInterceptor1, times(1)).incomingRequestPreHandled(opTypeCapt.capture(), arTypeCapt.capture());
|
||||||
order.verify(myInterceptor2, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class));
|
order.verify(myInterceptor2, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class));
|
||||||
order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class));
|
order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class));
|
||||||
|
order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class));
|
order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class));
|
||||||
|
order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
|
||||||
// Avoid concurrency issues
|
// Avoid concurrency issues
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
|
|
|
@ -23,6 +23,7 @@ import java.util.concurrent.TimeUnit;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
import org.apache.http.client.methods.CloseableHttpResponse;
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
@ -153,7 +154,7 @@ public class ResponseHighlightingInterceptorTest {
|
||||||
reqDetails.setServletRequest(req);
|
reqDetails.setServletRequest(req);
|
||||||
|
|
||||||
// true means it decided to not handle the request..
|
// true means it decided to not handle the request..
|
||||||
assertTrue(ic.outgoingResponse(reqDetails, resource, req, resp));
|
assertTrue(ic.outgoingResponse(reqDetails, new ResponseDetails(resource), req, resp));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -408,7 +409,7 @@ public class ResponseHighlightingInterceptorTest {
|
||||||
reqDetails.setServletRequest(req);
|
reqDetails.setServletRequest(req);
|
||||||
|
|
||||||
// false means it decided to handle the request..
|
// false means it decided to handle the request..
|
||||||
assertFalse(ic.outgoingResponse(reqDetails, resource, req, resp));
|
assertFalse(ic.outgoingResponse(reqDetails, new ResponseDetails(resource), req, resp));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -442,7 +443,7 @@ public class ResponseHighlightingInterceptorTest {
|
||||||
reqDetails.setServletRequest(req);
|
reqDetails.setServletRequest(req);
|
||||||
|
|
||||||
// false means it decided to handle the request..
|
// false means it decided to handle the request..
|
||||||
assertFalse(ic.outgoingResponse(reqDetails, resource, req, resp));
|
assertFalse(ic.outgoingResponse(reqDetails, new ResponseDetails(resource), req, resp));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -475,7 +476,7 @@ public class ResponseHighlightingInterceptorTest {
|
||||||
reqDetails.setServletRequest(req);
|
reqDetails.setServletRequest(req);
|
||||||
|
|
||||||
// true means it decided to not handle the request..
|
// true means it decided to not handle the request..
|
||||||
assertTrue(ic.outgoingResponse(reqDetails, resource, req, resp));
|
assertTrue(ic.outgoingResponse(reqDetails, new ResponseDetails(resource), req, resp));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -504,7 +505,7 @@ public class ResponseHighlightingInterceptorTest {
|
||||||
reqDetails.setServer(new RestfulServer(ourCtx));
|
reqDetails.setServer(new RestfulServer(ourCtx));
|
||||||
reqDetails.setServletRequest(req);
|
reqDetails.setServletRequest(req);
|
||||||
|
|
||||||
assertFalse(ic.outgoingResponse(reqDetails, resource, req, resp));
|
assertFalse(ic.outgoingResponse(reqDetails, new ResponseDetails(resource), req, resp));
|
||||||
|
|
||||||
String output = sw.getBuffer().toString();
|
String output = sw.getBuffer().toString();
|
||||||
ourLog.info(output);
|
ourLog.info(output);
|
||||||
|
@ -540,7 +541,7 @@ public class ResponseHighlightingInterceptorTest {
|
||||||
reqDetails.setServer(new RestfulServer(ourCtx));
|
reqDetails.setServer(new RestfulServer(ourCtx));
|
||||||
reqDetails.setServletRequest(req);
|
reqDetails.setServletRequest(req);
|
||||||
|
|
||||||
assertFalse(ic.outgoingResponse(reqDetails, resource, req, resp));
|
assertFalse(ic.outgoingResponse(reqDetails, new ResponseDetails(resource), req, resp));
|
||||||
|
|
||||||
String output = sw.getBuffer().toString();
|
String output = sw.getBuffer().toString();
|
||||||
ourLog.info(output);
|
ourLog.info(output);
|
||||||
|
@ -579,7 +580,7 @@ public class ResponseHighlightingInterceptorTest {
|
||||||
reqDetails.setServer(server);
|
reqDetails.setServer(server);
|
||||||
reqDetails.setServletRequest(req);
|
reqDetails.setServletRequest(req);
|
||||||
|
|
||||||
assertFalse(ic.outgoingResponse(reqDetails, resource, req, resp));
|
assertFalse(ic.outgoingResponse(reqDetails, new ResponseDetails(resource), req, resp));
|
||||||
|
|
||||||
String output = sw.getBuffer().toString();
|
String output = sw.getBuffer().toString();
|
||||||
ourLog.info(output);
|
ourLog.info(output);
|
||||||
|
@ -615,7 +616,7 @@ public class ResponseHighlightingInterceptorTest {
|
||||||
reqDetails.setServletRequest(req);
|
reqDetails.setServletRequest(req);
|
||||||
|
|
||||||
// True here means the interceptor didn't handle the request, because HTML wasn't the top ranked accept header
|
// True here means the interceptor didn't handle the request, because HTML wasn't the top ranked accept header
|
||||||
assertTrue(ic.outgoingResponse(reqDetails, resource, req, resp));
|
assertTrue(ic.outgoingResponse(reqDetails, new ResponseDetails(resource), req, resp));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -9,6 +9,7 @@ import ca.uhn.fhir.rest.annotation.*;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||||
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
import ca.uhn.fhir.rest.client.api.ServerValidationModeEnum;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
|
@ -213,6 +214,7 @@ public class ServerActionInterceptorTest {
|
||||||
when(ourInterceptor.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(ourInterceptor.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(ourInterceptor.outgoingResponse(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(ourInterceptor.outgoingResponse(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(ourInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(ourInterceptor.outgoingResponse(any(RequestDetails.class), any(IBaseResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
when(ourInterceptor.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class PlainProvider {
|
public static class PlainProvider {
|
||||||
|
|
|
@ -2,13 +2,12 @@ package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
import ca.uhn.fhir.rest.annotation.*;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
|
||||||
import ca.uhn.fhir.rest.annotation.Validate;
|
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||||
|
import ca.uhn.fhir.rest.api.server.ResponseDetails;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor.ActionRequestDetails;
|
||||||
|
@ -19,6 +18,8 @@ import ca.uhn.fhir.util.PortUtil;
|
||||||
import ca.uhn.fhir.util.TestUtil;
|
import ca.uhn.fhir.util.TestUtil;
|
||||||
import org.apache.commons.io.IOUtils;
|
import org.apache.commons.io.IOUtils;
|
||||||
import org.apache.http.HttpResponse;
|
import org.apache.http.HttpResponse;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpGet;
|
||||||
import org.apache.http.client.methods.HttpPost;
|
import org.apache.http.client.methods.HttpPost;
|
||||||
import org.apache.http.entity.ContentType;
|
import org.apache.http.entity.ContentType;
|
||||||
import org.apache.http.entity.StringEntity;
|
import org.apache.http.entity.StringEntity;
|
||||||
|
@ -28,7 +29,7 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
||||||
import org.eclipse.jetty.server.Server;
|
import org.eclipse.jetty.server.Server;
|
||||||
import org.eclipse.jetty.servlet.ServletHandler;
|
import org.eclipse.jetty.servlet.ServletHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.hamcrest.core.StringContains;
|
import org.hl7.fhir.dstu3.model.IdType;
|
||||||
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
import org.hl7.fhir.dstu3.model.OperationOutcome;
|
||||||
import org.hl7.fhir.dstu3.model.Patient;
|
import org.hl7.fhir.dstu3.model.Patient;
|
||||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||||
|
@ -38,9 +39,11 @@ import org.mockito.InOrder;
|
||||||
|
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.hamcrest.CoreMatchers.containsString;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
@ -81,6 +84,35 @@ public class InterceptorDstu3Test {
|
||||||
"}";
|
"}";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testModifyResponse() throws IOException {
|
||||||
|
InterceptorAdapter interceptor = new InterceptorAdapter() {
|
||||||
|
@Override
|
||||||
|
public boolean outgoingResponse(RequestDetails theRequestDetails, ResponseDetails theResponseDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException {
|
||||||
|
Patient retVal = new Patient();
|
||||||
|
retVal.setId(theResponseDetails.getResponseResource().getIdElement());
|
||||||
|
retVal.addName().setFamily("NAME1");
|
||||||
|
theResponseDetails.setResponseResource(retVal);
|
||||||
|
theResponseDetails.setResponseCode(202);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
ourServlet.registerInterceptor(interceptor);
|
||||||
|
try {
|
||||||
|
|
||||||
|
HttpGet get = new HttpGet("http://localhost:" + ourPort + "/Patient/1");
|
||||||
|
try (CloseableHttpResponse status = ourClient.execute(get)) {
|
||||||
|
String response = IOUtils.toString(status.getEntity().getContent(), Constants.CHARSET_UTF8);
|
||||||
|
assertThat(response, containsString("NAME1"));
|
||||||
|
assertEquals(202, status.getStatusLine().getStatusCode());
|
||||||
|
assertEquals("Accepted", status.getStatusLine().getReasonPhrase());
|
||||||
|
}
|
||||||
|
|
||||||
|
} finally {
|
||||||
|
ourServlet.unregisterInterceptor(interceptor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResourceResponseIncluded() throws Exception {
|
public void testResourceResponseIncluded() throws Exception {
|
||||||
ourServlet.setInterceptors(myInterceptor1, myInterceptor2);
|
ourServlet.setInterceptors(myInterceptor1, myInterceptor2);
|
||||||
|
@ -88,9 +120,11 @@ public class InterceptorDstu3Test {
|
||||||
when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
||||||
|
when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor2.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor2.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor2.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor2.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
||||||
|
when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
|
||||||
String input = createInput();
|
String input = createInput();
|
||||||
|
|
||||||
|
@ -109,7 +143,9 @@ public class InterceptorDstu3Test {
|
||||||
order.verify(myInterceptor1, times(1)).incomingRequestPreHandled(opTypeCapt.capture(), arTypeCapt.capture());
|
order.verify(myInterceptor1, times(1)).incomingRequestPreHandled(opTypeCapt.capture(), arTypeCapt.capture());
|
||||||
order.verify(myInterceptor2, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class));
|
order.verify(myInterceptor2, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class));
|
||||||
order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IBaseResource.class));
|
order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IBaseResource.class));
|
||||||
|
order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IBaseResource.class));
|
order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IBaseResource.class));
|
||||||
|
order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class));
|
||||||
|
|
||||||
// Avoid concurrency issues
|
// Avoid concurrency issues
|
||||||
Thread.sleep(500);
|
Thread.sleep(500);
|
||||||
|
@ -123,21 +159,6 @@ public class InterceptorDstu3Test {
|
||||||
assertNotNull(arTypeCapt.getValue().getResource());
|
assertNotNull(arTypeCapt.getValue().getResource());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testModifyResponse() {
|
|
||||||
InterceptorAdapter interceptor = new InterceptorAdapter(){
|
|
||||||
@Override
|
|
||||||
public boolean incomingRequestPostProcessed(RequestDetails theRequestDetails, HttpServletRequest theRequest, HttpServletResponse theResponse) throws AuthenticationException {
|
|
||||||
ServletRequestDetails srd = (ServletRequestDetails)theRequestDetails;
|
|
||||||
String input = new String(srd.loadRequestContents(), Constants.CHARSET_UTF8);
|
|
||||||
assertThat(input, StringContains.containsString("\"active\":true"));
|
|
||||||
|
|
||||||
String newInput = createInput().replace("true", "false");
|
|
||||||
srd.setRequestContents(newInput.getBytes(Constants.CHARSET_UTF8));
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testResponseWithNothing() throws Exception {
|
public void testResponseWithNothing() throws Exception {
|
||||||
ourServlet.setInterceptors(myInterceptor1);
|
ourServlet.setInterceptors(myInterceptor1);
|
||||||
|
@ -145,6 +166,7 @@ public class InterceptorDstu3Test {
|
||||||
when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true);
|
||||||
|
when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(ResponseDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true);
|
||||||
|
|
||||||
String input = createInput();
|
String input = createInput();
|
||||||
|
|
||||||
|
@ -240,7 +262,6 @@ public class InterceptorDstu3Test {
|
||||||
|
|
||||||
public static class DummyPatientResourceProvider implements IResourceProvider {
|
public static class DummyPatientResourceProvider implements IResourceProvider {
|
||||||
|
|
||||||
|
|
||||||
@Create()
|
@Create()
|
||||||
public MethodOutcome create(@ResourceParam Patient theResource) {
|
public MethodOutcome create(@ResourceParam Patient theResource) {
|
||||||
ourLastPatient = theResource;
|
ourLastPatient = theResource;
|
||||||
|
@ -252,6 +273,14 @@ public class InterceptorDstu3Test {
|
||||||
return Patient.class;
|
return Patient.class;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Read
|
||||||
|
public Patient read(@IdParam IdType theId) {
|
||||||
|
Patient retVal = new Patient();
|
||||||
|
retVal.setId(theId);
|
||||||
|
retVal.addName().setFamily("NAME0");
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
@Validate()
|
@Validate()
|
||||||
public MethodOutcome validate(@ResourceParam Patient theResource) {
|
public MethodOutcome validate(@ResourceParam Patient theResource) {
|
||||||
return new MethodOutcome();
|
return new MethodOutcome();
|
||||||
|
|
|
@ -8,15 +8,15 @@
|
||||||
<body>
|
<body>
|
||||||
<release version="3.3.0" date="TBD">
|
<release version="3.3.0" date="TBD">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
This release corrects an ineffiency in the JPA Server, but requires a schema
|
This release corrects an inefficiency in the JPA Server, but requires a schema
|
||||||
change in order to update. Prior to this version of HAPI FHIR, a CLOB column
|
change in order to update. Prior to this version of HAPI FHIR, a CLOB column
|
||||||
containing the complete resource body was stored in two
|
containing the complete resource body was stored in two
|
||||||
tables: HFJ_RESOURCE and HFJ_RES_VER. Because the same content was stored in two
|
tables: HFJ_RESOURCE and HFJ_RES_VER. Because the same content was stored in two
|
||||||
places, the database consumed more space than is needed to.
|
places, the database consumed more space than is needed to.
|
||||||
<![CDATA[<br/><br/>]]>
|
<![CDATA[<br/><br/>]]>
|
||||||
In order to reduce this duplication, the columns have been removed from the
|
In order to reduce this duplication, two columns have been removed from the
|
||||||
HFJ_RESOURCE column. This means that on any database that is being upgraded
|
HFJ_RESOURCE table. This means that on any database that is being upgraded
|
||||||
to HAPI FHIR 3.2.0, you will need to remove the columns
|
to HAPI FHIR 3.3.0+, you will need to remove the columns
|
||||||
<![CDATA[<code>RES_TEXT</code> and <code>RES_ENCODING</code>]]> (or
|
<![CDATA[<code>RES_TEXT</code> and <code>RES_ENCODING</code>]]> (or
|
||||||
set them to nullable if you want an easy means of rolling back). Naturally
|
set them to nullable if you want an easy means of rolling back). Naturally
|
||||||
you should back your database up prior to making this change.
|
you should back your database up prior to making this change.
|
||||||
|
@ -24,7 +24,7 @@
|
||||||
<action type="fix">
|
<action type="fix">
|
||||||
Fix a crash in the JSON parser when parsing extensions on repeatable
|
Fix a crash in the JSON parser when parsing extensions on repeatable
|
||||||
elements (e.g. Patient.address.line) where there is an extension on the
|
elements (e.g. Patient.address.line) where there is an extension on the
|
||||||
first repetion but not on subsequent repetitions of the
|
first repetition but not on subsequent repetitions of the
|
||||||
repeatable primitive. Thanks to Igor Sirkovich for providing a
|
repeatable primitive. Thanks to Igor Sirkovich for providing a
|
||||||
test case!
|
test case!
|
||||||
</action>
|
</action>
|
||||||
|
@ -32,6 +32,19 @@
|
||||||
Fix an issue where the JPA server crashed while attempting to normalize string values
|
Fix an issue where the JPA server crashed while attempting to normalize string values
|
||||||
containing Korean text. Thanks to GitHub user @JoonggeonLee for reporting!
|
containing Korean text. Thanks to GitHub user @JoonggeonLee for reporting!
|
||||||
</action>
|
</action>
|
||||||
|
<action type="add">
|
||||||
|
A new method overload has been added to IServerInterceptor:
|
||||||
|
<![CDATA[
|
||||||
|
<code>outgoingResponse(RequestDetails, ResponseDetails, HttpServletRequest, HttpServletResponse)
|
||||||
|
]]>. This new method allows an interceptor to completely replace
|
||||||
|
the resource being returned with a different resource instance, or
|
||||||
|
to modify the HTTP Status Code being returned. All other "outgoingResponse"
|
||||||
|
methods have been deprecated and are recommended to be migrated
|
||||||
|
to the new method. This new method (with its RequestDetails and ResponseDetails
|
||||||
|
parameters) should be flexible enough to
|
||||||
|
accommodate future needs which means that this should be the last
|
||||||
|
time we have to change it.
|
||||||
|
</action>
|
||||||
</release>
|
</release>
|
||||||
<release version="3.2.0" date="2018-01-13">
|
<release version="3.2.0" date="2018-01-13">
|
||||||
<action type="add">
|
<action type="add">
|
||||||
|
|
Loading…
Reference in New Issue