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> <artifactId>hapi-fhir-validation-resources-dstu2</artifactId>
<version>1.5-SNAPSHOT</version> <version>1.5-SNAPSHOT</version>
</dependency> </dependency>
<dependency>
<groupId>ca.uhn.hapi.fhir</groupId>
<artifactId>hapi-fhir-jaxrsserver-base</artifactId>
<version>1.5-SNAPSHOT</version>
</dependency>
<dependency> <dependency>
<groupId>javax.servlet</groupId> <groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId> <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 * @param theRestfulClientFactory
*/ */
public void setRestfulClientFactory(IRestfulClientFactory theRestfulClientFactory) { public void setRestfulClientFactory(IRestfulClientFactory theRestfulClientFactory) {
Validate.notNull(theRestfulClientFactory, "theRestfulClientFactory must not be null");
this.myRestfulClientFactory = theRestfulClientFactory; 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); 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 @Deprecated
ServerValidationModeEnum getServerValidationModeEnum(); ServerValidationModeEnum getServerValidationModeEnum();

View File

@ -150,10 +150,11 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
*/ */
@Override @Override
public synchronized <T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase) { public synchronized <T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase) {
validateConfigured();
if (!theClientType.isInterface()) { if (!theClientType.isInterface()) {
throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface"); throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface");
} }
ClientInvocationHandlerFactory invocationHandler = myInvocationHandlers.get(theClientType); ClientInvocationHandlerFactory invocationHandler = myInvocationHandlers.get(theClientType);
if (invocationHandler == null) { if (invocationHandler == null) {
@ -171,8 +172,20 @@ public abstract class RestfulClientFactory implements IRestfulClientFactory {
return proxy; 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 @Override
public synchronized IGenericClient newGenericClient(String theServerBase) { public synchronized IGenericClient newGenericClient(String theServerBase) {
validateConfigured();
IHttpClient httpClient = getHttpClient(theServerBase); IHttpClient httpClient = getHttpClient(theServerBase);
return new GenericClient(myContext, httpClient, theServerBase, this); return new GenericClient(myContext, httpClient, theServerBase, this);
} }

View File

@ -57,6 +57,17 @@
<scope>provided</scope> <scope>provided</scope>
</dependency> </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> <dependency>
<groupId>org.eclipse.jetty</groupId> <groupId>org.eclipse.jetty</groupId>
<artifactId>jetty-server</artifactId> <artifactId>jetty-server</artifactId>

View File

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

View File

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

View File

@ -41,14 +41,17 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
private Client myNativeClient; 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() { public JaxRsRestfulClientFactory() {
super();
} }
/** /**
* Constructor * Constructor
* @param theFhirContext The context *
* @param theFhirContext
* The context
*/ */
public JaxRsRestfulClientFactory(FhirContext theFhirContext) { public JaxRsRestfulClientFactory(FhirContext theFhirContext) {
super(theFhirContext); super(theFhirContext);
@ -64,10 +67,9 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
} }
@Override @Override
public IHttpClient getHttpClient(StringBuilder url, Map<String, List<String>> theIfNoneExistParams, public IHttpClient getHttpClient(StringBuilder url, Map<String, List<String>> theIfNoneExistParams, String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) {
String theIfNoneExistString, RequestTypeEnum theRequestType, List<Header> theHeaders) { Client client = getNativeClientClient();
return new JaxRsHttpClient(getNativeClientClient(), url, theIfNoneExistParams, theIfNoneExistString, theRequestType, return new JaxRsHttpClient(client, url, theIfNoneExistParams, theIfNoneExistString, theRequestType, theHeaders);
theHeaders);
} }
@Override @Override
@ -77,6 +79,7 @@ public class JaxRsRestfulClientFactory extends RestfulClientFactory {
/** /**
* Only accept clients of type javax.ws.rs.client.Client * Only accept clients of type javax.ws.rs.client.Client
*
* @param theHttpClient * @param theHttpClient
*/ */
@Override @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.MethodOutcome;
import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.PreferReturnEnum;
import ca.uhn.fhir.rest.api.SummaryEnum; 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.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.method.SearchStyleEnum; import ca.uhn.fhir.rest.method.SearchStyleEnum;
@ -88,6 +89,7 @@ public class GenericClientDstu2Test {
@Before @Before
public void before() { public void before() {
myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs()); myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
ourCtx.setRestfulClientFactory(new ApacheRestfulClientFactory(ourCtx));
ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient); ourCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER);
myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
@ -99,6 +101,7 @@ public class GenericClientDstu2Test {
return body; return body;
} }
private String getPatientFeedWithOneResult() { private String getPatientFeedWithOneResult() {
//@formatter:off //@formatter:off
String msg = "<Bundle xmlns=\"http://hl7.org/fhir\">\n" + String msg = "<Bundle xmlns=\"http://hl7.org/fhir\">\n" +
@ -1079,13 +1082,8 @@ public class GenericClientDstu2Test {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
@Override @Override
public boolean isEmpty() { public List<String> getFormatCommentsPost() {
return false; return null;
}
@Override
public boolean hasFormatComment() {
return false;
} }
@Override @Override
@ -1094,8 +1092,13 @@ public class GenericClientDstu2Test {
} }
@Override @Override
public List<String> getFormatCommentsPost() { public boolean hasFormatComment() {
return null; return false;
}
@Override
public boolean isEmpty() {
return false;
} }
}; };
@ -1109,60 +1112,6 @@ public class GenericClientDstu2Test {
//@formatter:on //@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 @Test
public void testOperationWithListOfParameterResponse() throws Exception { public void testOperationWithListOfParameterResponse() throws Exception {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
@ -1327,6 +1276,60 @@ public class GenericClientDstu2Test {
idx++; 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 @Test
public void testPageNext() throws Exception { public void testPageNext() throws Exception {
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class); 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 @Test
public void testReadByUri() throws Exception { 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 @Test
public void testSearchByString() throws Exception { 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\"}]}}]}"; 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 @Test
public void testSearchWithLastUpdated() throws Exception { 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\"}]}}]}"; 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 as well as a Maven plugin, and to use external
sources. Thanks to Bill Denton for the pull sources. Thanks to Bill Denton for the pull
request! request!
</actiom> </action>
<action type="fix"> <action type="fix">
JPA server now allows searching by token JPA server now allows searching by token
parameter using a system only and no code, parameter using a system only and no code,
@ -314,6 +314,19 @@
was not clear in the spec and HAPI had different was not clear in the spec and HAPI had different
behaviour from the other reference servers. behaviour from the other reference servers.
</action> </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>
<release version="1.4" date="2016-02-04"> <release version="1.4" date="2016-02-04">
<action type="add"> <action type="add">
@ -362,7 +375,7 @@
<action type="add" issue="251"> <action type="add" issue="251">
Introduce a JAX-RS version of the REST server, which can be used Introduce a JAX-RS version of the REST server, which can be used
to deploy the same resource provider implementations which work 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! Thanks to Peter Van Houte from Agfa for the amazing work!
</action> </action>
<action type="add"> <action type="add">

View File

@ -91,6 +91,7 @@
<item name="Interceptors (client)" href="./doc_rest_client_interceptor.html"/> <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 HTTP Configuration" href="./doc_rest_client_http_config.html"/>
<item name="Client Examples" href="./doc_rest_client_examples.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>
<item name="RESTful Server" href="./doc_rest_server.html" > <item name="RESTful Server" href="./doc_rest_server.html" >
<item name="Using 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>
<p> <p>
In many cases, the default configuration should suffice. However, In many cases, the default configuration should suffice. HAPI FHIR
if you require anything also encapsulates some of the more common configuration settings you
more sophisticated (username/password, HTTP might want to use (socket timesouts, proxy settings, etc.) so that these
proxy settings, etc.) you will need can be configured through HAPI's API without needing to understand the
to configure the underlying underlying HTTP Client library.
client.
</p> </p>
<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> <a href="./apidocs/ca/uhn/fhir/rest/client/IRestfulClientFactory.html">IRestfulClientFactory</a>
class from the FhirContext. class from the FhirContext.
</p> </p>
@ -39,8 +38,10 @@
<p> <p>
Note that individual requests and responses Note that individual requests and responses
can be tweaked using 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> </p>
<subsection name="Setting Socket Timeouts"> <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_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_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_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> </ul>
<h4>RESTful Server</h4> <h4>RESTful Server</h4>