More work on operations support for DSTU2

This commit is contained in:
James Agnew 2015-03-06 13:05:56 -05:00
parent 47c98ffe4e
commit 7a953bf5de
5 changed files with 208 additions and 98 deletions

View File

@ -337,7 +337,7 @@ public class GenericClientExample {
// START SNIPPET: historyDstu1 // START SNIPPET: historyDstu1
response = client response = client
.history() .history()
.ofServer() .onServer()
.andReturnDstu1Bundle() .andReturnDstu1Bundle()
.execute(); .execute();
// END SNIPPET: historyDstu1 // END SNIPPET: historyDstu1
@ -347,7 +347,7 @@ public class GenericClientExample {
// START SNIPPET: historyDstu2 // START SNIPPET: historyDstu2
response = client response = client
.history() .history()
.ofServer() .onServer()
.andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute(); .execute();
// END SNIPPET: historyDstu2 // END SNIPPET: historyDstu2
@ -357,7 +357,7 @@ public class GenericClientExample {
// START SNIPPET: historyFeatures // START SNIPPET: historyFeatures
response = client response = client
.history() .history()
.ofServer() .onServer()
.andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.since(new InstantDt("2012-01-01T12:22:32.038Z")) .since(new InstantDt("2012-01-01T12:22:32.038Z"))
.count(100) .count(100)
@ -384,7 +384,7 @@ public class GenericClientExample {
Parameters outParams = client Parameters outParams = client
.operation() .operation()
.ofInstance(new IdDt("Patient", "1")) .onInstance(new IdDt("Patient", "1"))
.named("$everything") .named("$everything")
.withParameters(inParams) .withParameters(inParams)
.execute(); .execute();

View File

@ -36,10 +36,14 @@ import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate; import org.apache.commons.lang3.Validate;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpRequestBase; import org.apache.http.client.methods.HttpRequestBase;
import org.hl7.fhir.instance.model.IBase;
import org.hl7.fhir.instance.model.IBaseResource; import org.hl7.fhir.instance.model.IBaseResource;
import org.hl7.fhir.instance.model.api.IBaseBundle; import org.hl7.fhir.instance.model.api.IBaseBundle;
import org.hl7.fhir.instance.model.api.IBaseParameters; import org.hl7.fhir.instance.model.api.IBaseParameters;
import ca.uhn.fhir.context.BaseRuntimeChildDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementCompositeDefinition;
import ca.uhn.fhir.context.BaseRuntimeElementDefinition;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.FhirVersionEnum; import ca.uhn.fhir.context.FhirVersionEnum;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
@ -57,6 +61,7 @@ import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.exceptions.InvalidResponseException;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.gclient.IClientExecutable; import ca.uhn.fhir.rest.gclient.IClientExecutable;
import ca.uhn.fhir.rest.gclient.ICreate; import ca.uhn.fhir.rest.gclient.ICreate;
@ -122,83 +127,17 @@ import ca.uhn.fhir.util.ICallable;
*/ */
public class GenericClient extends BaseClient implements IGenericClient { public class GenericClient extends BaseClient implements IGenericClient {
@SuppressWarnings("rawtypes")
public class OperationInternal extends BaseClientExecutable implements IOperation, IOperationUnnamed, IOperationUntyped, IOperationUntypedWithInput {
private IdDt myId;
private Class<? extends IBaseResource> myType;
private IBaseParameters myParameters;
private String myOperationName;
@Override
public IOperationUnnamed ofServer() {
return this;
}
@Override
public IOperationUnnamed ofType(Class<? extends IBaseResource> theResourceType) {
myType = theResourceType;
return this;
}
@Override
public IOperationUnnamed ofInstance(IdDt theId) {
myId = theId;
return this;
}
@SuppressWarnings({ "unchecked" })
@Override
public IOperationUntypedWithInput withParameters(IBaseParameters theParameters) {
Validate.notNull(theParameters, "theParameters can not be null");
myParameters = theParameters;
return this;
}
@SuppressWarnings("unchecked")
@Override
public Object execute() {
String resourceName;
String id;
if (myType != null) {
resourceName = myContext.getResourceDefinition(myType).getName();
id = null;
} else if (myId != null) {
resourceName = myId.getResourceType();
id = myId.getIdPart();
} else {
resourceName = null;
id = null;
}
HttpPostClientInvocation invocation = OperationMethodBinding.createOperationInvocation(myContext, resourceName, id, myOperationName, myParameters);
IClientResponseHandler handler;
handler = new ResourceResponseHandler(myParameters.getClass(), null);
return invoke(null, handler, invocation);
}
@Override
public IOperationUntyped named(String theName) {
Validate.notBlank(theName, "theName can not be null");
myOperationName =theName;
return this;
}
}
private static final String I18N_CANNOT_DETEMINE_RESOURCE_TYPE = "ca.uhn.fhir.rest.client.GenericClient.cannotDetermineResourceTypeFromUri"; private static final String I18N_CANNOT_DETEMINE_RESOURCE_TYPE = "ca.uhn.fhir.rest.client.GenericClient.cannotDetermineResourceTypeFromUri";
private static final String I18N_INCOMPLETE_URI_FOR_READ = "ca.uhn.fhir.rest.client.GenericClient.incompleteUriForRead"; private static final String I18N_INCOMPLETE_URI_FOR_READ = "ca.uhn.fhir.rest.client.GenericClient.incompleteUriForRead";
private static final String I18N_NO_VERSION_ID_FOR_VREAD = "ca.uhn.fhir.rest.client.GenericClient.noVersionIdForVread"; private static final String I18N_NO_VERSION_ID_FOR_VREAD = "ca.uhn.fhir.rest.client.GenericClient.noVersionIdForVread";
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class);
private FhirContext myContext; private FhirContext myContext;
private HttpRequestBase myLastRequest; private HttpRequestBase myLastRequest;
private boolean myLogRequestAndResponse;
private boolean myLogRequestAndResponse;
/** /**
* For now, this is a part of the internal API of HAPI - Use with caution as this method may change! * For now, this is a part of the internal API of HAPI - Use with caution as this method may change!
*/ */
@ -262,6 +201,11 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp; return resp;
} }
@Override
public MethodOutcome delete(Class<? extends IResource> theType, String theId) {
return delete(theType, new IdDt(theId));
}
// public IResource read(UriDt url) { // public IResource read(UriDt url) {
// return read(inferResourceClass(url), url); // return read(inferResourceClass(url), url);
// } // }
@ -275,11 +219,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
// return search(inferResourceClass(url), url); // return search(inferResourceClass(url), url);
// } // }
@Override
public MethodOutcome delete(Class<? extends IResource> theType, String theId) {
return delete(theType, new IdDt(theId));
}
private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IdDt theId, boolean theVRead, ICallable<T> theNotModifiedHandler, String theIfVersionMatches) { private <T extends IBaseResource> T doReadOrVRead(final Class<T> theType, IdDt theId, boolean theVRead, ICallable<T> theNotModifiedHandler, String theIfVersionMatches) {
String resName = toResourceName(theType); String resName = toResourceName(theType);
IdDt id = theId; IdDt id = theId;
@ -395,15 +334,15 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
public boolean isLogRequestAndResponse() {
return myLogRequestAndResponse;
}
// @Override // @Override
// public <T extends IBaseResource> T read(final Class<T> theType, IdDt theId) { // public <T extends IBaseResource> T read(final Class<T> theType, IdDt theId) {
// return doReadOrVRead(theType, theId, false, null, null); // return doReadOrVRead(theType, theId, false, null, null);
// } // }
public boolean isLogRequestAndResponse() {
return myLogRequestAndResponse;
}
@Override @Override
public IGetPage loadPage() { public IGetPage loadPage() {
return new LoadPageInternal(); return new LoadPageInternal();
@ -411,6 +350,9 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override @Override
public IOperation operation() { public IOperation operation() {
if (myContext.getVersion().getVersion().isNewerThan(FhirVersionEnum.DSTU1) == false) {
throw new IllegalStateException("Operations are only supported in FHIR DSTU2 and later. This client was created using a context configured for " + myContext.getVersion().getVersion().name());
}
return new OperationInternal(); return new OperationInternal();
} }
@ -968,7 +910,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
@SuppressWarnings("rawtypes") @SuppressWarnings("rawtypes")
public class HistoryInternal extends BaseClientExecutable implements IHistory, IHistoryUntyped, IHistoryTyped { private class HistoryInternal extends BaseClientExecutable implements IHistory, IHistoryUntyped, IHistoryTyped {
private Integer myCount; private Integer myCount;
private IdDt myId; private IdDt myId;
@ -1024,7 +966,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
@Override @Override
public IHistoryUntyped ofInstance(IdDt theId) { public IHistoryUntyped onInstance(IdDt theId) {
if (theId.hasResourceType() == false) { if (theId.hasResourceType() == false) {
throw new IllegalArgumentException("Resource ID does not have a resource type: " + theId.getValue()); throw new IllegalArgumentException("Resource ID does not have a resource type: " + theId.getValue());
} }
@ -1033,12 +975,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
@Override @Override
public IHistoryUntyped ofServer() { public IHistoryUntyped onServer() {
return this; return this;
} }
@Override @Override
public IHistoryUntyped ofType(Class<? extends IBaseResource> theResourceType) { public IHistoryUntyped onType(Class<? extends IBaseResource> theResourceType) {
myType = theResourceType; myType = theResourceType;
return this; return this;
} }
@ -1080,6 +1022,88 @@ public class GenericClient extends BaseClient implements IGenericClient {
} }
@SuppressWarnings("rawtypes")
private class OperationInternal extends BaseClientExecutable implements IOperation, IOperationUnnamed, IOperationUntyped, IOperationUntypedWithInput {
private IdDt myId;
private String myOperationName;
private IBaseParameters myParameters;
private Class<? extends IBaseResource> myType;
@SuppressWarnings("unchecked")
@Override
public Object execute() {
String resourceName;
String id;
if (myType != null) {
resourceName = myContext.getResourceDefinition(myType).getName();
id = null;
} else if (myId != null) {
resourceName = myId.getResourceType();
id = myId.getIdPart();
} else {
resourceName = null;
id = null;
}
HttpPostClientInvocation invocation = OperationMethodBinding.createOperationInvocation(myContext, resourceName, id, myOperationName, myParameters);
IClientResponseHandler handler;
handler = new ResourceResponseHandler(myParameters.getClass(), null);
Object retVal = invoke(null, handler, invocation);
if (myContext.getResourceDefinition((IBaseResource)retVal).getName().equals("Parameters")) {
return retVal;
} else {
RuntimeResourceDefinition def = myContext.getResourceDefinition("Parameters");
IBaseResource parameters = def.newInstance();
BaseRuntimeChildDefinition paramChild = def.getChildByName("parameter");
BaseRuntimeElementCompositeDefinition<?> paramChildElem = (BaseRuntimeElementCompositeDefinition<?>) paramChild.getChildByName("parameter");
IBase parameter = paramChildElem.newInstance();
paramChild.getMutator().addValue(parameters, parameter);
BaseRuntimeChildDefinition resourceElem = paramChildElem.getChildByName("resource");
resourceElem.getMutator().addValue(parameter, (IBase) retVal);
return parameters;
}
}
@Override
public IOperationUntyped named(String theName) {
Validate.notBlank(theName, "theName can not be null");
myOperationName =theName;
return this;
}
@Override
public IOperationUnnamed onInstance(IdDt theId) {
myId = theId;
return this;
}
@Override
public IOperationUnnamed onServer() {
return this;
}
@Override
public IOperationUnnamed onType(Class<? extends IBaseResource> theResourceType) {
myType = theResourceType;
return this;
}
@SuppressWarnings({ "unchecked" })
@Override
public IOperationUntypedWithInput withParameters(IBaseParameters theParameters) {
Validate.notNull(theParameters, "theParameters can not be null");
myParameters = theParameters;
return this;
}
}
private final class OperationOutcomeResponseHandler implements IClientResponseHandler<BaseOperationOutcome> { private final class OperationOutcomeResponseHandler implements IClientResponseHandler<BaseOperationOutcome> {
@Override @Override

View File

@ -9,12 +9,12 @@ public interface IBaseOn<T> {
/** /**
* Perform the operation across all versions of all resources of all types on the server * Perform the operation across all versions of all resources of all types on the server
*/ */
T ofServer(); T onServer();
/** /**
* Perform the operation across all versions of all resources of the given type on the server * Perform the operation across all versions of all resources of the given type on the server
*/ */
T ofType(Class<? extends IBaseResource> theResourceType); T onType(Class<? extends IBaseResource> theResourceType);
/** /**
* Perform the operation across all versions of a specific resource (by ID and type) on the server. * Perform the operation across all versions of a specific resource (by ID and type) on the server.
@ -23,6 +23,6 @@ public interface IBaseOn<T> {
* *
* @throws IllegalArgumentException If <code>theId</code> does not contain at least a resource type and ID * @throws IllegalArgumentException If <code>theId</code> does not contain at least a resource type and ID
*/ */
T ofInstance(IdDt theId); T onInstance(IdDt theId);
} }

View File

@ -665,7 +665,7 @@ public class GenericClientTest {
//@formatter:off //@formatter:off
response = client response = client
.history() .history()
.ofServer() .onServer()
.andReturnDstu1Bundle() .andReturnDstu1Bundle()
.execute(); .execute();
//@formatter:on //@formatter:on
@ -676,7 +676,7 @@ public class GenericClientTest {
//@formatter:off //@formatter:off
response = client response = client
.history() .history()
.ofType(Patient.class) .onType(Patient.class)
.andReturnDstu1Bundle() .andReturnDstu1Bundle()
.execute(); .execute();
//@formatter:on //@formatter:on
@ -687,7 +687,7 @@ public class GenericClientTest {
//@formatter:off //@formatter:off
response = client response = client
.history() .history()
.ofInstance(new IdDt("Patient", "123")) .onInstance(new IdDt("Patient", "123"))
.andReturnDstu1Bundle() .andReturnDstu1Bundle()
.execute(); .execute();
//@formatter:on //@formatter:on

View File

@ -105,7 +105,7 @@ public class GenericClientTestDstu2 {
//@formatter:off //@formatter:off
response = client response = client
.history() .history()
.ofServer() .onServer()
.andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute(); .execute();
//@formatter:on //@formatter:on
@ -116,7 +116,7 @@ public class GenericClientTestDstu2 {
//@formatter:off //@formatter:off
response = client response = client
.history() .history()
.ofType(Patient.class) .onType(Patient.class)
.andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute(); .execute();
//@formatter:on //@formatter:on
@ -127,7 +127,7 @@ public class GenericClientTestDstu2 {
//@formatter:off //@formatter:off
response = client response = client
.history() .history()
.ofInstance(new IdDt("Patient", "123")) .onInstance(new IdDt("Patient", "123"))
.andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) .andReturnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class)
.execute(); .execute();
//@formatter:on //@formatter:on
@ -163,7 +163,7 @@ public class GenericClientTestDstu2 {
@Test @Test
public void testOperationWithListOfParameterResponse() throws Exception { public void testOperationWithListOfParameterResponse() throws Exception {
IParser p = ourCtx.newXmlParser(); IParser p = ourCtx.newXmlParser();
Parameters inParams = new Parameters(); Parameters inParams = new Parameters();
inParams.addParameter().setValue(new StringDt("STRINGVALIN1")); inParams.addParameter().setValue(new StringDt("STRINGVALIN1"));
inParams.addParameter().setValue(new StringDt("STRINGVALIN2")); inParams.addParameter().setValue(new StringDt("STRINGVALIN2"));
@ -188,15 +188,101 @@ public class GenericClientTestDstu2 {
IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir"); IGenericClient client = ourCtx.newRestfulGenericClient("http://example.com/fhir");
int idx = 0; int idx = 0;
Parameters resp = client.operation().ofServer().named("$SOMEOPERATION").withParameters(inParams).execute(); //@formatter:off
Parameters resp = client
.operation()
.onServer()
.named("$SOMEOPERATION")
.withParameters(inParams).execute();
//@formatter:on
assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp)); assertEquals(respString, p.encodeResourceToString(resp));
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length); assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue()); assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertEquals(extractBody(capt, idx), reqString); assertEquals(extractBody(capt, idx), reqString);
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod()); assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++; idx++;
//@formatter:off
resp = client
.operation()
.onType(Patient.class)
.named("$SOMEOPERATION")
.withParameters(inParams).execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertEquals(extractBody(capt, idx), reqString);
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
//@formatter:off
resp = client
.operation()
.onInstance(new IdDt("Patient", "123"))
.named("$SOMEOPERATION")
.withParameters(inParams).execute();
//@formatter:on
assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(respString, p.encodeResourceToString(resp));
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertEquals(extractBody(capt, idx), reqString);
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
idx++;
resp = client.operation().onInstance(new IdDt("http://foo.com/bar/baz/Patient/123/_history/22")).named("$SOMEOPERATION").withParameters(inParams).execute();
// @formatter:on
assertEquals("http://example.com/fhir/Patient/123/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
idx++;
}
@Test
public void testOperationWithBundleResponse() throws Exception {
IParser p = ourCtx.newXmlParser();
Parameters inParams = new Parameters();
inParams.addParameter().setValue(new StringDt("STRINGVALIN1"));
inParams.addParameter().setValue(new StringDt("STRINGVALIN2"));
String reqString = p.encodeResourceToString(inParams);
ca.uhn.fhir.model.dstu2.resource.Bundle outParams = new ca.uhn.fhir.model.dstu2.resource.Bundle();
outParams.setTotal(123);
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
Parameters resp = client
.operation()
.onServer()
.named("$SOMEOPERATION")
.withParameters(inParams).execute();
//@formatter:on
assertEquals("http://example.com/fhir/$SOMEOPERATION", capt.getAllValues().get(idx).getURI().toASCIIString());
assertEquals(1, capt.getAllValues().get(idx).getHeaders(Constants.HEADER_CONTENT_TYPE).length);
assertEquals(EncodingEnum.XML.getResourceContentType() + Constants.HEADER_SUFFIX_CT_UTF_8, capt.getAllValues().get(idx).getFirstHeader(Constants.HEADER_CONTENT_TYPE).getValue());
assertEquals(extractBody(capt, idx), reqString);
assertEquals("POST", capt.getAllValues().get(idx).getRequestLine().getMethod());
assertEquals(1, resp.getParameter().size());
assertEquals(ca.uhn.fhir.model.dstu2.resource.Bundle.class, resp.getParameter().get(0).getResource().getClass());
idx++;
} }
@Test @Test