From 690bcb4b68bc87ee0c9c0c31995abe56dde7673f Mon Sep 17 00:00:00 2001
From: James Agnew
Date: Fri, 27 Mar 2015 16:52:48 -0400
Subject: [PATCH] Add support for a NarrativeModeEnum parameter to interact
with _narrative in the request URL
---
hapi-fhir-android/dependency-reduced-pom.xml | 6 +-
.../ca/uhn/fhir/rest/method/MethodUtil.java | 6 +-
.../rest/method/NarrativeModeParameter.java | 68 ++++++++++
.../fhir/rest/server/RestfulServerUtils.java | 75 +++++------
.../ca/uhn/fhir/rest/client/ClientTest.java | 60 +++++++--
.../ca/uhn/fhir/rest/server/SearchTest.java | 53 +++++++-
.../rest/server/CreateConditionalTest.java | 15 +--
.../ca/uhn/fhir/rest/server/PreferTest.java | 118 ++++++++++++++++++
src/changes/changes.xml | 10 ++
src/site/xdoc/doc_rest_operations.xml | 2 +-
src/site/xdoc/doc_rest_server.xml | 13 ++
11 files changed, 358 insertions(+), 68 deletions(-)
create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NarrativeModeParameter.java
create mode 100644 hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/PreferTest.java
diff --git a/hapi-fhir-android/dependency-reduced-pom.xml b/hapi-fhir-android/dependency-reduced-pom.xml
index 1e0c0d8a940..75c68235a10 100644
--- a/hapi-fhir-android/dependency-reduced-pom.xml
+++ b/hapi-fhir-android/dependency-reduced-pom.xml
@@ -94,7 +94,7 @@
maven-assembly-plugin
- ${maven_assembly_plugin_version}
+ 2.5.3
package
@@ -104,8 +104,8 @@
true
- ${project.basedir}/src/assembly/android-sources.xml
- ${project.basedir}/src/assembly/android-javadoc.xml
+ /Users/t3903uhn/git/hapi-fhir/hapi-fhir-android/src/assembly/android-sources.xml
+ /Users/t3903uhn/git/hapi-fhir/hapi-fhir-android/src/assembly/android-javadoc.xml
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java
index 1967883bdbf..94e1e6bd305 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/MethodUtil.java
@@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.method;
-import static org.apache.commons.lang3.StringUtils.*;
+import static org.apache.commons.lang3.StringUtils.isBlank;
+import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.PushbackReader;
@@ -77,6 +78,7 @@ import ca.uhn.fhir.rest.param.TokenAndListParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider;
+import ca.uhn.fhir.rest.server.RestfulServer.NarrativeModeEnum;
import ca.uhn.fhir.rest.server.SearchParameterMap;
import ca.uhn.fhir.util.ReflectionUtil;
@@ -342,6 +344,8 @@ public class MethodUtil {
param = new ServletRequestParameter();
} else if (parameterType.equals(HttpServletResponse.class) || parameterType.equals(ServletResponse.class)) {
param = new ServletResponseParameter();
+ } else if (parameterType.equals(NarrativeModeEnum.class)) {
+ param = new NarrativeModeParameter();
} else {
for (int i = 0; i < annotations.length && param == null; i++) {
Annotation nextAnnotation = annotations[i];
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NarrativeModeParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NarrativeModeParameter.java
new file mode 100644
index 00000000000..bacd41bdb32
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NarrativeModeParameter.java
@@ -0,0 +1,68 @@
+package ca.uhn.fhir.rest.method;
+
+/*
+ * #%L
+ * HAPI FHIR - Core Library
+ * %%
+ * Copyright (C) 2014 - 2015 University Health Network
+ * %%
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ * #L%
+ */
+
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+
+import org.hl7.fhir.instance.model.IBaseResource;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.rest.server.Constants;
+import ca.uhn.fhir.rest.server.RestfulServer.NarrativeModeEnum;
+import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
+import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
+
+class NarrativeModeParameter implements IParameter {
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletResponseParameter.class);
+
+ @Override
+ public void translateClientArgumentIntoQueryArgument(FhirContext theContext, Object theSourceClientArgument, Map> theTargetQueryArguments, IBaseResource theTargetResource)
+ throws InternalErrorException {
+ if (theSourceClientArgument != null) {
+ NarrativeModeEnum n = (NarrativeModeEnum) theSourceClientArgument;
+ theTargetQueryArguments.put(Constants.PARAM_NARRATIVE, Collections.singletonList(n.name().toLowerCase()));
+ }
+ }
+
+ @Override
+ public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
+ String val = theRequest.getServletRequest().getParameter(Constants.PARAM_NARRATIVE);
+ if (val != null) {
+ try {
+ return NarrativeModeEnum.valueOfCaseInsensitive(val);
+ } catch (IllegalArgumentException e) {
+ ourLog.debug("Invalid {} parameger: {}", Constants.PARAM_NARRATIVE, val);
+ return null;
+ }
+ }
+ return null;
+ }
+
+ @Override
+ public void initializeTypes(Method theMethod, Class extends Collection>> theOuterCollectionType, Class extends Collection>> theInnerCollectionType, Class> theParameterType) {
+ // ignore
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
index 37a497e1130..ee30880e663 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java
@@ -56,7 +56,7 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
public class RestfulServerUtils {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtils.class);
-
+
static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) {
String countString = theRequest.getParameter(name);
Integer count = null;
@@ -70,29 +70,29 @@ public class RestfulServerUtils {
return count;
}
- public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, RestfulServer.NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip,
- String theServerBase) throws IOException {
+ public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
+ boolean theRequestIsBrowser, RestfulServer.NarrativeModeEnum theNarrativeMode, int stausCode, boolean theRespondGzip, String theServerBase) throws IOException {
theHttpResponse.setStatus(stausCode);
-
+
if (theResource.getId() != null && theResource.getId().hasIdPart() && isNotBlank(theServerBase)) {
String resName = theServer.getFhirContext().getResourceDefinition(theResource).getName();
IdDt fullId = theResource.getId().withServerBase(theServerBase, resName);
theHttpResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue());
}
-
+
if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) {
if (theResource.getId().hasVersionIdPart()) {
theHttpResponse.addHeader(Constants.HEADER_ETAG, "W/\"" + theResource.getId().getVersionIdPart() + '"');
}
}
-
+
if (theServer.getAddProfileTag() != AddProfileTagEnum.NEVER) {
RuntimeResourceDefinition def = theServer.getFhirContext().getResourceDefinition(theResource);
if (theServer.getAddProfileTag() == AddProfileTagEnum.ALWAYS || !def.isStandardProfile()) {
addProfileToBundleEntry(theServer.getFhirContext(), theResource, theServerBase);
}
}
-
+
if (theResource instanceof IBaseBinary && theResponseEncoding == null) {
IBaseBinary bin = (IBaseBinary) theResource;
if (isNotBlank(bin.getContentType())) {
@@ -103,20 +103,20 @@ public class RestfulServerUtils {
if (bin.getContent() == null || bin.getContent().length == 0) {
return;
}
-
+
// Force binary resources to download - This is a security measure to prevent
// malicious images or HTML blocks being served up as content.
theHttpResponse.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;");
-
+
theHttpResponse.setContentLength(bin.getContent().length);
ServletOutputStream oos = theHttpResponse.getOutputStream();
oos.write(bin.getContent());
oos.close();
return;
}
-
+
EncodingEnum responseEncoding = theResponseEncoding != null ? theResponseEncoding : theServer.getDefaultResponseEncoding();
-
+
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType());
} else if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
@@ -125,14 +125,14 @@ public class RestfulServerUtils {
theHttpResponse.setContentType(responseEncoding.getResourceContentType());
}
theHttpResponse.setCharacterEncoding(Constants.CHARSETNAME_UTF_8);
-
+
theServer.addHeadersToResponse(theHttpResponse);
-
+
InstantDt lastUpdated = ResourceMetadataKeyEnum.UPDATED.get(theResource);
if (lastUpdated != null && lastUpdated.isEmpty() == false) {
theHttpResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue()));
}
-
+
TagList list = (TagList) theResource.getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST);
if (list != null) {
for (Tag tag : list) {
@@ -141,7 +141,7 @@ public class RestfulServerUtils {
}
}
}
-
+
Writer writer = getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
@@ -253,13 +253,13 @@ public class RestfulServerUtils {
}
public static void addProfileToBundleEntry(FhirContext theContext, IResource theResource, String theServerBase) {
-
+
TagList tl = ResourceMetadataKeyEnum.TAG_LIST.get(theResource);
if (tl == null) {
tl = new TagList();
ResourceMetadataKeyEnum.TAG_LIST.put(theResource, tl);
}
-
+
RuntimeResourceDefinition nextDef = theContext.getResourceDefinition(theResource);
String profile = nextDef.getResourceProfile(theServerBase);
if (isNotBlank(profile)) {
@@ -267,13 +267,17 @@ public class RestfulServerUtils {
}
}
-
public static RestfulServer.NarrativeModeEnum determineNarrativeMode(RequestDetails theRequest) {
Map requestParams = theRequest.getParameters();
String[] narrative = requestParams.remove(Constants.PARAM_NARRATIVE);
RestfulServer.NarrativeModeEnum narrativeMode = null;
if (narrative != null && narrative.length > 0) {
- narrativeMode = RestfulServer.NarrativeModeEnum.valueOfCaseInsensitive(narrative[0]);
+ try {
+ narrativeMode = RestfulServer.NarrativeModeEnum.valueOfCaseInsensitive(narrative[0]);
+ } catch (IllegalArgumentException e) {
+ ourLog.debug("Invalid {} parameger: {}", Constants.PARAM_NARRATIVE, narrative[0]);
+ narrativeMode = null;
+ }
}
if (narrativeMode == null) {
narrativeMode = RestfulServer.NarrativeModeEnum.NORMAL;
@@ -282,13 +286,12 @@ public class RestfulServerUtils {
}
/**
- * Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's
- * "_format"
parameter and "Accept:"
HTTP header.
+ * Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's "_format"
parameter and "Accept:"
HTTP header.
*/
public static EncodingEnum determineResponseEncodingWithDefault(RestfulServer theServer, HttpServletRequest theReq) {
EncodingEnum retVal = determineResponseEncodingNoDefault(theReq);
if (retVal == null) {
- retVal =theServer.getDefaultResponseEncoding();
+ retVal = theServer.getDefaultResponseEncoding();
}
return retVal;
}
@@ -317,7 +320,7 @@ public class RestfulServerUtils {
}
}
}
-
+
Enumeration acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT);
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
@@ -347,13 +350,14 @@ public class RestfulServerUtils {
return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT);
}
- public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase, boolean thePrettyPrint, RestfulServer.NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, boolean theRequestIsBrowser) throws IOException {
+ public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, EncodingEnum theResponseEncoding, String theServerBase,
+ boolean thePrettyPrint, RestfulServer.NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, boolean theRequestIsBrowser) throws IOException {
assert !theServerBase.endsWith("/");
-
+
theHttpResponse.setStatus(200);
-
- EncodingEnum responseEncoding = theResponseEncoding!= null? theResponseEncoding : theServer.getDefaultResponseEncoding();
-
+
+ EncodingEnum responseEncoding = theResponseEncoding != null ? theResponseEncoding : theServer.getDefaultResponseEncoding();
+
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType());
} else if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
@@ -361,11 +365,11 @@ public class RestfulServerUtils {
} else {
theHttpResponse.setContentType(responseEncoding.getBundleContentType());
}
-
+
theHttpResponse.setCharacterEncoding(Constants.CHARSETNAME_UTF_8);
-
+
theServer.addHeadersToResponse(theHttpResponse);
-
+
Writer writer = RestfulServerUtils.getWriter(theHttpResponse, theRespondGzip);
try {
if (theNarrativeMode == RestfulServer.NarrativeModeEnum.ONLY) {
@@ -383,10 +387,11 @@ public class RestfulServerUtils {
}
}
- public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, RestfulServer.NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase)
- throws IOException {
+ public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingEnum theResponseEncoding, boolean thePrettyPrint,
+ boolean theRequestIsBrowser, RestfulServer.NarrativeModeEnum theNarrativeMode, boolean theRespondGzip, String theServerBase) throws IOException {
int stausCode = 200;
- RestfulServerUtils.streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip, theServerBase);
+ RestfulServerUtils.streamResponseAsResource(theServer, theHttpResponse, theResource, theResponseEncoding, thePrettyPrint, theRequestIsBrowser, theNarrativeMode, stausCode, theRespondGzip,
+ theServerBase);
}
public static void validateResourceListNotNull(List theResourceList) {
@@ -395,6 +400,4 @@ public class RestfulServerUtils {
}
}
-
-
}
diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java
index 679c9892985..11f66b0026d 100644
--- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java
+++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java
@@ -71,6 +71,7 @@ import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingEnum;
+import ca.uhn.fhir.rest.server.RestfulServer.NarrativeModeEnum;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@@ -148,7 +149,7 @@ public class ClientTest {
HttpPost post = (HttpPost) capt.getValue();
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("6c1d93be-027f-468d-9d47-f826cd15cf42 2 2014-04-13T18:24:50-04:00 ca.uhn.fhir.rest.method.HistoryMethodBinding Patient 222 222 1969-12-31T19:00:20.000-05:00 1969-12-31T19:00:10.000-05:00 Patient 222 222 1969-12-31T19:00:30.000-05:00 1969-12-31T19:00:10.000-05:00 ";
//@formatter:on
@@ -528,17 +528,17 @@ public class ClientTest {
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
// ensures the local timezone
- String expectedDateString = new InstantDt(new InstantDt("2012-01-02T12:01:02").getValue()).getValueAsString();
+ String expectedDateString = new InstantDt(new InstantDt("2012-01-02T12:01:02").getValue()).getValueAsString();
expectedDateString = expectedDateString.replace(":", "%3A").replace("+", "%2B");
-
+
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T12:01:02"), new IntegerDt(12));
assertThat(capt.getAllValues().get(0).getURI().toString(), containsString("http://foo/Patient/111/_history?"));
- assertThat(capt.getAllValues().get(0).getURI().toString(), containsString("_since="+expectedDateString.replaceAll("\\..*", "")));
+ assertThat(capt.getAllValues().get(0).getURI().toString(), containsString("_since=" + expectedDateString.replaceAll("\\..*", "")));
assertThat(capt.getAllValues().get(0).getURI().toString(), containsString("_count=12"));
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T12:01:02").getValue(), new IntegerDt(12).getValue());
assertThat(capt.getAllValues().get(1).getURI().toString(), containsString("http://foo/Patient/111/_history?"));
- assertThat(capt.getAllValues().get(1).getURI().toString(), containsString("_since="+expectedDateString));
+ assertThat(capt.getAllValues().get(1).getURI().toString(), containsString("_since=" + expectedDateString));
assertThat(capt.getAllValues().get(1).getURI().toString(), containsString("_count=12"));
client.getHistoryPatientInstance(new IdDt("111"), null, new IntegerDt(12));
@@ -562,7 +562,7 @@ public class ClientTest {
// error message to tell the user why the method isn't working
FhirContext ctx = new FhirContext();
ctx.getRestfulClientFactory().setServerValidationModeEnum(ServerValidationModeEnum.NEVER);
-
+
ClientWithoutAnnotation client = ctx.newRestfulClient(ClientWithoutAnnotation.class, "http://wildfhir.aegis.net/fhir");
try {
@@ -592,7 +592,8 @@ public class ClientTest {
ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
- Header[] headers = new Header[] { new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"), new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"),
+ Header[] headers = new Header[] { new BasicHeader(Constants.HEADER_LAST_MODIFIED, "Wed, 15 Nov 1995 04:58:08 GMT"),
+ new BasicHeader(Constants.HEADER_CONTENT_LOCATION, "http://foo.com/Patient/123/_history/2333"),
new BasicHeader(Constants.HEADER_CATEGORY, "http://foo/tagdefinition.html; scheme=\"http://hl7.org/fhir/tag\"; label=\"Some tag\"") };
when(httpResponse.getAllHeaders()).thenReturn(headers);
@@ -653,7 +654,6 @@ public class ClientTest {
}
-
@Test
public void testReadFailureInternalError() throws Exception {
@@ -1085,7 +1085,7 @@ public class ClientTest {
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString(" capt = ArgumentCaptor.forClass(HttpUriRequest.class);
+ when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
+ when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
+ when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
+ when(httpResponse.getEntity().getContent()).thenAnswer(new Answer() {
+ @Override
+ public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
+ return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
+ }
+ });
+
+ ITestClientWithNarrativeParam client = ctx.newRestfulClient(ITestClientWithNarrativeParam.class, "http://foo");
+
+ int idx = 0;
+
+ Patient response = client.getPatients(null);
+ assertEquals("http://foo/Patient", capt.getAllValues().get(idx).getURI().toString());
+ assertNotNull(response);
+ idx++;
+
+ response = client.getPatients(NarrativeModeEnum.ONLY);
+ assertEquals("http://foo/Patient?_narrative=only", capt.getAllValues().get(idx).getURI().toString());
+ assertNotNull(response);
+
+ }
+
private Header[] toHeaderArray(String theName, String theValue) {
return new Header[] { new BasicHeader(theName, theValue) };
}
@@ -1240,4 +1270,10 @@ public class ClientTest {
@Search()
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringParam theString, @IncludeParam String theInclude);
}
+
+ public interface ITestClientWithNarrativeParam extends IBasicClient {
+ @Search()
+ public Patient getPatients(NarrativeModeEnum theNarrativeMode);
+ }
+
}
diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java
index 943cc4f7e58..66a0ee07316 100644
--- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java
+++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/SearchTest.java
@@ -23,11 +23,10 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
+import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
-import com.google.common.net.UrlEscapers;
-
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
@@ -41,7 +40,6 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
-import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.IGenericClient;
@@ -50,8 +48,11 @@ import ca.uhn.fhir.rest.param.StringOrListParam;
import ca.uhn.fhir.rest.param.StringParam;
import ca.uhn.fhir.rest.param.TokenOrListParam;
import ca.uhn.fhir.rest.param.TokenParam;
+import ca.uhn.fhir.rest.server.RestfulServer.NarrativeModeEnum;
import ca.uhn.fhir.util.PortUtil;
+import com.google.common.net.UrlEscapers;
+
/**
* Created by dsotnikov on 2/25/2014.
*/
@@ -63,7 +64,13 @@ public class SearchTest {
private static int ourPort;
private static Server ourServer;
+ private static NarrativeModeEnum ourLastNarrativeMode;
+ @Before
+ public void before() {
+ ourLastNarrativeMode=null;
+ }
+
@Test
public void testEncodeConvertsReferencesToRelative() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithRef");
@@ -78,6 +85,36 @@ public class SearchTest {
assertEquals("Organization/555", ref);
}
+ @Test
+ public void testNarrativeParamNone() throws Exception {
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithNarrative");
+ HttpResponse status = ourClient.execute(httpGet);
+ IOUtils.closeQuietly(status.getEntity().getContent());
+
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ assertEquals(null, ourLastNarrativeMode);
+ }
+
+ @Test
+ public void testNarrativeParamPopulated() throws Exception {
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithNarrative&_narrative=ONly");
+ HttpResponse status = ourClient.execute(httpGet);
+ IOUtils.closeQuietly(status.getEntity().getContent());
+
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ assertEquals(NarrativeModeEnum.ONLY, ourLastNarrativeMode);
+ }
+
+ @Test
+ public void testNarrativeParamPopulatedInvalid() throws Exception {
+ HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_query=searchWithNarrative&_narrative=BLAH");
+ HttpResponse status = ourClient.execute(httpGet);
+ IOUtils.closeQuietly(status.getEntity().getContent());
+
+ assertEquals(200, status.getStatusLine().getStatusCode());
+ assertEquals(null, ourLastNarrativeMode);
+ }
+
@Test
public void testOmitEmptyOptionalParam() throws Exception {
HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient?_id=");
@@ -327,7 +364,15 @@ public class SearchTest {
retVal.add(patient);
return retVal;
}
-
+
+ @Search(queryName="searchWithNarrative")
+ public Patient searchWithNarrative(NarrativeModeEnum theNarrativeMode) {
+ ourLastNarrativeMode = theNarrativeMode;
+ Patient patient = new Patient();
+ patient.setId("Patient/1/_history/1");
+ return patient;
+ }
+
@Search(queryName="searchWithRef")
public Patient searchWithRef() {
Patient patient = new Patient();
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/CreateConditionalTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/CreateConditionalTest.java
index 0b810b659db..6ad36c6d173 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/CreateConditionalTest.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/CreateConditionalTest.java
@@ -1,6 +1,8 @@
package ca.uhn.fhir.rest.server;
-import static org.junit.Assert.*;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.List;
@@ -10,7 +12,6 @@ import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
-import org.apache.http.client.methods.HttpPut;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
@@ -26,13 +27,6 @@ import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
-import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
-import ca.uhn.fhir.model.api.TagList;
-import ca.uhn.fhir.model.dstu2.resource.DiagnosticOrder;
-import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport;
-import ca.uhn.fhir.model.dstu2.resource.Observation;
-import ca.uhn.fhir.model.dstu2.resource.OperationOutcome;
-import ca.uhn.fhir.model.dstu2.resource.Organization;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
@@ -42,7 +36,6 @@ import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
-import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.util.PortUtil;
@@ -51,10 +44,10 @@ import ca.uhn.fhir.util.PortUtil;
*/
public class CreateConditionalTest {
private static CloseableHttpClient ourClient;
+
private static String ourLastConditionalUrl;
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(CreateConditionalTest.class);
private static int ourPort;
-
private static Server ourServer;
private static IdDt ourLastId;
private static IdDt ourLastIdParam;
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/PreferTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/PreferTest.java
new file mode 100644
index 00000000000..9915ce84b78
--- /dev/null
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/PreferTest.java
@@ -0,0 +1,118 @@
+package ca.uhn.fhir.rest.server;
+
+import static org.junit.Assert.assertEquals;
+
+import java.util.concurrent.TimeUnit;
+
+import org.apache.commons.io.IOUtils;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpPost;
+import org.apache.http.entity.ContentType;
+import org.apache.http.entity.StringEntity;
+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.junit.AfterClass;
+import org.junit.BeforeClass;
+import org.junit.Test;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.dstu2.resource.Patient;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.rest.annotation.Create;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.ResourceParam;
+import ca.uhn.fhir.rest.annotation.Update;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.util.PortUtil;
+
+/**
+ * Created by dsotnikov on 2/25/2014.
+ */
+public class PreferTest {
+ private static CloseableHttpClient ourClient;
+
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(PreferTest.class);
+ private static int ourPort;
+ private static Server ourServer;
+
+
+
+ @Test
+ public void testCreateWithNoPrefer() throws Exception {
+
+ Patient patient = new Patient();
+ patient.addIdentifier().setValue("002");
+
+ HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
+ httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
+
+ HttpResponse status = ourClient.execute(httpPost);
+
+ String responseContent = IOUtils.toString(status.getEntity().getContent());
+ IOUtils.closeQuietly(status.getEntity().getContent());
+
+ ourLog.info("Response was:\n{}", responseContent);
+
+ assertEquals(201, status.getStatusLine().getStatusCode());
+ assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("location").getValue());
+ assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("content-location").getValue());
+
+ }
+
+
+
+
+ @AfterClass
+ public static void afterClass() throws Exception {
+ ourServer.stop();
+ }
+
+
+ @BeforeClass
+ public static void beforeClass() throws Exception {
+ ourPort = PortUtil.findFreePort();
+ ourServer = new Server(ourPort);
+
+ PatientProvider patientProvider = new PatientProvider();
+
+ ServletHandler proxyHandler = new ServletHandler();
+ RestfulServer servlet = new RestfulServer();
+ 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 PatientProvider implements IResourceProvider {
+
+ @Override
+ public Class extends IResource> getResourceType() {
+ return Patient.class;
+ }
+
+ @Create()
+ public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
+ MethodOutcome retVal = new MethodOutcome(new IdDt("Patient/001/_history/002"));
+ return retVal;
+ }
+
+ @Update()
+ public MethodOutcome updatePatient(@ResourceParam Patient thePatient, @IdParam IdDt theIdParam) {
+ return new MethodOutcome(new IdDt("Patient/001/_history/002"));
+ }
+
+ }
+
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 3fbfdbd2f8c..5d92c55e171 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -76,6 +76,16 @@
Remove Eclipse and IntelliJ artifacts (.project, *.iml, etc) from version control. Thanks
to Doug Martin for the suggestion!
+
+ REST server methods may now have a parameter of
+ type NarrativeModeEnum which will be populated with
+ the value of the _narrative URL parameter
+ if one was supplied. Annotation client methods
+ may also include a parameter of this type, and it
+ will be used to populate this parameter on the request
+ URL if it is not null. Thanks to Neal Acharya for the
+ idea!
+
diff --git a/src/site/xdoc/doc_rest_operations.xml b/src/site/xdoc/doc_rest_operations.xml
index 227073747a8..83c25d4188c 100644
--- a/src/site/xdoc/doc_rest_operations.xml
+++ b/src/site/xdoc/doc_rest_operations.xml
@@ -1006,7 +1006,7 @@
@Description
annotation. This annotation allows you to add a description of the method
and the individual parameters. These descriptions will be placed in the
- server's metadata statement, which can be helpful to anyone who is developing
+ server's conformance statement, which can be helpful to anyone who is developing
software against your server.
diff --git a/src/site/xdoc/doc_rest_server.xml b/src/site/xdoc/doc_rest_server.xml
index de2338dfcf5..4d5634c7181 100644
--- a/src/site/xdoc/doc_rest_server.xml
+++ b/src/site/xdoc/doc_rest_server.xml
@@ -350,6 +350,19 @@
+
+
+
+ There are different ways of
+ generating narratives for use on your server. HAPI's Server
+ also provides a non-standard parameter called _narrative
which can be used to
+ control narrative behavour. If you add a parameter to any server (or annotation client) method
+ with a type of NarrativeModeEnum
, the value will be populated with the value
+ of this URL parameter.
+
+
+
+