Create methd

This commit is contained in:
jamesagnew 2014-03-27 18:38:24 -04:00
parent ad189d66e2
commit b38be51668
42 changed files with 962 additions and 259 deletions

View File

@ -7,6 +7,6 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface Id {
public @interface IdParam {
// just a marker
}

View File

@ -20,7 +20,7 @@ import java.lang.annotation.Target;
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value= {ElementType.PARAMETER})
public @interface Include {
public @interface IncludeParam {
/**
* Optional parameter, if provided the server will only allow the values

View File

@ -5,6 +5,6 @@ import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Optional {
public @interface OptionalParam {
String name();
}

View File

@ -5,6 +5,6 @@ import java.lang.annotation.RetentionPolicy;
@Retention(RetentionPolicy.RUNTIME)
public @interface Required {
public @interface RequiredParam {
String name();
}

View File

@ -0,0 +1,12 @@
package ca.uhn.fhir.rest.annotation;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target(value=ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceParam {
}

View File

@ -7,6 +7,6 @@ import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER)
public @interface VersionId {
public @interface VersionIdParam {
// just a marker
}

View File

@ -7,10 +7,6 @@ public class MethodOutcome {
private IdDt myId;
private IdDt myVersionId;
public IdDt getId() {
return myId;
}
public MethodOutcome() {
}
@ -20,14 +16,18 @@ public class MethodOutcome {
myVersionId = theVersionId;
}
public void setId(IdDt theId) {
myId = theId;
public IdDt getId() {
return myId;
}
public IdDt getVersionId() {
return myVersionId;
}
public void setId(IdDt theId) {
myId = theId;
}
public void setVersionId(IdDt theVersionId) {
myVersionId = theVersionId;
}

View File

@ -1,7 +1,11 @@
package ca.uhn.fhir.rest.client;
import java.io.IOException;
import org.apache.http.client.methods.HttpRequestBase;
import ca.uhn.fhir.parser.DataFormatException;
public abstract class BaseClientInvocation {
/**
@ -9,6 +13,6 @@ public abstract class BaseClientInvocation {
*
* @param theUrlBase The FHIR server base url (with a trailing "/")
*/
public abstract HttpRequestBase asHttpRequest(String theUrlBase);
public abstract HttpRequestBase asHttpRequest(String theUrlBase) throws DataFormatException, IOException;
}

View File

@ -7,10 +7,13 @@ import java.io.StringReader;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.io.IOUtils;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.CloseableHttpResponse;
@ -57,8 +60,8 @@ public class ClientInvocationHandler implements InvocationHandler {
}
BaseMethodBinding binding = myBindings.get(theMethod);
GetClientInvocation clientInvocation = binding.invokeClient(theArgs);
BaseClientInvocation clientInvocation = binding.invokeClient(theArgs);
HttpRequestBase httpRequest = clientInvocation.asHttpRequest(myUrlBase);
HttpResponse response = myClient.execute(httpRequest);
try {
@ -74,9 +77,22 @@ public class ClientInvocationHandler implements InvocationHandler {
ContentType ct = ContentType.get(response.getEntity());
String mimeType = ct.getMimeType();
Map<String, List<String>> headers = new HashMap<String, List<String>>();
if (response.getAllHeaders() != null) {
for (Header next : response.getAllHeaders()) {
String name = next.getName().toLowerCase();
List<String> list = headers.get(name);
if (list == null) {
list = new ArrayList<String>();
headers.put(name, list);
}
list.add(next.getValue());
}
}
return binding.invokeClient(mimeType, reader, response.getStatusLine().getStatusCode());
return binding.invokeClient(mimeType, reader, response.getStatusLine().getStatusCode(), headers);
} finally {
if (response instanceof CloseableHttpResponse) {
((CloseableHttpResponse) response).close();

View File

@ -0,0 +1,47 @@
package ca.uhn.fhir.rest.client;
import java.io.IOException;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpRequestBase;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.Constants;
public class PostClientInvocation extends BaseClientInvocation {
private final IResource myResource;
private final Bundle myBundle;
private final FhirContext myContext;
private String myUrlExtension;
public PostClientInvocation(FhirContext theContext, Bundle theBundle) {
super();
myContext = theContext;
myResource = null;
myBundle = theBundle;
}
public PostClientInvocation(FhirContext theContext, IResource theResource, String theUrlExtension) {
super();
myContext = theContext;
myResource = theResource;
myBundle = null;
myUrlExtension = theUrlExtension;
}
@Override
public HttpRequestBase asHttpRequest(String theUrlBase) throws DataFormatException, IOException {
HttpPost httpPost = new HttpPost(theUrlBase + StringUtils.defaultString(myUrlExtension));
String contents = myContext.newXmlParser().encodeResourceToString(myResource);
httpPost.setEntity(new StringEntity(contents, ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
return httpPost;
}
}

View File

@ -6,19 +6,29 @@ import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import ca.uhn.fhir.context.ConfigurationException;
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.parser.IParser;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Metadata;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -29,11 +39,11 @@ public abstract class BaseMethodBinding {
private FhirContext myContext;
public BaseMethodBinding(Method theMethod, FhirContext theConetxt) {
assert theMethod!=null;
assert theConetxt!=null;
assert theMethod != null;
assert theConetxt != null;
myMethod = theMethod;
myContext=theConetxt;
myContext = theConetxt;
}
public FhirContext getContext() {
@ -44,7 +54,7 @@ public abstract class BaseMethodBinding {
return myMethod;
}
public abstract GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
public abstract BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
public abstract RestfulOperationTypeEnum getResourceOperationType();
@ -52,11 +62,24 @@ public abstract class BaseMethodBinding {
public abstract boolean matches(Request theRequest);
protected IParser createAppropriateParser(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException {
IParser parser;
if (Constants.CT_ATOM_XML.equals(theResponseMimeType)) {
parser = getContext().newXmlParser();
} else if (Constants.CT_FHIR_XML.equals(theResponseMimeType)) {
parser = getContext().newXmlParser();
} else {
throw new NonFhirResponseException("Response contains non-FHIR content-type: " + theResponseMimeType, theResponseMimeType, theResponseStatusCode, IOUtils.toString(theResponseReader));
}
return parser;
}
public static BaseMethodBinding bindMethod(Class<? extends IResource> theReturnType, Method theMethod, FhirContext theContext) {
Read read = theMethod.getAnnotation(Read.class);
Search search = theMethod.getAnnotation(Search.class);
Metadata conformance = theMethod.getAnnotation(Metadata.class);
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance)) {
Create create = theMethod.getAnnotation(Create.class);
if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance,create)) {
return null;
}
@ -77,9 +100,11 @@ public abstract class BaseMethodBinding {
return new ReadMethodBinding(returnType, theMethod, theContext);
} else if (search != null) {
String queryName = search.queryName();
return new SearchMethodBinding(returnType, theMethod, queryName,theContext);
return new SearchMethodBinding(returnType, theMethod, queryName, theContext);
} else if (conformance != null) {
return new ConformanceMethodBinding(theMethod, theContext);
} else if (create != null) {
return new CreateMethodBinding(theMethod, theContext);
} else {
throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
}
@ -113,8 +138,8 @@ public abstract class BaseMethodBinding {
if (obj1 == null) {
obj1 = object;
} else {
throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @" + obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName()
+ ". Can not have both.");
throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @"
+ obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName() + ". Can not have both.");
}
}
@ -145,10 +170,31 @@ public abstract class BaseMethodBinding {
}
}
static EncodingUtil determineResponseEncoding(HttpServletRequest theRequest, Map<String, String[]> theParams) {
String[] format = theParams.remove(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
EncodingUtil retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
if (retVal != null) {
return retVal;
}
}
}
Enumeration<String> acceptValues = theRequest.getHeaders("Accept");
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
EncodingUtil retVal = Constants.FORMAT_VAL_TO_ENCODING.get(acceptValues.nextElement());
if (retVal != null) {
return retVal;
}
}
}
return EncodingUtil.XML;
}
public abstract void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException;
public abstract Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException;
public abstract Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException;
}

View File

@ -6,14 +6,12 @@ import java.io.Reader;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
@ -78,15 +76,8 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
public abstract ReturnTypeEnum getReturnType();
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException {
IParser parser;
if (Constants.CT_ATOM_XML.equals(theResponseMimeType)) {
parser = getContext().newXmlParser();
} else if (Constants.CT_FHIR_XML.equals(theResponseMimeType)) {
parser = getContext().newXmlParser();
} else {
throw new NonFhirResponseException("Response contains non-FHIR content-type: " + theResponseMimeType, theResponseMimeType, theResponseStatusCode, IOUtils.toString(theResponseReader));
}
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode,Map<String, List<String>> theHeaders) throws IOException {
IParser parser = createAppropriateParser(theResponseMimeType, theResponseReader, theResponseStatusCode);
switch (getReturnType()) {
case BUNDLE: {
@ -125,6 +116,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
throw new IllegalStateException("Should not get here!");
}
public abstract List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException;
@Override
@ -176,28 +168,6 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
}
}
private EncodingUtil determineResponseEncoding(HttpServletRequest theRequest, Map<String, String[]> theParams) {
String[] format = theParams.remove(Constants.PARAM_FORMAT);
if (format != null) {
for (String nextFormat : format) {
EncodingUtil retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat);
if (retVal != null) {
return retVal;
}
}
}
Enumeration<String> acceptValues = theRequest.getHeaders("Accept");
if (acceptValues != null) {
while (acceptValues.hasMoreElements()) {
EncodingUtil retVal = Constants.FORMAT_VAL_TO_ENCODING.get(acceptValues.nextElement());
if (retVal != null) {
return retVal;
}
}
}
return EncodingUtil.XML;
}
static {
HashSet<String> set = new HashSet<String>();
@ -237,6 +207,8 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
theHttpResponse.setCharacterEncoding("UTF-8");
theServer.addHapiHeader(theHttpResponse);
Bundle bundle = new Bundle();
bundle.getAuthorName().setValue(getClass().getCanonicalName());
bundle.getBundleId().setValue(UUID.randomUUID().toString());
@ -300,6 +272,7 @@ public abstract class BaseResourceReturningMethodBinding extends BaseMethodBindi
}
}
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
theHttpResponse.setStatus(200);

