diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java index 7aa3bda5ab6..ee52ceb090d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/GenericClient.java @@ -788,7 +788,7 @@ public class GenericClient extends BaseClient implements IGenericClient { RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); final String resourceName = def.getName(); - OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); + OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName, myPrefer); Map> params = new HashMap>(); return invoke(params, binding, invocation); @@ -1608,6 +1608,12 @@ public class GenericClient extends BaseClient implements IGenericClient { private final class OutcomeResponseHandler implements IClientResponseHandler { private final String myResourceName; + private PreferReturnEnum myPrefer; + + private OutcomeResponseHandler(String theResourceName, PreferReturnEnum thePrefer) { + this(theResourceName); + myPrefer = thePrefer; + } private OutcomeResponseHandler(String theResourceName) { myResourceName = theResourceName; @@ -1619,6 +1625,17 @@ public class GenericClient extends BaseClient implements IGenericClient { if (theResponseStatusCode == Constants.STATUS_HTTP_201_CREATED) { response.setCreated(true); } + + if (myPrefer == PreferReturnEnum.REPRESENTATION) { + if (response.getResource() == null) { + if (response.getId() != null && isNotBlank(response.getId().getValue()) && response.getId().hasBaseUrl()) { + ourLog.info("Server did not return resource for Prefer-representation, going to fetch: {}", response.getId().getValue()); + IBaseResource resource = read().resource(response.getId().getResourceType()).withUrl(response.getId()).execute(); + response.setResource(resource); + } + } + } + return response; } } @@ -2242,7 +2259,7 @@ public class GenericClient extends BaseClient implements IGenericClient { RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource); final String resourceName = def.getName(); - OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName); + OutcomeResponseHandler binding = new OutcomeResponseHandler(resourceName, myPrefer); Map> params = new HashMap>(); return invoke(params, binding, invocation); 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 012644338f6..093e453ef7f 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 @@ -26,6 +26,7 @@ import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IAnyResource; import org.hl7.fhir.instance.model.api.IBaseMetaType; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -781,8 +782,8 @@ public class MethodUtil { if (reader != null) { IParser parser = ct.newParser(theContext); IBaseResource outcome = parser.parseResource(reader); - if (outcome instanceof BaseOperationOutcome) { - retVal.setOperationOutcome((BaseOperationOutcome) outcome); + if (outcome instanceof IBaseOperationOutcome) { + retVal.setOperationOutcome((IBaseOperationOutcome) outcome); } else { retVal.setResource(outcome); } diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java index 69c38f4bea5..1994a39bd31 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/client/GenericClientDstu3Test.java @@ -1,9 +1,7 @@ package ca.uhn.fhir.rest.client; import static org.hamcrest.Matchers.containsString; -import static org.junit.Assert.assertArrayEquals; -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; +import static org.junit.Assert.*; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -16,6 +14,7 @@ import java.util.List; import org.apache.commons.io.IOUtils; import org.apache.commons.io.input.ReaderInputStream; +import org.apache.http.Header; import org.apache.http.HttpResponse; import org.apache.http.ProtocolVersion; import org.apache.http.client.HttpClient; @@ -43,6 +42,8 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.CustomTypeDstu3Test; import ca.uhn.fhir.parser.CustomTypeDstu3Test.MyCustomPatient; import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.util.TestUtil; import ca.uhn.fhir.util.VersionUtil; @@ -50,21 +51,21 @@ import ca.uhn.fhir.util.VersionUtil; public class GenericClientDstu3Test { private static FhirContext ourCtx; private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClientDstu3Test.class); + private int myAnswerCount; private HttpClient myHttpClient; private HttpResponse myHttpResponse; - @AfterClass - public static void afterClassClearContext() { - TestUtil.clearAllStaticFieldsForUnitTest(); - } - - @Before public void before() { myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + myAnswerCount = 0; + } + + private String expectedUserAgent() { + return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR DSTU3; apache)"; } private byte[] extractBodyAsByteArray(ArgumentCaptor capt) throws IOException { @@ -77,262 +78,6 @@ public class GenericClientDstu3Test { return body; } - @Test - public void testUserAgentForConformance() throws Exception { - IParser p = ourCtx.newXmlParser(); - - Conformance conf = new Conformance(); - conf.setCopyright("COPY"); - - final String respString = p.encodeResourceToString(conf); - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - client.fetchConformance().ofType(Conformance.class).execute(); - assertEquals("http://example.com/fhir/metadata", capt.getAllValues().get(0).getURI().toASCIIString()); - validateUserAgent(capt); - } - - @Test - public void testExplicitCustomTypeSearch() throws Exception { - final String respString = CustomTypeDstu3Test.createBundle(CustomTypeDstu3Test.createResource(false)); - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle resp = client - .search() - .forResource(CustomTypeDstu3Test.MyCustomPatient.class) - .returnBundle(Bundle.class) - .execute(); - //@formatter:on - - assertEquals(1, resp.getEntry().size()); - assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass()); - assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); - } - - @Test - public void testExplicitCustomTypeHistoryType() throws Exception { - final String respString = CustomTypeDstu3Test.createBundle(CustomTypeDstu3Test.createResource(false)); - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Bundle resp = client - .history() - .onType(CustomTypeDstu3Test.MyCustomPatient.class) - .andReturnBundle(Bundle.class) - .execute(); - //@formatter:on - - assertEquals(1, resp.getEntry().size()); - assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass()); - assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(0).getURI().toASCIIString()); - } - - @Test - public void testExplicitCustomTypeLoadPage() throws Exception { - final String respString = CustomTypeDstu3Test.createBundle(CustomTypeDstu3Test.createResource(false)); - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - Bundle bundle = new Bundle(); - bundle.addLink().setRelation("next").setUrl("http://foo/next"); - - //@formatter:off - Bundle resp = client - .loadPage() - .next(bundle) - .preferResponseType(MyCustomPatient.class) - .execute(); - //@formatter:on - - assertEquals(1, resp.getEntry().size()); - assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass()); - assertEquals("http://foo/next", capt.getAllValues().get(0).getURI().toASCIIString()); - - //@formatter:off - resp = client - .loadPage() - .next(bundle) - .preferResponseTypes(toTypeList(MyCustomPatient.class)) - .execute(); - //@formatter:on - - assertEquals(1, resp.getEntry().size()); - assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass()); - assertEquals("http://foo/next", capt.getAllValues().get(0).getURI().toASCIIString()); - } - - @Test - public void testExplicitCustomTypeOperation() throws Exception { - - Parameters param = new Parameters(); - Patient patient = new Patient(); - patient.addName().addFamily("FOO"); - param.addParameter().setName("foo").setResource(patient); - final String respString = ourCtx.newXmlParser().encodeResourceToString(param); - - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - //@formatter:off - Parameters resp = client - .operation() - .onServer() - .named("foo") - .withNoParameters(Parameters.class) - .preferResponseType(MyCustomPatient.class) - .execute(); - //@formatter:on - - assertEquals(1, resp.getParameter().size()); - assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getParameter().get(0).getResource().getClass()); - assertEquals("http://example.com/fhir/$foo", capt.getAllValues().get(0).getURI().toASCIIString()); - - //@formatter:off - resp = client - .operation() - .onType(MyCustomPatient.class) - .named("foo") - .withNoParameters(Parameters.class) - .execute(); - //@formatter:on - - assertEquals(1, resp.getParameter().size()); - assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getParameter().get(0).getResource().getClass()); - assertEquals("http://example.com/fhir/Patient/$foo", capt.getAllValues().get(1).getURI().toASCIIString()); - } - - private List> toTypeList(Class theClass) { - ArrayList> retVal = new ArrayList>(); - retVal.add(theClass); - return retVal; - } - - @Test - public void testUserAgentForBinary() throws Exception { - IParser p = ourCtx.newXmlParser(); - - Conformance conf = new Conformance(); - conf.setCopyright("COPY"); - - final String respString = p.encodeResourceToString(conf); - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Binary bin = new Binary(); - bin.setContentType("application/foo"); - bin.setContent(new byte[] { 0, 1, 2, 3, 4 }); - client.create().resource(bin).execute(); - - ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); - - assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); - validateUserAgent(capt); - - assertEquals("application/foo", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue()); - assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); - assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, extractBodyAsByteArray(capt)); - - } - - @Test - public void testBinaryCreateWithNoContentType() throws Exception { - IParser p = ourCtx.newXmlParser(); - - OperationOutcome conf = new OperationOutcome(); - conf.getText().setDivAsString("OK!"); - - final String respString = p.encodeResourceToString(conf); - ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); - when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); - when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); - when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); - when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { - @Override - public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { - return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); - } - }); - - IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); - - Binary bin = new Binary(); - bin.setContent(new byte[] { 0, 1, 2, 3, 4 }); - client.create().resource(bin).execute(); - - ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); - - assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); - validateUserAgent(capt); - - assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); - assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); - assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent()); - - } - @Test public void testBinaryCreateWithFhirContentType() throws Exception { IParser p = ourCtx.newXmlParser(); @@ -376,6 +121,285 @@ public class GenericClientDstu3Test { assertEquals("
A PATIENT
", outputPt.getText().getDivAsString()); } + @Test + public void testBinaryCreateWithNoContentType() throws Exception { + IParser p = ourCtx.newXmlParser(); + + OperationOutcome conf = new OperationOutcome(); + conf.getText().setDivAsString("OK!"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Binary bin = new Binary(); + bin.setContent(new byte[] { 0, 1, 2, 3, 4 }); + client.create().resource(bin).execute(); + + ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); + + assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + + assertEquals("application/xml+fhir;charset=utf-8", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue().toLowerCase().replace(" ", "")); + assertEquals(Constants.CT_FHIR_XML, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, ourCtx.newXmlParser().parseResource(Binary.class, extractBodyAsString(capt)).getContent()); + + } + + @Test + public void testCreateWithPreferRepresentationServerReturnsOO() throws Exception { + final IParser p = ourCtx.newXmlParser(); + + final OperationOutcome resp0 = new OperationOutcome(); + resp0.getText().setDivAsString("OK!"); + + final Patient resp1 = new Patient(); + resp1.getText().setDivAsString("FINAL VALUE"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + if (myAnswerCount++ == 0) { + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); + } else { + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + } + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.getText().setDivAsString("A PATIENT"); + + MethodOutcome outcome = client.create().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute(); + + assertEquals(2, myAnswerCount); + assertNotNull(outcome.getOperationOutcome()); + assertNotNull(outcome.getResource()); + + assertEquals("
OK!
", ((OperationOutcome) outcome.getOperationOutcome()).getText().getDivAsString()); + assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); + + assertEquals(myAnswerCount, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); + assertEquals("http://foo.com/base/Patient/222/_history/3", capt.getAllValues().get(1).getURI().toASCIIString()); + } + + @Test + public void testCreateWithPreferRepresentationServerReturnsResource() throws Exception { + final IParser p = ourCtx.newXmlParser(); + + final Patient resp1 = new Patient(); + resp1.getText().setDivAsString("FINAL VALUE"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + myAnswerCount++; + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.getText().setDivAsString("A PATIENT"); + + MethodOutcome outcome = client.create().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute(); + + assertEquals(1, myAnswerCount); + assertNull(outcome.getOperationOutcome()); + assertNotNull(outcome.getResource()); + + assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); + + assertEquals(myAnswerCount, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); + } + + @Test + public void testExplicitCustomTypeHistoryType() throws Exception { + final String respString = CustomTypeDstu3Test.createBundle(CustomTypeDstu3Test.createResource(false)); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle resp = client + .history() + .onType(CustomTypeDstu3Test.MyCustomPatient.class) + .andReturnBundle(Bundle.class) + .execute(); + //@formatter:on + + assertEquals(1, resp.getEntry().size()); + assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass()); + assertEquals("http://example.com/fhir/Patient/_history", capt.getAllValues().get(0).getURI().toASCIIString()); + } + + @Test + public void testExplicitCustomTypeLoadPage() throws Exception { + final String respString = CustomTypeDstu3Test.createBundle(CustomTypeDstu3Test.createResource(false)); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + Bundle bundle = new Bundle(); + bundle.addLink().setRelation("next").setUrl("http://foo/next"); + + //@formatter:off + Bundle resp = client + .loadPage() + .next(bundle) + .preferResponseType(MyCustomPatient.class) + .execute(); + //@formatter:on + + assertEquals(1, resp.getEntry().size()); + assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass()); + assertEquals("http://foo/next", capt.getAllValues().get(0).getURI().toASCIIString()); + + //@formatter:off + resp = client + .loadPage() + .next(bundle) + .preferResponseTypes(toTypeList(MyCustomPatient.class)) + .execute(); + //@formatter:on + + assertEquals(1, resp.getEntry().size()); + assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass()); + assertEquals("http://foo/next", capt.getAllValues().get(0).getURI().toASCIIString()); + } + + @Test + public void testExplicitCustomTypeOperation() throws Exception { + + Parameters param = new Parameters(); + Patient patient = new Patient(); + patient.addName().addFamily("FOO"); + param.addParameter().setName("foo").setResource(patient); + final String respString = ourCtx.newXmlParser().encodeResourceToString(param); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Parameters resp = client + .operation() + .onServer() + .named("foo") + .withNoParameters(Parameters.class) + .preferResponseType(MyCustomPatient.class) + .execute(); + //@formatter:on + + assertEquals(1, resp.getParameter().size()); + assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getParameter().get(0).getResource().getClass()); + assertEquals("http://example.com/fhir/$foo", capt.getAllValues().get(0).getURI().toASCIIString()); + + //@formatter:off + resp = client + .operation() + .onType(MyCustomPatient.class) + .named("foo") + .withNoParameters(Parameters.class) + .execute(); + //@formatter:on + + assertEquals(1, resp.getParameter().size()); + assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getParameter().get(0).getResource().getClass()); + assertEquals("http://example.com/fhir/Patient/$foo", capt.getAllValues().get(1).getURI().toASCIIString()); + } + + @Test + public void testExplicitCustomTypeSearch() throws Exception { + final String respString = CustomTypeDstu3Test.createBundle(CustomTypeDstu3Test.createResource(false)); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + //@formatter:off + Bundle resp = client + .search() + .forResource(CustomTypeDstu3Test.MyCustomPatient.class) + .returnBundle(Bundle.class) + .execute(); + //@formatter:on + + assertEquals(1, resp.getEntry().size()); + assertEquals(CustomTypeDstu3Test.MyCustomPatient.class, resp.getEntry().get(0).getResource().getClass()); + assertEquals("http://example.com/fhir/Patient", capt.getAllValues().get(0).getURI().toASCIIString()); + } + @Test public void testUpdateById() throws Exception { IParser p = ourCtx.newXmlParser(); @@ -414,18 +438,181 @@ public class GenericClientDstu3Test { assertThat(body, containsString("")); } + @Test + public void testUpdateWithPreferRepresentationServerReturnsOO() throws Exception { + final IParser p = ourCtx.newXmlParser(); + + final OperationOutcome resp0 = new OperationOutcome(); + resp0.getText().setDivAsString("OK!"); + + final Patient resp1 = new Patient(); + resp1.getText().setDivAsString("FINAL VALUE"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + if (myAnswerCount++ == 0) { + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp0)), Charset.forName("UTF-8")); + } else { + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + } + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.setId("Patient/222"); + pt.getText().setDivAsString("A PATIENT"); + + MethodOutcome outcome = client.update().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute(); + + assertEquals(2, myAnswerCount); + assertNotNull(outcome.getOperationOutcome()); + assertNotNull(outcome.getResource()); + + assertEquals("
OK!
", ((OperationOutcome) outcome.getOperationOutcome()).getText().getDivAsString()); + assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); + + assertEquals(myAnswerCount, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient/222", capt.getAllValues().get(0).getURI().toASCIIString()); + assertEquals("http://foo.com/base/Patient/222/_history/3", capt.getAllValues().get(1).getURI().toASCIIString()); + } + + @Test + public void testUpdateWithPreferRepresentationServerReturnsResource() throws Exception { + final IParser p = ourCtx.newXmlParser(); + + final Patient resp1 = new Patient(); + resp1.getText().setDivAsString("FINAL VALUE"); + + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getAllHeaders()).thenAnswer(new Answer() { + @Override + public Header[] answer(InvocationOnMock theInvocation) throws Throwable { + return new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "http://foo.com/base/Patient/222/_history/3") }; + } + }); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + myAnswerCount++; + return new ReaderInputStream(new StringReader(p.encodeResourceToString(resp1)), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Patient pt = new Patient(); + pt.setId("Patient/222"); + pt.getText().setDivAsString("A PATIENT"); + + MethodOutcome outcome = client.update().resource(pt).prefer(PreferReturnEnum.REPRESENTATION).execute(); + + assertEquals(1, myAnswerCount); + assertNull(outcome.getOperationOutcome()); + assertNotNull(outcome.getResource()); + + assertEquals("
FINAL VALUE
", ((Patient) outcome.getResource()).getText().getDivAsString()); + + assertEquals(myAnswerCount, capt.getAllValues().size()); + assertEquals("http://example.com/fhir/Patient/222", capt.getAllValues().get(0).getURI().toASCIIString()); + } + + @Test + public void testUserAgentForBinary() throws Exception { + IParser p = ourCtx.newXmlParser(); + + Conformance conf = new Conformance(); + conf.setCopyright("COPY"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + Binary bin = new Binary(); + bin.setContentType("application/foo"); + bin.setContent(new byte[] { 0, 1, 2, 3, 4 }); + client.create().resource(bin).execute(); + + ourLog.info(Arrays.asList(capt.getAllValues().get(0).getAllHeaders()).toString()); + + assertEquals("http://example.com/fhir/Binary", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + + assertEquals("application/foo", capt.getAllValues().get(0).getHeaders("Content-Type")[0].getValue()); + assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON, capt.getAllValues().get(0).getHeaders("Accept")[0].getValue()); + assertArrayEquals(new byte[] { 0, 1, 2, 3, 4 }, extractBodyAsByteArray(capt)); + + } + + @Test + public void testUserAgentForConformance() throws Exception { + IParser p = ourCtx.newXmlParser(); + + Conformance conf = new Conformance(); + conf.setCopyright("COPY"); + + final String respString = p.encodeResourceToString(conf); + ArgumentCaptor capt = ArgumentCaptor.forClass(HttpUriRequest.class); + when(myHttpClient.execute(capt.capture())).thenReturn(myHttpResponse); + when(myHttpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK")); + when(myHttpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8")); + when(myHttpResponse.getEntity().getContent()).thenAnswer(new Answer() { + @Override + public ReaderInputStream answer(InvocationOnMock theInvocation) throws Throwable { + return new ReaderInputStream(new StringReader(respString), Charset.forName("UTF-8")); + } + }); + + IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); + + client.fetchConformance().ofType(Conformance.class).execute(); + assertEquals("http://example.com/fhir/metadata", capt.getAllValues().get(0).getURI().toASCIIString()); + validateUserAgent(capt); + } + + private List> toTypeList(Class theClass) { + ArrayList> retVal = new ArrayList>(); + retVal.add(theClass); + return retVal; + } + private void validateUserAgent(ArgumentCaptor capt) { assertEquals(1, capt.getAllValues().get(0).getHeaders("User-Agent").length); assertEquals(expectedUserAgent(), capt.getAllValues().get(0).getHeaders("User-Agent")[0].getValue()); } - private String expectedUserAgent() { - return "HAPI-FHIR/" + VersionUtil.getVersion() + " (FHIR Client; FHIR DSTU3; apache)"; + @AfterClass + public static void afterClassClearContext() { + TestUtil.clearAllStaticFieldsForUnitTest(); } @BeforeClass public static void beforeClass() { ourCtx = FhirContext.forDstu3(); } - } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index ee5bfa8be26..7c823371a26 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -56,6 +56,13 @@ if a resource being saved contained references that had a display value but not an actual reference. Thanks to David Hay for reporting! + + When performing a REST Client create or update with + Prefer: return=representation]]> set, + if the server does not honour the Prefer header, the client + will automatically fetch the resource before returning. Thanks + to Ewout Kramer for the idea! +