A few fixes and additions to the fluent client, as well as fixing a bug on extension encoding (null extensions were still being encoded)

This commit is contained in:
jamesagnew 2014-07-23 09:15:07 -04:00
parent 622e528f43
commit 4cd3bdf441
24 changed files with 778 additions and 290 deletions

View File

@ -67,6 +67,14 @@
<![CDATA[<a href="http://gforge.hl7.org/gf/project/fhir/tracker/?action=TrackerItemEdit&tracker_id=677&tracker_item_id=3298">FHIR Tracker Bug 3298</a>]]>
for more information.
</action>
<action type="add">
Support has been added for using an HTTP proxy for outgoing requests.
</action>
<action type="fix">
Fix: Primitive extensions declared against custom resource types
are encoded even if they have no value. Thanks to David Hay of Orion for
reporting this!
</action>
</release>
<release version="0.4" date="2014-Jul-13">
<action type="add">

View File

@ -318,12 +318,12 @@ public class RuntimeResourceDefinition extends BaseRuntimeElementCompositeDefini
RuntimeResourceDefinition def = this;
if (StringUtils.isNotBlank(myId)) {
retVal.setId(new IdDt(myId));
}else {
throw new ConfigurationException("Resource class " + getImplementingClass().getCanonicalName() + " has no ID specified");
if (StringUtils.isBlank(myId)) {
myId = getName().toLowerCase();
}
retVal.setId(new IdDt(myId));
// Scan for extensions
scanForExtensions(retVal, def);
Collections.sort(retVal.getExtensionDefn(), new Comparator<ExtensionDefn>() {

View File

@ -432,4 +432,8 @@ public class IdDt extends BasePrimitive<String> {
return new IdDt(value + '/' + Constants.PARAM_HISTORY + '/' + theVersion);
}
public IdDt withResourceType(String theResourceName) {
return new IdDt(theResourceName, getIdPart(), getVersionIdPart());
}
}

View File

@ -560,6 +560,9 @@ public class JsonParser extends BaseParser implements IParser {
for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsNonModifier()) {
for (IElement nextValue : nextDef.getAccessor().getValues(theResource)) {
if (nextValue != null) {
if (nextValue == null || nextValue.isEmpty()) {
continue;
}
extensions.add(new HeldExtension(nextDef, nextValue));
}
}
@ -567,6 +570,9 @@ public class JsonParser extends BaseParser implements IParser {
for (RuntimeChildDeclaredExtensionDefinition nextDef : resDef.getExtensionsModifier()) {
for (IElement nextValue : nextDef.getAccessor().getValues(theResource)) {
if (nextValue != null) {
if (nextValue == null || nextValue.isEmpty()) {
continue;
}
modifierExtensions.add(new HeldExtension(nextDef, nextValue));
}
}
@ -577,11 +583,17 @@ public class JsonParser extends BaseParser implements IParser {
if (theResource instanceof ISupportsUndeclaredExtensions) {
List<ExtensionDt> ext = ((ISupportsUndeclaredExtensions) theResource).getUndeclaredExtensions();
for (ExtensionDt next : ext) {
if (next == null || next.isEmpty()) {
continue;
}
extensions.add(new HeldExtension(next));
}
ext = ((ISupportsUndeclaredExtensions) theResource).getUndeclaredModifierExtensions();
for (ExtensionDt next : ext) {
if (next == null || next.isEmpty()) {
continue;
}
modifierExtensions.add(new HeldExtension(next));
}
}
@ -708,7 +720,7 @@ public class JsonParser extends BaseParser implements IParser {
if (elementId != null) {
IElement object = (IElement) theState.getObject();
if (object instanceof IIdentifiableElement) {
((IIdentifiableElement) object).setId(new IdDt(elementId));
((IIdentifiableElement) object).setElementSpecificId(elementId);
} else if (object instanceof IResource) {
((IResource) object).setId(new IdDt(elementId));
}

View File

@ -489,7 +489,7 @@ public class XmlParser extends BaseParser implements IParser {
}
for (IElement nextValue : values) {
if (nextValue == null) {
if (nextValue == null || nextValue.isEmpty()) {
continue;
}
Class<? extends IElement> type = nextValue.getClass();

View File

@ -138,6 +138,15 @@ public abstract class BaseClient {
try {
Map<String, List<String>> params = createExtraParams();
if (theEncoding == EncodingEnum.XML) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("xml"));
} else if (theEncoding == EncodingEnum.JSON) {
params.put(Constants.PARAM_FORMAT, Collections.singletonList("json"));
}
if (thePrettyPrint == Boolean.TRUE) {
params.put(Constants.PARAM_PRETTY, Collections.singletonList(Constants.PARAM_PRETTY_VALUE_TRUE));
}
EncodingEnum encoding = getEncoding();
if (theEncoding != null) {
encoding=theEncoding;

View File

@ -25,7 +25,6 @@ import static org.apache.commons.lang3.StringUtils.*;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
@ -45,8 +44,10 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
@ -55,6 +56,8 @@ import ca.uhn.fhir.rest.gclient.ICreate;
import ca.uhn.fhir.rest.gclient.ICreateTyped;
import ca.uhn.fhir.rest.gclient.ICriterion;
import ca.uhn.fhir.rest.gclient.ICriterionInternal;
import ca.uhn.fhir.rest.gclient.IDelete;
import ca.uhn.fhir.rest.gclient.IDeleteTyped;
import ca.uhn.fhir.rest.gclient.IGetPage;
import ca.uhn.fhir.rest.gclient.IGetPageTyped;
import ca.uhn.fhir.rest.gclient.IGetTags;
@ -83,6 +86,8 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class GenericClient extends BaseClient implements IGenericClient {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(GenericClient.class);
private FhirContext myContext;
private HttpRequestBase myLastRequest;
@ -131,9 +136,14 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
public IDelete delete() {
return new DeleteInternal();
}
@Override
public MethodOutcome delete(final Class<? extends IResource> theType, IdDt theId) {
HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(toResourceName(theType), theId);
HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(theId.withResourceType(toResourceName(theType)));
if (isKeepResponses()) {
myLastRequest = invocation.asHttpRequest(getServerBase(), createExtraParams(), getEncoding());
}
@ -210,7 +220,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
@Override
public IUntypedQuery search() {
return new QueryInternal();
return new SearchInternal();
}
@Override
@ -249,10 +259,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
myLogRequestAndResponse = theLogRequestAndResponse;
}
private String toResourceName(Class<? extends IResource> theType) {
return myContext.getResourceDefinition(theType).getName();
}
@Override
public ITransaction transaction() {
return new TransactionInternal();
@ -322,18 +328,15 @@ public class GenericClient extends BaseClient implements IGenericClient {
return vread(theType, new IdDt(theId), new IdDt(theVersionId));
}
private String toResourceName(Class<? extends IResource> theType) {
return myContext.getResourceDefinition(theType).getName();
}
private abstract class BaseClientExecutable<T extends IClientExecutable<?, ?>, Y> implements IClientExecutable<T, Y> {
private EncodingEnum myParamEncoding;
private Boolean myPrettyPrint;
private boolean myQueryLogRequestAndResponse;
protected void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
if (!params.containsKey(parameterName)) {
params.put(parameterName, new ArrayList<String>());
}
params.get(parameterName).add(parameterValue);
}
@SuppressWarnings("unchecked")
@Override
public T andLogRequestAndResponse(boolean theLogRequestAndResponse) {
@ -355,14 +358,28 @@ public class GenericClient extends BaseClient implements IGenericClient {
return (T) this;
}
protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) {
if (myParamEncoding != null) {
theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType()));
}
@SuppressWarnings("unchecked")
@Override
public T prettyPrint() {
myPrettyPrint = true;
return (T) this;
}
if (myPrettyPrint != null) {
theParams.put(Constants.PARAM_PRETTY, Collections.singletonList(myPrettyPrint.toString()));
protected void addParam(Map<String, List<String>> params, String parameterName, String parameterValue) {
if (!params.containsKey(parameterName)) {
params.put(parameterName, new ArrayList<String>());
}
params.get(parameterName).add(parameterValue);
}
protected <Z> Z invoke(Map<String, List<String>> theParams, IClientResponseHandler<Z> theHandler, BaseHttpClientInvocation theInvocation) {
// if (myParamEncoding != null) {
// theParams.put(Constants.PARAM_FORMAT, Collections.singletonList(myParamEncoding.getFormatContentType()));
// }
//
// if (myPrettyPrint != null) {
// theParams.put(Constants.PARAM_PRETTY, Collections.singletonList(myPrettyPrint.toString()));
// }
if (isKeepResponses()) {
myLastRequest = theInvocation.asHttpRequest(getServerBase(), theParams, getEncoding());
@ -372,13 +389,6 @@ public class GenericClient extends BaseClient implements IGenericClient {
return resp;
}
@SuppressWarnings("unchecked")
@Override
public T prettyPrint() {
myPrettyPrint = true;
return (T) this;
}
}
private final class BundleResponseHandler implements IClientResponseHandler<Bundle> {
@ -390,8 +400,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
public Bundle invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);
@ -427,7 +436,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
myResource = encoding.newParser(myContext).parseResource(myResourceBody);
}
BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(myResource,myResourceBody, myId, myContext);
BaseHttpClientInvocation invocation = MethodUtil.createCreateInvocation(myResource, myResourceBody, myId, myContext);
RuntimeResourceDefinition def = myContext.getResourceDefinition(myResource);
final String resourceName = def.getName();
@ -467,100 +476,53 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private class ForInternal extends BaseClientExecutable<IQuery, Bundle> implements IQuery {
private class DeleteInternal extends BaseClientExecutable<IDeleteTyped, OperationOutcome> implements IDelete, IDeleteTyped {
private List<ICriterionInternal> myCriterion = new ArrayList<ICriterionInternal>();
private List<Include> myInclude = new ArrayList<Include>();
private Integer myParamLimit;
private final String myResourceName;
private final Class<? extends IResource> myResourceType;
private List<SortInternal> mySort = new ArrayList<SortInternal>();
public ForInternal() {
myResourceType = null;
myResourceName = null;
}
public ForInternal(Class<? extends IResource> theResourceType) {
myResourceType = theResourceType;
RuntimeResourceDefinition definition = myContext.getResourceDefinition(theResourceType);
myResourceName = definition.getName();
}
public ForInternal(String theResourceName) {
myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass();
myResourceName = theResourceName;
}
private IdDt myId;
@Override
public IQuery and(ICriterion theCriterion) {
myCriterion.add((ICriterionInternal) theCriterion);
return this;
}
@Override
public Bundle execute() {
Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
Map<String, List<String>> initial = createExtraParams();
if (initial != null) {
params.putAll(initial);
}
for (ICriterionInternal next : myCriterion) {
String parameterName = next.getParameterName();
String parameterValue = next.getParameterValue();
addParam(params, parameterName, parameterValue);
}
for (Include next : myInclude) {
addParam(params, Constants.PARAM_INCLUDE, next.getValue());
}
for (SortInternal next : mySort) {
addParam(params, next.getParamName(), next.getParamValue());
}
if (myParamLimit != null) {
addParam(params, Constants.PARAM_COUNT, Integer.toString(myParamLimit));
}
BundleResponseHandler binding = new BundleResponseHandler(myResourceType);
HttpGetClientInvocation invocation = new HttpGetClientInvocation(params, myResourceName);
public OperationOutcome execute() {
HttpDeleteClientInvocation invocation = DeleteMethodBinding.createDeleteInvocation(myId);
OperationOutcomeResponseHandler binding = new OperationOutcomeResponseHandler();
Map<String, List<String>> params = new HashMap<String, List<String>>();
return invoke(params, binding, invocation);
}
@Override
public IQuery include(Include theInclude) {
myInclude.add(theInclude);
return this;
}
@Override
public IQuery limitTo(int theLimitTo) {
if (theLimitTo > 0) {
myParamLimit = theLimitTo;
} else {
myParamLimit = null;
public IDeleteTyped resource(IResource theResource) {
Validate.notNull(theResource, "theResource can not be null");
IdDt id = theResource.getId();
Validate.notNull(id, "theResource.getId() can not be null");
if (id.hasResourceType() == false || id.hasIdPart() == false) {
throw new IllegalArgumentException("theResource.getId() must contain a resource type and logical ID at a minimum (e.g. Patient/1234), found: " + id.getValue());
}
myId = id;
return this;
}
@Override
public ISort sort() {
SortInternal retVal = new SortInternal(this);
mySort.add(retVal);
return retVal;
}
@Override
public IQuery where(ICriterion theCriterion) {
myCriterion.add((ICriterionInternal) theCriterion);
public IDeleteTyped resourceById(IdDt theId) {
Validate.notNull(theId, "theId can not be null");
if (theId.hasResourceType() == false || theId.hasIdPart() == false) {
throw new IllegalArgumentException("theId must contain a resource type and logical ID at a minimum (e.g. Patient/1234)found: " + theId.getValue());
}
myId = theId;
return this;
}
@Override
public IDeleteTyped resourceById(String theResourceType, String theLogicalId) {
Validate.notBlank(theResourceType, "theResourceType can not be blank/null");
if (myContext.getResourceDefinition(theResourceType) == null) {
throw new IllegalArgumentException("Unknown resource type");
}
Validate.notBlank(theLogicalId, "theLogicalId can not be blank/null");
if (theLogicalId.contains("/")) {
throw new IllegalArgumentException("LogicalId can not contain '/' (should only be the logical ID portion, not a qualified ID)");
}
myId = new IdDt(theResourceType, theLogicalId);
return this;
}
}
private class GetPageInternal extends BaseClientExecutable<IGetPageTyped, Bundle> implements IGetPageTyped {
@ -669,6 +631,28 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
private final class OperationOutcomeResponseHandler implements IClientResponseHandler<OperationOutcome> {
@Override
public OperationOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
return null;
}
IParser parser = respType.newParser(myContext);
OperationOutcome retVal;
try {
retVal = parser.parseResource(OperationOutcome.class, theResponseReader);
} catch (DataFormatException e) {
ourLog.warn("Failed to parse OperationOutcome response", e);
return null;
}
MethodUtil.parseClientRequestResourceHeaders(theHeaders, retVal);
return retVal;
}
}
private final class OutcomeResponseHandler implements IClientResponseHandler<MethodOutcome> {
private final String myResourceName;
@ -677,32 +661,12 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
public MethodOutcome invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
MethodOutcome response = MethodUtil.process2xxResponse(myContext, myResourceName, theResponseStatusCode, theResponseMimeType, theResponseReader, theHeaders);
return response;
}
}
private class QueryInternal implements IUntypedQuery {
@Override
public IQuery forAllResources() {
return new ForInternal();
}
@Override
public IQuery forResource(Class<? extends IResource> theResourceType) {
return new ForInternal(theResourceType);
}
@Override
public IQuery forResource(String theResourceName) {
return new ForInternal(theResourceName);
}
}
private final class ResourceListResponseHandler implements IClientResponseHandler<List<IResource>> {
private Class<? extends IResource> myType;
@ -712,8 +676,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
@Override
public List<IResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
public List<IResource> invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
return new BundleResponseHandler(myType).invokeClient(theResponseMimeType, theResponseReader, theResponseStatusCode, theHeaders).toListOfResources();
}
}
@ -742,18 +705,133 @@ public class GenericClient extends BaseClient implements IGenericClient {
}
MethodUtil.parseClientRequestResourceHeaders(theHeaders, retVal);
return retVal;
}
}
private class SearchInternal extends BaseClientExecutable<IQuery, Bundle> implements IQuery, IUntypedQuery {
private List<ICriterionInternal> myCriterion = new ArrayList<ICriterionInternal>();
private List<Include> myInclude = new ArrayList<Include>();
private Integer myParamLimit;
private String myResourceName;
private Class<? extends IResource> myResourceType;
private List<SortInternal> mySort = new ArrayList<SortInternal>();
public SearchInternal() {
myResourceType = null;
myResourceName = null;
}
@Override
public IQuery and(ICriterion theCriterion) {
myCriterion.add((ICriterionInternal) theCriterion);
return this;
}
@Override
public Bundle execute() {
Map<String, List<String>> params = new LinkedHashMap<String, List<String>>();
Map<String, List<String>> initial = createExtraParams();
if (initial != null) {
params.putAll(initial);
}
for (ICriterionInternal next : myCriterion) {
String parameterName = next.getParameterName();
String parameterValue = next.getParameterValue();
addParam(params, parameterName, parameterValue);
}
for (Include next : myInclude) {
addParam(params, Constants.PARAM_INCLUDE, next.getValue());
}
for (SortInternal next : mySort) {
addParam(params, next.getParamName(), next.getParamValue());
}
if (myParamLimit != null) {
addParam(params, Constants.PARAM_COUNT, Integer.toString(myParamLimit));
}
BundleResponseHandler binding = new BundleResponseHandler(myResourceType);
HttpGetClientInvocation invocation = new HttpGetClientInvocation(params, myResourceName);
return invoke(params, binding, invocation);
}
@Override
public IQuery forAllResources() {
return this;
}
private void setType(Class<? extends IResource> theResourceType) {
myResourceType = theResourceType;
RuntimeResourceDefinition definition = myContext.getResourceDefinition(theResourceType);
myResourceName = definition.getName();
}
private void setType(String theResourceName) {
myResourceType = myContext.getResourceDefinition(theResourceName).getImplementingClass();
myResourceName = theResourceName;
}
@Override
public IQuery forResource(Class<? extends IResource> theResourceType) {
setType(theResourceType);
return this;
}
@Override
public IQuery forResource(String theResourceName) {
setType(theResourceName);
return this;
}
@Override
public IQuery include(Include theInclude) {
myInclude.add(theInclude);
return this;
}
@Override
public IQuery limitTo(int theLimitTo) {
if (theLimitTo > 0) {
myParamLimit = theLimitTo;
} else {
myParamLimit = null;
}
return this;
}
@Override
public ISort sort() {
SortInternal retVal = new SortInternal(this);
mySort.add(retVal);
return retVal;
}
@Override
public IQuery where(ICriterion theCriterion) {
myCriterion.add((ICriterionInternal) theCriterion);
return this;
}
}
private class SortInternal implements ISort {
private ForInternal myFor;
private SearchInternal myFor;
private String myParamName;
private String myParamValue;
public SortInternal(ForInternal theFor) {
public SortInternal(SearchInternal theFor) {
myFor = theFor;
}
@ -791,8 +869,7 @@ public class GenericClient extends BaseClient implements IGenericClient {
private final class TagListResponseHandler implements IClientResponseHandler<TagList> {
@Override
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
BaseServerResponseException {
public TagList invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
EncodingEnum respType = EncodingEnum.forContentType(theResponseMimeType);
if (respType == null) {
throw NonFhirResponseException.newInstance(theResponseStatusCode, theResponseMimeType, theResponseReader);

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.gclient.ICreate;
import ca.uhn.fhir.rest.gclient.IDelete;
import ca.uhn.fhir.rest.gclient.IGetPage;
import ca.uhn.fhir.rest.gclient.IGetTags;
import ca.uhn.fhir.rest.gclient.ITransaction;
@ -83,6 +84,7 @@ public interface IGenericClient {
* @param theId
* the ID of the resource to delete
* @return An outcome
* @deprecated Use {@link #delete()) instead
*/
MethodOutcome delete(Class<? extends IResource> theType, IdDt theId);
@ -94,6 +96,7 @@ public interface IGenericClient {
* @param theId
* the ID of the resource to delete
* @return An outcome
* @deprecated Use {@link #delete()) instead
*/
MethodOutcome delete(Class<? extends IResource> theType, String theId);
@ -260,4 +263,9 @@ public interface IGenericClient {
*/
ICreate create();
/**
* Fluent method for the "delete" operation, which performs a logical delete on a server resource
*/
IDelete delete();
}

View File

@ -75,4 +75,12 @@ public interface IRestfulClientFactory {
void setConnectionRequestTimeout(int theConnectionRequestTimeout);
/**
* Sets the HTTP proxy to use for outgoing connections
*
* @param theHost The host (or null to disable proxying, as is the default)
* @param thePort The port (or null to disable proxying, as is the default)
*/
void setProxy(String theHost, Integer thePort);
}

View File

@ -27,6 +27,7 @@ import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import org.apache.http.HttpHost;
import org.apache.http.client.HttpClient;
import org.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.HttpClients;
@ -39,12 +40,22 @@ import ca.uhn.fhir.rest.method.BaseMethodBinding;
public class RestfulClientFactory implements IRestfulClientFactory {
private int myConnectionRequestTimeout=10000;
private int myConnectTimeout=10000;
private int myConnectionRequestTimeout = 10000;
private int myConnectTimeout = 10000;
private FhirContext myContext;
private HttpClient myHttpClient;
private Map<Class<? extends IRestfulClient>, ClientInvocationHandler> myInvocationHandlers = new HashMap<Class<? extends IRestfulClient>, ClientInvocationHandler>();
private int mySocketTimeout = 10000;
private HttpHost myProxy;
@Override
public void setProxy(String theHost, Integer thePort) {
if (theHost != null) {
myProxy = new HttpHost(theHost, thePort, "http");
} else {
myProxy = null;
}
}
/**
* Constructor
@ -53,7 +64,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
* The context
*/
public RestfulClientFactory(FhirContext theFhirContext) {
myContext=theFhirContext;
myContext = theFhirContext;
}
/**
@ -63,11 +74,10 @@ public class RestfulClientFactory implements IRestfulClientFactory {
}
/**
* Sets the context associated with this client factory. Must not be called
* more than once.
* Sets the context associated with this client factory. Must not be called more than once.
*/
public void setFhirContext(FhirContext theContext) {
if(myContext!=null&&myContext!=theContext) {
if (myContext != null && myContext != theContext) {
throw new IllegalStateException("RestfulClientFactory instance is already associated with one FhirContext. RestfulClientFactory instances can not be shared.");
}
myContext = theContext;
@ -86,13 +96,14 @@ public class RestfulClientFactory implements IRestfulClientFactory {
if (myHttpClient == null) {
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
//@formatter:off
RequestConfig defaultRequestConfig = RequestConfig.custom()
.setSocketTimeout(mySocketTimeout)
.setConnectTimeout(myConnectTimeout)
.setConnectionRequestTimeout(myConnectionRequestTimeout)
.setStaleConnectionCheckEnabled(true)
.setProxy(myProxy)
.build();
myHttpClient = HttpClients.custom()
@ -103,7 +114,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
//@formatter:on
}
return myHttpClient;
}
@ -144,7 +155,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
}
myInvocationHandlers.put(theClientType, invocationHandler);
}
T proxy = instantiateProxy(theClientType, invocationHandler);
return proxy;
@ -158,18 +169,18 @@ public class RestfulClientFactory implements IRestfulClientFactory {
@Override
public synchronized void setConnectionRequestTimeout(int theConnectionRequestTimeout) {
myConnectionRequestTimeout = theConnectionRequestTimeout;
myHttpClient=null;
myHttpClient = null;
}
@Override
public synchronized void setConnectTimeout(int theConnectTimeout) {
myConnectTimeout = theConnectTimeout;
myHttpClient=null;
myHttpClient = null;
}
/**
* Sets the Apache HTTP client instance to be used by any new restful clients created by this factory. If set to <code>null</code>, which is the default, a new HTTP client with default settings
* will be created.
* Sets the Apache HTTP client instance to be used by any new restful clients created by this factory. If set to
* <code>null</code>, which is the default, a new HTTP client with default settings will be created.
*
* @param theHttpClient
* An HTTP client instance to use, or <code>null</code>
@ -182,7 +193,7 @@ public class RestfulClientFactory implements IRestfulClientFactory {
@Override
public synchronized void setSocketTimeout(int theSocketTimeout) {
mySocketTimeout = theSocketTimeout;
myHttpClient=null;
myHttpClient = null;
}
@SuppressWarnings("unchecked")

View File

@ -144,6 +144,7 @@ public class LoggingInterceptor implements IClientInterceptor {
if (myLogResponseBody) {
HttpEntity respEntity = theResponse.getEntity();
if (respEntity != null) {
final byte[] bytes;
try {
bytes = IOUtils.toByteArray(respEntity.getContent());
@ -152,8 +153,10 @@ public class LoggingInterceptor implements IClientInterceptor {
}
myLog.info("Client response body:\n{}", new String(bytes));
theResponse.setEntity(new MyEntityWrapper(respEntity, bytes));
} else {
myLog.info("Client response body: (none)");
}
}
}

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.gclient;
import ca.uhn.fhir.model.api.IResource;
/*
* #%L
* HAPI FHIR - Core Library
@ -20,12 +22,8 @@ package ca.uhn.fhir.rest.gclient;
* #L%
*/
import ca.uhn.fhir.model.api.IResource;
public interface ICreate {
public interface ICreate {
ICreateTyped resource(IResource theResource);
ICreateTyped resource(String theResourceAsText);
}

View File

@ -0,0 +1,34 @@
package ca.uhn.fhir.rest.gclient;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
public interface IDelete {
IDeleteTyped resource(IResource theResource);
IDeleteTyped resourceById(IdDt theId);
IDeleteTyped resourceById(String theResourceType, String theLogicalId);
}

View File

@ -0,0 +1,29 @@
package ca.uhn.fhir.rest.gclient;
/*
* #%L
* HAPI FHIR - Core Library
* %%
* Copyright (C) 2014 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
public interface IDeleteTyped extends IClientExecutable<IDeleteTyped, OperationOutcome> {
// nothing for now
}

View File

@ -38,6 +38,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
@ -120,9 +121,14 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
if (idDt == null) {
throw new NullPointerException("ID can not be null");
}
String resourceName = getResourceName();
HttpDeleteClientInvocation retVal = createDeleteInvocation(resourceName, idDt);
if (idDt.hasResourceType()==false) {
idDt = idDt.withResourceType(getResourceName());
}else if (getResourceName().equals(idDt.getResourceType())==false) {
throw new InvalidRequestException("ID parameter has the wrong resource type, expected '" + getResourceName() + "', found: " + idDt.getResourceType());
}
HttpDeleteClientInvocation retVal = createDeleteInvocation(idDt);
for (int idx = 0; idx < theArgs.length; idx++) {
IParameter nextParam = getParameters().get(idx);
@ -132,9 +138,8 @@ public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding {
return retVal;
}
public static HttpDeleteClientInvocation createDeleteInvocation(String theResourceName, IdDt idDt) {
String id = idDt.getValue();
HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theResourceName, id);
public static HttpDeleteClientInvocation createDeleteInvocation(IdDt theId) {
HttpDeleteClientInvocation retVal = new HttpDeleteClientInvocation(theId);
return retVal;
}

View File

@ -23,10 +23,10 @@ package ca.uhn.fhir.rest.method;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.HttpDelete;
import org.apache.http.client.methods.HttpRequestBase;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.BaseHttpClientInvocation;
import ca.uhn.fhir.rest.server.EncodingEnum;
@ -34,9 +34,9 @@ public class HttpDeleteClientInvocation extends BaseHttpClientInvocation {
private String myUrlPath;
public HttpDeleteClientInvocation(String... theUrlFragments) {
public HttpDeleteClientInvocation(IdDt theId) {
super();
myUrlPath = StringUtils.join(theUrlFragments, '/');
myUrlPath = theId.toUnqualifiedVersionless().getValue();
}
@Override

View File

@ -1,7 +1,5 @@
package example;
import org.apache.http.impl.client.HttpClientBuilder;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.IRestfulClientFactory;
@ -16,6 +14,16 @@ public class ClientExamples {
// nothing yet
}
@SuppressWarnings("unused")
public void createProxy() {
//START SNIPPET: proxy
FhirContext ctx = new FhirContext();
ctx.getRestfulClientFactory().setProxy("example.com", 8888);
IGenericClient genericClient = ctx.newRestfulGenericClient("http://localhost:9999/fhir");
//START SNIPPET: end
}
@SuppressWarnings("unused")
public void createSecurity() {
//START SNIPPET: security

View File

@ -7,8 +7,10 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.IGenericClient;
public class GenericClientExample {
@ -60,6 +62,19 @@ System.out.println(conf.getDescription().getValue());
//END SNIPPET: conformance
}
{
//START SNIPPET: delete
// Retrieve the server's conformance statement and print its description
OperationOutcome outcome = client.delete()
.resourceById(new IdDt("Patient", "1234"))
.execute();
// outcome may be null if the server didn't return one
if (outcome != null) {
System.out.println(outcome.getIssueFirstRep().getDetails().getValue());
}
//END SNIPPET: delete
}
{
//START SNIPPET: search
Bundle response = client.search()
.forResource(Patient.class)
@ -68,6 +83,20 @@ Bundle response = client.search()
.andLogRequestAndResponse(true)
.execute();
//END SNIPPET: search
//START SNIPPET: searchAdv
response = client.search()
.forResource(Patient.class)
.encodedJson()
.where(Patient.BIRTHDATE.beforeOrEquals().day("2012-01-22"))
.and(Patient.BIRTHDATE.after().day("2011-01-01"))
.include(Patient.INCLUDE_MANAGINGORGANIZATION)
.sort().ascending(Patient.BIRTHDATE)
.sort().descending(Patient.NAME)
.limitTo(123)
.execute();
//END SNIPPET: searchAdv
//START SNIPPET: searchPaging
if (response.getLinkNext().isEmpty() == false) {

View File

@ -106,6 +106,27 @@
<param name="file"
value="src/site/example/java/example/GenericClientExample.java" />
</macro>
<p>
The fluent search also has methods for sorting, limiting, specifying
JSON encoding, etc.
</p>
<macro name="snippet">
<param name="id" value="searchAdv" />
<param name="file"
value="src/site/example/java/example/GenericClientExample.java" />
</macro>
</subsection>
<subsection name="Instance - Delete">
<p>
The following example shows how to perform a delete
operation using the generic client:
</p>
<macro name="snippet">
<param name="id" value="delete" />
<param name="file"
value="src/site/example/java/example/GenericClientExample.java" />
</macro>
</subsection>
<subsection name="Server - Conformance">
@ -289,9 +310,9 @@
</p>
<p>
The underlying client configuration is provided by setting an
<a href="./apidocs/ca/uhn/fhir/rest/client/IRestfulClientFactory.html#setHttpClient(org.apache.http.client.HttpClient)">HttpClient</a>
on the RestfulClientFactory.
The underlying client configuration is provided by accessing the
<a href="./apidocs/ca/uhn/fhir/rest/client/IRestfulClientFactory.html">IRestfulClientFactory</a>
class from the FhirContext.
</p>
<subsection name="Interceptors">
@ -338,6 +359,20 @@
</subsection>
<subsection name="Configuring an HTTP Proxy">
<p>
An HTTP proxy may be configured directly on the
restful client factory, as shown below.
</p>
<macro name="snippet">
<param name="id" value="logging" />
<param name="file" value="src/site/example/java/example/ClientExamples.java" />
</macro>
</subsection>
</section>
</body>

View File

@ -0,0 +1,91 @@
package ca.uhn.fhir.context;
//START SNIPPET: patientDef
import java.util.ArrayList;
import java.util.List;
import ca.uhn.fhir.model.api.annotation.Child;
import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.api.annotation.Extension;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.util.ElementUtil;
/**
* Definition class for adding extensions to the built-in
* Patient resource type.
*/
@ResourceDef(name="Patient")
public class DummyPatientWithExtensions extends Patient {
/**
* Each extension is defined in a field. Any valid HAPI Data Type
* can be used for the field type. Note that the [name=""] attribute
* in the @Child annotation needs to match the name for the bean accessor
* and mutator methods.
*/
@Child(name="petName")
@Extension(url="http://example.com/dontuse#petname", definedLocally=false, isModifier=false)
@Description(shortDefinition="The name of the patient's favourite pet")
private StringDt myPetName;
/**
* The second example extension uses a List type to provide
* repeatable values. Note that a [max=] value has been placed in
* the @Child annotation.
*
* Note also that this extension is a modifier extension
*/
@Child(name="importantDates", max=Child.MAX_UNLIMITED)
@Extension(url="http://example.com/dontuse#importantDates", definedLocally=false, isModifier=true)
@Description(shortDefinition="Some dates of note for this patient")
private List<DateTimeDt> myImportantDates;
/**
* It is important to override the isEmpty() method, adding a check for any
* newly added fields.
*/
@Override
public boolean isEmpty() {
return super.isEmpty() && ElementUtil.isEmpty(myPetName, myImportantDates);
}
/********
* Accessors and mutators follow
*
* IMPORTANT:
* Each extension is required to have an getter/accessor and a stter/mutator.
* You are highly recommended to create getters which create instances if they
* do not already exist, since this is how the rest of the HAPI FHIR API works.
********/
/** Getter for important dates */
public List<DateTimeDt> getImportantDates() {
if (myImportantDates==null) {
myImportantDates = new ArrayList<DateTimeDt>();
}
return myImportantDates;
}
/** Getter for pet name */
public StringDt getPetName() {
if (myPetName == null) {
myPetName = new StringDt();
}
return myPetName;
}
/** Setter for important dates */
public void setImportantDates(List<DateTimeDt> theImportantDates) {
myImportantDates = theImportantDates;
}
/** Setter for pet name */
public void setPetName(StringDt thePetName) {
myPetName = thePetName;
}
}
//END SNIPPET: patientDef

View File

@ -0,0 +1,87 @@
package ca.uhn.fhir.context;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.io.IOException;
import org.junit.Test;
import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.parser.MyPatient;
public class ExtensionTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ExtensionTest.class);
private static FhirContext ourCtx = new FhirContext();
@Test
public void testExtensionType() {
DummyPatientWithExtensions patient = new DummyPatientWithExtensions();
patient.setPetName(new StringDt("Fido"));
patient.getImportantDates().add(new DateTimeDt("2010-01-02"));
patient.getImportantDates().add(new DateTimeDt("2014-01-26T11:11:11"));
patient.addName().addFamily("Smith").addGiven("John").addGiven("Quincy").addSuffix("Jr");
String messageString = ourCtx.newXmlParser().encodeResourceToString(patient);
ourLog.info(messageString);
assertThat(messageString, containsString("<modifierExtension url=\"http://example.com/dontuse#importantDates\"><valueDateTime value=\"2010-01-02\"/></modifierExtension>"));
}
@Test
public void testEmptyExtensionType() {
DummyPatientWithExtensions patient = new DummyPatientWithExtensions();
patient.addName().addFamily("Smith").addGiven("John").addGiven("Quincy").addSuffix("Jr");
String messageString = ourCtx.newXmlParser().encodeResourceToString(patient);
ourLog.info(messageString);
assertThat(messageString, not(containsString("xtension")));
}
@Test
public void testEmptyExtensionTypeJson() {
DummyPatientWithExtensions patient = new DummyPatientWithExtensions();
patient.addName().addFamily("Smith").addGiven("John").addGiven("Quincy").addSuffix("Jr");
String messageString = ourCtx.newJsonParser().encodeResourceToString(patient);
ourLog.info(messageString);
assertThat(messageString, not(containsString("xtension")));
}
@SuppressWarnings("unused")
public static void main(String[] args) throws DataFormatException, IOException {
// START SNIPPET: patientUse
MyPatient patient = new MyPatient();
patient.setPetName(new StringDt("Fido"));
patient.getImportantDates().add(new DateTimeDt("2010-01-02"));
patient.getImportantDates().add(new DateTimeDt("2014-01-26T11:11:11"));
patient.addName().addFamily("Smith").addGiven("John").addGiven("Quincy").addSuffix("Jr");
IParser p = new FhirContext().newXmlParser().setPrettyPrint(true);
String messageString = p.encodeResourceToString(patient);
System.out.println(messageString);
// END SNIPPET: patientUse
// START SNIPPET: patientParse
IParser parser = new FhirContext().newXmlParser();
MyPatient newPatient = parser.parseResource(MyPatient.class, messageString);
// END SNIPPET: patientParse
{
FhirContext ctx2 = new FhirContext();
RuntimeResourceDefinition def = ctx2.getResourceDefinition(patient);
System.out.println(ctx2.newXmlParser().setPrettyPrint(true).encodeResourceToString(def.toProfile()));
}
}
}

View File

@ -70,17 +70,18 @@ public class JsonParserTest {
p.addUndeclaredExtension(extension);
String str = ourCtx.newJsonParser().encodeResourceToString(p);
assertEquals("{\"resourceType\":\"Patient\",\"extension\":[{\"url\":\"http://foo#bar\"}]}", str);
assertEquals("{\"resourceType\":\"Patient\"}", str);
extension.setValue(new StringDt());
str = ourCtx.newJsonParser().encodeResourceToString(p);
assertEquals("{\"resourceType\":\"Patient\",\"extension\":[{\"url\":\"http://foo#bar\"}]}", str);
assertEquals("{\"resourceType\":\"Patient\"}", str);
extension.setValue(new StringDt(""));
str = ourCtx.newJsonParser().encodeResourceToString(p);
assertEquals("{\"resourceType\":\"Patient\",\"extension\":[{\"url\":\"http://foo#bar\"}]}", str);
assertEquals("{\"resourceType\":\"Patient\"}", str);
}

View File

@ -32,6 +32,7 @@ import ca.uhn.fhir.model.api.Tag;
import ca.uhn.fhir.model.api.TagList;
import ca.uhn.fhir.model.dstu.resource.Encounter;
import ca.uhn.fhir.model.dstu.resource.Observation;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
@ -135,6 +136,37 @@ public class GenericClientTest {
assertEquals(resourceText, IOUtils.toString(((HttpPost)capt.getAllValues().get(2)).getEntity().getContent()));
}
@Test
public void testDelete() throws Exception {
OperationOutcome oo = new OperationOutcome();
oo.addIssue().addLocation().setValue("testDelete01");
String ooStr = myCtx.newXmlParser().encodeResourceToString(oo);
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), 201, "OK"));
when(myHttpResponse.getAllHeaders()).thenReturn(new Header[] { new BasicHeader(Constants.HEADER_LOCATION, "/Patient/44/_history/22") });
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(ooStr), Charset.forName("UTF-8")));
IGenericClient client = myCtx.newRestfulGenericClient("http://example.com/fhir");
OperationOutcome outcome = client.delete().resourceById("Patient", "123").execute();
assertEquals("http://example.com/fhir/Patient/123", capt.getValue().getURI().toString());
assertEquals("DELETE", capt.getValue().getMethod());
assertEquals("testDelete01",outcome.getIssueFirstRep().getLocationFirstRep().getValue());
when(myHttpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("LKJHLKJGLKJKLL"), Charset.forName("UTF-8")));
outcome = client.delete().resourceById(new IdDt("Location", "123","456")).prettyPrint().encodedJson().execute();
assertEquals("http://example.com/fhir/Location/123?_format=json&_pretty=true", capt.getAllValues().get(1).getURI().toString());
assertEquals("DELETE", capt.getValue().getMethod());
assertEquals(null,outcome);
}
private String getPatientFeedWithOneResult() {
@ -420,7 +452,7 @@ public class GenericClientTest {
assertNotNull(ct);
assertEquals(Constants.CT_FHIR_JSON + "; charset=UTF-8", ct.getValue());
assertEquals("http://example.com/fhir", value.getURI().toString());
assertEquals("http://example.com/fhir?_format=json", value.getURI().toString());
assertThat(IOUtils.toString(value.getEntity().getContent()), StringContains.containsString("\"resourceType\""));
assertEquals(bundle.getEntries().get(0).getId(), response.getEntries().get(0).getId());
}

View File

@ -1,10 +1,7 @@
package ca.uhn.fhir.jpa.test;
import static org.hamcrest.Matchers.containsString;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.fail;
import static org.hamcrest.Matchers.*;
import static org.junit.Assert.*;
import java.util.Date;
@ -22,6 +19,7 @@ import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
import ca.uhn.fhir.jpa.provider.JpaSystemProvider;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.PeriodDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.Encounter;
@ -32,10 +30,13 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.resource.Questionnaire;
import ca.uhn.fhir.model.dstu.valueset.EncounterClassEnum;
import ca.uhn.fhir.model.dstu.valueset.EncounterStateEnum;
import ca.uhn.fhir.model.dstu.valueset.NarrativeStatusEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor;
import ca.uhn.fhir.rest.gclient.StringClientParam;
import ca.uhn.fhir.rest.gclient.TokenClientParam;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -82,7 +83,9 @@ public class CompleteResourceProviderTest {
private static IFhirResourceDao<Questionnaire> questionnaireDao;
@Test
public void testCreateWithId() {
public void testCreateWithClientSuppliedId() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testCreateWithId01");
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testCreateWithId01");
IdDt p1Id = ourClient.create().resource(p1).withId("testCreateWithId").execute().getId();
@ -103,71 +106,37 @@ public class CompleteResourceProviderTest {
// good
}
Bundle history = ourClient.history(null, (String)null, null, null);
Bundle history = ourClient.history(null, (String) null, null, null);
assertEquals(p1Id.getIdPart(), history.getEntries().get(0).getId().getIdPart());
assertNotNull(history.getEntries().get(0).getResource());
}
@Test
public void testInsertBadReference() {
public void testTryToCreateResourceWithReferenceThatDoesntExist() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testTryToCreateResourceWithReferenceThatDoesntExist01");
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01");
p1.addName().addFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01");
p1.setManagingOrganization(new ResourceReferenceDt("Organization/132312323"));
p1.addIdentifier().setSystem("urn:system").setValue("testTryToCreateResourceWithReferenceThatDoesntExist01");
p1.addName().addFamily("testTryToCreateResourceWithReferenceThatDoesntExistFamily01").addGiven("testTryToCreateResourceWithReferenceThatDoesntExistGiven01");
p1.setManagingOrganization(new ResourceReferenceDt("Organization/1323123232349875324987529835"));
try {
ourClient.create(p1).getId();
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), containsString("Organization/132312323"));
assertThat(e.getMessage(), containsString("Organization/1323123232349875324987529835"));
}
}
@Test
public void testInsertUpdatesConformance() {
// Conformance conf = ourConfProvider.getServerConformance();
// ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conf));
//
// RestResource res=null;
// for (Rest nextRest : conf.getRest()) {
// for (RestResource nextRes : nextRest.getResource()) {
// if (nextRes.getType().getValueAsEnum()==ResourceTypeEnum.PATIENT) {
// res = nextRes;
// }
// }
// }
// List<ExtensionDt> resCounts = res.getUndeclaredExtensionsByUrl(ExtensionConstants.CONF_RESOURCE_COUNT);
//
// int initial = 0;
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01");
p1.addName().addFamily("testSearchByResourceChainFamily01").addGiven("testSearchByResourceChainGiven01");
ourClient.create(p1).getId();
// conf = ourConfProvider.getServerConformance();
// res=null;
// for (Rest nextRest : conf.getRest()) {
// for (RestResource nextRes : nextRest.getResource()) {
// if (nextRes.getType().getValueAsEnum()==ResourceTypeEnum.PATIENT) {
// res = nextRes;
// }
// }
// }
// resCounts = res.getUndeclaredExtensionsByUrl(ExtensionConstants.CONF_RESOURCE_COUNT);
// assertNotNull(resCounts);
// assertEquals(1, resCounts.size());
// DecimalDt number = (DecimalDt) resCounts.get(0).getValue();
// assertEquals(initial+1, number.getValueAsInteger());
}
@Test
public void testSaveAndRetrieveExistingNarrative() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSaveAndRetrieveExistingNarrative01");
Patient p1 = new Patient();
p1.getText().setStatus(NarrativeStatusEnum.GENERATED);
p1.getText().getDiv().setValueAsString("<div>HELLO WORLD</div>");
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByResourceChain01");
p1.addIdentifier().setSystem("urn:system").setValue("testSaveAndRetrieveExistingNarrative01");
IdDt newId = ourClient.create(p1).getId();
@ -188,6 +157,9 @@ public class CompleteResourceProviderTest {
@Test
public void testSearchByIdentifier() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSearchByIdentifier01");
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSearchByIdentifier02");
Patient p1 = new Patient();
p1.addIdentifier().setSystem("urn:system").setValue("testSearchByIdentifier01");
p1.addName().addFamily("testSearchByIdentifierFamily01").addGiven("testSearchByIdentifierGiven01");
@ -205,12 +177,13 @@ public class CompleteResourceProviderTest {
@Test
public void testSearchByIdentifierWithoutSystem() {
deleteToken("Patient", Patient.SP_IDENTIFIER, "", "testSearchByIdentifierWithoutSystem01");
Patient p1 = new Patient();
p1.addIdentifier().setValue("testSearchByIdentifierWithoutSystem01");
IdDt p1Id = ourClient.create(p1).getId();
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint()
.execute();
Bundle actual = ourClient.search().forResource(Patient.class).where(Patient.IDENTIFIER.exactly().systemAndCode(null, "testSearchByIdentifierWithoutSystem01")).encodedJson().prettyPrint().execute();
assertEquals(1, actual.size());
assertEquals(p1Id.getIdPart(), actual.getEntries().get(0).getId().getIdPart());
@ -218,20 +191,19 @@ public class CompleteResourceProviderTest {
@Test
public void testDeepChaining() {
// ourClient = ourCtx.newRestfulGenericClient("http://fhir.healthintersections.com.au/open");
// ourClient = ourCtx.newRestfulGenericClient("https://fhir.orionhealth.com/blaze/fhir");
// ourClient = ourCtx.newRestfulGenericClient("http://spark.furore.com/fhir");
// ourClient.registerInterceptor(new LoggingInterceptor(true));
delete("Location", Location.SP_NAME, "testDeepChainingL1");
delete("Location", Location.SP_NAME, "testDeepChainingL2");
deleteToken("Encounter", Encounter.SP_IDENTIFIER, "urn:foo", "testDeepChainingE1");
Location l1 = new Location();
l1.getName().setValue("testDeepChainingL1");
IdDt l1id = ourClient.create().resource(l1).execute().getId();
Location l2 = new Location();
l2.getName().setValue("testDeepChainingL2");
l2.getPartOf().setReference(l1id.toVersionless().toUnqualified());
IdDt l2id = ourClient.create().resource(l2).execute().getId();
Encounter e1 = new Encounter();
e1.addIdentifier().setSystem("urn:foo").setValue("testDeepChainingE1");
e1.getStatus().setValueAsEnum(EncounterStateEnum.IN_PROGRESS);
@ -240,7 +212,7 @@ public class CompleteResourceProviderTest {
location.getLocation().setReference(l2id.toUnqualifiedVersionless());
location.setPeriod(new PeriodDt().setStartWithSecondsPrecision(new Date()).setEndWithSecondsPrecision(new Date()));
IdDt e1id = ourClient.create().resource(e1).execute().getId();
//@formatter:off
Bundle res = ourClient.search()
.forResource(Encounter.class)
@ -249,15 +221,34 @@ public class CompleteResourceProviderTest {
.include(Location.INCLUDE_PARTOF)
.execute();
//@formatter:on
assertEquals(3, res.size());
assertEquals(1, res.getResources(Encounter.class).size());
assertEquals(e1id.toUnqualifiedVersionless(), res.getResources(Encounter.class).get(0).getId().toUnqualifiedVersionless());
}
private void delete(String theResourceType, String theParamName, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new StringClientParam(theParamName).matches().value(theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
}
private void deleteToken(String theResourceType, String theParamName, String theParamSystem, String theParamValue) {
Bundle resources = ourClient.search().forResource(theResourceType).where(new TokenClientParam(theParamName).exactly().systemAndCode(theParamSystem, theParamValue)).execute();
for (IResource next : resources.toListOfResources()) {
ourLog.info("Deleting resource: {}", next.getId());
ourClient.delete().resource(next).execute();
}
}
@Test
public void testSearchByResourceChain() {
delete("Organization", Organization.SP_NAME, "testSearchByResourceChainName01");
deleteToken("Patient", Patient.SP_IDENTIFIER, "urn:system", "testSearchByResourceChain01");
Organization o1 = new Organization();
o1.setName("testSearchByResourceChainName01");
IdDt o1id = ourClient.create(o1).getId();
@ -297,63 +288,71 @@ public class CompleteResourceProviderTest {
@SuppressWarnings("unchecked")
@BeforeClass
public static void beforeClass() throws Exception {
ourAppCtx = new ClassPathXmlApplicationContext("fhir-spring-test-config.xml");
patientDao = (IFhirResourceDao<Patient>) ourAppCtx.getBean("myPatientDao", IFhirResourceDao.class);
PatientResourceProvider patientRp = new PatientResourceProvider();
patientRp.setDao(patientDao);
questionnaireDao = (IFhirResourceDao<Questionnaire>) ourAppCtx.getBean("myQuestionnaireDao", IFhirResourceDao.class);
QuestionnaireResourceProvider questionnaireRp = new QuestionnaireResourceProvider();
questionnaireRp.setDao(questionnaireDao);
observationDao = (IFhirResourceDao<Observation>) ourAppCtx.getBean("myObservationDao", IFhirResourceDao.class);
ObservationResourceProvider observationRp = new ObservationResourceProvider();
observationRp.setDao(observationDao);
IFhirResourceDao<Location> locationDao = (IFhirResourceDao<Location>) ourAppCtx.getBean("myLocationDao", IFhirResourceDao.class);
LocationResourceProvider locationRp = new LocationResourceProvider();
locationRp.setDao(locationDao);
IFhirResourceDao<Encounter> encounterDao = (IFhirResourceDao<Encounter>) ourAppCtx.getBean("myEncounterDao", IFhirResourceDao.class);
EncounterResourceProvider encounterRp = new EncounterResourceProvider();
encounterRp.setDao(encounterDao);
IFhirResourceDao<Organization> organizationDao = (IFhirResourceDao<Organization>) ourAppCtx.getBean("myOrganizationDao", IFhirResourceDao.class);
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
organizationRp.setDao(organizationDao);
int port = RandomServerPortProvider.findFreePort();
RestfulServer restServer = new RestfulServer();
restServer.setResourceProviders(encounterRp, locationRp, patientRp, questionnaireRp, observationRp, organizationRp);
restServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
String serverBase = "http://localhost:" + port + "/fhir/context";
IFhirSystemDao systemDao = (IFhirSystemDao) ourAppCtx.getBean("mySystemDao", IFhirSystemDao.class);
JpaSystemProvider systemProv = new JpaSystemProvider(systemDao);
restServer.setPlainProviders(systemProv);
// ourConfProvider = new JpaConformanceProvider(restServer, systemDao, Collections.singletonList((IFhirResourceDao)patientDao));
if (true) {
ourAppCtx = new ClassPathXmlApplicationContext("fhir-spring-test-config.xml");
int myPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(myPort);
patientDao = (IFhirResourceDao<Patient>) ourAppCtx.getBean("myPatientDao", IFhirResourceDao.class);
PatientResourceProvider patientRp = new PatientResourceProvider();
patientRp.setDao(patientDao);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
questionnaireDao = (IFhirResourceDao<Questionnaire>) ourAppCtx.getBean("myQuestionnaireDao", IFhirResourceDao.class);
QuestionnaireResourceProvider questionnaireRp = new QuestionnaireResourceProvider();
questionnaireRp.setDao(questionnaireDao);
String serverBase = "http://localhost:" + myPort + "/fhir/context";
// testerServlet.setServerBase("http://fhir.healthintersections.com.au/open");
observationDao = (IFhirResourceDao<Observation>) ourAppCtx.getBean("myObservationDao", IFhirResourceDao.class);
ObservationResourceProvider observationRp = new ObservationResourceProvider();
observationRp.setDao(observationDao);
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(restServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
IFhirResourceDao<Location> locationDao = (IFhirResourceDao<Location>) ourAppCtx.getBean("myLocationDao", IFhirResourceDao.class);
LocationResourceProvider locationRp = new LocationResourceProvider();
locationRp.setDao(locationDao);
ourServer.setHandler(proxyHandler);
ourServer.start();
IFhirResourceDao<Encounter> encounterDao = (IFhirResourceDao<Encounter>) ourAppCtx.getBean("myEncounterDao", IFhirResourceDao.class);
EncounterResourceProvider encounterRp = new EncounterResourceProvider();
encounterRp.setDao(encounterDao);
IFhirResourceDao<Organization> organizationDao = (IFhirResourceDao<Organization>) ourAppCtx.getBean("myOrganizationDao", IFhirResourceDao.class);
OrganizationResourceProvider organizationRp = new OrganizationResourceProvider();
organizationRp.setDao(organizationDao);
restServer.setResourceProviders(encounterRp, locationRp, patientRp, questionnaireRp, observationRp, organizationRp);
restServer.getFhirContext().setNarrativeGenerator(new DefaultThymeleafNarrativeGenerator());
IFhirSystemDao systemDao = (IFhirSystemDao) ourAppCtx.getBean("mySystemDao", IFhirSystemDao.class);
JpaSystemProvider systemProv = new JpaSystemProvider(systemDao);
restServer.setPlainProviders(systemProv);
// ourConfProvider = new JpaConformanceProvider(restServer, systemDao,
// Collections.singletonList((IFhirResourceDao)patientDao));
ourServer = new Server(port);
ServletContextHandler proxyHandler = new ServletContextHandler();
proxyHandler.setContextPath("/");
// testerServlet.setServerBase("http://fhir.healthintersections.com.au/open");
ServletHolder servletHolder = new ServletHolder();
servletHolder.setServlet(restServer);
proxyHandler.addServlet(servletHolder, "/fhir/context/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
}
ourCtx = restServer.getFhirContext();
// ourCtx.getRestfulClientFactory().setProxy("localhost", 8888);
ourClient = ourCtx.newRestfulGenericClient(serverBase);
// ourClient = ourCtx.newRestfulGenericClient("http://fhir.healthintersections.com.au/open");
// ourClient = ourCtx.newRestfulGenericClient("https://fhir.orionhealth.com/blaze/fhir");
// ourClient = ourCtx.newRestfulGenericClient("http://spark.furore.com/fhir");
ourClient.registerInterceptor(new LoggingInterceptor(true));
}
}