Add unit tests and documentation to the JAX-RS client implementation

This commit is contained in:
James Agnew 2016-03-25 19:17:41 +01:00
parent f9fa6265df
commit f49130baf8
17 changed files with 2596 additions and 165 deletions

View File

@ -40,6 +40,11 @@
<artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
<version>1.5-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jaxrsserver-base</artifactId>
<version>1.5-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>

View File

@ -0,0 +1,38 @@
package example;
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jaxrs.client.JaxRsRestfulClientFactory;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IResourceProvider;
@SuppressWarnings(value= {"serial"})
public class JaxRsClient {
public static void main(String[] args) {
//START SNIPPET: createClient
// Create a client
FhirContext ctx = FhirContext.forDstu2();
// Create an instance of the JAX RS client factory and
// set it on the context
JaxRsRestfulClientFactory clientFactory = new JaxRsRestfulClientFactory(ctx);
ctx.setRestfulClientFactory(clientFactory);
// This client uses JAX-RS!
IGenericClient client = ctx.newRestfulGenericClient("http://fhirtest.uhn.ca/baseDstu2");
//END SNIPPET: createClient
}
}

View File

@ -570,6 +570,7 @@ public class FhirContext {
* @param theRestfulClientFactory
*/
public void setRestfulClientFactory(IRestfulClientFactory theRestfulClientFactory) {
Validate.notNull(theRestfulClientFactory, "theRestfulClientFactory must not be null");
this.myRestfulClientFactory = theRestfulClientFactory;
}

View File

@ -96,7 +96,7 @@ public interface IRestfulClientFactory {
IHttpClient getHttpClient(StringBuilder theUrl, Map<String, List<String>> theIfNoneExistParams, String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders);
/**
* @deprecated Use {@link #getServerValidationMode()} instead
* @deprecated Use {@link #getServerValidationMode()} instead (this method is a synonym for that method, but this method is poorly named and will be removed at some point)
*/
@Deprecated
ServerValidationModeEnum getServerValidationModeEnum();

View File

@ -150,10 +150,11 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
*/
@Override
public synchronized <T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase) {
validateConfigured();
if (!theClientType.isInterface()) {
throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface");
}
ClientInvocationHandlerFactory invocationHandler = myInvocationHandlers.get(theClientType);
if (invocationHandler == null) {
@ -171,8 +172,20 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
return proxy;
}
/**
* Called automatically before the first use of this factory to ensure that
* the configuration is sane. Subclasses may override, but should also call
* <code>super.validateConfigured()</code>
*/
protected void validateConfigured() {
if (getFhirContext() == null) {
throw new IllegalStateException(getClass().getSimpleName() + " does not have FhirContext defined. This must be set via " + getClass().getSimpleName() + "#setFhirContext(FhirContext)");
}
}
@Override
public synchronized IGenericClient newGenericClient(String theServerBase) {
validateConfigured();
IHttpClient httpClient = getHttpClient(theServerBase);
return new GenericClient(myContext, httpClient, theServerBase, this);
}

View File

@ -57,6 +57,17 @@
<scope>provided</scope>
</dependency>
<!-- Unit test dependencies -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId>

View File

@ -87,7 +87,7 @@ public class JaxRsHttpClient implements IHttpClient {
}
Entity<Form> entity = Entity.form(map);
JaxRsHttpRequest retVal = createHttpRequest(entity);
addHeadersToRequest(retVal, null, theContext);
addHeadersToRequest(retVal, theEncoding, theContext);
return retVal;
}

View File

@ -59,6 +59,9 @@ public class JaxRsHttpResponse implements IHttpResponse {
@Override
public String getMimeType() {
MediaType mediaType = myResponse.getMediaType();
if (mediaType == null) {
return null;
}
//Keep only type and subtype and do not include the parameters such as charset
return new MediaType(mediaType.getType(), mediaType.getSubtype()).toString();
}

View File

@ -41,14 +41,17 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
private Client myNativeClient;
/**
* Constructor
* Constructor. Note that you must set the {@link FhirContext} manually using {@link #setFhirContext(FhirContext)} if this constructor is used!
*/
public JaxRsRestfulClientFactory() {
super();
}
/**
* Constructor
* @param theFhirContext The context
*
* @param theFhirContext
* The context
*/
public JaxRsRestfulClientFactory(FhirContext theFhirContext) {
super(theFhirContext);
@ -64,10 +67,9 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
}
@Override
public IHttpClient getHttpClient(StringBuilder url, Map<String, List<String>> theIfNoneExistParams,
String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) {
return new JaxRsHttpClient(getNativeClientClient(), url, theIfNoneExistParams, theIfNoneExistString, theRequestType,
theHeaders);
public IHttpClient getHttpClient(StringBuilder url, Map<String, List<String>> theIfNoneExistParams, String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) {
Client client = getNativeClientClient();
return new JaxRsHttpClient(client, url, theIfNoneExistParams, theIfNoneExistString, theRequestType, theHeaders);
}
@Override
@ -77,6 +79,7 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
/**
* Only accept clients of type javax.ws.rs.client.Client
*
* @param theHttpClient
*/
@Override

View File

@ -0,0 +1,69 @@
<Bundle xmlns="http://hl7.org/fhir">
<id value="487d3669-0e1c-4867-b124-400d1849548d"></id>
<type value="searchset"></type>
<base value="http://localhost:19080/fhir/dstu1"></base>
<link>
<relation value="just trying add link"></relation>
<url value="blarion"></url>
</link>
<link>
<relation value="self"></relation>
<url value="http://localhost:19080/fhir/dstu1/Observation?subject.identifier=puppet|CLONE-AA102"></url>
</link>
<entry>
<link>
<relation value="orionhealth.edit"></relation>
<url value="Observation"></url>
</link>
<resource>
<Observation xmlns="http://hl7.org/fhir">
<id value="0d87f02c-da2c-4551-9ead-2956f0165a4f"></id>
<extension url="http://orionhealth.com/fhir/extensions#created-by"></extension>
<extension url="http://orionhealth.com/fhir/extensions#last-modified-by"></extension>
<extension url="http://orionhealth.com/fhir/extensions#received-instant">
<valueInstant value="2015-06-25T17:08:47.190+12:00"></valueInstant>
</extension>
<code>
<coding>
<system value="http://loinc.org"></system>
<code value="8867-4"></code>
</coding>
</code>
<valueString value="observationValue"></valueString>
<status value="final"></status>
<reliability value="ok"></reliability>
<subject>
<reference value="Patient/INGE6TSFFVAUCMJQGJAHA5LQOBSXI"></reference>
</subject>
</Observation>
</resource>
</entry>
<entry>
<link>
<relation value="orionhealth.edit"></relation>
<url value="Observation"></url>
</link>
<resource>
<Observation xmlns="http://hl7.org/fhir">
<id value="c54ac0cc-a99f-40aa-9541-c5aa853a2e88"></id>
<extension url="http://orionhealth.com/fhir/extensions#created-by"></extension>
<extension url="http://orionhealth.com/fhir/extensions#last-modified-by"></extension>
<extension url="http://orionhealth.com/fhir/extensions#received-instant">
<valueInstant value="2015-06-25T17:08:47.190+12:00"></valueInstant>
</extension>
<code>
<coding>
<system value="http://loinc.org"></system>
<code value="3141-9"></code>
</coding>
</code>
<valueString value="observationValue"></valueString>
<status value="final"></status>
<reliability value="ok"></reliability>
<subject>
<reference value="Patient/INGE6TSFFVAUCMJQGJAHA5LQOBSXI"></reference>
</subject>
</Observation>
</resource>
</entry>
</Bundle>

View File

@ -68,6 +68,7 @@ import ca.uhn.fhir.parser.XmlParserDstu2Test;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.SummaryEnum;
import ca.uhn.fhir.rest.client.apache.ApacheRestfulClientFactory;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.method.SearchStyleEnum;
@ -88,6 +89,7 @@ public class GenericClientDstu2Test {
@Before
public void before() {
myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
ourCtx.setRestfulClientFactory(new ApacheRestfulClientFactory(ourCtx));
ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
@ -99,6 +101,7 @@ public class GenericClientDstu2Test {
return body;
}
private String getPatientFeedWithOneResult() {
//@formatter:off
String msg = "<Bundle xmlns=\"http://hl7.org/fhir\">\n" +
@ -1079,13 +1082,8 @@ public class GenericClientDstu2Test {
private static final long serialVersionUID = 1L;
@Override
public boolean isEmpty() {
return false;
}
@Override
public boolean hasFormatComment() {
return false;
public List<String> getFormatCommentsPost() {
return null;
}
@Override
@ -1094,8 +1092,13 @@ public class GenericClientDstu2Test {
}
@Override
public List<String> getFormatCommentsPost() {
return null;
public boolean hasFormatComment() {
return false;
}
@Override
public boolean isEmpty() {
return false;
}
};
@ -1109,60 +1112,6 @@ public class GenericClientDstu2Test {
//@formatter:on
}
@Test
public void testOperationWithProfiledDatatypeParam() throws IOException, Exception {
IParser p = ourCtx.newXmlParser();
Parameters outParams = new Parameters();
outParams.addParameter().setValue(new StringDt("STRINGVALOUT1"));
outParams.addParameter().setValue(new StringDt("STRINGVALOUT2"));
final String respString = p.encodeResourceToString(outParams);
ArgumentCaptor<HttpUriRequest> 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<ReaderInputStream>() {
@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");
int idx = 0;
//@formatter:off
client
.operation()
.onInstance(new IdDt("http://foo/Patient/1"))
.named("validate-code")
.withParameter(Parameters.class, "code", new CodeDt("8495-4"))
.andParameter("system", new UriDt("http://loinc.org"))
.useHttpGet()
.execute();
//@formatter:off
assertEquals("http://example.com/fhir/Patient/1/$validate-code?code=8495-4&system=http%3A%2F%2Floinc.org", capt.getAllValues().get(idx).getURI().toASCIIString());
//@formatter:off
idx++;
client
.operation()
.onInstance(new IdDt("http://foo/Patient/1"))
.named("validate-code")
.withParameter(Parameters.class, "code", new CodeDt("8495-4"))
.andParameter("system", new UriDt("http://loinc.org"))
.execute();
//@formatter:off
assertEquals("http://example.com/fhir/Patient/1/$validate-code", capt.getAllValues().get(idx).getURI().toASCIIString());
ourLog.info(extractBody(capt, idx));
assertEquals("<Parameters xmlns=\"http://hl7.org/fhir\"><parameter><name value=\"code\"/><valueCode value=\"8495-4\"/></parameter><parameter><name value=\"system\"/><valueUri value=\"http://loinc.org\"/></parameter></Parameters>",extractBody(capt, idx));
}
@Test
public void testOperationWithListOfParameterResponse() throws Exception {
IParser p = ourCtx.newXmlParser();
@ -1327,6 +1276,60 @@ public class GenericClientDstu2Test {
idx++;
}
@Test
public void testOperationWithProfiledDatatypeParam() throws IOException, Exception {
IParser p = ourCtx.newXmlParser();
Parameters outParams = new Parameters();
outParams.addParameter().setValue(new StringDt("STRINGVALOUT1"));
outParams.addParameter().setValue(new StringDt("STRINGVALOUT2"));
final String respString = p.encodeResourceToString(outParams);
ArgumentCaptor<HttpUriRequest> 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<ReaderInputStream>() {
@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");
int idx = 0;
//@formatter:off
client
.operation()
.onInstance(new IdDt("http://foo/Patient/1"))
.named("validate-code")
.withParameter(Parameters.class, "code", new CodeDt("8495-4"))
.andParameter("system", new UriDt("http://loinc.org"))
.useHttpGet()
.execute();
//@formatter:off
assertEquals("http://example.com/fhir/Patient/1/$validate-code?code=8495-4&system=http%3A%2F%2Floinc.org", capt.getAllValues().get(idx).getURI().toASCIIString());
//@formatter:off
idx++;
client
.operation()
.onInstance(new IdDt("http://foo/Patient/1"))
.named("validate-code")
.withParameter(Parameters.class, "code", new CodeDt("8495-4"))
.andParameter("system", new UriDt("http://loinc.org"))
.execute();
//@formatter:off
assertEquals("http://example.com/fhir/Patient/1/$validate-code", capt.getAllValues().get(idx).getURI().toASCIIString());
ourLog.info(extractBody(capt, idx));
assertEquals("<Parameters xmlns=\"http://hl7.org/fhir\"><parameter><name value=\"code\"/><valueCode value=\"8495-4\"/></parameter><parameter><name value=\"system\"/><valueUri value=\"http://loinc.org\"/></parameter></Parameters>",extractBody(capt, idx));
}
@Test
public void testPageNext() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
@ -1424,6 +1427,21 @@ public class GenericClientDstu2Test {
}
@Test
public void testProviderWhereWeForgotToSetTheContext() throws Exception {
ApacheRestfulClientFactory clientFactory = new ApacheRestfulClientFactory(); // no ctx
clientFactory.setServerValidationMode(ServerValidationModeEnum.NEVER);
ourCtx.setRestfulClientFactory(clientFactory);
try {
ourCtx.newRestfulGenericClient("http://localhost:8080/fhir");
fail();
} catch (IllegalStateException e) {
assertEquals("ApacheRestfulClientFactory does not have FhirContext defined. This must be set via ApacheRestfulClientFactory#setFhirContext(FhirContext)", e.getMessage());
}
}
@Test
public void testReadByUri() throws Exception {
@ -1605,6 +1623,88 @@ public class GenericClientDstu2Test {
}
@Test
public void testSearchByPost() throws Exception {
String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<HttpUriRequest> 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_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle response = client.search()
.forResource("Patient")
.where(Patient.NAME.matches().value("james"))
.elementsSubset("name", "identifier")
.usingStyle(SearchStyleEnum.POST)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/_search?_elements=identifier%2Cname", capt.getValue().getURI().toString());
// assertThat(capt.getValue().getURI().toString(),
// either(equalTo("http://example.com/fhir/Patient?name=james&_elements=name%2Cidentifier")).or(equalTo("http://example.com/fhir/Patient?name=james&_elements=identifier%2Cname")));
assertEquals(Patient.class, response.getEntries().get(0).getResource().getClass());
ourLog.info(Arrays.asList(capt.getValue().getAllHeaders()).toString());
ourLog.info(capt.getValue().toString());
HttpEntityEnclosingRequestBase v = (HttpEntityEnclosingRequestBase) capt.getValue();
String req = IOUtils.toString(v.getEntity().getContent(), "UTF-8");
assertEquals("name=james", req);
assertEquals("application/x-www-form-urlencoded;charset=utf-8", v.getEntity().getContentType().getValue().replace(" ", "").toLowerCase());
assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON, capt.getValue().getFirstHeader("accept").getValue());
assertThat(capt.getValue().getFirstHeader("user-agent").getValue(), not(emptyString()));
}
@Test
public void testSearchByPostUseJson() throws Exception {
String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<HttpUriRequest> 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_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle response = client.search()
.forResource("Patient")
.where(Patient.NAME.matches().value("james"))
.elementsSubset("name", "identifier")
.usingStyle(SearchStyleEnum.POST)
.encodedJson()
.execute();
//@formatter:on
assertThat(capt.getValue().getURI().toString(), containsString("http://example.com/fhir/Patient/_search?"));
assertThat(capt.getValue().getURI().toString(), containsString("_elements=identifier%2Cname"));
assertThat(capt.getValue().getURI().toString(), containsString("_format=json"));
// assertThat(capt.getValue().getURI().toString(),
// either(equalTo("http://example.com/fhir/Patient?name=james&_elements=name%2Cidentifier")).or(equalTo("http://example.com/fhir/Patient?name=james&_elements=identifier%2Cname")));
assertEquals(Patient.class, response.getEntries().get(0).getResource().getClass());
ourLog.info(Arrays.asList(capt.getValue().getAllHeaders()).toString());
ourLog.info(capt.getValue().toString());
HttpEntityEnclosingRequestBase v = (HttpEntityEnclosingRequestBase) capt.getValue();
String req = IOUtils.toString(v.getEntity().getContent(), "UTF-8");
assertEquals("name=james", req);
assertEquals("application/x-www-form-urlencoded;charset=utf-8", v.getEntity().getContentType().getValue().replace(" ", "").toLowerCase());
assertEquals(Constants.CT_FHIR_JSON, capt.getValue().getFirstHeader("accept").getValue());
}
@Test
public void testSearchByString() throws Exception {
String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
@ -1765,88 +1865,6 @@ public class GenericClientDstu2Test {
}
@Test
public void testSearchByPost() throws Exception {
String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<HttpUriRequest> 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_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle response = client.search()
.forResource("Patient")
.where(Patient.NAME.matches().value("james"))
.elementsSubset("name", "identifier")
.usingStyle(SearchStyleEnum.POST)
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/_search?_elements=identifier%2Cname", capt.getValue().getURI().toString());
// assertThat(capt.getValue().getURI().toString(),
// either(equalTo("http://example.com/fhir/Patient?name=james&_elements=name%2Cidentifier")).or(equalTo("http://example.com/fhir/Patient?name=james&_elements=identifier%2Cname")));
assertEquals(Patient.class, response.getEntries().get(0).getResource().getClass());
ourLog.info(Arrays.asList(capt.getValue().getAllHeaders()).toString());
ourLog.info(capt.getValue().toString());
HttpEntityEnclosingRequestBase v = (HttpEntityEnclosingRequestBase) capt.getValue();
String req = IOUtils.toString(v.getEntity().getContent(), "UTF-8");
assertEquals("name=james", req);
assertEquals("application/x-www-form-urlencoded;charset=utf-8", v.getEntity().getContentType().getValue().replace(" ", "").toLowerCase());
assertEquals(Constants.HEADER_ACCEPT_VALUE_XML_OR_JSON, capt.getValue().getFirstHeader("accept").getValue());
assertThat(capt.getValue().getFirstHeader("user-agent").getValue(), not(emptyString()));
}
@Test
public void testSearchByPostUseJson() throws Exception {
String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";
ArgumentCaptor<HttpUriRequest> 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_JSON + "; charset=UTF-8"));
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle response = client.search()
.forResource("Patient")
.where(Patient.NAME.matches().value("james"))
.elementsSubset("name", "identifier")
.usingStyle(SearchStyleEnum.POST)
.encodedJson()
.execute();
//@formatter:on
assertThat(capt.getValue().getURI().toString(), containsString("http://example.com/fhir/Patient/_search?"));
assertThat(capt.getValue().getURI().toString(), containsString("_elements=identifier%2Cname"));
assertThat(capt.getValue().getURI().toString(), containsString("_format=json"));
// assertThat(capt.getValue().getURI().toString(),
// either(equalTo("http://example.com/fhir/Patient?name=james&_elements=name%2Cidentifier")).or(equalTo("http://example.com/fhir/Patient?name=james&_elements=identifier%2Cname")));
assertEquals(Patient.class, response.getEntries().get(0).getResource().getClass());
ourLog.info(Arrays.asList(capt.getValue().getAllHeaders()).toString());
ourLog.info(capt.getValue().toString());
HttpEntityEnclosingRequestBase v = (HttpEntityEnclosingRequestBase) capt.getValue();
String req = IOUtils.toString(v.getEntity().getContent(), "UTF-8");
assertEquals("name=james", req);
assertEquals("application/x-www-form-urlencoded;charset=utf-8", v.getEntity().getContentType().getValue().replace(" ", "").toLowerCase());
assertEquals(Constants.CT_FHIR_JSON, capt.getValue().getFirstHeader("accept").getValue());
}
@Test
public void testSearchWithLastUpdated() throws Exception {
String msg = "{\"resourceType\":\"Bundle\",\"id\":null,\"base\":\"http://localhost:57931/fhir/contextDev\",\"total\":1,\"link\":[{\"relation\":\"self\",\"url\":\"http://localhost:57931/fhir/contextDev/Patient?identifier=urn%3AMultiFhirVersionTest%7CtestSubmitPatient01&_format=json\"}],\"entry\":[{\"resource\":{\"resourceType\":\"Patient\",\"id\":\"1\",\"meta\":{\"versionId\":\"1\",\"lastUpdated\":\"2014-12-20T18:41:29.706-05:00\"},\"identifier\":[{\"system\":\"urn:MultiFhirVersionTest\",\"value\":\"testSubmitPatient01\"}]}}]}";

View File

@ -304,7 +304,7 @@
as well as a Maven plugin, and to use external
sources. Thanks to Bill Denton for the pull
request!
</actiom>
</action>
<action type="fix">
JPA server now allows searching by token
parameter using a system only and no code,
@ -314,6 +314,19 @@
was not clear in the spec and HAPI had different
behaviour from the other reference servers.
</action>
<action type="add">
Introduce a JAX-RS client provider which can be used instead of the
default Apache HTTP Client provider to provide low level HTTP
services to HAPI's REST client. See
<![CDATA[<a href="./doc_rest_client_alternate_provider.html">JAX-RS &amp; Alternate HTTP Client Providers</a>]]>
for more information.
<![CDATA[<br/><br/>]]>
This is useful in cases where you have other non-FHIR REST clients
using a JAX-RS provider and want to take advantage of the
rest of the framework.
<![CDATA[<br/><br/>]]>
Thanks to Peter Van Houte from Agfa for the amazing work!
</action>
</release>
<release version="1.4" date="2016-02-04">
<action type="add">
@ -362,7 +375,7 @@
<action type="add" issue="251">
Introduce a JAX-RS version of the REST server, which can be used
to deploy the same resource provider implementations which work
on the existing REST server into a JAX-RS (Jersey) environment.
on the existing REST server into a JAX-RS (e.g. Jersey) environment.
Thanks to Peter Van Houte from Agfa for the amazing work!
</action>
<action type="add">

View File

@ -91,6 +91,7 @@
<item name="Interceptors (client)" href="./doc_rest_client_interceptor.html"/>
<item name="Client HTTP Configuration" href="./doc_rest_client_http_config.html"/>
<item name="Client Examples" href="./doc_rest_client_examples.html"/>
<item name="JAX-RS Client &amp; Alternate HTTP Providers" href="./doc_rest_client_alternate_provider.html"/>
</item>
<item name="RESTful Server" href="./doc_rest_server.html" >
<item name="Using RESTful Server" href="./doc_rest_server.html" />

View File

@ -0,0 +1,58 @@
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
<properties>
<title>JAX-RS &amp; Alternate HTTP Client Providers</title>
<author email="jamesagnew@users.sourceforge.net">James Agnew</author>
</properties>
<body>
<section name="JAX-RS &amp; Alternate HTTP Client Providers">
<p>
By default, the HAPI FHIR client uses the
<a href="http://hc.apache.org/httpcomponents-client-ga/">Apache HTTP Client (HC)</a>
as it's underlying HTTP provider. HC is a very powerful and efficient provider,
so it is generally a good choice.
</p>
<p>
It can be replaced however by providing an alternate implementation of
<code><a href="./apidocs/ca/uhn/fhir/rest/client/IRestfulClientFactory.html">IRestfulClientFactory</a></code>
to the <code>FhirContext</code>.
</p>
<subsection name="JAX-RS">
<p>
If you are using HAPI FHIR's client in an environment where other
JAX-RS clients are being used, you may want to use the JAX-RS provider
instead of the Apache HC provider.
</p>
<p>
Using this provider is as simple as creating an instance and providing it
to the context:
</p>
<macro name="snippet">
<param name="id" value="createClient"/>
<param name="file" value="examples/src/main/java/example/JaxRsClient.java"/>
</macro>
<p>
Note that this provider is defined in the JAX-RS Server module, so you need
to add the following dependency to your project in order for this to work:
</p>
<pre><![CDATA[<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jaxrsserver-base</artifactId>
<version>[version]</version>
</dependency>]]></pre>
</subsection>
</section>
</body>
</document>

View File

@ -22,16 +22,15 @@
</p>
<p>
In many cases, the default configuration should suffice. However,
if you require anything
more sophisticated (username/password, HTTP
proxy settings, etc.) you will need
to configure the underlying
client.
In many cases, the default configuration should suffice. HAPI FHIR
also encapsulates some of the more common configuration settings you
might want to use (socket timesouts, proxy settings, etc.) so that these
can be configured through HAPI's API without needing to understand the
underlying HTTP Client library.
</p>
<p>
The underlying client configuration is provided by accessing the
This configuration is provided by accessing the
<a href="./apidocs/ca/uhn/fhir/rest/client/IRestfulClientFactory.html">IRestfulClientFactory</a>
class from the FhirContext.
</p>
@ -39,8 +38,10 @@
<p>
Note that individual requests and responses
can be tweaked using
<a href="./doc_rest_client_interceptor.html">Client Interceptors</a>
.
<a href="./doc_rest_client_interceptor.html">Client Interceptors.</a>
This approach is generally useful for configuration involving
tweaking the HTTP request/response, such as adding authorization headers
or logging.
</p>
<subsection name="Setting Socket Timeouts">

View File

@ -36,6 +36,7 @@
<li><a href="./doc_rest_client_interceptor.html">Interceptors (client)</a></li>
<li><a href="./doc_rest_client_http_config.html">Client HTTP Configuration</a></li>
<li><a href="./doc_rest_client_examples.html">Client Examples</a></li>
<li><a href="./doc_rest_client_alternate_provider.html">JAX-RS Client &amp; Alternate HTTP Providers</a></li>
</ul>
<h4>RESTful Server</h4>