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> theOuterCollectionType, Class> 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("<id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 2222221969-12-31T19:00:20.000-05:001969-12-31T19:00:10.000-05:00Patient 2222221969-12-31T19:00:30.000-05:001969-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 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. +

+ +
+