From 5ad9e39d7b93d5727bcfb949ccfe26d492755db3 Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Wed, 7 May 2014 18:14:08 -0400 Subject: [PATCH] Fluent interface starting up.. A few bit break right now --- .../uhn/fhir/model/dstu/resource/Patient.java | 9 ++ .../ca/uhn/fhir/model/primitive/StringDt.java | 9 +- .../uhn/fhir/rest/client/GenericClient.java | 103 ++++++++++++++ .../uhn/fhir/rest/client/IGenericClient.java | 3 + .../ca/uhn/fhir/rest/gclient/DateParam.java | 114 ++++++++++++++++ .../ca/uhn/fhir/rest/gclient/ICriterion.java | 5 + .../fhir/rest/gclient/ICriterionInternal.java | 9 ++ .../java/ca/uhn/fhir/rest/gclient/IFor.java | 17 +++ .../java/ca/uhn/fhir/rest/gclient/IQuery.java | 8 ++ .../ca/uhn/fhir/rest/gclient/Include.java | 15 ++ .../ca/uhn/fhir/rest/gclient/StringParam.java | 48 +++++++ .../fhir/rest/client/GenericClientTest.java | 129 ++++++++++++++++++ 12 files changed, 464 insertions(+), 5 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateParam.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterion.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterionInternal.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IFor.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/Include.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringParam.java create mode 100644 hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Patient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Patient.java index 8d1420bab85..8a2cfb5a0d9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Patient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/dstu/resource/Patient.java @@ -70,6 +70,9 @@ import ca.uhn.fhir.model.primitive.BoundCodeableConceptDt; import ca.uhn.fhir.model.primitive.CodeDt; 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.StringParam; /** @@ -260,6 +263,12 @@ public class Patient extends BaseResource implements IResource { @SearchParamDefinition(name="link", path="Patient.link.other", description="All patients linked to the given patient") public static final String SP_LINK = "link"; + public static final StringParam PARAM_NAME = new StringParam(SP_NAME); + + public static final DateParam PARAM_BIRTHDATE = new DateParam(SP_BIRTHDATE); + + public static final Include INCLUDE_MANAGINGORGANIZATION = new Include("managingOrganization"); + @Child(name="identifier", type=IdentifierDt.class, order=0, min=0, max=Child.MAX_UNLIMITED) @Description( diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/StringDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/StringDt.java index 21b0808c854..2a41bbc61bd 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/StringDt.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/StringDt.java @@ -44,8 +44,8 @@ public class StringDt extends BasePrimitive implements IQueryParameterTy * Create a new String */ @SimpleSetter - public StringDt(@SimpleSetter.Parameter(name="theString") String theValue) { - myValue=theValue; + public StringDt(@SimpleSetter.Parameter(name = "theString") String theValue) { + myValue = theValue; } @Override @@ -120,10 +120,9 @@ public class StringDt extends BasePrimitive implements IQueryParameterTy public String getValueAsQueryToken() { return getValue(); } - + /** - * Returns true if this datatype has no extensions, and has either a null - * value or an empty ("") value. + * Returns true if this datatype has no extensions, and has either a null value or an empty ("") value. */ @Override public boolean isEmpty() { 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 c4a32f6f540..f258c79246f 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 @@ -23,6 +23,7 @@ package ca.uhn.fhir.rest.client; import java.io.IOException; import java.io.Reader; import java.util.ArrayList; +import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; @@ -40,6 +41,11 @@ import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.gclient.ICriterion; +import ca.uhn.fhir.rest.gclient.ICriterionInternal; +import ca.uhn.fhir.rest.gclient.IFor; +import ca.uhn.fhir.rest.gclient.IQuery; +import ca.uhn.fhir.rest.gclient.Include; import ca.uhn.fhir.rest.method.BaseOutcomeReturningMethodBinding; import ca.uhn.fhir.rest.method.ConformanceMethodBinding; import ca.uhn.fhir.rest.method.CreateMethodBinding; @@ -281,5 +287,102 @@ public class GenericClient extends BaseClient implements IGenericClient { Conformance resp = (Conformance) invokeClient(binding, invocation); return resp; } + + @Override + public IQuery search() { + return new QueryInternal(); + } + + public class ForInternal implements IFor { + + private Class myResourceType; + private List myCriterion = new ArrayList(); + private List myInclude = new ArrayList(); + private String myResourceName; + + public ForInternal(String theResourceName) { + myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass(); + myResourceName = theResourceName; + } + + public ForInternal(Class theResourceType) { + myResourceType = theResourceType; + myResourceName = myContext.getResourceDefinition(theResourceType).getName(); + } + + @Override + public IFor and(ICriterion theCriterion) { + myCriterion.add((ICriterionInternal) theCriterion); + return this; + } + + @Override + public IFor where(ICriterion theCriterion) { + myCriterion.add((ICriterionInternal) theCriterion); + return this; + } + + public Bundle execute() { + + StringBuilder b = new StringBuilder(); + b.append(getServerBase()); + b.append('/'); + b.append(myResourceType); + b.append('?'); + + Map> params=new HashMap>(); + for (ICriterionInternal next : myCriterion) { + String parameterName = next.getParameterName(); + String parameterValue = next.getParameterValue(); + addParam(params, parameterName, parameterValue); + } + + for (Include next : myInclude) { + addParam(params, "_include", next.getInclude()); + } + + GetClientInvocation invocation = new GetClientInvocation(params, myResourceName); + if (isKeepResponses()) { + myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding()); + } + + IClientResponseHandler binding = new IClientResponseHandler() { + @Override + public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException { + EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType); + IParser parser = respType.newParser(myContext); + return parser.parseBundle(myResourceType, theResponseReader); + } + }; + + Bundle resp = (Bundle) invokeClient(binding, invocation); + return resp; + + } + + private void addParam(Map> params, String parameterName, String parameterValue) { + if (!params.containsKey(parameterName)) { + params.put(parameterName, new ArrayList()); + } + params.get(parameterName).add(parameterValue); + } + + @Override + public IFor include(Include theInclude) { + myInclude.add(theInclude); + return this; + } + + } + + public class QueryInternal implements IQuery { + + @Override + public IFor forResource(String theResourceName) { + return new ForInternal(theResourceName); + } + + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java index e0557cb4c27..02f9a2674e1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/IGenericClient.java @@ -29,6 +29,7 @@ import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.gclient.IQuery; public interface IGenericClient { @@ -105,4 +106,6 @@ public interface IGenericClient { */ MethodOutcome create(IResource theResource); + IQuery search(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateParam.java new file mode 100644 index 00000000000..37c8adf21a6 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/DateParam.java @@ -0,0 +1,114 @@ +package ca.uhn.fhir.rest.gclient; + +import java.util.Date; + +import ca.uhn.fhir.model.api.TemporalPrecisionEnum; +import ca.uhn.fhir.model.primitive.DateTimeDt; + +public class DateParam { + + private String myParamName; + + public DateParam(String theParamName) { + myParamName = theParamName; + } + + public IDateSpecifier after() { + return new DateWithPrefix(">"); + } + + public IDateSpecifier afterOrEquals() { + return new DateWithPrefix(">="); + } + + public IDateSpecifier before() { + return new DateWithPrefix("<="); + } + + public IDateSpecifier beforeOrEquals() { + return new DateWithPrefix("<="); + } + + public IDateSpecifier exactly() { + return new DateWithPrefix(""); + } + + private class Criterion implements ICriterion, ICriterionInternal { + + private String myValue; + + public Criterion(String theValue) { + myValue = theValue; + } + + @Override + public String getParameterName() { + return myParamName; + } + + @Override + public String getParameterValue() { + return myValue; + } + + } + + private class DateWithPrefix implements IDateSpecifier { + private String myPrefix; + + public DateWithPrefix(String thePrefix) { + myPrefix = thePrefix; + } + + @Override + public ICriterion day(Date theValue) { + DateTimeDt dt = new DateTimeDt(theValue); + dt.setPrecision(TemporalPrecisionEnum.DAY); + return new Criterion(myPrefix + dt.getValueAsString()); + } + + @Override + public ICriterion day(String theValue) { + DateTimeDt dt = new DateTimeDt(theValue); + dt.setPrecision(TemporalPrecisionEnum.DAY); + return new Criterion(myPrefix + dt.getValueAsString()); + } + + @Override + public ICriterion now() { + DateTimeDt dt = new DateTimeDt(); + dt.setPrecision(TemporalPrecisionEnum.DAY); + return new Criterion(myPrefix + dt.getValueAsString()); + } + + @Override + public ICriterion second(Date theValue) { + DateTimeDt dt = new DateTimeDt(theValue); + dt.setPrecision(TemporalPrecisionEnum.DAY); + return new Criterion(myPrefix + dt.getValueAsString()); + } + + @Override + public ICriterion second(String theValue) { + DateTimeDt dt = new DateTimeDt(theValue); + dt.setPrecision(TemporalPrecisionEnum.DAY); + return new Criterion(myPrefix + dt.getValueAsString()); + } + + } + + public interface IDateSpecifier { + + ICriterion day(Date theValue); + + ICriterion day(String theValue); + + ICriterion now(); + + ICriterion second(Date theValue); + + ICriterion second(String theValue); + + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterion.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterion.java new file mode 100644 index 00000000000..6298dce7a85 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterion.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.rest.gclient; + +public interface ICriterion { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterionInternal.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterionInternal.java new file mode 100644 index 00000000000..d86b79bc3cb --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/ICriterionInternal.java @@ -0,0 +1,9 @@ +package ca.uhn.fhir.rest.gclient; + +public interface ICriterionInternal { + + String getParameterValue(); + + String getParameterName(); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IFor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IFor.java new file mode 100644 index 00000000000..bb490f94de3 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IFor.java @@ -0,0 +1,17 @@ +package ca.uhn.fhir.rest.gclient; + +import ca.uhn.fhir.model.api.Bundle; + +public interface IFor { + + IFor where(ICriterion theCriterion); + + IFor and(ICriterion theCriterion); + + Bundle execute(); + + IFor include(Include theIncludeManagingorganization); + + IFor json(); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java new file mode 100644 index 00000000000..ed7e2a9fdc5 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/IQuery.java @@ -0,0 +1,8 @@ +package ca.uhn.fhir.rest.gclient; + + +public interface IQuery { + + IFor forResource(String theResourceName); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/Include.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/Include.java new file mode 100644 index 00000000000..68ff53f0ac4 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/Include.java @@ -0,0 +1,15 @@ +package ca.uhn.fhir.rest.gclient; + +public class Include { + + private String myInclude; + + public Include(String theInclude) { + myInclude = theInclude; + } + + public String getInclude() { + return myInclude; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringParam.java new file mode 100644 index 00000000000..2cf82d598b2 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/gclient/StringParam.java @@ -0,0 +1,48 @@ +package ca.uhn.fhir.rest.gclient; + +public class StringParam { + + private String myParamName; + + public StringParam(String theParamName) { + myParamName = theParamName; + } + + public IStringExactly exactly() { + return new StringExactly(); + } + + private class EqualsExactlyCriterion implements ICriterion, ICriterionInternal { + + private String myValue; + + public EqualsExactlyCriterion(String theValue) { + myValue = theValue; + } + + @Override + public String getParameterName() { + return myParamName; + } + + @Override + public String getParameterValue() { + return myValue; + } + + } + + public interface IStringExactly { + + ICriterion value(String theValue); + + } + + public class StringExactly implements IStringExactly { + @Override + public ICriterion value(String theValue) { + return new EqualsExactlyCriterion(theValue); + } + } + +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java new file mode 100644 index 00000000000..6301a7a8d53 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/GenericClientTest.java @@ -0,0 +1,129 @@ +package ca.uhn.fhir.rest.client; + +import static org.junit.Assert.assertEquals; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.io.StringReader; +import java.nio.charset.Charset; +import java.util.List; + +import org.apache.commons.io.input.ReaderInputStream; +import org.apache.http.HttpResponse; +import org.apache.http.ProtocolVersion; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpUriRequest; +import org.apache.http.message.BasicHeader; +import org.apache.http.message.BasicStatusLine; +import org.junit.Before; +import org.junit.Test; +import org.mockito.ArgumentCaptor; +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.Conformance; +import ca.uhn.fhir.model.dstu.resource.Patient; +import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum; +import ca.uhn.fhir.rest.param.QualifiedDateParam; +import ca.uhn.fhir.rest.server.Constants; + +public class GenericClientTest { + + private FhirContext ctx; + private HttpClient httpClient; + private HttpResponse httpResponse; + + @Before + public void before() { + ctx = new FhirContext(); + + httpClient = mock(HttpClient.class, new ReturnsDeepStubs()); + ctx.getRestfulClientFactory().setHttpClient(httpClient); + + httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs()); + } + + private String getPatientFeedWithOneResult() { + //@formatter:off + String msg = "\n" + + "\n" + + "<id>d039f91a-cc3c-4013-988e-af4d8d0614bd</id>\n" + + "<os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">1</os:totalResults>\n" + + "<published>2014-03-11T16:35:07-04:00</published>\n" + + "<author>\n" + + "<name>ca.uhn.fhir.rest.server.DummyRestfulServer</name>\n" + + "</author>\n" + + "<entry>\n" + + "<content type=\"text/xml\">" + + "<Patient xmlns=\"http://hl7.org/fhir\">" + + "<text><status value=\"generated\" /><div xmlns=\"http://www.w3.org/1999/xhtml\">John Cardinal: 444333333 </div></text>" + + "<identifier><label value=\"SSN\" /><system value=\"http://orionhealth.com/mrn\" /><value value=\"PRP1660\" /></identifier>" + + "<name><use value=\"official\" /><family value=\"Cardinal\" /><given value=\"John\" /></name>" + + "<name><family value=\"Kramer\" /><given value=\"Doe\" /></name>" + + "<telecom><system value=\"phone\" /><value value=\"555-555-2004\" /><use value=\"work\" /></telecom>" + + "<gender><coding><system value=\"http://hl7.org/fhir/v3/AdministrativeGender\" /><code value=\"M\" /></coding></gender>" + + "<address><use value=\"home\" /><line value=\"2222 Home Street\" /></address><active value=\"true\" />" + + "</Patient>" + + "</content>\n" + + " </entry>\n" + + "</feed>"; + //@formatter:on + return msg; + } + + @SuppressWarnings("unused") + @Test + public void testSearchByString() throws Exception { + + 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"))); + + IGenericClient client = ctx.newRestfulGenericClient("http://foo"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .where(Patient.PARAM_NAME.exactly().value("james")) + .execute(); + //@formatter:on + + assertEquals("http://foo/Patient?name=james", capt.getValue().getURI().toString()); + + } + + @SuppressWarnings("unused") + @Test + public void testSearchByDate() throws Exception { + + 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"))); + + IGenericClient client = ctx.newRestfulGenericClient("http://foo"); + + //@formatter:off + Bundle response = client.search() + .forResource("Patient") + .json() + .where(Patient.PARAM_BIRTHDATE.beforeOrEquals().day("2012-01-22")) + .and(Patient.PARAM_BIRTHDATE.after().day("2011-01-01")) + .include(Patient.INCLUDE_MANAGINGORGANIZATION) + .sort().ascending(Patient.PARAM_BIRTHDATE) + .execute(); + //@formatter:on + + assertEquals("http://foo/Patient?birthdate=%3C%3D2012-01-22&birthdate=%3E2011-01-01&_include=Patient.managingOrganization", capt.getValue().getURI().toString()); + + } + +}