Client security working

This commit is contained in:
jamesagnew 2014-04-23 14:23:32 -04:00
parent 0b93dcc491
commit 018e780343
23 changed files with 847 additions and 511 deletions

View File

@ -0,0 +1,44 @@
package ca.uhn.fhir.rest.client;
import java.io.IOException;
import org.apache.http.HttpException;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.protocol.HttpClientContext;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.protocol.HttpContext;
/**
* HTTP interceptor to be used for adding HTTP basic auth username/password tokens
* to requests
* <p>
* See the
* </p>
*/
public class HttpBasicAuthInterceptor implements HttpRequestInterceptor {
private String myUsername;
private String myPassword;
public HttpBasicAuthInterceptor(String theUsername, String thePassword) {
super();
myUsername = theUsername;
myPassword = thePassword;
}
@Override
public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException {
AuthState authState = (AuthState) context.getAttribute(HttpClientContext.TARGET_AUTH_STATE);
if (authState.getAuthScheme() == null) {
Credentials creds = new UsernamePasswordCredentials(myUsername, myPassword);
authState.update(new BasicScheme(), creds);
}
}
}

View File

@ -69,7 +69,7 @@ public abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends
if (param == null) {
continue;
}
params[i] = param.translateQueryParametersIntoServerArgument(theRequest.getParameters(), resource);
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, resource);
}
addParametersForServerRequest(theRequest, params);

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.rest.method;
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isNotBlank;
import java.io.IOException;
import java.io.PrintWriter;
@ -130,7 +130,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
throw new IllegalStateException("Should not get here!");
}
public abstract List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException;
public abstract List<IResource> invokeServer(Object theResourceProvider, Request theRequest) throws InvalidRequestException, InternalErrorException;
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
@ -165,7 +165,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
requestIsBrowser = true;
}
List<IResource> result = invokeServer(getProvider(), theRequest.getId(), theRequest.getVersion(), theRequest.getParameters());
List<IResource> result = invokeServer(getProvider(), theRequest);
switch (getReturnType()) {
case BUNDLE:
streamResponseAsBundle(theServer, theResponse, result, responseEncoding, theRequest.getFhirServerBase(), theRequest.getCompleteUrl(), prettyPrint, requestIsBrowser, narrativeMode);

View File

@ -3,7 +3,6 @@ package ca.uhn.fhir.rest.method;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
@ -11,7 +10,6 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Conformance;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -39,7 +37,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding
}
@Override
public List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException,
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest) throws InvalidRequestException,
InternalErrorException {
IResource conf;
try {

View File

@ -1,11 +1,10 @@
package ca.uhn.fhir.rest.method;
import static org.apache.commons.lang3.StringUtils.*;
import static org.apache.commons.lang3.StringUtils.isBlank;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
@ -151,10 +150,10 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException {
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest) throws InvalidRequestException, InternalErrorException {
Object[] args = new Object[getMethod().getParameterTypes().length];
if (myCountParamIndex != null) {
String[] countValues = theParameterValues.remove(Constants.PARAM_COUNT);
String[] countValues = theRequest.getParameters().remove(Constants.PARAM_COUNT);
if (countValues.length > 0 && StringUtils.isNotBlank(countValues[0])) {
try {
args[myCountParamIndex] = new IntegerDt(countValues[0]);
@ -164,7 +163,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
}
}
if (mySinceParamIndex != null) {
String[] sinceValues = theParameterValues.remove(Constants.PARAM_SINCE);
String[] sinceValues = theRequest.getParameters().remove(Constants.PARAM_SINCE);
if (sinceValues.length > 0 && StringUtils.isNotBlank(sinceValues[0])) {
try {
args[mySinceParamIndex] = new InstantDt(sinceValues[0]);
@ -175,7 +174,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
}
if (myIdParamIndex!=null) {
args[myIdParamIndex] = theId;
args[myIdParamIndex] = theRequest.getId();
}
Object response;

View File

@ -2,7 +2,6 @@ package ca.uhn.fhir.rest.method;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.Validate;
@ -88,11 +87,11 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException {
public List<IResource> invokeServer(Object theResourceProvider,Request theRequest) throws InvalidRequestException, InternalErrorException {
Object[] params = new Object[myParameterCount];
params[myIdIndex] = theId;
params[myIdIndex] = theRequest.getId();
if (myVersionIdIndex != null) {
params[myVersionIdIndex] = theVersionId;
params[myVersionIdIndex] = theRequest.getId();
}
Object response;

View File

@ -15,10 +15,9 @@ import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.IQueryParameter;
import ca.uhn.fhir.rest.param.BaseQueryParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -85,20 +84,21 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public List<IResource> invokeServer(Object theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> parameterValues) throws InvalidRequestException,
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest) throws InvalidRequestException,
InternalErrorException {
assert theId == null;
assert theVersionId == null;
assert theRequest.getId() == null;
assert theRequest.getVersion() == null;
Object[] params = new Object[myParameters.size()];
for (int i = 0; i < myParameters.size(); i++) {
IParameter param = myParameters.get(i);
params[i] = param.translateQueryParametersIntoServerArgument(parameterValues, null);
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null);
}
Object response;
try {
response = this.getMethod().invoke(theResourceProvider, params);
Method method = this.getMethod();
response = method.invoke(theResourceProvider, params);
} catch (IllegalAccessException e) {
throw new InternalErrorException(e);
} catch (IllegalArgumentException e) {
@ -140,10 +140,10 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
Set<String> methodParamsTemp = new HashSet<String>();
for (int i = 0; i < this.myParameters.size(); i++) {
if (!(myParameters.get(i) instanceof IQueryParameter)) {
if (!(myParameters.get(i) instanceof BaseQueryParameter)) {
continue;
}
IQueryParameter temp = (IQueryParameter) myParameters.get(i);
BaseQueryParameter temp = (BaseQueryParameter) myParameters.get(i);
methodParamsTemp.add(temp.getName());
if (temp.isRequired() && !theRequest.getParameters().containsKey(temp.getName())) {
ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), temp.getName());

View File

@ -0,0 +1,26 @@
package ca.uhn.fhir.rest.method;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
class ServletRequestParameter implements IParameter {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestParameter.class);
@Override
public void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
/*
* Does nothing, since we just ignore HttpServletRequest arguments
*/
ourLog.trace("Ignoring HttpServletRequest argument: {}", theSourceClientArgument);
}
@Override
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
return theRequest.getServletRequest();
}
}

View File

@ -24,7 +24,7 @@ import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam {
private Integer myIdParameterIndex;

View File

@ -10,6 +10,9 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.ServletRequest;
import javax.servlet.http.HttpServletRequest;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.PathSpecification;
@ -88,9 +91,14 @@ class Util {
int paramIndex = 0;
for (Annotation[] annotations : method.getParameterAnnotations()) {
boolean haveHandledMethod = false;
Class<?> parameterType = parameterTypes[paramIndex];
if (parameterType.equals(HttpServletRequest.class) || parameterType.equals(ServletRequest.class)) {
ServletRequestParameter param = new ServletRequestParameter();
parameters.add(param);
} else {
for (int i = 0; i < annotations.length; i++) {
Annotation nextAnnotation = annotations[i];
Class<?> parameterType = parameterTypes[paramIndex];
Class<? extends java.util.Collection<?>> outerCollectionType = null;
Class<? extends java.util.Collection<?>> innerCollectionType = null;
@ -117,7 +125,7 @@ class Util {
SearchParameter parameter = new SearchParameter();
parameter.setName(((OptionalParam) nextAnnotation).name());
parameter.setRequired(false);
parameter.setType(parameterType, innerCollectionType, innerCollectionType);
parameter.setType(parameterType, innerCollectionType, outerCollectionType);
param = parameter;
} else if (nextAnnotation instanceof IncludeParam) {
if (parameterType != PathSpecification.class || innerCollectionType == null || outerCollectionType != null) {
@ -142,6 +150,7 @@ class Util {
haveHandledMethod = true;
parameters.add(param);
break;
}
@ -149,6 +158,7 @@ class Util {
throw new ConfigurationException("Parameter #" + paramIndex + " of method '" + method.getName() + "' on type '" + method.getDeclaringClass().getCanonicalName()
+ "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
}
}
paramIndex++;
}

View File

@ -6,11 +6,12 @@ import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.QueryUtil;
public abstract class IQueryParameter implements IParameter {
public abstract class BaseQueryParameter implements IParameter {
public abstract List<List<String>> encode(Object theObject) throws InternalErrorException;
@ -54,8 +55,8 @@ public abstract class IQueryParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Map<String, String[]> theQueryParameters, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
String[] value = theQueryParameters.get(getName());
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
String[] value = theRequest.getParameters().get(getName());
if (value == null || value.length == 0) {
if (handlesMissing()) {
return parse(new ArrayList<List<String>>(0));

View File

@ -4,6 +4,7 @@ import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -16,12 +17,12 @@ public interface IParameter {
* This <b>server method</b> method takes the data received by the server in an incoming request, and translates that data into a single argument for a server method invocation. Note that all
* received data is passed to this method, but the expectation is that not necessarily that all data is used by every parameter.
*
* @param theQueryParameters
* The query params, e.g. ?family=smith&given=john
* @param theRequest
* The incoming request object
* @param theRequestContents
* The parsed contents of the incoming request. E.g. if the request was an HTTP POST with a resource in the body, this argument would contain the parsed {@link IResource} instance.
* @return Returns the argument object as it will be passed to the {@link IResourceProvider} method.
*/
Object translateQueryParametersIntoServerArgument(Map<String, String[]> theQueryParameters, Object theRequestContents) throws InternalErrorException, InvalidRequestException;
Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException;
}

View File

@ -13,7 +13,7 @@ import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class IncludeParameter extends IQueryParameter {
public class IncludeParameter extends BaseQueryParameter {
private Class<? extends Collection<PathSpecification>> myInstantiableCollectionType;
private HashSet<String> myAllow;

View File

@ -4,6 +4,7 @@ import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -22,7 +23,7 @@ public class ResourceParameter implements IParameter {
}
@Override
public Object translateQueryParametersIntoServerArgument(Map<String, String[]> theQueryParameters, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
IResource resource = (IResource) theRequestContents;
return resource;
}

View File

@ -16,7 +16,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class SearchParameter extends IQueryParameter {
public class SearchParameter extends BaseQueryParameter {
private String name;
private IParamBinder myParamBinder;

View File

@ -162,6 +162,7 @@ public class RestfulServer extends HttpServlet {
}
ourLog.info("Got {} resource providers", typeToProvider.size());
for (IResourceProvider provider : typeToProvider.values()) {
assertProviderIsValid(provider);
findResourceMethods(provider);
}
}
@ -169,6 +170,7 @@ public class RestfulServer extends HttpServlet {
Collection<Object> providers = getProviders();
if (providers != null) {
for (Object next : providers) {
assertProviderIsValid(next);
findResourceMethods(next);
}
}
@ -184,6 +186,12 @@ public class RestfulServer extends HttpServlet {
ourLog.info("A FHIR has been lit on this server");
}
private void assertProviderIsValid(Object theNext) throws ConfigurationException {
if (Modifier.isPublic(theNext.getClass().getModifiers()) == false) {
throw new ConfigurationException("Can not use provider '" + theNext.getClass() + "' - Must be public");
}
}
public boolean isUseBrowserFriendlyContentTypes() {
return myUseBrowserFriendlyContentTypes;
}

View File

@ -22,7 +22,7 @@ import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.IQueryParameter;
import ca.uhn.fhir.rest.param.BaseQueryParameter;
import ca.uhn.fhir.rest.server.ResourceBinding;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.util.ExtensionConstants;
@ -86,11 +86,11 @@ public class ServerConformanceProvider {
RestResourceSearchParam searchParam = null;
StringDt searchParamChain = null;
for (IParameter nextParameterObj : params) {
if (!(nextParameterObj instanceof IQueryParameter)) {
if (!(nextParameterObj instanceof BaseQueryParameter)) {
continue;
}
IQueryParameter nextParameter = (IQueryParameter)nextParameterObj;
BaseQueryParameter nextParameter = (BaseQueryParameter)nextParameterObj;
if (nextParameter.getName().startsWith("_")) {
continue;
}

View File

@ -15,7 +15,7 @@ DIV.hapiHeaderText {
TABLE.hapiTableOfValues THEAD TR TD {
background: #A0A0F0;
color: #000080;
font-size: 1.em;
font-size: 1.2em;
font-weight: bold;
padding: 5px;
}

View File

@ -0,0 +1,40 @@
package example;
import org.apache.http.impl.client.HttpClientBuilder;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.client.HttpBasicAuthInterceptor;
import ca.uhn.fhir.rest.client.IRestfulClientFactory;
import ca.uhn.fhir.rest.client.api.IBasicClient;
public class ClientExamples {
public interface PatientClient extends IBasicClient {
// nothing yet
}
@SuppressWarnings("unused")
public void createSecurity() {
//START SNIPPET: security
// Create a context and get the client factory so it can be configured
FhirContext ctx = new FhirContext();
IRestfulClientFactory clientFactory = ctx.getRestfulClientFactory();
// Create an HTTP Client Builder
HttpClientBuilder builder = HttpClientBuilder.create();
// This interceptor adds HTTP username/password to every request
String username = "foobar";
String password = "boobear";
builder.addInterceptorFirst(new HttpBasicAuthInterceptor(username, password));
// Use the new HTTP client builder
clientFactory.setHttpClient(builder.build());
// Actually create a client instance
PatientClient client = ctx.newRestfulClient(PatientClient.class, "http://localhost:9999/");
//END SNIPPET: security
}
}

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
<document xmlns="http://maven.apache.org/XDOC/2.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/XDOC/2.0 http://maven.apache.org/xsd/xdoc-2.0.xsd">
<properties>
<title>RESTful Client - HAPI FHIR</title>
@ -15,16 +16,22 @@
</macro>
<p>
HAPI provides a built-in mechanism for connecting to FHIR RESTful servers.
The HAPI RESTful client is designed to be easy to set up and to allow strong
HAPI provides a built-in mechanism for connecting to FHIR RESTful
servers.
The HAPI RESTful client is designed to be easy to set up and
to allow strong
compile-time type checking wherever possible.
</p>
<p>
Setup is mostly done using simple annotations, which means that it should
be possible to create a FHIR compliant server quickly and easily. Once again,
this design is intended to be similar to that of JAX-WS, so users of that
specification should be comfortable with this one.
Setup is mostly done using simple annotations, which means that it
should
be possible to create a FHIR compliant server quickly and
easily. Once again,
this design is intended to be similar to that of
JAX-WS, so users of that
specification should be comfortable with
this one.
</p>
<subsection name="Defining A Restful Client Interface">
@ -36,35 +43,45 @@
<p>
A restful client interface class must extend the
<a href="./apidocs/ca/uhn/fhir/rest/client/api/IRestfulClient.html">IRestfulClient</a> interface,
<a href="./apidocs/ca/uhn/fhir/rest/client/api/IRestfulClient.html">IRestfulClient</a>
interface,
and will contain one or more methods which have been
annotated with special annotations indicating which RESTful operation
that method supports. Below is a simple example of a resource provider
annotated with special annotations indicating which RESTful
operation
that method supports. Below is a simple example of a
resource provider
which supports the
<a href="http://hl7.org/implement/standards/fhir/http.html#read">read</a>
operation (i.e. retrieve a single resource by ID) as well as the
<a href="http://hl7.org/implement/standards/fhir/http.html#search">search</a>
operation (i.e. find any resources matching a given criteria) for a specific
operation (i.e. find any resources matching a given criteria) for a
specific
search criteria.
</p>
<p>
You may notice that this interface looks a lot like the Resource Provider
which is defined for use by the RESTful server. In fact, it supports all
of the same annotations and is essentially identical, other than the
You may notice that this interface looks a lot like the Resource
Provider
which is defined for use by the RESTful server. In fact, it
supports all
of the same annotations and is essentially identical,
other than the
fact that for a client you must use an interface but for a server you
must use a concrete class with method implementations.
</p>
<macro name="snippet">
<param name="id" value="provider" />
<param name="file" value="src/site/example/java/example/RestfulClientImpl.java" />
<param name="file"
value="src/site/example/java/example/RestfulClientImpl.java" />
</macro>
<p>
You will probably want to add more methods
to your client interface. See
<a href="./doc_rest_operations.html">RESTful Operations</a> for
to your client interface.
See
<a href="./doc_rest_operations.html">RESTful Operations</a>
for
lots more examples of how to add methods for various operations.
</p>
@ -75,12 +92,14 @@
<p>
Once your client interface is created, all that is left is to
create a FhirContext and instantiate the client and you are
ready to start using it.
ready to
start using it.
</p>
<macro name="snippet">
<param name="id" value="client" />
<param name="file" value="src/site/example/java/example/ExampleRestfulClient.java" />
<param name="file"
value="src/site/example/java/example/ExampleRestfulClient.java" />
</macro>
</subsection>
@ -91,16 +110,53 @@
<p>
The following is a complete example showing a RESTful client
using HAPI FHIR.
using
HAPI FHIR.
</p>
<macro name="snippet">
<param name="id" value="client" />
<param name="file" value="src/site/example/java/example/CompleteExampleClient.java" />
<param name="file"
value="src/site/example/java/example/CompleteExampleClient.java" />
</macro>
</section>
<section name="Configuring the HTTP Client">
<p>
The client uses <a href="http://hc.apache.org/httpcomponents-client-ga/">Apache HTTP Client</a>
as a provider. The HTTP Client is very powerful, but can be a bit tricky to configure.
</p>
<p>
In many cases, the default configuration should suffice. However, if you require anything
more sophisticated (username/password, HTTP proxy settings, etc.) you will need
to configure the underlying client.
</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.
</p>
<subsection name="HTTP Basic Authorization">
<p>
The following example shows how to configure your client to
use a specific username and password in every request.
</p>
<macro name="snippet">
<param name="id" value="security" />
<param name="file" value="src/site/example/java/example/ClientExamples.java" />
</macro>
</subsection>
</section>
</body>
</document>

View File

@ -0,0 +1,134 @@
package ca.uhn.fhir.rest.client;
import static org.junit.Assert.*;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpServletRequest;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.impl.client.BasicCredentialsProvider;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.ResfulServerMethodTest.DummyDiagnosticReportResourceProvider;
import ca.uhn.fhir.rest.server.ResfulServerMethodTest.DummyPatientResourceProvider;
import ca.uhn.fhir.rest.server.ResfulServerMethodTest.DummyRestfulServer;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
public class ClientIntegrationTest {
private int myPort;
private Server myServer;
private FhirContext myCtx;
private MyPatientResourceProvider myPatientProvider;
@Before
public void before() {
myPort = RandomServerPortProvider.findFreePort();
myServer = new Server(myPort);
myCtx = new FhirContext(Patient.class);
myPatientProvider = new MyPatientResourceProvider();
ServletHandler proxyHandler = new ServletHandler();
RestfulServer servlet = new RestfulServer();
servlet.setResourceProviders(myPatientProvider);
ServletHolder servletHolder = new ServletHolder(servlet);
proxyHandler.addServletWithMapping(servletHolder, "/*");
myServer.setHandler(proxyHandler);
}
@Test
public void testClientSecurity() throws Exception {
// BasicCredentialsProvider credentialsProvider = new BasicCredentialsProvider();
// UsernamePasswordCredentials credentials = new UsernamePasswordCredentials("foobar", "boobear");
// AuthScope scope = new AuthScope(AuthScope.ANY_HOST, AuthScope.ANY_PORT);
// credentialsProvider.setCredentials(scope, credentials);
// builder.setDefaultCredentialsProvider(credentialsProvider);
//
myServer.start();
FhirContext ctx = new FhirContext();
HttpClientBuilder builder = HttpClientBuilder.create();
// PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
// builder.setConnectionManager(connectionManager);
builder.addInterceptorFirst(new HttpBasicAuthInterceptor("foobar", "boobear"));
CloseableHttpClient httpClient = builder.build();
ctx.getRestfulClientFactory().setHttpClient(httpClient);
PatientClient client = ctx.newRestfulClient(PatientClient.class, "http://localhost:" + myPort + "/");
List<Patient> actualPatients = client.searchForPatients(new StringDt("AAAABBBB"));
assertEquals(1, actualPatients.size());
assertEquals("AAAABBBB", actualPatients.get(0).getNameFirstRep().getFamilyAsSingleString());
assertEquals("Basic Zm9vYmFyOmJvb2JlYXI=", myPatientProvider.getAuthorizationHeader());
}
@After
public void after() throws Exception {
myServer.stop();
}
public static class MyPatientResourceProvider implements IResourceProvider {
private String myAuthorizationHeader;
public String getAuthorizationHeader() {
return myAuthorizationHeader;
}
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
@Search
public List<Patient> searchForPatients(@RequiredParam(name="fooParam") StringDt theFooParam, HttpServletRequest theRequest) {
myAuthorizationHeader = theRequest.getHeader("authorization");
Patient retVal = new Patient();
retVal.addName().addFamily(theFooParam.getValue());
return Collections.singletonList(retVal);
}
}
private static interface PatientClient extends IBasicClient {
@Search
public List<Patient> searchForPatients(@RequiredParam(name="fooParam") StringDt theFooParam);
}
}

View File

@ -52,9 +52,9 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
public class ClientTest {
private FhirContext ctx;
private HttpClient httpClient;
private HttpResponse httpResponse;
private FhirContext ctx;
// atom-document-large.xml
@ -68,6 +68,133 @@ public class ClientTest {
httpResponse = mock(HttpResponse.class, new ReturnsDeepStubs());
}
private String getPatientFeedWithOneResult() {
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" +
"<title/>\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;
}
@Test
public void testCreate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
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), 201, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
MethodOutcome response = client.createPatient(patient);
assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue();
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
assertEquals("100", response.getId().getValue());
assertEquals("200", response.getVersionId().getValue());
}
@Test
public void testCreateBad() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
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), 400, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("foobar"), Charset.forName("UTF-8")));
try {
ctx.newRestfulClient(ITestClient.class, "http://foo").createPatient(patient);
fail();
} catch (InvalidRequestException e) {
assertThat(e.getMessage(), StringContains.containsString("foobar"));
}
}
@Test
public void testDelete() throws Exception {
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setDetails("Hello");
String resp = new FhirContext().newXmlParser().encodeResourceToString(oo);
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), 201, "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(resp), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
MethodOutcome response = client.deletePatient(new IdDt("1234"));
assertEquals(HttpDelete.class, capt.getValue().getClass());
assertEquals("http://foo/Patient/1234", capt.getValue().getURI().toString());
assertEquals("Hello", response.getOperationOutcome().getIssueFirstRep().getDetails().getValue());
}
@Test
public void testDeleteNoResponse() throws Exception {
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), 204, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.deleteDiagnosticReport(new IdDt("1234"));
assertEquals(HttpDelete.class, capt.getValue().getClass());
assertEquals("http://foo/DiagnosticReport/1234", capt.getValue().getURI().toString());
}
@Test
public void testGetConformance() throws Exception {
String msg = IOUtils.toString(ClientTest.class.getResourceAsStream("/example-metadata.xml"));
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")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
Conformance response = client.getServerConformanceStatement();
assertEquals("http://foo/metadata", capt.getValue().getURI().toString());
assertEquals("Health Intersections", response.getPublisher().getValue());
}
@Test
public void testHistoryResourceInstance() throws Exception {
@ -260,230 +387,6 @@ public class ClientTest {
}
@Test
public void testCreate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
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), 201, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
MethodOutcome response = client.createPatient(patient);
assertEquals(HttpPost.class, capt.getValue().getClass());
HttpPost post = (HttpPost) capt.getValue();
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
assertEquals("100", response.getId().getValue());
assertEquals("200", response.getVersionId().getValue());
}
@Test
public void testDelete() throws Exception {
OperationOutcome oo = new OperationOutcome();
oo.addIssue().setDetails("Hello");
String resp = new FhirContext().newXmlParser().encodeResourceToString(oo);
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), 201, "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(resp), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
MethodOutcome response = client.deletePatient(new IdDt("1234"));
assertEquals(HttpDelete.class, capt.getValue().getClass());
assertEquals("http://foo/Patient/1234", capt.getValue().getURI().toString());
assertEquals("Hello", response.getOperationOutcome().getIssueFirstRep().getDetails().getValue());
}
@Test
public void testDeleteNoResponse() throws Exception {
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), 204, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.deleteDiagnosticReport(new IdDt("1234"));
assertEquals(HttpDelete.class, capt.getValue().getClass());
assertEquals("http://foo/DiagnosticReport/1234", capt.getValue().getURI().toString());
}
@Test
public void testUpdate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
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), 201, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
MethodOutcome response = client.updatePatient(new IdDt("100"), patient);
assertEquals(HttpPut.class, capt.getValue().getClass());
HttpPut post = (HttpPut) capt.getValue();
assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100"));
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
assertEquals("100", response.getId().getValue());
assertEquals("200", response.getVersionId().getValue());
}
/**
* Return a FHIR content type, but no content and make sure we handle this without crashing
*/
@Test
public void testUpdateWithEmptyResponse() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
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), 201, "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(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.updatePatient(new IdDt("100"), new IdDt("200"), patient);
}
@Test
public void testUpdateWithVersion() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
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), 201, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location" , "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
MethodOutcome response = client.updatePatient(new IdDt("100"), new IdDt("200"), patient);
assertEquals(HttpPut.class, capt.getValue().getClass());
HttpPut post = (HttpPut) capt.getValue();
assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100"));
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
assertThat(post.getFirstHeader("Content-Location").getValue(), StringEndsWith.endsWith("/Patient/100/_history/200"));
assertEquals("100", response.getId().getValue());
assertEquals("200", response.getVersionId().getValue());
}
@Test(expected=ResourceVersionConflictException.class)
public void testUpdateWithResourceConflict() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
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(""), Charset.forName("UTF-8")));
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_409_CONFLICT, "Conflict"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.updatePatient(new IdDt("100"), new IdDt("200"), patient);
}
@Test
public void testCreateBad() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
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), 400, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader("foobar"), Charset.forName("UTF-8")));
try {
ctx.newRestfulClient(ITestClient.class, "http://foo").createPatient(patient);
fail();
}catch (InvalidRequestException e) {
assertThat(e.getMessage(), StringContains.containsString("foobar"));
}
}
private Header[] toHeaderArray(String theName, String theValue) {
return new Header[] {new BasicHeader(theName, theValue)};
}
@Test
public void testVRead() throws Exception {
//@formatter:off
String msg = "<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>";
//@formatter:on
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")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
// Patient response = client.findPatientByMrn(new IdentifierDt("urn:foo", "123"));
Patient response = client.getPatientByVersionId(new IdDt("111"), new IdDt("999"));
assertEquals("http://foo/Patient/111/_history/999", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue());
}
@Test
public void testSearchByToken() 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")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
Patient response = client.findPatientByMrn(new IdentifierDt("urn:foo", "123"));
assertEquals("http://foo/Patient?identifier=urn%3Afoo%7C123", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue());
}
@Test
public void testSearchByDateRange() throws Exception {
@ -505,10 +408,6 @@ public class ClientTest {
}
@Test
public void testSearchByDob() throws Exception {
@ -529,7 +428,7 @@ public class ClientTest {
}
@Test
public void testSearchWithIncludes() throws Exception {
public void testSearchByToken() throws Exception {
String msg = getPatientFeedWithOneResult();
@ -540,9 +439,10 @@ public class ClientTest {
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getPatientWithIncludes(new StringDt("aaa"), Arrays.asList(new PathSpecification[] {new PathSpecification("inc1"), new PathSpecification("inc2")}));
Patient response = client.findPatientByMrn(new IdentifierDt("urn:foo", "123"));
assertEquals("http://foo/Patient?withIncludes=aaa&_include=inc1&_include=inc2", capt.getValue().getURI().toString());
assertEquals("http://foo/Patient?identifier=urn%3Afoo%7C123", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue());
}
@ -568,9 +468,9 @@ public class ClientTest {
}
@Test
public void testGetConformance() throws Exception {
public void testSearchNamedQueryNoParams() throws Exception {
String msg = IOUtils.toString(ClientTest.class.getResourceAsStream("/example-metadata.xml"));
String msg = getPatientFeedWithOneResult();
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
@ -579,10 +479,45 @@ public class ClientTest {
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
Conformance response = client.getServerConformanceStatement();
client.getPatientNoParams();
assertEquals("http://foo/metadata", capt.getValue().getURI().toString());
assertEquals("Health Intersections", response.getPublisher().getValue());
assertEquals("http://foo/Patient?_query=someQueryNoParams", capt.getValue().getURI().toString());
}
@Test
public void testSearchNamedQueryOneParam() 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")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getPatientOneParam(new StringDt("BB"));
assertEquals("http://foo/Patient?_query=someQueryOneParam&param1=BB", capt.getValue().getURI().toString());
}
@Test
public void testSearchWithIncludes() 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")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getPatientWithIncludes(new StringDt("aaa"), Arrays.asList(new PathSpecification[] { new PathSpecification("inc1"), new PathSpecification("inc2") }));
assertEquals("http://foo/Patient?withIncludes=aaa&_include=inc1&_include=inc2", capt.getValue().getURI().toString());
}
@ -619,54 +554,95 @@ public class ClientTest {
}
@Test
public void testSearchNamedQueryOneParam() throws Exception {
public void testUpdate() throws Exception {
String msg = getPatientFeedWithOneResult();
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getPatientOneParam(new StringDt("BB"));
MethodOutcome response = client.updatePatient(new IdDt("100"), patient);
assertEquals("http://foo/Patient?_query=someQueryOneParam&param1=BB", capt.getValue().getURI().toString());
assertEquals(HttpPut.class, capt.getValue().getClass());
HttpPut post = (HttpPut) capt.getValue();
assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100"));
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
assertEquals("100", response.getId().getValue());
assertEquals("200", response.getVersionId().getValue());
}
/**
* Return a FHIR content type, but no content and make sure we handle this without crashing
*/
@Test
public void testUpdateWithEmptyResponse() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
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), 201, "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(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.updatePatient(new IdDt("100"), new IdDt("200"), patient);
}
@Test(expected = ResourceVersionConflictException.class)
public void testUpdateWithResourceConflict() throws Exception {
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
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(""), Charset.forName("UTF-8")));
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), Constants.STATUS_HTTP_409_CONFLICT, "Conflict"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.updatePatient(new IdDt("100"), new IdDt("200"), patient);
}
@Test
public void testSearchNamedQueryNoParams() throws Exception {
public void testUpdateWithVersion() throws Exception {
String msg = getPatientFeedWithOneResult();
Patient patient = new Patient();
patient.addIdentifier("urn:foo", "123");
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_FHIR_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8")));
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 201, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_TEXT + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenReturn(new ReaderInputStream(new StringReader(""), Charset.forName("UTF-8")));
when(httpResponse.getAllHeaders()).thenReturn(toHeaderArray("Location", "http://example.com/fhir/Patient/100/_history/200"));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getPatientNoParams();
assertEquals("http://foo/Patient?_query=someQueryNoParams", capt.getValue().getURI().toString());
MethodOutcome response = client.updatePatient(new IdDt("100"), new IdDt("200"), patient);
assertEquals(HttpPut.class, capt.getValue().getClass());
HttpPut post = (HttpPut) capt.getValue();
assertThat(post.getURI().toASCIIString(), StringEndsWith.endsWith("/Patient/100"));
assertThat(IOUtils.toString(post.getEntity().getContent()), StringContains.containsString("<Patient"));
assertThat(post.getFirstHeader("Content-Location").getValue(), StringEndsWith.endsWith("/Patient/100/_history/200"));
assertEquals("100", response.getId().getValue());
assertEquals("200", response.getVersionId().getValue());
}
private String getPatientFeedWithOneResult() {
@Test
public void testVRead() throws Exception {
//@formatter:off
String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\">\n" +
"<title/>\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\">"
String msg = "<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>"
@ -674,11 +650,25 @@ public class ClientTest {
+ "<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>";
+ "</Patient>";
//@formatter:on
return msg;
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")));
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
// Patient response = client.findPatientByMrn(new IdentifierDt("urn:foo", "123"));
Patient response = client.getPatientByVersionId(new IdDt("111"), new IdDt("999"));
assertEquals("http://foo/Patient/111/_history/999", capt.getValue().getURI().toString());
assertEquals("PRP1660", response.getIdentifier().get(0).getValue().getValue());
}
private Header[] toHeaderArray(String theName, String theValue) {
return new Header[] { new BasicHeader(theName, theValue) };
}
}

View File

@ -30,6 +30,20 @@ public class ServerInvalidDefinitionTest {
}
}
@Test
public void testPrivateResourceProvider() {
RestfulServer srv = new RestfulServer();
srv.setResourceProviders(new PrivateResourceProvider());
try {
srv.init();
fail();
} catch (ServletException e) {
assertThat(e.getCause().toString(), StringContains.containsString("ConfigurationException"));
assertThat(e.getCause().toString(), StringContains.containsString("public"));
}
}
/**
* Normal, should initialize properly
*/
@ -40,8 +54,23 @@ public class ServerInvalidDefinitionTest {
srv.init();
}
private static class PrivateResourceProvider implements IResourceProvider
{
private static class NonInstantiableTypeForResourceProvider implements IResourceProvider
@Override
public Class<? extends IResource> getResourceType() {
return Patient.class;
}
@SuppressWarnings("unused")
@Read
public Patient read(@IdParam IdDt theId) {
return null;
}
}
public static class NonInstantiableTypeForResourceProvider implements IResourceProvider
{
@Override
@ -57,7 +86,7 @@ public class ServerInvalidDefinitionTest {
}
private static class InstantiableTypeForResourceProvider implements IResourceProvider
public static class InstantiableTypeForResourceProvider implements IResourceProvider
{
@Override