Adding history
This commit is contained in:
parent
acb5b02672
commit
84096da74d
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
}
|
|
@ -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 "";
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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>();
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -81,6 +81,10 @@
|
|||
background-color: #F8F8F8 !important;
|
||||
}
|
||||
|
||||
.table th, .table td {
|
||||
padding: 2px;
|
||||
}
|
||||
|
||||
tt {
|
||||
margin-left: 10px;
|
||||
white-space: pre;
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
||||
|
|
Loading…
Reference in New Issue