View File

@ -2,34 +2,86 @@ package ca.uhn.fhir.rest.method;
import java.io.IOException;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.PostClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ResourceParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.UnclassifiedServerFailureException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public class CreateMethodBinding extends BaseMethodBinding {
private static Set<String> ALLOWED_PARAMS;
static {
HashSet<String> set = new HashSet<String>();
set.add(Constants.PARAM_FORMAT);
ALLOWED_PARAMS = Collections.unmodifiableSet(set);
}
private int myResourceParameterIndex;
private List<IParameter> myParameters;
private String myResourceName;
public CreateMethodBinding(Method theMethod, FhirContext theContext) {
super(theMethod, theContext);
}
myParameters = Util.getResourceParameters(theMethod);
ResourceParameter resourceParameter = null;
@Override
public GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
// TODO Auto-generated method stub
return null;
}
int index = 0;
for (IParameter next : myParameters) {
if (next instanceof ResourceParameter) {
resourceParameter = (ResourceParameter) next;
myResourceName = theContext.getResourceDefinition(resourceParameter.getResourceType()).getName();
myResourceParameterIndex = index;
index++;
}
}
if (resourceParameter == null) {
throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a parameter annotated with @"
+ ResourceParam.class.getSimpleName());
}
if (!theMethod.getReturnType().equals(MethodOutcome.class)) {
throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " is a @" + Create.class.getSimpleName()
+ " method but it does not return " + MethodOutcome.class);
}
}
@Override
public RestfulOperationTypeEnum getResourceOperationType() {
@ -41,12 +93,122 @@ public class CreateMethodBinding extends BaseMethodBinding {
return null;
}
@Override
public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
IResource resource = (IResource) theArgs[myResourceParameterIndex];
if (resource == null) {
throw new NullPointerException("Resource can not be null");
}
RuntimeResourceDefinition def = getContext().getResourceDefinition(resource);
StringBuilder urlExtension = new StringBuilder();
urlExtension.append(def.getName());
return new PostClientInvocation(getContext(), resource, urlExtension.toString());
}
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode,Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
switch (theResponseStatusCode) {
case 400:
throw new InvalidRequestException("Server responded with: " + IOUtils.toString(theResponseReader));
case 404:
throw new ResourceNotFoundException("Server responded with: " + IOUtils.toString(theResponseReader));
case 422:
IParser parser = createAppropriateParser(theResponseMimeType, theResponseReader, theResponseStatusCode);
OperationOutcome operationOutcome = parser.parseResource(OperationOutcome.class, theResponseReader);
throw new UnprocessableEntityException(operationOutcome);
case 200: // Support 200 just to be lenient
case 201: // but it should be 201
MethodOutcome retVal = new MethodOutcome();
List<String> locationHeaders = theHeaders.get("location");
if (locationHeaders != null && locationHeaders.size() > 0) {
String h = locationHeaders.get(0);
String resourceNamePart = "/" + myResourceName + "/";
int resourceIndex = h.lastIndexOf(resourceNamePart);
if (resourceIndex > -1) {
int idIndexStart = resourceIndex + resourceNamePart.length();
int idIndexEnd = h.indexOf('/', idIndexStart);
if (idIndexEnd == -1) {
retVal.setId(new IdDt(h.substring(idIndexStart)));
}else {
retVal.setId(new IdDt(h.substring(idIndexStart, idIndexEnd)));
String versionIdPart = "/_history/";
int historyIdStart = h.indexOf(versionIdPart, idIndexEnd);
if (historyIdStart != -1) {
retVal.setVersionId(new IdDt(h.substring(historyIdStart + versionIdPart.length())));
}
}
}
}
return retVal;
default:
throw new UnclassifiedServerFailureException(theResponseStatusCode, IOUtils.toString(theResponseReader));
}
}
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
EncodingUtil encoding = determineResponseEncoding(theRequest.getServletRequest(), theRequest.getParameters());
IParser parser = encoding.newParser(getContext());
IResource resource = parser.parseResource(theRequest.getInputReader());
Object[] params = new Object[myParameters.size()];
for (int i = 0; i < myParameters.size(); i++) {
IParameter param = myParameters.get(i);
params[i] = param.translateQueryParametersIntoServerArgument(theRequest.getParameters(), resource);
}
MethodOutcome response;
try {
response = (MethodOutcome) this.getMethod().invoke(theRequest.getResourceProvider(), params);
} catch (IllegalAccessException e) {
throw new InternalErrorException(e);
} catch (IllegalArgumentException e) {
throw new InternalErrorException(e);
} catch (InvocationTargetException e) {
throw new InternalErrorException(e);
}
if (response == null) {
throw new ConfigurationException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null");
}
theResponse.setStatus(Constants.STATUS_HTTP_201_CREATED);
StringBuilder b = new StringBuilder();
b.append(theRequest.getFhirServerBase());
b.append('/');
b.append(myResourceName);
b.append('/');
b.append(response.getId().getValue());
if (response.getVersionId().isEmpty() == false) {
b.append("/_history/");
b.append(response.getVersionId().getValue());
}
theResponse.addHeader("Location", b.toString());
theServer.addHapiHeader(theResponse);
theResponse.setContentType(Constants.CT_TEXT);
Writer writer = theResponse.getWriter();
try {
writer.append("Resource has been created");
} finally {
writer.close();
}
// getMethod().in
}
@Override
public boolean matches(Request theRequest) {
if (theRequest.getRequestType()!= RequestType.POST) {
if (theRequest.getRequestType() != RequestType.POST) {
return false;
}
if (StringUtils.isBlank(theRequest.getResourceName())) {
if (!myResourceName.equals(theRequest.getResourceName())) {
return false;
}
if (StringUtils.isNotBlank(theRequest.getOperation())) {
@ -55,16 +217,4 @@ public class CreateMethodBinding extends BaseMethodBinding {
return true;
}
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
// TODO Auto-generated method stub
}
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException {
// TODO Auto-generated method stub
return null;
}
}

View File

@ -2,7 +2,6 @@ package ca.uhn.fhir.rest.method;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.LinkedHashMap;
@ -19,11 +18,11 @@ 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.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.util.QueryUtil;
/**
* Created by dsotnikov on 2/25/2014.
@ -69,43 +68,20 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
public GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
assert (myQueryName == null || ((theArgs != null ? theArgs.length : 0) == myParameters.size())) : "Wrong number of arguments: " + theArgs;
Map<String, List<String>> args = new LinkedHashMap<String, List<String>>();
Map<String, List<String>> queryStringArgs = new LinkedHashMap<String, List<String>>();
if (myQueryName != null) {
args.put(Constants.PARAM_QUERY, Collections.singletonList(myQueryName));
queryStringArgs.put(Constants.PARAM_QUERY, Collections.singletonList(myQueryName));
}
if (theArgs != null) {
for (int idx = 0; idx < theArgs.length; idx++) {
Object object = theArgs[idx];
IParameter nextParam = myParameters.get(idx);
if (object == null) {
if (nextParam.isRequired()) {
throw new NullPointerException("SearchParameter '" + nextParam.getName() + "' is required and may not be null");
}
} else {
List<List<String>> value = nextParam.encode(object);
ArrayList<String> paramValues = new ArrayList<String>(value.size());
args.put(nextParam.getName(), paramValues);
for (List<String> nextParamEntry : value) {
StringBuilder b = new StringBuilder();
for (String str : nextParamEntry) {
if (b.length() > 0) {
b.append(",");
}
b.append(str.replace(",", "\\,"));
}
paramValues.add(b.toString());
}
}
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], queryStringArgs);
}
}
return new GetClientInvocation(args, getResourceName());
return new GetClientInvocation(queryStringArgs, getResourceName());
}
@Override
@ -117,25 +93,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
Object[] params = new Object[myParameters.size()];
for (int i = 0; i < myParameters.size(); i++) {
IParameter param = myParameters.get(i);
String[] value = parameterValues.get(param.getName());
if (value == null || value.length == 0) {
if (param.handlesMissing()) {
params[i] = param.parse(new ArrayList<List<String>>(0));
}
continue;
}
List<List<String>> paramList = new ArrayList<List<String>>(value.length);
for (String nextParam : value) {
if (nextParam.contains(",") == false) {
paramList.add(Collections.singletonList(nextParam));
} else {
paramList.add(QueryUtil.splitQueryStringByCommasIgnoreEscape(nextParam));
}
}
params[i] = param.parse(paramList);
params[i] = param.translateQueryParametersIntoServerArgument(parameterValues, null);
}
Object response;
@ -174,7 +132,10 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
Set<String> methodParamsTemp = new HashSet<String>();
for (int i = 0; i < this.myParameters.size(); i++) {
IParameter temp = this.myParameters.get(i);
if (!(myParameters.get(i) instanceof IQueryParameter)) {
continue;
}
IQueryParameter temp = (IQueryParameter) 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

@ -11,15 +11,18 @@ import java.util.List;
import java.util.Map;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.PathSpecification;
import ca.uhn.fhir.rest.annotation.Id;
import ca.uhn.fhir.rest.annotation.Include;
import ca.uhn.fhir.rest.annotation.Optional;
import ca.uhn.fhir.rest.annotation.Required;
import ca.uhn.fhir.rest.annotation.VersionId;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.param.CollectionBinder;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.IncludeParameter;
import ca.uhn.fhir.rest.param.ResourceParameter;
import ca.uhn.fhir.rest.param.SearchParameter;
import ca.uhn.fhir.util.ReflectionUtil;
@ -71,25 +74,30 @@ class Util {
}
IParameter param;
if (nextAnnotation instanceof Required) {
if (nextAnnotation instanceof RequiredParam) {
SearchParameter parameter = new SearchParameter();
parameter.setName(((Required) nextAnnotation).name());
parameter.setName(((RequiredParam) nextAnnotation).name());
parameter.setRequired(true);
parameter.setType(parameterType, innerCollectionType, outerCollectionType);
param = parameter;
} else if (nextAnnotation instanceof Optional) {
} else if (nextAnnotation instanceof OptionalParam) {
SearchParameter parameter = new SearchParameter();
parameter.setName(((Optional) nextAnnotation).name());
parameter.setName(((OptionalParam) nextAnnotation).name());
parameter.setRequired(false);
parameter.setType(parameterType, innerCollectionType, innerCollectionType);
param = parameter;
} else if (nextAnnotation instanceof Include) {
} else if (nextAnnotation instanceof IncludeParam) {
if (parameterType != PathSpecification.class || innerCollectionType == null || outerCollectionType != null) {
throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + Include.class.getSimpleName() + " but has a type other than Collection<"+PathSpecification.class.getSimpleName() + ">");
throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<"+PathSpecification.class.getSimpleName() + ">");
}
Class<? extends Collection<PathSpecification>> instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + method.getName() + "'");
param = new IncludeParameter((Include) nextAnnotation, instantiableCollectionType);
param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType);
} else if (nextAnnotation instanceof ResourceParam) {
if (!IResource.class.isAssignableFrom(parameterType)) {
throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName());
}
param = new ResourceParameter((Class<? extends IResource>) parameterType);
} else {
continue;
}
@ -108,11 +116,11 @@ class Util {
}
public static Integer findReadIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, Id.class);
return findParamIndex(theMethod, IdParam.class);
}
public static Integer findReadVersionIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, VersionId.class);
return findParamIndex(theMethod, VersionIdParam.class);
}
private static Integer findParamIndex(Method theMethod, Class<?> toFind) {

View File

@ -1,27 +1,27 @@
package ca.uhn.fhir.rest.param;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public interface IParameter {
List<List<String>> encode(Object theObject) throws InternalErrorException;
void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException;
String getName();
Object parse(List<List<String>> theString) throws InternalErrorException, InvalidRequestException;
boolean isRequired();
/**
* Parameter should return true if {@link #parse(List)} should be called even
* if the query string contained no values for the given parameter
* 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 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.
*/
boolean handlesMissing();
Object translateQueryParametersIntoServerArgument(Map<String, String[]> theQueryParameters, Object theRequestContents) throws InternalErrorException, InvalidRequestException;
SearchParamTypeEnum getParamType();
}
}

