Get fluent client working

This commit is contained in:
jamesagnew 2014-05-08 17:52:36 -04:00
parent 2b683b8f02
commit e66bd31171
14 changed files with 394 additions and 42 deletions

View File

@ -61,6 +61,7 @@ import ca.uhn.fhir.model.dstu.valueset.OrganizationTypeEnum;
import ca.uhn.fhir.model.primitive.BooleanDt;
import ca.uhn.fhir.model.primitive.BoundCodeableConceptDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.gclient.StringParam;
/**
@ -97,6 +98,8 @@ public class Organization extends BaseResource implements IResource {
@SearchParamDefinition(name="name", path="Organization.name", description="A portion of the organization's name")
public static final String SP_NAME = "name";
public static final StringParam NAME = new StringParam(SP_NAME);
/**
* Search parameter constant for <b>phonetic</b>
* <p>

View File

@ -72,7 +72,9 @@ import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.rest.gclient.DateParam;
import ca.uhn.fhir.rest.gclient.Include;
import ca.uhn.fhir.rest.gclient.ReferenceParam;
import ca.uhn.fhir.rest.gclient.StringParam;
import ca.uhn.fhir.rest.gclient.TokenParam;
/**
@ -264,8 +266,9 @@ public class Patient extends BaseResource implements IResource {
public static final String SP_LINK = "link";
public static final StringParam PARAM_NAME = new StringParam(SP_NAME);
public static final TokenParam PARAM_IDENTIFIER = new TokenParam(SP_IDENTIFIER);
public static final DateParam PARAM_BIRTHDATE = new DateParam(SP_BIRTHDATE);
public static final ReferenceParam PARAM_PROVIDER = new ReferenceParam(SP_PROVIDER);
public static final Include INCLUDE_MANAGINGORGANIZATION = new Include("Patient.managingOrganization");

View File

@ -107,12 +107,21 @@ public abstract class BaseClient {
}
Object invokeClient(IClientResponseHandler binding, BaseClientInvocation clientInvocation) {
return invokeClient(binding, clientInvocation, false);
}
Object invokeClient(IClientResponseHandler binding, BaseClientInvocation clientInvocation, boolean theLogRequestAndResponse) {
// TODO: handle non 2xx status codes by throwing the correct exception,
// and ensure it's passed upwards
HttpRequestBase httpRequest;
HttpResponse response;
try {
httpRequest = clientInvocation.asHttpRequest(myUrlBase, createExtraParams(), getEncoding());
if (theLogRequestAndResponse) {
ourLog.info("Client invoking: {}", httpRequest);
}
response = myClient.execute(httpRequest);
} catch (DataFormatException e) {
throw new FhirClientConnectionException(e);

View File

@ -305,6 +305,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
private String myResourceName;
private Class<? extends IResource> myResourceType;
private List<SortInternal> mySort = new ArrayList<SortInternal>();
private boolean myLogRequestAndResponse;
public ForInternal(Class<? extends IResource> theResourceType) {
myResourceType = theResourceType;
@ -380,7 +381,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
};
Bundle resp = (Bundle) invokeClient(binding, invocation);
Bundle resp = (Bundle) invokeClient(binding, invocation, myLogRequestAndResponse);
return resp;
}
@ -421,6 +422,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
params.get(parameterName).add(parameterValue);
}
@Override
public IFor andLogRequestAndResponse(boolean theLogRequestAndResponse) {
myLogRequestAndResponse =theLogRequestAndResponse;
return this;
}
}
private class QueryInternal implements IQuery {

View File

@ -5,6 +5,9 @@ import java.util.Date;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt;
/**
* Date parameter type for use in fluent client interfaces
*/
public class DateParam implements IParam {
private String myParamName;

View File

@ -19,5 +19,11 @@ public interface IFor {
ISort sort();
IFor limitTo(int theLimitTo);
/**
* If set to true, the client will log the request and response to the SLF4J logger. This
* can be useful for debugging, but is generally not desirable in a production situation.
*/
IFor andLogRequestAndResponse(boolean theLogRequestAndResponse);
}

View File

@ -0,0 +1,51 @@
package ca.uhn.fhir.rest.gclient;
public class ReferenceParam implements IParam {
private String myName;
public ReferenceParam(String theName) {
myName = theName;
}
@Override
public String getParamName() {
return myName;
}
public ICriterion hasChainedProperty(ICriterion theICriterion) {
return new ReferenceChainCriterion(getParamName(), theICriterion);
}
/**
* Match the referenced resource if the resource has the given ID (this can be
* the logical ID or the absolute URL of the resource)
*/
public ICriterion hasId(String theId) {
return new StringCriterion(getParamName(), theId);
}
private static class ReferenceChainCriterion implements ICriterion, ICriterionInternal {
private ICriterionInternal myWrappedCriterion;
private String myParamName;
public ReferenceChainCriterion(String theParamName, ICriterion theWrappedCriterion) {
myParamName = theParamName;
myWrappedCriterion = (ICriterionInternal) theWrappedCriterion;
}
@Override
public String getParameterValue() {
return myWrappedCriterion.getParameterValue();
}
@Override
public String getParameterName() {
return myParamName + "." + myWrappedCriterion.getParameterName();
}
}
}

View File

@ -0,0 +1,23 @@
package ca.uhn.fhir.rest.gclient;
class StringCriterion implements ICriterion, ICriterionInternal {
private String myValue;
private String myName;
public StringCriterion(String theName, String theValue) {
myValue = theValue;
myName=theName;
}
@Override
public String getParameterName() {
return myName;
}
@Override
public String getParameterValue() {
return myValue;
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.gclient;
import ca.uhn.fhir.rest.server.Constants;
public class StringParam implements IParam {
private String myParamName;
@ -8,46 +10,44 @@ public class StringParam implements IParam {
myParamName = theParamName;
}
public IStringExactly exactly() {
/**
* The string matches exactly the given value
*/
public IStringMatch matchesExactly() {
return new StringExactly();
}
/**
* The string matches the given value (servers will often, but are not required to) implement this
* as a left match, meaning that a value of "smi" would match "smi" and "smith".
*/
public IStringMatch matches() {
return new StringMatches();
}
@Override
public String getParamName() {
return myParamName;
}
public interface IStringExactly {
public interface IStringMatch {
ICriterion value(String theValue);
}
public class StringExactly implements IStringExactly {
private class StringExactly implements IStringMatch {
@Override
public ICriterion value(String theValue) {
return new EqualsExactlyCriterion(theValue);
return new StringCriterion(getParamName() + Constants.PARAMNAME_SUFFIX_EXACT, theValue);
}
}
private class EqualsExactlyCriterion implements ICriterion, ICriterionInternal {
private String myValue;
public EqualsExactlyCriterion(String theValue) {
myValue = theValue;
}
private class StringMatches implements IStringMatch {
@Override
public String getParameterName() {
return myParamName;
public ICriterion value(String theValue) {
return new StringCriterion(getParamName(), theValue);
}
@Override
public String getParameterValue() {
return myValue;
}
}
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.rest.gclient;
import org.apache.commons.lang3.StringUtils;
class TokenCriterion implements ICriterion, ICriterionInternal {
private String myValue;
private String myName;
public TokenCriterion(String theName, String theSystem, String theCode) {
myName = theName;
if (StringUtils.isNotBlank(theSystem)) {
myValue = theSystem + "|" + StringUtils.defaultString(theCode);
} else {
myValue = StringUtils.defaultString(theCode);
}
}
@Override
public String getParameterValue() {
return myValue;
}
@Override
public String getParameterName() {
return myName;
}
}

View File

@ -0,0 +1,86 @@
package ca.uhn.fhir.rest.gclient;
/**
* Token parameter type for use in fluent client interfaces
*/
public class TokenParam implements IParam {
private String myParamName;
@Override
public String getParamName() {
return myParamName;
}
public TokenParam(String theParamName) {
myParamName = theParamName;
}
public IMatches exactly() {
return new IMatches() {
@Override
public ICriterion systemAndCode(String theSystem, String theCode) {
return new TokenCriterion(getParamName(), theSystem, theCode);
}
@Override
public ICriterion systemAndIdentifier(String theSystem, String theCode) {
return new TokenCriterion(getParamName(), theSystem, theCode);
}
@Override
public ICriterion code(String theCode) {
return new TokenCriterion(getParamName(), null, theCode);
}
@Override
public ICriterion identifier(String theIdentifier) {
return new TokenCriterion(getParamName(), null, theIdentifier);
}
};
}
public interface IMatches {
/**
* Creates a search criterion that matches against the given code system and code
*
* @param theSystem
* The code system (should be a URI)
* @param theCode
* The code
* @return A criterion
*/
ICriterion systemAndCode(String theSystem, String theCode);
/**
* Creates a search criterion that matches against the given system and identifier
*
* @param theSystem
* The code system (should be a URI)
* @param theIdentifier
* The identifier
* @return A criterion
*/
ICriterion systemAndIdentifier(String theSystem, String theIdentifier);
/**
* Creates a search criterion that matches against the given identifier, with no system specified
*
* @param theIdentifier
* The identifier
* @return A criterion
*/
ICriterion identifier(String theIdentifier);
/**
* Creates a search criterion that matches against the given code, with no code system specified
*
* @param theIdentifier
* The identifier
* @return A criterion
*/
ICriterion code(String theIdentifier);
}
}

View File

@ -74,6 +74,7 @@ public class Constants {
public static final String FORMAT_XML = "xml";
public static final String FORMAT_JSON = "json";
public static final String PARAM_INCLUDE = "_include";
public static final String PARAMNAME_SUFFIX_EXACT = ":exact";
static {
Map<String, EncodingEnum> valToEncoding = new HashMap<String, EncodingEnum>();

View File

@ -0,0 +1,29 @@
package example;
import static org.junit.Assert.assertEquals;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.client.IGenericClient;
public class GenericClientExample {
public static void main(String[] args) {
FhirContext ctx = new FhirContext();
IGenericClient client = ctx.newRestfulGenericClient("http://fhir.healthintersections.com.au/open");
Bundle response = client.search()
.forResource(Patient.class)
.where(Patient.PARAM_BIRTHDATE.beforeOrEquals().day("2011-01-01"))
.and(Patient.PARAM_PROVIDER.hasChainedProperty(Organization.NAME.matches().value("Health")))
.andLogRequestAndResponse(true)
.execute();
System.out.println(ctx.newXmlParser().setPrettyPrint(true).encodeBundleToString(response));
}
}

View File

@ -20,23 +20,24 @@ import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.server.Constants;
public class GenericClientTest {
private FhirContext ctx;
private HttpClient httpClient;
private HttpResponse httpResponse;
private FhirContext myCtx;
private HttpClient myHttpClient;
private HttpResponse myHttpResponse;
@Before
public void before() {
ctx = new FhirContext();
myCtx = new FhirContext();
httpClient = mock(HttpClient.class, new ReturnsDeepStubs());
ctx.getRestfulClientFactory().setHttpClient(httpClient);
myHttpClient = mock(HttpClient.class, new ReturnsDeepStubs());
myCtx.getRestfulClientFactory().setHttpClient(myHttpClient);
httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
myHttpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
}
private String getPatientFeedWithOneResult() {
@ -74,24 +75,125 @@ public class GenericClientTest {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
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()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = ctx.newRestfulGenericClient("http://foo");
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle response = client.search()
.forResource("Patient")
.where(Patient.PARAM_NAME.exactly().value("james"))
.where(Patient.PARAM_NAME.matches().value("james"))
.execute();
//@formatter:on
assertEquals("http://foo/Patient?name=james", capt.getValue().getURI().toString());
assertEquals("http://example.com/fhir/Patient?name=james", capt.getValue().getURI().toString());
}
@SuppressWarnings("unused")
@Test
public void testSearchByStringExact() throws Exception {
String msg = getPatientFeedWithOneResult();
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()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle response = client.search()
.forResource("Patient")
.where(Patient.PARAM_NAME.matchesExactly().value("james"))
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?name:exact=james", capt.getValue().getURI().toString());
}
@SuppressWarnings("unused")
@Test
public void testSearchByToken() throws Exception {
String msg = getPatientFeedWithOneResult();
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()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle response = client.search()
.forResource("Patient")
.where(Patient.PARAM_IDENTIFIER.exactly().systemAndCode("http://example.com/fhir", "ZZZ"))
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?identifier=http%3A%2F%2Ffoo%7CZZZ", capt.getValue().getURI().toString());
}
@SuppressWarnings("unused")
@Test
public void testSearchByReferenceSimple() throws Exception {
String msg = getPatientFeedWithOneResult();
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()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle response = client.search()
.forResource("Patient")
.where(Patient.PARAM_PROVIDER.hasId("123"))
.execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient?provider=123", capt.getValue().getURI().toString());
}
@SuppressWarnings("unused")
@Test
public void testSearchByReferenceProperty() throws Exception {
String msg = getPatientFeedWithOneResult();
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()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
//@formatter:off
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
Bundle response = client.search()
.forResource(Patient.class)
.where(Patient.PARAM_PROVIDER.hasChainedProperty(Organization.NAME.matches().value("ORG0")))
.execute();
assertEquals("http://example.com/fhir/Patient?provider.name=ORG0", capt.getValue().getURI().toString());
//@formatter:on
}
@SuppressWarnings("unused")
@Test
public void testSearchByDate() throws Exception {
@ -99,12 +201,12 @@ public class GenericClientTest {
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
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()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
IGenericClient client = ctx.newRestfulGenericClient("http://foo");
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
//@formatter:off
Bundle response = client.search()
@ -119,7 +221,7 @@ public class GenericClientTest {
.execute();
//@formatter:on
assertEquals("http://foo/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_format=json&_count=123", capt.getValue().getURI().toString());
assertEquals("http://example.com/fhir/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization&_sort%3Aasc=birthdate&_sort%3Adesc=name&_format=json&_count=123", capt.getValue().getURI().toString());
}