Adding history

This commit is contained in:
jamesagnew 2014-04-10 15:57:45 -04:00
parent acb5b02672
commit 84096da74d
19 changed files with 458 additions and 63 deletions

View File

@ -303,7 +303,7 @@
<replace dir="target/site" summary="true">
<include name="**/*.html"></include>
<replacefilter token="#build#" value="${label}" />
<replacefilter token="#version#" value="${version}" />
<replacefilter token="#version#" value="${project.version}" />
<replacetoken><![CDATA[</body>]]></replacetoken>
<replacevalue><![CDATA[
<script>

View File

@ -8,17 +8,21 @@ public interface IQueryParameterAnd {
/**
*
* @see See FHIR specification
* <p>
* See FHIR specification
* <a href="http://www.hl7.org/implement/standards/fhir/search.html#ptypes">2.2.2 Search SearchParameter Types</a>
* for information on the <b>token</b> format
* </p>
*/
public void setValuesAsQueryTokens(List<List<String>> theParameters) throws InvalidRequestException;
/**
*
* @see See FHIR specification
* <p>
* See FHIR specification
* <a href="http://www.hl7.org/implement/standards/fhir/search.html#ptypes">2.2.2 Search SearchParameter Types</a>
* for information on the <b>token</b> format
* </p>
*/
public List<List<String>> getValuesAsQueryTokens();

View File

@ -7,10 +7,11 @@ public interface IQueryParameterOr {
/**
* Sets the value of this type using the <b>token</b> format. This
* format is used in HTTP queries as a parameter format.
*
* @see See FHIR specification
* <p>
* See FHIR specification
* <a href="http://www.hl7.org/implement/standards/fhir/search.html#ptypes">2.2.2 Search SearchParameter Types</a>
* for information on the <b>token</b> format
* </p>
*/
public void setValuesAsQueryTokens(List<String> theParameters);
@ -18,9 +19,11 @@ public interface IQueryParameterOr {
* Returns the value of this type using the <b>token</b> format. This
* format is used in HTTP queries as a parameter format.
*
* @see See FHIR specification
* <p>
* See FHIR specification
* <a href="http://www.hl7.org/implement/standards/fhir/search.html#ptypes">2.2.2 Search SearchParameter Types</a>
* for information on the <b>token</b> format
* </p>
*/
public List<String> getValuesAsQueryTokens();

View File

@ -6,9 +6,10 @@ public interface IQueryParameterType {
* Sets the value of this type using the <b>token</b> format. This
* format is used in HTTP queries as a parameter format.
*
* @see See FHIR specification
* <p>See FHIR specification
* <a href="http://www.hl7.org/implement/standards/fhir/search.html#ptypes">2.2.2 Search SearchParameter Types</a>
* for information on the <b>token</b> format
* </p>
*/
public void setValueAsQueryToken(String theParameter);
@ -16,9 +17,10 @@ public interface IQueryParameterType {
* Returns the value of this type using the <b>token</b> format. This
* format is used in HTTP queries as a parameter format.
*
* @see See FHIR specification
* <p>See FHIR specification
* <a href="http://www.hl7.org/implement/standards/fhir/search.html#ptypes">2.2.2 Search SearchParameter Types</a>
* for information on the <b>token</b> format
* </p>
*/
public String getValueAsQueryToken();

View File

@ -37,7 +37,7 @@ public interface ISupportsUndeclaredExtensions extends IElement {
* </ul>
* </ul>
*
* @param The extension to add. Can not be null.
* @param theExtension The extension to add. Can not be null.
*/
void addUndeclaredExtension(ExtensionDt theExtension);

View File

@ -6,6 +6,7 @@ import java.util.TimeZone;
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name="instant")
public class InstantDt extends BaseDateTimeDt {
@ -46,6 +47,17 @@ public class InstantDt extends BaseDateTimeDt {
setTimeZone(TimeZone.getDefault());
}
/**
* Create a new InstantDt from a string value
*
* @param theString The string representation of the string. Must be in
* a valid format according to the FHIR specification
* @throws DataFormatException
*/
public InstantDt(String theString) {
setValueAsString(theString);
}
@Override
boolean isPrecisionAllowed(TemporalPrecisionEnum thePrecision) {
switch (thePrecision) {

View File

@ -5,7 +5,7 @@ import ca.uhn.fhir.model.api.annotation.DatatypeDef;
import ca.uhn.fhir.model.api.annotation.SimpleSetter;
import ca.uhn.fhir.parser.DataFormatException;
@DatatypeDef(name="integer")
@DatatypeDef(name = "integer")
public class IntegerDt extends BasePrimitive<Integer> {
private Integer myValue;
@ -24,7 +24,17 @@ public class IntegerDt extends BasePrimitive<Integer> {
public IntegerDt(@SimpleSetter.Parameter(name = "theInteger") int theInteger) {
setValue(theInteger);
}
/**
* Constructor
*
* @param theIntegerAsString A string representation of an integer
* @throws DataFormatException If the string is not a valid integer representation
*/
public IntegerDt(String theIntegerAsString) {
setValueAsString(theIntegerAsString);
}
@Override
public Integer getValue() {
return myValue;
@ -39,17 +49,21 @@ public class IntegerDt extends BasePrimitive<Integer> {
public void setValueAsString(String theValue) throws DataFormatException {
if (theValue == null) {
myValue = null;
}else {
myValue = Integer.parseInt(theValue);
} else {
try {
myValue = Integer.parseInt(theValue);
} catch (NumberFormatException e) {
throw new DataFormatException(e);
}
}
}
@Override
public String getValueAsString() {
if (myValue==null) {
if (myValue == null) {
return null;
}
return Integer.toString(myValue);
}
}

View File

@ -0,0 +1,18 @@
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;
/**
* Parameter annotation for the _count parameter, which indicates to the
* server the maximum number of desired results.
*
* @see History
*/
@Target(value=ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Count {
//nothing
}

View File

@ -0,0 +1,67 @@
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;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
/**
* RESTful method annotation to be used for the FHIR
* <a href="http://hl7.org/implement/standards/fhir/http.html#history">history</a> method.
*
* <p>
* History returns a feed containing all versions (or a selected range of versions) of
* a resource or a specific set of resources.
* </p>
* <p>
* The history command supports three usage patterns, as described in the
* <a href="http://hl7.org/implement/standards/fhir/http.html#history">FHIR history</a> documentation:
* <ul>
* <li>
* A search for the history of all resources on a server. In this case, {@link #resourceType()}
* should be set to {@link AllResources} (as is the default) and the method should not have an ID parameter.
* <ul><li>
* To invoke this pattern: <code>GET [base]/_history{?[parameters]&_format=[mime-type]}</code>
* </li></ul>
* </li>
* <li>
* A search for the history of all instances of a specific resource type on a server. In this case, {@link #resourceType()}
* should be set to the specific resource type (e.g. {@link Patient Patient.class} and the method should not have an ID parameter.
* <ul><li>
* To invoke this pattern: <code>GET [base]/[type]/_history{?[parameters]&_format=[mime-type]}</code>
* </li></ul>
* </li>
* <li>
* A search for the history of a specific instances of a specific resource type on a server. In this case, {@link #resourceType()}
* should be set to the specific resource type (e.g. {@link Patient Patient.class} and the method should
* have one parameter of type {@link IdDt} annotated with the {@link IdParam} annotation.
* <ul><li>
* To invoke this pattern: <code>GET [base]/[type]/[id]/_history{?[parameters]&_format=[mime-type]}</code>
* </li></ul>
* </li>
* </ul>
* </p>
*
* @see Count
* @see Since
*/
@Retention(RetentionPolicy.RUNTIME)
@Target(value=ElementType.METHOD)
public @interface History {
/**
* The resource type that this method applies to. See the {@link History History annotation type documentation}
* for information on usage patterns.
*/
Class<? extends IResource> resourceType() default AllResources.class;
interface AllResources extends IResource {
// nothing
}
}

View File

@ -21,8 +21,10 @@ public @interface Search {
/**
* If specified, this the name for the Named Query
*
* @see See the FHIR specification section on
* <p>
* See the FHIR specification section on
* <a href="http://www.hl7.org/implement/standards/fhir/search.html#advanced">named queries</a>
* </p>
*/
String queryName() default "";

View File

@ -0,0 +1,18 @@
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;
/**
* Parameter annotation for the _since parameter, which indicates to the
* server that only results dated since the given instant will be returned.
*
* @see History
*/
@Target(value=ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface Since {
//nothing
}

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.method;
import java.io.IOException;
import java.io.PushbackReader;
import java.io.Reader;
import java.io.Writer;
import java.lang.reflect.InvocationTargetException;
@ -48,7 +49,8 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
if (!theMethod.getReturnType().equals(MethodOutcome.class)) {
if (!allowVoidReturnType()) {
throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " is a @" + theMethodAnnotation.getSimpleName() + " method but it does not return " + MethodOutcome.class);
throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " is a @" + theMethodAnnotation.getSimpleName()
+ " method but it does not return " + MethodOutcome.class);
} else if (theMethod.getReturnType() == void.class) {
myReturnVoid = true;
}
@ -76,9 +78,27 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
if (theResponseStatusCode != Constants.STATUS_HTTP_204_NO_CONTENT) {
EncodingUtil ct = EncodingUtil.forContentType(theResponseMimeType);
if (ct != null) {
IParser parser = ct.newParser(getContext());
OperationOutcome outcome = parser.parseResource(OperationOutcome.class, theResponseReader);
retVal.setOperationOutcome(outcome);
PushbackReader reader = new PushbackReader(theResponseReader);
try {
int firstByte = reader.read();
if (firstByte == -1) {
ourLog.debug("No content in response, not going to read");
reader = null;
} else {
reader.unread(firstByte);
}
} catch (IOException e) {
ourLog.debug("No content in response, not going to read", e);
reader = null;
}
if (reader != null) {
IParser parser = ct.newParser(getContext());
OperationOutcome outcome = parser.parseResource(OperationOutcome.class, reader);
retVal.setOperationOutcome(outcome);
}
} else {
ourLog.debug("Ignoring response content of type: {}", theResponseMimeType);
}
@ -191,8 +211,7 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin
}
/**
* Subclasses may override to allow a void method return type, which is
* allowable for some methods (e.g. delete)
* Subclasses may override to allow a void method return type, which is allowable for some methods (e.g. delete)
*/
protected boolean allowVoidReturnType() {
return false;

View File

@ -0,0 +1,142 @@
package ca.uhn.fhir.rest.method;
import java.io.IOException;
import java.io.Reader;
import java.lang.reflect.Method;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
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.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider;
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;
public class HistoryMethodBinding extends BaseMethodBinding {
private final Integer myIdParamIndex;
private final RestfulOperationTypeEnum myResourceOperationType;
private final Class<? extends IResource> myType;
private final RestfulOperationSystemEnum mySystemOperationType;
private String myResourceName;
private Integer mySinceParamIndex;
private Integer myCountParamIndex;
public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, IResourceProvider theProvider) {
super(theMethod, theConetxt);
myIdParamIndex = Util.findIdParameterIndex(theMethod);
mySinceParamIndex = Util.findSinceParameterIndex(theMethod);
myCountParamIndex = Util.findCountParameterIndex(theMethod);
History historyAnnotation = theMethod.getAnnotation(History.class);
Class<? extends IResource> type = historyAnnotation.resourceType();
if (type == History.AllResources.class) {
if (theProvider != null) {
type = theProvider.getResourceType();
if (myIdParamIndex != null) {
myResourceOperationType = RestfulOperationTypeEnum.HISTORY_INSTANCE;
} else {
myResourceOperationType = RestfulOperationTypeEnum.HISTORY_TYPE;
}
mySystemOperationType = null;
} else {
myResourceOperationType = null;
mySystemOperationType = RestfulOperationSystemEnum.HISTORY_SYSTEM;
}
} else {
if (myIdParamIndex != null) {
myResourceOperationType = RestfulOperationTypeEnum.HISTORY_INSTANCE;
} else {
myResourceOperationType = RestfulOperationTypeEnum.HISTORY_TYPE;
}
mySystemOperationType = null;
}
if (type != History.AllResources.class) {
myResourceName = theConetxt.getResourceDefinition(type).getName();
myType = type;
} else {
myResourceName = null;
myType = null;
}
}
@Override
public RestfulOperationTypeEnum getResourceOperationType() {
return myResourceOperationType;
}
@Override
public RestfulOperationSystemEnum getSystemOperationType() {
return mySystemOperationType;
}
@Override
public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
// TODO Auto-generated method stub
return null;
}
@Override
public Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException {
// TODO Auto-generated method stub
return null;
}
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
Object[] args = new Object[getMethod().getParameterTypes().length];
if (myCountParamIndex != null) {
String[] countValues = theRequest.getParameters().remove(Constants.PARAM_COUNT);
if (countValues.length > 0 && StringUtils.isNotBlank(countValues[0])) {
try {
args[myCountParamIndex] = new IntegerDt(countValues[0]);
} catch (DataFormatException e) {
throw new InvalidRequestException("Invalid _count parameter value: " + countValues[0]);
}
}
}
if (mySinceParamIndex != null) {
String[] sinceValues = theRequest.getParameters().remove(Constants.PARAM_SINCE);
if (sinceValues.length > 0 && StringUtils.isNotBlank(sinceValues[0])) {
try {
args[mySinceParamIndex] = new InstantDt(sinceValues[0]);
} catch (DataFormatException e) {
throw new InvalidRequestException("Invalid _since parameter value: " + sinceValues[0]);
}
}
}
}
@Override
public boolean matches(Request theRequest) {
if (!theRequest.getOperation().equals(Constants.PARAM_HISTORY)) {
return false;
}
if (theRequest.getResourceName() == null) {
return mySystemOperationType == RestfulOperationSystemEnum.HISTORY_SYSTEM;
}
if (!theRequest.getResourceName().equals(myResourceName)) {
return false;
}
return false;
}
}

View File

@ -13,11 +13,13 @@ 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.Count;
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.Since;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.param.CollectionBinder;
import ca.uhn.fhir.rest.param.IParameter;
@ -30,6 +32,37 @@ import ca.uhn.fhir.util.ReflectionUtil;
* Created by dsotnikov on 2/25/2014.
*/
class Util {
public static Integer findCountParameterIndex(Method theMethod) {
return findParamIndex(theMethod, Count.class);
}
public static Integer findIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, IdParam.class);
}
private static Integer findParamIndex(Method theMethod, Class<?> toFind) {
int paramIndex = 0;
for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) {
Annotation nextAnnotation = annotations[annotationIndex];
Class<? extends Annotation> class1 = nextAnnotation.getClass();
if (toFind.isAssignableFrom(class1)) {
return paramIndex;
}
}
paramIndex++;
}
return null;
}
public static Integer findSinceParameterIndex(Method theMethod) {
return findParamIndex(theMethod, Since.class);
}
public static Integer findVersionIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, VersionIdParam.class);
}
public static Map<String, String> getQueryParams(String query) {
try {
@ -58,10 +91,10 @@ class Util {
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;
if (Collection.class.isAssignableFrom(parameterType)) {
innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(method, paramIndex);
@ -88,14 +121,17 @@ class Util {
param = parameter;
} else if (nextAnnotation instanceof IncludeParam) {
if (parameterType != PathSpecification.class || innerCollectionType == null || outerCollectionType != null) {
throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + IncludeParam.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() + "'");
Class<? extends Collection<PathSpecification>> instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(
innerCollectionType, "Method '" + method.getName() + "'");
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());
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 if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) {
@ -103,41 +139,20 @@ class Util {
} else {
continue;
}
haveHandledMethod= true;
haveHandledMethod = true;
parameters.add(param);
}
if (!haveHandledMethod) {
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");
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++;
}
return parameters;
}
public static Integer findIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, IdParam.class);
}
public static Integer findVersionIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, VersionIdParam.class);
}
private static Integer findParamIndex(Method theMethod, Class<?> toFind) {
int paramIndex = 0;
for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
for (int annotationIndex = 0; annotationIndex < annotations.length; annotationIndex++) {
Annotation nextAnnotation = annotations[annotationIndex];
Class<? extends Annotation> class1 = nextAnnotation.getClass();
if (toFind.isAssignableFrom(class1)) {
return paramIndex;
}
}
paramIndex++;
}
return null;
}
}

View File

@ -34,6 +34,8 @@ public class Constants {
public static final int STATUS_HTTP_412_PRECONDITION_FAILED = 412;
public static final String HEADER_CONTENT_LOCATION = "Content-Location";
public static final int STATUS_HTTP_204_NO_CONTENT = 204;
public static final String PARAM_COUNT = "_count";
public static final String PARAM_SINCE = "_since";
static {
Map<String, EncodingUtil> valToEncoding = new HashMap<String, EncodingUtil>();

View File

@ -39,6 +39,7 @@ 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.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceVersionConflictException;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
@ -46,6 +47,8 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
public abstract class RestfulPatientResourceProviderMore implements IResourceProvider {
private boolean detectedVersionConflict;
private boolean conflictHappened;
private boolean couldntFindThisId;
//START SNIPPET: searchAll
@Search
public List<Organization> getAllOrganizations() {
@ -63,6 +66,22 @@ public Patient getResourceById(@IdParam IdDt theId) {
}
//END SNIPPET: read
//START SNIPPET: delete
@Read()
public void deletePatient(@IdParam IdDt theId) {
// .. Delete the patient ..
if (couldntFindThisId) {
throw new ResourceNotFoundException("Unknown version");
}
if (conflictHappened) {
throw new ResourceVersionConflictException("Couldn't delete because [foo]");
}
// otherwise, delete was successful
return; // can also return MethodOutcome
}
//END SNIPPET: delete
//START SNIPPET: vread
@Read()
public Patient getResourceById(@IdParam IdDt theId,

View File

@ -81,6 +81,10 @@
background-color: #F8F8F8 !important;
}
.table th, .table td {
padding: 2px;
}
tt {
margin-left: 10px;
white-space: pre;

View File

@ -168,7 +168,7 @@
<p>
The
<a href="http://latest.fhir.me/http.html#read"><b>read</b></a>
<a href="http://hl7.org/implement/standards/fhir/http.html#read"><b>read</b></a>
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
@ -197,7 +197,7 @@
<p>
The
<a href="http://latest.fhir.me/http.html#vread"><b>vread</b></a>
<a href="http://hl7.org/implement/standards/fhir/http.html#vread"><b>vread</b></a>
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
@ -293,7 +293,40 @@
<a name="instance_delete"/>
<p>
Not yet implemented
The
<a href="http://hl7.org/implement/standards/fhir/http.html#delete"><b>delete</b></a>
operation retrieves a specific version of a resource with a given ID. It takes a single
ID parameter annotated with an
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
annotation, which supplies the ID of the resource to delete.
</p>
<macro name="snippet">
<param name="id" value="delete" />
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<p>
Delete methods are allowed to return the following types:
</p>
<ul>
<li>
<b>void</b>: This method may return <code>void</code>, in which case
the server will return an empty response and the client will ignore
any successful response from the server (failure responses will still throw
an exception)
</li>
<li>
<b><a href="./apidocs/ca/uhn/fhir/rest/api/MethodOutcome.html">MethodOutcome</a></b>:
This method may return <code>MethodOutcome</code>,
which is a wrapper for the FHIR OperationOutcome resource, which may optionally be returned
by the server according to the FHIR specification.
</li>
</ul>
<p>
Example URL to invoke this method (HTTP DELETE):<br/>
<code>http://fhir.example.com/Patient/111</code>
</p>
</section>
@ -372,7 +405,7 @@
<p>
The
<a href="http://latest.fhir.me/http.html#search"><b>search</b></a> operation returns a bundle
<a href="http://hl7.org/implement/standards/fhir/http.html#search"><b>search</b></a> operation returns a bundle
with zero-to-many resources of a given type, matching a given set of parameters.
</p>

View File

@ -104,7 +104,7 @@ public class ClientTest {
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().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"));
@ -166,7 +166,7 @@ public class ClientTest {
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().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"));
@ -181,6 +181,26 @@ public class ClientTest {
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 {
@ -190,7 +210,7 @@ public class ClientTest {
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().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"));
@ -206,6 +226,7 @@ public class ClientTest {
assertEquals("200", response.getVersionId().getValue());
}
@Test(expected=ResourceVersionConflictException.class)
public void testUpdateWithResourceConflict() throws Exception {