diff --git a/hapi-fhir-base/pom.xml b/hapi-fhir-base/pom.xml
index ef50c521aef..c1f81bbcf68 100644
--- a/hapi-fhir-base/pom.xml
+++ b/hapi-fhir-base/pom.xml
@@ -303,7 +303,7 @@
-
+
]]>
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterAnd.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterAnd.java
index 4dc2f5fee06..f314975d1d4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterAnd.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterAnd.java
@@ -8,17 +8,21 @@ public interface IQueryParameterAnd {
/**
*
- * @see See FHIR specification
+ *
+ * See FHIR specification
* 2.2.2 Search SearchParameter Types
* for information on the token format
+ *
*/
public void setValuesAsQueryTokens(List> theParameters) throws InvalidRequestException;
/**
*
- * @see See FHIR specification
+ *
+ * See FHIR specification
* 2.2.2 Search SearchParameter Types
* for information on the token format
+ *
*/
public List> getValuesAsQueryTokens();
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java
index bceb25cbef8..674c801a154 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterOr.java
@@ -7,10 +7,11 @@ public interface IQueryParameterOr {
/**
* Sets the value of this type using the token format. This
* format is used in HTTP queries as a parameter format.
- *
- * @see See FHIR specification
+ *
+ * See FHIR specification
* 2.2.2 Search SearchParameter Types
* for information on the token format
+ *
*/
public void setValuesAsQueryTokens(List theParameters);
@@ -18,9 +19,11 @@ public interface IQueryParameterOr {
* Returns the value of this type using the token format. This
* format is used in HTTP queries as a parameter format.
*
- * @see See FHIR specification
+ *
+ * See FHIR specification
* 2.2.2 Search SearchParameter Types
* for information on the token format
+ *
*/
public List getValuesAsQueryTokens();
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterType.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterType.java
index 1cf5406f790..eea6794422b 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterType.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/IQueryParameterType.java
@@ -6,9 +6,10 @@ public interface IQueryParameterType {
* Sets the value of this type using the token format. This
* format is used in HTTP queries as a parameter format.
*
- * @see See FHIR specification
+ * See FHIR specification
* 2.2.2 Search SearchParameter Types
* for information on the token format
+ *
*/
public void setValueAsQueryToken(String theParameter);
@@ -16,9 +17,10 @@ public interface IQueryParameterType {
* Returns the value of this type using the token format. This
* format is used in HTTP queries as a parameter format.
*
- * @see See FHIR specification
+ * See FHIR specification
* 2.2.2 Search SearchParameter Types
* for information on the token format
+ *
*/
public String getValueAsQueryToken();
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ISupportsUndeclaredExtensions.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ISupportsUndeclaredExtensions.java
index c334840ff83..42e477c87ad 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ISupportsUndeclaredExtensions.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/ISupportsUndeclaredExtensions.java
@@ -37,7 +37,7 @@ public interface ISupportsUndeclaredExtensions extends IElement {
*
*
*
- * @param The extension to add. Can not be null.
+ * @param theExtension The extension to add. Can not be null.
*/
void addUndeclaredExtension(ExtensionDt theExtension);
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java
index 09d2b61dcbe..ac77cbf93d9 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/InstantDt.java
@@ -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) {
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IntegerDt.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IntegerDt.java
index 409e0f4e336..b10d9f3f7ff 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IntegerDt.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/primitive/IntegerDt.java
@@ -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 {
private Integer myValue;
@@ -24,7 +24,17 @@ public class IntegerDt extends BasePrimitive {
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 {
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);
}
-
+
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Count.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Count.java
new file mode 100644
index 00000000000..b0e40ff5e13
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Count.java
@@ -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
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/History.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/History.java
new file mode 100644
index 00000000000..b839648224d
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/History.java
@@ -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
+ * history method.
+ *
+ *
+ * History returns a feed containing all versions (or a selected range of versions) of
+ * a resource or a specific set of resources.
+ *
+ *
+ * The history command supports three usage patterns, as described in the
+ * FHIR history documentation:
+ *
+ * -
+ * 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.
+ *
-
+ * To invoke this pattern:
GET [base]/_history{?[parameters]&_format=[mime-type]}
+ *
+ *
+ * -
+ * 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.
+ *
-
+ * To invoke this pattern:
GET [base]/[type]/_history{?[parameters]&_format=[mime-type]}
+ *
+ *
+ * -
+ * 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.
+ *
-
+ * To invoke this pattern:
GET [base]/[type]/[id]/_history{?[parameters]&_format=[mime-type]}
+ *
+ *
+ *
+ *
+ *
+ * @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
+ }
+
+}
\ No newline at end of file
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Search.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Search.java
index e68ff8e4dec..238361d1542 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Search.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Search.java
@@ -21,8 +21,10 @@ public @interface Search {
/**
* If specified, this the name for the Named Query
*
- * @see See the FHIR specification section on
+ *
+ * See the FHIR specification section on
* named queries
+ *
*/
String queryName() default "";
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Since.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Since.java
new file mode 100644
index 00000000000..ca0c164883b
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Since.java
@@ -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
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java
index 5186b6a27f3..0ba73755edc 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java
@@ -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;
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java
new file mode 100644
index 00000000000..e4dce42aeed
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java
@@ -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> 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;
+ }
+
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/Util.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/Util.java
index d4aba4dfca2..b618873ac13 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/Util.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/Util.java
@@ -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 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> instantiableCollectionType = (Class extends Collection>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + method.getName() + "'");
-
+ Class extends Collection> instantiableCollectionType = (Class extends Collection>) 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;
- }
}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java
index 864dbc5c749..4079daad3e4 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java
@@ -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 valToEncoding = new HashMap();
diff --git a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java
index 2336de75ca7..2fbab06a08c 100644
--- a/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java
+++ b/hapi-fhir-base/src/site/example/java/example/RestfulPatientResourceProviderMore.java
@@ -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 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,
diff --git a/hapi-fhir-base/src/site/resources/hapi.css b/hapi-fhir-base/src/site/resources/hapi.css
index 42056b7fa04..2e58f368b31 100644
--- a/hapi-fhir-base/src/site/resources/hapi.css
+++ b/hapi-fhir-base/src/site/resources/hapi.css
@@ -81,6 +81,10 @@
background-color: #F8F8F8 !important;
}
+.table th, .table td {
+ padding: 2px;
+}
+
tt {
margin-left: 10px;
white-space: pre;
diff --git a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml
index 154994670ae..d46b5f48a97 100644
--- a/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml
+++ b/hapi-fhir-base/src/site/xdoc/doc_rest_operations.xml
@@ -168,7 +168,7 @@
The
- read
+ read
operation retrieves a resource by ID. It is annotated with the
@Read
annotation, and has a single parameter annotated with the
@@ -197,7 +197,7 @@
The
- vread
+ vread
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 @@
- Not yet implemented
+ The
+ delete
+ operation retrieves a specific version of a resource with a given ID. It takes a single
+ ID parameter annotated with an
+ @IdParam
+ annotation, which supplies the ID of the resource to delete.
+
+
+
+
+
+
+
+
+ Delete methods are allowed to return the following types:
+
+
+ -
+ void: This method may return
void
, 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)
+
+ -
+ MethodOutcome:
+ This method may return
MethodOutcome
,
+ which is a wrapper for the FHIR OperationOutcome resource, which may optionally be returned
+ by the server according to the FHIR specification.
+
+
+
+
+ Example URL to invoke this method (HTTP DELETE):
+ http://fhir.example.com/Patient/111
@@ -372,7 +405,7 @@
The
- search operation returns a bundle
+ search operation returns a bundle
with zero-to-many resources of a given type, matching a given set of parameters.
diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java
index f0c743d73cc..e2745aa9633 100644
--- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java
+++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ClientTest.java
@@ -104,7 +104,7 @@ public class ClientTest {
ArgumentCaptor 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 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 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 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 {