View File

@ -0,0 +1,81 @@
package ca.uhn.fhir.rest.param;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
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 List<List<String>> encode(Object theObject) throws InternalErrorException;
public abstract String getName();
public abstract Object parse(List<List<String>> theString) throws InternalErrorException, InvalidRequestException;
public abstract boolean isRequired();
/**
* Parameter should return true if {@link #parse(List)} should be called even
* if the query string contained no values for the given parameter
*/
public abstract boolean handlesMissing();
public abstract SearchParamTypeEnum getParamType();
@Override
public void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
if (theSourceClientArgument == null) {
if (isRequired()) {
throw new NullPointerException("SearchParameter '" + getName() + "' is required and may not be null");
}
} else {
List<List<String>> value = encode(theSourceClientArgument);
ArrayList<String> paramValues = new ArrayList<String>(value.size());
theTargetQueryArguments.put(getName(), paramValues);
for (List<String> nextParamEntry : value) {
StringBuilder b = new StringBuilder();
for (String str : nextParamEntry) {
if (b.length() > 0) {
b.append(",");
}
b.append(str.replace(",", "\\,"));
}
paramValues.add(b.toString());
}
}
}
@Override
public Object translateQueryParametersIntoServerArgument(Map<String, String[]> theQueryParameters, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
String[] value = theQueryParameters.get(getName());
if (value == null || value.length == 0) {
if (handlesMissing()) {
return parse(new ArrayList<List<String>>(0));
}else {
return null;
}
}
List<List<String>> paramList = new ArrayList<List<String>>(value.length);
for (String nextParam : value) {
if (nextParam.contains(",") == false) {
paramList.add(Collections.singletonList(nextParam));
} else {
paramList.add(QueryUtil.splitQueryStringByCommasIgnoreEscape(nextParam));
}
}
return parse(paramList);
}
}

View File

@ -9,16 +9,16 @@ import java.util.TreeSet;
import ca.uhn.fhir.model.api.PathSpecification;
import ca.uhn.fhir.model.dstu.valueset.SearchParamTypeEnum;
import ca.uhn.fhir.rest.annotation.Include;
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 implements IParameter {
public class IncludeParameter extends IQueryParameter {
private Class<? extends Collection<PathSpecification>> myInstantiableCollectionType;
private HashSet<String> myAllow;
public IncludeParameter(Include theAnnotation, Class<? extends Collection<PathSpecification>> theInstantiableCollectionType) {
public IncludeParameter(IncludeParam theAnnotation, Class<? extends Collection<PathSpecification>> theInstantiableCollectionType) {
myInstantiableCollectionType = theInstantiableCollectionType;
if (theAnnotation.allow().length > 0) {
myAllow = new HashSet<String>();

View File

@ -0,0 +1,36 @@
package ca.uhn.fhir.rest.param;
import java.util.List;
import java.util.Map;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class ResourceParameter implements IParameter {
private Class<? extends IResource> myResourceName ;
public ResourceParameter(Class<? extends IResource> theParameterType) {
myResourceName =theParameterType;
}
@Override
public void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
// TODO Auto-generated method stub
}
@Override
public Object translateQueryParametersIntoServerArgument(Map<String, String[]> theQueryParameters, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
IResource resource = (IResource) theRequestContents;
return resource;
}
public Class<? extends IResource> getResourceType() {
return myResourceName;
}
// public IResource
}

View File

@ -16,10 +16,10 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
/**
* Created by dsotnikov on 2/25/2014.
*/
public class SearchParameter implements IParameter {
public class SearchParameter extends IQueryParameter {
private String name;
private IParamBinder parser;
private IParamBinder myParamBinder;
private boolean required;
private Class<?> type;
private SearchParamTypeEnum myParamType;
@ -37,7 +37,7 @@ public class SearchParameter implements IParameter {
*/
@Override
public List<List<String>> encode(Object theObject) throws InternalErrorException {
return parser.encode(theObject);
return myParamBinder.encode(theObject);
}
/* (non-Javadoc)
@ -62,7 +62,7 @@ public class SearchParameter implements IParameter {
*/
@Override
public Object parse(List<List<String>> theString) throws InternalErrorException, InvalidRequestException {
return parser.parse(theString);
return myParamBinder.parse(theString);
}
public void setName(String name) {
@ -77,11 +77,11 @@ public class SearchParameter implements IParameter {
public void setType(final Class<?> type, Class<? extends Collection<?>> theInnerCollectionType, Class<? extends Collection<?>> theOuterCollectionType) {
this.type = type;
if (IQueryParameterType.class.isAssignableFrom(type)) {
this.parser = new QueryParameterTypeBinder((Class<? extends IQueryParameterType>) type);
this.myParamBinder = new QueryParameterTypeBinder((Class<? extends IQueryParameterType>) type);
} else if (IQueryParameterOr.class.isAssignableFrom(type)) {
this.parser = new QueryParameterOrBinder((Class<? extends IQueryParameterOr>) type);
this.myParamBinder = new QueryParameterOrBinder((Class<? extends IQueryParameterOr>) type);
} else if (IQueryParameterAnd.class.isAssignableFrom(type)) {
this.parser = new QueryParameterAndBinder((Class<? extends IQueryParameterAnd>) type);
this.myParamBinder = new QueryParameterAndBinder((Class<? extends IQueryParameterAnd>) type);
} else {
throw new ConfigurationException("Unsupported data type for parameter: " + type.getCanonicalName());
}

View File

@ -23,6 +23,8 @@ public class Constants {
public static final String PARAM_HISTORY = "_history";
public static final String PARAM_PRETTY = "_pretty";
public static final String PARAM_QUERY = "_query";
public static final int STATUS_HTTP_201_CREATED = 201;
public static final String CT_TEXT = "text/plain";
static {
Map<String, EncodingUtil> valToEncoding = new HashMap<String, EncodingUtil>();

View File

@ -1,10 +1,23 @@
package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.parser.IParser;
public enum EncodingUtil {
XML(Constants.CT_FHIR_XML, Constants.CT_ATOM_XML, Constants.CT_XML),
XML(Constants.CT_FHIR_XML, Constants.CT_ATOM_XML, Constants.CT_XML) {
@Override
public IParser newParser(FhirContext theContext) {
return theContext.newXmlParser();
}
},
JSON(Constants.CT_FHIR_JSON, Constants.CT_FHIR_JSON, Constants.CT_JSON)
JSON(Constants.CT_FHIR_JSON, Constants.CT_FHIR_JSON, Constants.CT_JSON) {
@Override
public IParser newParser(FhirContext theContext) {
return theContext.newJsonParser();
}
}
;
@ -18,6 +31,8 @@ public enum EncodingUtil {
myBrowserFriendlyContentType = theBrowserFriendlyContentType;
}
public abstract IParser newParser(FhirContext theContext);
public String getBundleContentType() {
return myBundleContentType;
}

View File

@ -30,6 +30,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotFoundException;
import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider;
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
import ca.uhn.fhir.util.VersionUtil;
public abstract class RestfulServer extends HttpServlet {
@ -312,6 +313,9 @@ public abstract class RestfulServer extends HttpServlet {
} catch (AuthenticationException e) {
response.setStatus(e.getStatusCode());
addHapiHeader(response);
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().write(e.getMessage());
} catch (BaseServerResponseException e) {
@ -322,6 +326,7 @@ public abstract class RestfulServer extends HttpServlet {
}
response.setStatus(e.getStatusCode());
addHapiHeader(response);
response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8");
response.getWriter().append(e.getMessage());
@ -352,5 +357,8 @@ public abstract class RestfulServer extends HttpServlet {
}
}
public void addHapiHeader(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-CatchingFhir", "HAPI FHIR " + VersionUtil.getVersion());
}
}

View File

@ -1,6 +1,6 @@
package ca.uhn.fhir.rest.server.exceptions;
public abstract class BaseServerResponseException extends Exception {
public abstract class BaseServerResponseException extends RuntimeException {
private static final long serialVersionUID = 1L;

View File

@ -14,6 +14,10 @@ public class ResourceNotFoundException extends BaseServerResponseException {
super(404, "Resource of type " + theClass.getSimpleName() + " with ID " + thePatientId + " is not known");
}
public ResourceNotFoundException(String theMessage) {
super(404, theMessage);
}
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,15 @@
package ca.uhn.fhir.rest.server.exceptions;
/**
* Exception for use when a response is received or being sent that
* does not correspond to any other exception type
*/
public class UnclassifiedServerFailureException extends BaseServerResponseException {
public UnclassifiedServerFailureException(int theStatusCode, String theMessage) {
super(theStatusCode, theMessage);
}
private static final long serialVersionUID = 1L;
}

View File

@ -0,0 +1,46 @@
package ca.uhn.fhir.rest.server.exceptions;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
/**
* Represents an <b>HTTP 422 Unprocessable Entity</b> response, which means that a resource was rejected by the server because it "violated applicable FHIR profiles or server business rules".
*
* <p>
* This exception will generally contain an {@link OperationOutcome} instance which details the failure.
* </p>
*/
public class UnprocessableEntityException extends BaseServerResponseException {
private OperationOutcome myOperationOutcome;
public UnprocessableEntityException(OperationOutcome theOperationOutcome) {
super(422, "Unprocessable Entity");
myOperationOutcome = theOperationOutcome;
}
public UnprocessableEntityException(String theMessage) {
super(422, theMessage);
myOperationOutcome = new OperationOutcome();
myOperationOutcome.addIssue().setDetails(theMessage);
}
public UnprocessableEntityException(String... theMessage) {
super(422, theMessage != null && theMessage.length > 0 ? theMessage[0] : "");
myOperationOutcome = new OperationOutcome();
if (theMessage != null) {
for (String next : theMessage) {
myOperationOutcome.addIssue().setDetails(next);
}
}
}
public OperationOutcome getOperationOutcome() {
return myOperationOutcome;
}
private static final long serialVersionUID = 1L;
}

View File

@ -23,6 +23,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.server.IResourceProvider;
import ca.uhn.fhir.rest.server.ResourceBinding;
import ca.uhn.fhir.rest.server.RestfulServer;
@ -87,7 +88,12 @@ public class ServerConformanceProvider implements IResourceProvider {
RestResourceSearchParam searchParam = null;
StringDt searchParamChain = null;
for (IParameter nextParameter : params) {
for (IParameter nextParameterObj : params) {
if (!(nextParameterObj instanceof IQueryParameter)) {
continue;
}
IQueryParameter nextParameter = (IQueryParameter)nextParameterObj;
if (nextParameter.getName().startsWith("_")) {
continue;
}

View File

@ -10,7 +10,7 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Profile;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Id;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.server.IResourceProvider;
@ -29,7 +29,7 @@ public class ServerProfileProvider implements IResourceProvider {
}
@Read()
public Profile getProfileById(@Id IdDt theId) {
public Profile getProfileById(@IdParam IdDt theId) {
RuntimeResourceDefinition retVal = myContext.getResourceDefinitionById(theId.getValue());
if (retVal==null) {
return null;

View File

@ -9,7 +9,7 @@ import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.composite.ResourceReferenceDt;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.annotation.Required;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
@ -27,7 +27,7 @@ public class CompleteExampleClient {
* http://fhir.healthintersections.com.au/open/Patient?identifier=urn:oid:1.2.36.146.595.217.0.1%7C12345
*/
@Search
List<Patient> findPatientsForMrn(@Required(name=Patient.SP_IDENTIFIER) IdentifierDt theIdentifier);
List<Patient> findPatientsForMrn(@RequiredParam(name=Patient.SP_IDENTIFIER) IdentifierDt theIdentifier);
}

View File

@ -1,14 +1,54 @@
package example;
import java.io.IOException;
import java.util.List;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import static ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum.*;
public class QuickUsage {
public void format() {
public static void main(String[] args) throws DataFormatException, IOException {
Patient obs = new Patient();
Patient patient = new Patient();
patient.addIdentifier().setUse(OFFICIAL).setSystem("urn:fake:mrns").setValue("7000135");
patient.addIdentifier().setUse(SECONDARY).setSystem("urn:fake:otherids").setValue("3287486");
patient.addName().addFamily("Smith").addGiven("John").addGiven("Q").addSuffix("Junior");
patient.setGender(AdministrativeGenderCodesEnum.M);
FhirContext ctx = new FhirContext();
String xmlEncoded = ctx.newXmlParser().encodeResourceToString(patient);
String jsonEncoded = ctx.newJsonParser().encodeResourceToString(patient);
MyClientInterface client = ctx.newRestfulClient(MyClientInterface.class, "http://foo/fhir");
IdentifierDt searchParam = new IdentifierDt("urn:someidentifiers", "7000135");
List<Patient> clients = client.findPatientsByIdentifier(searchParam);
}
public interface MyClientInterface extends IRestfulClient
{
/** A FHIR search */
@Search
public List<Patient> findPatientsByIdentifier(@RequiredParam(name="identifier") IdentifierDt theIdentifier);
/** A FHIR create */
@Create
public MethodOutcome createPatient(@ResourceParam Patient thePatient);
}
}

View File

@ -5,9 +5,9 @@ import java.util.List;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Id;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Required;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.client.api.IBasicClient;
@ -29,7 +29,7 @@ public interface RestfulClientImpl extends IBasicClient {
* Returns a resource matching this identifier, or null if none exists.
*/
@Read()
public Patient getResourceById(@Id IdDt theId);
public Patient getResourceById(@IdParam IdDt theId);
/**
* The "@Search" annotation indicates that this method supports the
@ -48,7 +48,7 @@ public interface RestfulClientImpl extends IBasicClient {
* matching resources, or it may also be empty.
*/
@Search()
public List<Patient> getPatient(@Required(name = Patient.SP_FAMILY) StringDt theFamilyName);
public List<Patient> getPatient(@RequiredParam(name = Patient.SP_FAMILY) StringDt theFamilyName);
}
//END SNIPPET: provider

View File

@ -9,9 +9,9 @@ import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.Id;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Required;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.server.IResourceProvider;
@ -42,7 +42,7 @@ public class RestfulObservationResourceProvider implements IResourceProvider {
* Returns a resource matching this identifier, or null if none exists.
*/
@Read()
public Patient getResourceById(@Id IdDt theId) {
public Patient getResourceById(@IdParam IdDt theId) {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
@ -70,7 +70,7 @@ public class RestfulObservationResourceProvider implements IResourceProvider {
* matching resources, or it may also be empty.
*/
@Search()
public List<Patient> getPatient(@Required(name = Patient.SP_FAMILY) StringDt theFamilyName) {
public List<Patient> getPatient(@RequiredParam(name = Patient.SP_FAMILY) StringDt theFamilyName) {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);

View File

@ -9,9 +9,9 @@ import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.Id;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Required;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.server.IResourceProvider;
@ -43,7 +43,7 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
* Returns a resource matching this identifier, or null if none exists.
*/
@Read()
public Patient getResourceById(@Id IdDt theId) {
public Patient getResourceById(@IdParam IdDt theId) {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
@ -71,7 +71,7 @@ public class RestfulPatientResourceProvider implements IResourceProvider {
* matching resources, or it may also be empty.
*/
@Search()
public List<Patient> getPatient(@Required(name = Patient.SP_FAMILY) StringDt theFamilyName) {
public List<Patient> getPatient(@RequiredParam(name = Patient.SP_FAMILY) StringDt theFamilyName) {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);

View File

@ -17,21 +17,25 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Id;
import ca.uhn.fhir.rest.annotation.Include;
import ca.uhn.fhir.rest.annotation.Optional;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Required;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.VersionId;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.ITestClient;
import ca.uhn.fhir.rest.param.CodingListParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@SuppressWarnings("unused")
public class RestfulPatientResourceProviderMore implements IResourceProvider {
public abstract class RestfulPatientResourceProviderMore implements IResourceProvider {
//START SNIPPET: searchAll
@Search
@ -43,7 +47,7 @@ public List<Organization> getAllOrganizations() {
//START SNIPPET: read
@Read()
public Patient getResourceById(@Id IdDt theId) {
public Patient getResourceById(@IdParam IdDt theId) {
Patient retVal = new Patient();
// ...populate...
return retVal;
@ -52,8 +56,8 @@ public Patient getResourceById(@Id IdDt theId) {
//START SNIPPET: vread
@Read()
public Patient getResourceById(@Id IdDt theId,
@VersionId IdDt theVersionId) {
public Patient getResourceById(@IdParam IdDt theId,
@VersionIdParam IdDt theVersionId) {
Patient retVal = new Patient();
// ...populate...
return retVal;
@ -62,7 +66,7 @@ public Patient getResourceById(@Id IdDt theId,
//START SNIPPET: searchStringParam
@Search()
public List<Patient> searchByLastName(@Required(name=Patient.SP_FAMILY) StringDt theId) {
public List<Patient> searchByLastName(@RequiredParam(name=Patient.SP_FAMILY) StringDt theId) {
List<Patient> retVal = new ArrayList<Patient>();
// ...populate...
return retVal;
@ -71,7 +75,7 @@ public List<Patient> searchByLastName(@Required(name=Patient.SP_FAMILY) StringDt
//START SNIPPET: searchNamedQuery
@Search(queryName="namedQuery1")
public List<Patient> searchByNamedQuery(@Required(name="someparam") StringDt theSomeParam) {
public List<Patient> searchByNamedQuery(@RequiredParam(name="someparam") StringDt theSomeParam) {
List<Patient> retVal = new ArrayList<Patient>();
// ...populate...
return retVal;
@ -80,7 +84,7 @@ public List<Patient> searchByNamedQuery(@Required(name="someparam") StringDt the
//START SNIPPET: searchIdentifierParam
@Search()
public List<Patient> searchByIdentifier(@Required(name=Patient.SP_IDENTIFIER) IdentifierDt theId) {
public List<Patient> searchByIdentifier(@RequiredParam(name=Patient.SP_IDENTIFIER) IdentifierDt theId) {
String identifierSystem = theId.getSystem().getValueAsString();
String identifier = theId.getValue().getValue();
@ -92,8 +96,8 @@ public List<Patient> searchByIdentifier(@Required(name=Patient.SP_IDENTIFIER) Id
//START SNIPPET: searchOptionalParam
@Search()
public List<Patient> searchByNames( @Required(name=Patient.SP_FAMILY) StringDt theFamilyName,
@Optional(name=Patient.SP_GIVEN) StringDt theGivenName ) {
public List<Patient> searchByNames( @RequiredParam(name=Patient.SP_FAMILY) StringDt theFamilyName,
@OptionalParam(name=Patient.SP_GIVEN) StringDt theGivenName ) {
String familyName = theFamilyName.getValue();
String givenName = theGivenName != null ? theGivenName.getValue() : null;
@ -105,7 +109,7 @@ public List<Patient> searchByNames( @Required(name=Patient.SP_FAMILY) StringDt t
//START SNIPPET: searchMultiple
@Search()
public List<Observation> searchByObservationNames( @Required(name=Observation.SP_NAME) CodingListParam theCodings ) {
public List<Observation> searchByObservationNames( @RequiredParam(name=Observation.SP_NAME) CodingListParam theCodings ) {
// This search should return any observations matching one or more
// of the codings here.
List<CodingDt> wantedCodings = theCodings.getCodings();
@ -118,7 +122,7 @@ public List<Observation> searchByObservationNames( @Required(name=Observation.SP
//START SNIPPET: dates
@Search()
public List<Patient> searchByObservationNames( @Required(name=Patient.SP_BIRTHDATE) QualifiedDateParam theDate ) {
public List<Patient> searchByObservationNames( @RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theDate ) {
QuantityCompararatorEnum comparator = theDate.getComparator(); // e.g. <=
Date date = theDate.getValue(); // e.g. 2011-01-02
TemporalPrecisionEnum precision = theDate.getPrecision(); // e.g. DAY
@ -150,9 +154,9 @@ public Class<? extends IResource> getResourceType() {
//START SNIPPET: pathSpec
@Search()
public List<DiagnosticReport> getDiagnosticReport(
@Required(name=DiagnosticReport.SP_IDENTIFIER)
@RequiredParam(name=DiagnosticReport.SP_IDENTIFIER)
IdentifierDt theIdentifier,
@Include(allow= {"DiagnosticReport.subject"})
@IncludeParam(allow= {"DiagnosticReport.subject"})
Set<PathSpecification> theIncludes ) {
List<DiagnosticReport> retVal = new ArrayList<DiagnosticReport>();
@ -178,8 +182,8 @@ public List<DiagnosticReport> getDiagnosticReport(
//START SNIPPET: dateRange
@Search()
public List<Observation> getObservationsByDateRange(@Required(name="subject.identifier") IdentifierDt theSubjectId,
@Required(name=Observation.SP_DATE) DateRangeParam theRange) {
public List<Observation> getObservationsByDateRange(@RequiredParam(name="subject.identifier") IdentifierDt theSubjectId,
@RequiredParam(name=Observation.SP_DATE) DateRangeParam theRange) {
List<Observation> retVal = new ArrayList<Observation>();
// The following two will be set as the start and end
@ -200,6 +204,43 @@ private Patient loadSomePatientFromDatabase(IdDt theId) {
return null;
}
//START SNIPPET: create
@Create
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
/*
* First we might want to do business validation. The UnprocessableEntityException
* results in an HTTP 422, which is appropriate for business rule failure
*/
if (thePatient.getIdentifierFirstRep().isEmpty()) {
throw new UnprocessableEntityException("No identifier supplied");
}
// Save this patient to the database...
savePatientToDatabase(thePatient);
// This method returns a MethodOutcome object which contains
// the ID and Version ID for the newly saved resource
MethodOutcome retVal = new MethodOutcome();
retVal.setId(new IdDt("3746"));
retVal.setVersionId(new IdDt("1"));
return retVal;
}
//END SNIPPET: create
//START SNIPPET: createClient
@Create
public abstract MethodOutcome createNewPatient(@ResourceParam Patient thePatient);
//END SNIPPET: createClient
private void savePatientToDatabase(Patient thePatient) {
// nothing
}
}

View File

@ -23,7 +23,7 @@
</links>
<menu name="Welcome">
<item name="Introduction" href="index.html" />
<item name="Welcome" href="index.html" />
</menu>
<menu name="Documentation">

View File

@ -37,7 +37,7 @@
operation retrieves a resource by ID. It is annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/Read.html">@Read</a>
annotation, and has a single parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/Read.IdParam.html">@Read.IdParam</a>
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
annotation.
</p>
@ -61,7 +61,7 @@
operation retrieves a specific version of a resource with a given ID. It looks exactly
like a "read" operation, but with a second
parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/Read.VersionIdParam.html">@VersionIdParam</a>
<a href="./apidocs/ca/uhn/fhir/rest/annotation/VersionIdParam.html">@VersionIdParam</a>
annotation.
</p>
@ -109,9 +109,9 @@
<p>
To allow a search using given search parameters, add one or more parameters
to your search method and tag these parameters as either
<a href="./apidocs/ca/uhn/fhir/rest/server/parameters/Required.html">@Required</a>
<a href="./apidocs/ca/uhn/fhir/rest/server/parameters/RequiredParam.html">@RequiredParam</a>
or
<a href="./apidocs/ca/uhn/fhir/rest/server/parameters/Optional.html">@Optional</a>.
<a href="./apidocs/ca/uhn/fhir/rest/server/parameters/OptionalParam.html">@OptionalParam</a>.
</p>
<p>
@ -158,16 +158,16 @@
Search methods may take multiple parameters, and these parameters
may be optional. To add a second required parameter, annotate the
parameter with
<a href="./apidocs/ca/uhn/fhir/rest/server/parameters/Required.html">@Required</a>.
<a href="./apidocs/ca/uhn/fhir/rest/server/parameters/RequiredParam.html">@RequiredParam</a>.
To add an optional parameter (which will be passed in as <code>null</code> if no value
is supplied), annotate the method with
<a href="./apidocs/ca/uhn/fhir/rest/server/parameters/Required.html">@Optional</a>.
<a href="./apidocs/ca/uhn/fhir/rest/server/parameters/OptionalParam.html">@OptionalParam</a>.
</p>
<p>
You may annotate a method with any combination of as many @Required and as many @Optional
parameters as you want. It is valid to have only @Required parameters, or
only @Optional parameters, or any combination of the two.
You may annotate a method with any combination of as many @RequiredParam and as many @OptionalParam
parameters as you want. It is valid to have only @RequiredParam parameters, or
only @OptionalParam parameters, or any combination of the two.
</p>
<macro name="snippet">
@ -376,6 +376,48 @@
</section>
<section name="Type Level - create">
<p>
The
<a href="http://hl7.org/implement/standards/fhir/http.html#create"><b>create</b></a>
operation saves a new resource to the server, allowing the server to
give that resource an ID and version ID.
</p>
<p>
Create methods must be annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/Create.html">@Create</a>
annotation, and have a single parameter annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/ResourceParam.html">@Resource</a>
annotation. This parameter contains the resource instance to be created.
</p>
<p>
The following snippet shows how to define a server create method:
</p>
<macro name="snippet">
<param name="id" value="create" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Example URL to invoke this method (this would be invoked using an HTTP POST,
with the resource in the POST body):<br/>
<code>http://fhir.example.com/Patient</code>
</p>
<p>
The following snippet shows how the corresponding client interface
would look:
</p>
<macro name="snippet">
<param name="id" value="createClient" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
</section>
</body>

View File

@ -18,8 +18,63 @@
an open-source implementation of the FHIR specification in Java.
This library is still in experimental stage, so please tread with caution.
</p>
<p>
Note that this is the home for the FHIR version of HAPI. If you are
looking for HL7 v2 support, <a href="../">click here</a>.
</p>
</section>
<section name="Why HAPI FHIR?">
<p>
HAPI FHIR is a simple-but-powerful library for adding FHIR messaging to your application. It
is pure Java (1.6+ compatible), and licensed under the business-friendly Apache Software
License, version 2.0.
</p>
<p>
The HAPI API makes it very easy to create FHIR resources:
</p>
<source><![CDATA[Patient patient = new Patient();
patient.addIdentifier().setUse(OFFICIAL).setSystem("urn:fake:mrns").setValue("7000135");
patient.addIdentifier().setUse(SECONDARY).setSystem("urn:fake:otherids").setValue("3287486");
patient.addName().addFamily("Smith").addGiven("John").addGiven("Q").addSuffix("Junior");
patient.setGender(AdministrativeGenderCodesEnum.M);]]></source>
<p>
This library supports both XML and JSON encoding natively:
</p>
<source><![CDATA[FhirContext ctx = new FhirContext();
String xmlEncoded = ctx.newXmlParser().encodeResourceToString(patient);
String jsonEncoded = ctx.newJsonParser().encodeResourceToString(patient);
]]></source>
<p>
Creating clients is simple and uses an annotation based format
that will be familiar to users of JAX-WS.
</p>
<source><![CDATA[public interface MyClientInterface
{
/** A FHIR search */
@Search
public List<Patient> findPatientsByIdentifier(@RequiredParam(name="identifier") IdentifierDt theIdentifier);
/** A FHIR create */
@Create
public MethodOutcome createPatient(@ResourceParam Patient thePatient);
}]]></source>
<p>
Using this client is as simple as:
</p>
<source><![CDATA[MyClientInterface client = ctx.newRestfulClient(MyClientInterface.class, "http://foo/fhir");
IdentifierDt searchParam = new IdentifierDt("urn:someidentifiers", "7000135");
List<Patient> clients = client.findPatientsByIdentifier(searchParam);]]></source>
</section>
</body>

View File

@ -10,12 +10,15 @@ import java.util.List;
import org.apache.commons.io.IOUtils;
import org.apache.commons.io.input.ReaderInputStream;
import org.apache.http.Header;
import org.apache.http.HttpResponse;
import org.apache.http.ProtocolVersion;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.methods.HttpUriRequest;
import org.apache.http.message.BasicHeader;
import org.apache.http.message.BasicStatusLine;
import org.hamcrest.core.StringContains;
import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
@ -31,10 +34,12 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.CodingListParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class ClientTest {
@ -84,6 +89,54 @@ 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_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");
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"));
}
}
private Header[] toHeaderArray(String theName, String theValue) {
return new Header[] {new BasicHeader(theName, theValue)};
}
@Test
public void testVRead() throws Exception {

View File

@ -8,13 +8,16 @@ import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Id;
import ca.uhn.fhir.rest.annotation.Include;
import ca.uhn.fhir.rest.annotation.Optional;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Required;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.VersionId;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.param.CodingListParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
@ -23,33 +26,36 @@ import ca.uhn.fhir.rest.param.QualifiedDateParam;
public interface ITestClient extends IBasicClient {
@Read(type=Patient.class)
Patient getPatientById(@Id IdDt theId);
Patient getPatientById(@IdParam IdDt theId);
@Read(type=Patient.class)
Patient getPatientByVersionId(@Id IdDt theId, @VersionId IdDt theVersionId);
Patient getPatientByVersionId(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId);
@Search(type=Patient.class)
Patient findPatientByMrn(@Required(name = Patient.SP_IDENTIFIER) IdentifierDt theId);
Patient findPatientByMrn(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theId);
@Search(type=Patient.class)
Bundle findPatientByName(@Required(name = Patient.SP_FAMILY) StringDt theId, @Optional(name=Patient.SP_GIVEN) StringDt theGiven);
Bundle findPatientByName(@RequiredParam(name = Patient.SP_FAMILY) StringDt theId, @OptionalParam(name=Patient.SP_GIVEN) StringDt theGiven);
@Search()
public List<Patient> getPatientMultipleIdentifiers(@Required(name = "ids") CodingListParam theIdentifiers);
public List<Patient> getPatientMultipleIdentifiers(@RequiredParam(name = "ids") CodingListParam theIdentifiers);
@Search()
public List<Patient> getPatientByDateRange(@Required(name = "dateRange") DateRangeParam theIdentifiers);
public List<Patient> getPatientByDateRange(@RequiredParam(name = "dateRange") DateRangeParam theIdentifiers);
@Search()
public List<Patient> getPatientByDob(@Required(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
public List<Patient> getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
@Search()
public Patient getPatientWithIncludes(@Required(name = "withIncludes") StringDt theString, @Include List<PathSpecification> theIncludes);
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam List<PathSpecification> theIncludes);
@Search(queryName="someQueryNoParams")
public Patient getPatientNoParams();
@Search(queryName="someQueryOneParam")
public Patient getPatientOneParam(@Required(name="param1") StringDt theParam);
public Patient getPatientOneParam(@RequiredParam(name="param1") StringDt theParam);
@Create
public MethodOutcome createPatient(@ResourceParam Patient thePatient);
}

View File

@ -1,6 +1,8 @@
package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
import java.util.ArrayList;
import java.util.Arrays;
@ -15,6 +17,8 @@ import org.apache.commons.io.IOUtils;
import org.apache.http.HttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ContentType;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClientBuilder;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
@ -42,13 +46,16 @@ import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.parser.IParser;
import ca.uhn.fhir.rest.annotation.Id;
import ca.uhn.fhir.rest.annotation.Include;
import ca.uhn.fhir.rest.annotation.Optional;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Required;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.VersionId;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.CodingListParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
import ca.uhn.fhir.rest.param.QualifiedDateParam;
@ -391,6 +398,27 @@ public class ResfulServerMethodTest {
}
@Test
public void testCreate() throws Exception {
Patient patient = new Patient();
patient.addIdentifier().setValue("001");
patient.addIdentifier().setValue("002");
HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient");
httpPost.setEntity(new StringEntity(new FhirContext().newXmlParser().encodeResourceToString(patient), ContentType.create(Constants.CT_FHIR_XML, "UTF-8")));
HttpResponse status = ourClient.execute(httpPost);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(201, status.getStatusLine().getStatusCode());
assertEquals("http://localhost:" + ourPort + "/Patient/001/_history/002", status.getFirstHeader("Location").getValue());
}
@Test
public void testFormatParamXml() throws Exception {
@ -655,15 +683,15 @@ public class ResfulServerMethodTest {
}
@Search(queryName="someQueryOneParam")
public Patient getPatientOneParam(@Required(name="param1") StringDt theParam) {
public Patient getPatientOneParam(@RequiredParam(name="param1") StringDt theParam) {
Patient next = getIdToPatient().get("1");
next.addName().addFamily(theParam.getValue());
return next;
}
@Search()
public Patient getPatientWithIncludes(@Required(name = "withIncludes") StringDt theString,
@Include(allow= {"include1","include2", "include3"}) List<PathSpecification> theIncludes) {
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString,
@IncludeParam(allow= {"include1","include2", "include3"}) List<PathSpecification> theIncludes) {
Patient next = getIdToPatient().get("1");
next.addCommunication().setText(theString.getValue());
@ -675,8 +703,16 @@ public class ResfulServerMethodTest {
return next;
}
@Create()
public MethodOutcome createPatient(@ResourceParam Patient thePatient) {
IdDt id = new IdDt(thePatient.getIdentifier().get(0).getValue().getValue());
IdDt version = new IdDt(thePatient.getIdentifier().get(1).getValue().getValue());
return new MethodOutcome(id, version);
}
@Search()
public Patient getPatient(@Required(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
public Patient getPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
for (Patient next : getIdToPatient().values()) {
for (IdentifierDt nextId : next.getIdentifier()) {
if (nextId.matchesSystemAndValue(theIdentifier)) {
@ -688,13 +724,13 @@ public class ResfulServerMethodTest {
}
@SuppressWarnings("unused")
public List<Patient> findDiagnosticReportsByPatient(@Required(name = "Patient.identifier") IdentifierDt thePatientId, @Required(name = DiagnosticReport.SP_NAME) CodingListParam theNames,
@Optional(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange) throws Exception {
public List<Patient> findDiagnosticReportsByPatient(@RequiredParam(name = "Patient.identifier") IdentifierDt thePatientId, @RequiredParam(name = DiagnosticReport.SP_NAME) CodingListParam theNames,
@OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange) throws Exception {
return Collections.emptyList();
}
@Search()
public Patient getPatientWithDOB(@Required(name = "dob") QualifiedDateParam theDob) {
public Patient getPatientWithDOB(@RequiredParam(name = "dob") QualifiedDateParam theDob) {
Patient next = getIdToPatient().get("1");
if (theDob.getComparator() != null) {
next.addIdentifier().setValue(theDob.getComparator().getCode());
@ -706,7 +742,7 @@ public class ResfulServerMethodTest {
}
@Search()
public List<Patient> getPatientWithOptionalName(@Required(name = "name1") StringDt theName1, @Optional(name = "name2") StringDt theName2) {
public List<Patient> getPatientWithOptionalName(@RequiredParam(name = "name1") StringDt theName1, @OptionalParam(name = "name2") StringDt theName2) {
List<Patient> retVal = new ArrayList<Patient>();
Patient next = getIdToPatient().get("1");
next.getName().get(0).getFamily().set(0, theName1);
@ -722,7 +758,7 @@ public class ResfulServerMethodTest {
* @param theName3
*/
@Search()
public List<Patient> getPatientWithOptionalName(@Required(name = "aaa") StringDt theName1, @Optional(name = "bbb") StringDt theName2, @Optional(name = "ccc") StringDt theName3) {
public List<Patient> getPatientWithOptionalName(@RequiredParam(name = "aaa") StringDt theName1, @OptionalParam(name = "bbb") StringDt theName2, @OptionalParam(name = "ccc") StringDt theName3) {
List<Patient> retVal = new ArrayList<Patient>();
Patient next = getIdToPatient().get("1");
next.getName().get(0).getFamily().set(0, theName1);
@ -735,7 +771,7 @@ public class ResfulServerMethodTest {
}
@Search()
public List<Patient> getPatientMultipleIdentifiers(@Required(name = "ids") CodingListParam theIdentifiers) {
public List<Patient> getPatientMultipleIdentifiers(@RequiredParam(name = "ids") CodingListParam theIdentifiers) {
List<Patient> retVal = new ArrayList<Patient>();
Patient next = getIdToPatient().get("1");
@ -756,12 +792,12 @@ public class ResfulServerMethodTest {
* @return The resource
*/
@Read()
public Patient getResourceById(@Id IdDt theId) {
public Patient getResourceById(@IdParam IdDt theId) {
return getIdToPatient().get(theId.getValue());
}
@Read()
public Patient getResourceById(@Id IdDt theId, @VersionId IdDt theVersionId) {
public Patient getResourceById(@IdParam IdDt theId, @VersionIdParam IdDt theVersionId) {
Patient retVal = getIdToPatient().get(theId.getValue());
retVal.getName().get(0).setText(theVersionId.getValue());
return retVal;
@ -778,7 +814,7 @@ public class ResfulServerMethodTest {
}
@Search()
public Patient getPatientByDateRange(@Required(name = "dateRange") DateRangeParam theIdentifiers) {
public Patient getPatientByDateRange(@RequiredParam(name = "dateRange") DateRangeParam theIdentifiers) {
Patient retVal = getIdToPatient().get("1");
retVal.getName().get(0).addSuffix().setValue(theIdentifiers.getLowerBound().getValueAsQueryToken());
retVal.getName().get(0).addSuffix().setValue(theIdentifiers.getUpperBound().getValueAsQueryToken());

View File

@ -29,9 +29,9 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.Id;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.Required;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
@ -162,7 +162,7 @@ public class ResfulServerSelfReferenceTest {
@Search()
public Patient getPatient(@Required(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
public Patient getPatient(@RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) {
for (Patient next : getIdToPatient().values()) {
for (IdentifierDt nextId : next.getIdentifier()) {
if (nextId.matchesSystemAndValue(theIdentifier)) {
@ -182,7 +182,7 @@ public class ResfulServerSelfReferenceTest {
* @return The resource
*/
@Read()
public Patient getResourceById(@Id IdDt theId) {
public Patient getResourceById(@IdParam IdDt theId) {
return getIdToPatient().get(theId.getValue());
}