Lots of documentation (site and javadoc) updates, and make since and count params work for history operation
This commit is contained in:
parent
6877c0628e
commit
e69464f34d
|
@ -248,11 +248,13 @@
|
|||
<artifactId>maven-jxr-plugin</artifactId>
|
||||
<version>2.4</version>
|
||||
</plugin>
|
||||
<!--
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>findbugs-maven-plugin</artifactId>
|
||||
<version>2.5.3</version>
|
||||
</plugin>
|
||||
-->
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
||||
|
|
|
@ -160,7 +160,7 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
|
|||
}
|
||||
}
|
||||
|
||||
private final class ListAccessor implements IAccessor {
|
||||
private final static class ListAccessor implements IAccessor {
|
||||
private final Method myAccessorMethod;
|
||||
|
||||
private ListAccessor(Method theAccessor) {
|
||||
|
|
|
@ -27,6 +27,8 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import ca.uhn.fhir.model.api.IElement;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
|
@ -117,6 +119,8 @@ public class FhirContext {
|
|||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
|
||||
Validate.notBlank(theResourceName, "Resource name must not be blank");
|
||||
|
||||
RuntimeResourceDefinition retVal = myNameToElementDefinition.get(theResourceName);
|
||||
|
||||
if (retVal == null) {
|
||||
|
|
|
@ -41,12 +41,15 @@ public abstract class BasePrimitive<T> extends BaseElement implements IPrimitive
|
|||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (!(obj.getClass() == getClass())) {
|
||||
public boolean equals(Object theObj) {
|
||||
if (theObj == null) {
|
||||
return false;
|
||||
}
|
||||
if (!(theObj.getClass() == getClass())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
BasePrimitive<?> o = (BasePrimitive<?>)obj;
|
||||
BasePrimitive<?> o = (BasePrimitive<?>) theObj;
|
||||
|
||||
EqualsBuilder b = new EqualsBuilder();
|
||||
b.append(getValue(), o.getValue());
|
||||
|
|
|
@ -26,8 +26,6 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
|
||||
import ca.uhn.fhir.model.api.IElement;
|
||||
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(value= {ElementType.FIELD})
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.model.primitive;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.TimeZone;
|
||||
|
||||
|
@ -67,6 +68,15 @@ public class InstantDt extends BaseDateTimeDt {
|
|||
setTimeZone(TimeZone.getDefault());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new DateTimeDt
|
||||
*/
|
||||
public InstantDt(Calendar theCalendar) {
|
||||
setValue(theCalendar.getTime());
|
||||
setPrecision(DEFAULT_PRECISION);
|
||||
setTimeZone(theCalendar.getTimeZone());
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new InstantDt from a string value
|
||||
*
|
||||
|
|
|
@ -26,16 +26,26 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
public @interface Read {
|
||||
|
||||
/**
|
||||
* The return type for this search method. This generally does not need
|
||||
* to be populated for a server implementation, since servers will return
|
||||
* only one resource per class, but generally does need to be populated
|
||||
* for client implementations.
|
||||
* The return type for this method. This generally does not need
|
||||
* to be populated for {@link IResourceProvider resource providers} in a server implementation,
|
||||
* but often does need to be populated in client implementations using {@link IBasicClient} or
|
||||
* {@link IRestfulClient}, or in plain providers on a server.
|
||||
* <p>
|
||||
* This value also does not need to be populated if the return type for a method annotated with
|
||||
* this annotation is sufficient to determine the type of resource provided. E.g. if the
|
||||
* method returns <code>Patient</code> or <code>List<Patient></code>, the server/client
|
||||
* will automatically determine that the Patient resource is the return type, and this value
|
||||
* may be left blank.
|
||||
* </p>
|
||||
*/
|
||||
// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
|
||||
Class<? extends IResource> type() default IResource.class;
|
||||
|
|
|
@ -26,6 +26,9 @@ import java.lang.annotation.RetentionPolicy;
|
|||
import java.lang.annotation.Target;
|
||||
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -49,12 +52,19 @@ public @interface Search {
|
|||
String queryName() default "";
|
||||
|
||||
/**
|
||||
* The return type for this search method. This generally does not need
|
||||
* to be populated for a server implementation, since servers will return
|
||||
* only one resource per class, but generally does need to be populated
|
||||
* for client implementations.
|
||||
* The return type for this method. This generally does not need
|
||||
* to be populated for {@link IResourceProvider resource providers} in a server implementation,
|
||||
* but often does need to be populated in client implementations using {@link IBasicClient} or
|
||||
* {@link IRestfulClient}, or in plain providers on a server.
|
||||
* <p>
|
||||
* This value also does not need to be populated if the return type for a method annotated with
|
||||
* this annotation is sufficient to determine the type of resource provided. E.g. if the
|
||||
* method returns <code>Patient</code> or <code>List<Patient></code>, the server/client
|
||||
* will automatically determine that the Patient resource is the return type, and this value
|
||||
* may be left blank.
|
||||
* </p>
|
||||
*/
|
||||
// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
|
||||
// NB: Read, Search (maybe others) share this annotation method, so update the javadocs everywhere
|
||||
Class<? extends IResource> type() default IResource.class;
|
||||
|
||||
}
|
||||
|
|
|
@ -55,6 +55,8 @@ import ca.uhn.fhir.rest.annotation.Validate;
|
|||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.BaseClientInvocation;
|
||||
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
|
||||
import ca.uhn.fhir.rest.param.IParameter;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.EncodingUtil;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
|
@ -65,8 +67,9 @@ import ca.uhn.fhir.util.ReflectionUtil;
|
|||
|
||||
public abstract class BaseMethodBinding {
|
||||
|
||||
private Method myMethod;
|
||||
private FhirContext myContext;
|
||||
private Method myMethod;
|
||||
private List<IParameter> myParameters;
|
||||
private Object myProvider;
|
||||
|
||||
public BaseMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
|
||||
|
@ -76,14 +79,55 @@ public abstract class BaseMethodBinding {
|
|||
myMethod = theMethod;
|
||||
myContext = theConetxt;
|
||||
myProvider = theProvider;
|
||||
myParameters = ParameterUtil.getResourceParameters(theMethod);
|
||||
}
|
||||
|
||||
public FhirContext getContext() {
|
||||
return myContext;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return myMethod;
|
||||
}
|
||||
|
||||
public Object getProvider() {
|
||||
return myProvider;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the name of the resource this method handles, or <code>null</code> if this
|
||||
* method is not resource specific
|
||||
* Returns the name of the resource this method handles, or
|
||||
* <code>null</code> if this method is not resource specific
|
||||
*/
|
||||
public abstract String getResourceName();
|
||||
|
||||
public abstract RestfulOperationTypeEnum getResourceOperationType();
|
||||
|
||||
public abstract RestfulOperationSystemEnum getSystemOperationType();
|
||||
|
||||
public abstract BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
|
||||
|
||||
public abstract Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException;
|
||||
|
||||
public abstract void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException;
|
||||
|
||||
public abstract boolean matches(Request theRequest);
|
||||
|
||||
protected IParser createAppropriateParser(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException {
|
||||
IParser parser;
|
||||
if (Constants.CT_ATOM_XML.equals(theResponseMimeType)) {
|
||||
parser = getContext().newXmlParser();
|
||||
} else if (Constants.CT_FHIR_XML.equals(theResponseMimeType)) {
|
||||
parser = getContext().newXmlParser();
|
||||
} else {
|
||||
throw new NonFhirResponseException("Response contains non-FHIR content-type: " + theResponseMimeType, theResponseMimeType, theResponseStatusCode, IOUtils.toString(theResponseReader));
|
||||
}
|
||||
return parser;
|
||||
}
|
||||
|
||||
public List<IParameter> getParameters() {
|
||||
return myParameters;
|
||||
}
|
||||
|
||||
protected Object invokeServerMethod(Object theResourceProvider, Object[] theMethodParams) {
|
||||
try {
|
||||
return getMethod().invoke(theResourceProvider, theMethodParams);
|
||||
|
@ -98,37 +142,9 @@ public abstract class BaseMethodBinding {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
public Object getProvider() {
|
||||
return myProvider;
|
||||
}
|
||||
|
||||
public FhirContext getContext() {
|
||||
return myContext;
|
||||
}
|
||||
|
||||
public Method getMethod() {
|
||||
return myMethod;
|
||||
}
|
||||
|
||||
public abstract BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
|
||||
|
||||
public abstract RestfulOperationTypeEnum getResourceOperationType();
|
||||
|
||||
public abstract RestfulOperationSystemEnum getSystemOperationType();
|
||||
|
||||
public abstract boolean matches(Request theRequest);
|
||||
|
||||
protected IParser createAppropriateParser(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException {
|
||||
IParser parser;
|
||||
if (Constants.CT_ATOM_XML.equals(theResponseMimeType)) {
|
||||
parser = getContext().newXmlParser();
|
||||
} else if (Constants.CT_FHIR_XML.equals(theResponseMimeType)) {
|
||||
parser = getContext().newXmlParser();
|
||||
} else {
|
||||
throw new NonFhirResponseException("Response contains non-FHIR content-type: " + theResponseMimeType, theResponseMimeType, theResponseStatusCode, IOUtils.toString(theResponseReader));
|
||||
}
|
||||
return parser;
|
||||
/** For unit tests only */
|
||||
public void setParameters(List<IParameter> theParameters) {
|
||||
myParameters = theParameters;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
|
@ -152,8 +168,7 @@ public abstract class BaseMethodBinding {
|
|||
if (theProvider instanceof IResourceProvider) {
|
||||
returnTypeFromRp = ((IResourceProvider) theProvider).getResourceType();
|
||||
if (!verifyIsValidResourceReturnType(returnTypeFromRp)) {
|
||||
throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned "
|
||||
+ toLogString(returnTypeFromRp) + " - Must return a resource type");
|
||||
throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned " + toLogString(returnTypeFromRp) + " - Must return a resource type");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -166,14 +181,14 @@ public abstract class BaseMethodBinding {
|
|||
// returns a bundle
|
||||
} else if (Collection.class.isAssignableFrom(returnTypeFromMethod)) {
|
||||
returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
|
||||
if (!verifyIsValidResourceReturnType(returnTypeFromMethod)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ " returns a collection with generic type " + toLogString(returnTypeFromMethod) + " - Must return a resource type or a collection (List, Set) of a resource type");
|
||||
if (!verifyIsValidResourceReturnType(returnTypeFromMethod) && !IResource.class.equals(returnTypeFromMethod)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns a collection with generic type " + toLogString(returnTypeFromMethod)
|
||||
+ " - Must return a resource type or a collection (List, Set) of a resource type");
|
||||
}
|
||||
} else {
|
||||
if (!verifyIsValidResourceReturnType(returnTypeFromMethod)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ " returns " + toLogString(returnTypeFromMethod) + " - Must return a resource type");
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromMethod)
|
||||
+ " - Must return a resource type");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -197,13 +212,12 @@ public abstract class BaseMethodBinding {
|
|||
if (returnTypeFromRp != null) {
|
||||
if (returnTypeFromAnnotation != null && returnTypeFromAnnotation != IResource.class) {
|
||||
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type "
|
||||
+ returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type " + returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName()
|
||||
+ " (or a subclass of it) per IResourceProvider contract");
|
||||
}
|
||||
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type "
|
||||
+ returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return " + returnTypeFromRp.getCanonicalName()
|
||||
+ " (or a subclass of it) per IResourceProvider contract");
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type " + returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return "
|
||||
+ returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
|
||||
}
|
||||
returnType = returnTypeFromAnnotation;
|
||||
} else {
|
||||
|
@ -212,8 +226,8 @@ public abstract class BaseMethodBinding {
|
|||
} else {
|
||||
if (returnTypeFromAnnotation != IResource.class) {
|
||||
if (!verifyIsValidResourceReturnType(returnTypeFromAnnotation)) {
|
||||
throw new ConfigurationException("Method '"+theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns "
|
||||
+ toLogString(returnTypeFromAnnotation) + " according to annotation - Must return a resource type");
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromAnnotation)
|
||||
+ " according to annotation - Must return a resource type");
|
||||
}
|
||||
returnType = returnTypeFromAnnotation;
|
||||
} else {
|
||||
|
@ -264,63 +278,6 @@ public abstract class BaseMethodBinding {
|
|||
// return sm;
|
||||
}
|
||||
|
||||
private static boolean verifyIsValidResourceReturnType(Class<?> theReturnType) {
|
||||
if (theReturnType == null) {
|
||||
return false;
|
||||
}
|
||||
if (!IResource.class.isAssignableFrom(theReturnType)) {
|
||||
return false;
|
||||
}
|
||||
boolean retVal = Modifier.isAbstract(theReturnType.getModifiers()) == false;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
private static String toLogString(Class<?> theType) {
|
||||
if (theType == null) {
|
||||
return null;
|
||||
}
|
||||
return theType.getCanonicalName();
|
||||
}
|
||||
|
||||
public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) {
|
||||
Object obj1 = null;
|
||||
for (Object object : theAnnotations) {
|
||||
if (object != null) {
|
||||
if (obj1 == null) {
|
||||
obj1 = object;
|
||||
} else {
|
||||
throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @"
|
||||
+ obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName() + ". Can not have both.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (obj1 == null) {
|
||||
return false;
|
||||
// throw new ConfigurationException("Method '" +
|
||||
// theNextMethod.getName() + "' on type '" +
|
||||
// theNextMethod.getDeclaringClass().getSimpleName() +
|
||||
// " has no FHIR method annotations.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
protected static List<IResource> toResourceList(Object response) throws InternalErrorException {
|
||||
if (response == null) {
|
||||
return Collections.emptyList();
|
||||
} else if (response instanceof IResource) {
|
||||
return Collections.singletonList((IResource) response);
|
||||
} else if (response instanceof Collection) {
|
||||
List<IResource> retVal = new ArrayList<IResource>();
|
||||
for (Object next : ((Collection<?>) response)) {
|
||||
retVal.add((IResource) next);
|
||||
}
|
||||
return retVal;
|
||||
} else {
|
||||
throw new InternalErrorException("Unexpected return type: " + response.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
public static EncodingUtil determineResponseEncoding(HttpServletRequest theRequest, Map<String, String[]> theParams) {
|
||||
String[] format = theParams.remove(Constants.PARAM_FORMAT);
|
||||
if (format != null) {
|
||||
|
@ -344,9 +301,61 @@ public abstract class BaseMethodBinding {
|
|||
return EncodingUtil.XML;
|
||||
}
|
||||
|
||||
public abstract void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException;
|
||||
|
||||
public abstract Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
|
||||
BaseServerResponseException;
|
||||
public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) {
|
||||
Object obj1 = null;
|
||||
for (Object object : theAnnotations) {
|
||||
if (object != null) {
|
||||
if (obj1 == null) {
|
||||
obj1 = object;
|
||||
} else {
|
||||
throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @" + obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName()
|
||||
+ ". Can not have both.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
if (obj1 == null) {
|
||||
return false;
|
||||
// throw new ConfigurationException("Method '" +
|
||||
// theNextMethod.getName() + "' on type '" +
|
||||
// theNextMethod.getDeclaringClass().getSimpleName() +
|
||||
// " has no FHIR method annotations.");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String toLogString(Class<?> theType) {
|
||||
if (theType == null) {
|
||||
return null;
|
||||
}
|
||||
return theType.getCanonicalName();
|
||||
}
|
||||
|
||||
private static boolean verifyIsValidResourceReturnType(Class<?> theReturnType) {
|
||||
if (theReturnType == null) {
|
||||
return false;
|
||||
}
|
||||
if (!IResource.class.isAssignableFrom(theReturnType)) {
|
||||
return false;
|
||||
}
|
||||
boolean retVal = Modifier.isAbstract(theReturnType.getModifiers()) == false;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected static List<IResource> toResourceList(Object response) throws InternalErrorException {
|
||||
if (response == null) {
|
||||
return Collections.emptyList();
|
||||
} else if (response instanceof IResource) {
|
||||
return Collections.singletonList((IResource) response);
|
||||
} else if (response instanceof Collection) {
|
||||
List<IResource> retVal = new ArrayList<IResource>();
|
||||
for (Object next : ((Collection<?>) response)) {
|
||||
retVal.add((IResource) next);
|
||||
}
|
||||
return retVal;
|
||||
} else {
|
||||
throw new InternalErrorException("Unexpected return type: " + response.getClass().getCanonicalName());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -44,7 +44,6 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
|
|||
import ca.uhn.fhir.rest.client.BaseClientInvocation;
|
||||
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
|
||||
import ca.uhn.fhir.rest.param.IParameter;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.EncodingUtil;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
|
@ -59,12 +58,10 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
|||
|
||||
abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
|
||||
private List<IParameter> myParameters;
|
||||
private boolean myReturnVoid;
|
||||
|
||||
public BaseOutcomeReturningMethodBinding(Method theMethod, FhirContext theContext, Class<?> theMethodAnnotation, Object theProvider) {
|
||||
super(theMethod, theContext, theProvider);
|
||||
myParameters = ParameterUtil.getResourceParameters(theMethod);
|
||||
|
||||
if (!theMethod.getReturnType().equals(MethodOutcome.class)) {
|
||||
if (!allowVoidReturnType()) {
|
||||
|
@ -143,9 +140,9 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
|||
|
||||
@Override
|
||||
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
|
||||
Object[] params = new Object[myParameters.size()];
|
||||
for (int i = 0; i < myParameters.size(); i++) {
|
||||
IParameter param = myParameters.get(i);
|
||||
Object[] params = new Object[getParameters().size()];
|
||||
for (int i = 0; i < getParameters().size(); i++) {
|
||||
IParameter param = getParameters().get(i);
|
||||
if (param != null) {
|
||||
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null);
|
||||
}
|
||||
|
@ -182,7 +179,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
|||
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
|
||||
}
|
||||
|
||||
theServer.addHapiHeader(theResponse);
|
||||
theServer.addHeadersToResponse(theResponse);
|
||||
|
||||
Writer writer = theResponse.getWriter();
|
||||
try {
|
||||
|
@ -243,10 +240,6 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
|||
|
||||
protected abstract BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName);
|
||||
|
||||
protected List<IParameter> getParameters() {
|
||||
return myParameters;
|
||||
}
|
||||
|
||||
protected void parseContentLocation(MethodOutcome theOutcomeToPopulate, String theLocationHeader) {
|
||||
String resourceNamePart = "/" + getResourceName() + "/";
|
||||
int resourceIndex = theLocationHeader.lastIndexOf(resourceNamePart);
|
||||
|
|
|
@ -125,7 +125,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
|
|||
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
|
||||
}
|
||||
|
||||
theServer.addHapiHeader(theResponse);
|
||||
theServer.addHeadersToResponse(theResponse);
|
||||
|
||||
if (response != null && response.getOperationOutcome() != null) {
|
||||
theResponse.setContentType(encoding.getResourceContentType());
|
||||
|
@ -147,7 +147,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
|
|||
private void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingUtil theEncoding, HttpServletResponse theResponse) throws IOException {
|
||||
theResponse.setStatus(theE.getStatusCode());
|
||||
|
||||
theServer.addHapiHeader(theResponse);
|
||||
theServer.addHeadersToResponse(theResponse);
|
||||
|
||||
if (theE.getOperationOutcome() != null) {
|
||||
theResponse.setContentType(theEncoding.getResourceContentType());
|
||||
|
|
|
@ -77,13 +77,11 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
|||
|
||||
private MethodReturnTypeEnum myMethodReturnType;
|
||||
private String myResourceName;
|
||||
private List<IParameter> myParameters;
|
||||
|
||||
|
||||
public BaseResourceReturningMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, FhirContext theConetxt, Object theProvider) {
|
||||
super(theMethod, theConetxt, theProvider);
|
||||
|
||||
myParameters = ParameterUtil.getResourceParameters(theMethod);
|
||||
|
||||
Class<?> methodReturnType = theMethod.getReturnType();
|
||||
if (Collection.class.isAssignableFrom(methodReturnType)) {
|
||||
myMethodReturnType = MethodReturnTypeEnum.LIST_OF_RESOURCES;
|
||||
|
@ -92,8 +90,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
|||
} else if (Bundle.class.isAssignableFrom(methodReturnType)) {
|
||||
myMethodReturnType = MethodReturnTypeEnum.BUNDLE;
|
||||
} else {
|
||||
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: "
|
||||
+ theMethod.getDeclaringClass().getCanonicalName());
|
||||
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
|
||||
}
|
||||
|
||||
if (theReturnResourceType != null) {
|
||||
|
@ -200,9 +197,9 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
|||
}
|
||||
|
||||
// Method params
|
||||
Object[] params = new Object[myParameters.size()];
|
||||
for (int i = 0; i < myParameters.size(); i++) {
|
||||
IParameter param = myParameters.get(i);
|
||||
Object[] params = new Object[getParameters().size()];
|
||||
for (int i = 0; i < getParameters().size(); i++) {
|
||||
IParameter param = getParameters().get(i);
|
||||
if (param != null) {
|
||||
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null);
|
||||
}
|
||||
|
@ -241,8 +238,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
|||
return (IdDt) retValObj;
|
||||
}
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
|
||||
+ IdDt.class.getCanonicalName());
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
private InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
|
||||
|
@ -258,8 +254,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
|||
return (InstantDt) retValObj;
|
||||
}
|
||||
}
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
|
||||
+ InstantDt.class.getCanonicalName());
|
||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + InstantDt.class.getCanonicalName());
|
||||
}
|
||||
|
||||
private IParser getNewParser(EncodingUtil theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) {
|
||||
|
@ -276,8 +271,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
|||
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS);
|
||||
}
|
||||
|
||||
private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingUtil theResponseEncoding, String theServerBase,
|
||||
String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
|
||||
private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingUtil theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser,
|
||||
NarrativeModeEnum theNarrativeMode) throws IOException {
|
||||
assert !theServerBase.endsWith("/");
|
||||
|
||||
theHttpResponse.setStatus(200);
|
||||
|
@ -292,7 +287,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
|||
|
||||
theHttpResponse.setCharacterEncoding("UTF-8");
|
||||
|
||||
theServer.addHapiHeader(theHttpResponse);
|
||||
theServer.addHeadersToResponse(theHttpResponse);
|
||||
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.getAuthorName().setValue(getClass().getCanonicalName());
|
||||
|
@ -322,10 +317,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
|||
b.append(resId);
|
||||
|
||||
/*
|
||||
* If this is a history operation, we add the version of the resource to the self link to indicate the version
|
||||
* If this is a history operation, we add the version of the
|
||||
* resource to the self link to indicate the version
|
||||
*/
|
||||
if (getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_INSTANCE || getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_TYPE
|
||||
|| getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) {
|
||||
if (getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_INSTANCE || getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_TYPE || getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) {
|
||||
IdDt versionId = getIdFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.VERSION_ID);
|
||||
if (versionId != null) {
|
||||
b.append('/');
|
||||
|
@ -387,8 +382,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
|||
}
|
||||
}
|
||||
|
||||
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint,
|
||||
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
|
||||
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
|
||||
|
||||
theHttpResponse.setStatus(200);
|
||||
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
|
||||
|
|
|
@ -20,26 +20,23 @@ package ca.uhn.fhir.rest.method;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
import static org.apache.commons.lang3.StringUtils.*;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
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.IdDt;
|
||||
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.client.GetClientInvocation;
|
||||
import ca.uhn.fhir.rest.param.IParameter;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
|
@ -48,18 +45,14 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
|||
public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
||||
|
||||
private final Integer myIdParamIndex;
|
||||
private String myResourceName;
|
||||
private final RestfulOperationTypeEnum myResourceOperationType;
|
||||
private final RestfulOperationSystemEnum mySystemOperationType;
|
||||
private String myResourceName;
|
||||
private Integer mySinceParamIndex;
|
||||
private Integer myCountParamIndex;
|
||||
|
||||
public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
|
||||
super(toReturnType(theMethod, theProvider), theMethod, theConetxt, theProvider);
|
||||
|
||||
myIdParamIndex = Util.findIdParameterIndex(theMethod);
|
||||
mySinceParamIndex = Util.findSinceParameterIndex(theMethod);
|
||||
myCountParamIndex = Util.findCountParameterIndex(theMethod);
|
||||
|
||||
History historyAnnotation = theMethod.getAnnotation(History.class);
|
||||
Class<? extends IResource> type = historyAnnotation.type();
|
||||
|
@ -93,23 +86,16 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
|
||||
}
|
||||
|
||||
private static Class<? extends IResource> toReturnType(Method theMethod, Object theProvider) {
|
||||
if (theProvider instanceof IResourceProvider) {
|
||||
return ((IResourceProvider) theProvider).getResourceType();
|
||||
}
|
||||
History historyAnnotation = theMethod.getAnnotation(History.class);
|
||||
Class<? extends IResource> type = historyAnnotation.type();
|
||||
if (type != IResource.class) {
|
||||
return type;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestfulOperationTypeEnum getResourceOperationType() {
|
||||
return myResourceOperationType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnTypeEnum getReturnType() {
|
||||
return ReturnTypeEnum.BUNDLE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestfulOperationSystemEnum getSystemOperationType() {
|
||||
return mySystemOperationType;
|
||||
|
@ -134,11 +120,30 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
b.append(Constants.PARAM_HISTORY);
|
||||
|
||||
return new GetClientInvocation(b.toString());
|
||||
LinkedHashMap<String, List<String>> queryStringArgs=new LinkedHashMap<String, List<String>>();
|
||||
if (theArgs != null) {
|
||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||
IParameter nextParam = getParameters().get(idx);
|
||||
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], queryStringArgs);
|
||||
}
|
||||
}
|
||||
|
||||
return new GetClientInvocation(queryStringArgs, b.toString());
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // ObjectUtils.equals is replaced by a JDK7 method..
|
||||
@Override
|
||||
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
|
||||
if (myIdParamIndex != null) {
|
||||
theMethodParams[myIdParamIndex] = theRequest.getId();
|
||||
}
|
||||
|
||||
Object response = invokeServerMethod(theResourceProvider, theMethodParams);
|
||||
|
||||
return toResourceList(response);
|
||||
}
|
||||
|
||||
// ObjectUtils.equals is replaced by a JDK7 method..
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public boolean matches(Request theRequest) {
|
||||
if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) {
|
||||
|
@ -164,41 +169,16 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ReturnTypeEnum getReturnType() {
|
||||
return ReturnTypeEnum.BUNDLE;
|
||||
private static Class<? extends IResource> toReturnType(Method theMethod, Object theProvider) {
|
||||
if (theProvider instanceof IResourceProvider) {
|
||||
return ((IResourceProvider) theProvider).getResourceType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
|
||||
if (myCountParamIndex != null) {
|
||||
String[] countValues = theRequest.getParameters().remove(Constants.PARAM_COUNT);
|
||||
if (countValues.length > 0 && StringUtils.isNotBlank(countValues[0])) {
|
||||
try {
|
||||
theMethodParams[myCountParamIndex] = new IntegerDt(countValues[0]);
|
||||
} catch (DataFormatException e) {
|
||||
throw new InvalidRequestException("Invalid _count parameter value: " + countValues[0]);
|
||||
History historyAnnotation = theMethod.getAnnotation(History.class);
|
||||
Class<? extends IResource> type = historyAnnotation.type();
|
||||
if (type != IResource.class) {
|
||||
return type;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (mySinceParamIndex != null) {
|
||||
String[] sinceValues = theRequest.getParameters().remove(Constants.PARAM_SINCE);
|
||||
if (sinceValues.length > 0 && StringUtils.isNotBlank(sinceValues[0])) {
|
||||
try {
|
||||
theMethodParams[mySinceParamIndex] = new InstantDt(sinceValues[0]);
|
||||
} catch (DataFormatException e) {
|
||||
throw new InvalidRequestException("Invalid _since parameter value: " + sinceValues[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (myIdParamIndex!=null) {
|
||||
theMethodParams[myIdParamIndex] = theRequest.getId();
|
||||
}
|
||||
|
||||
Object response = invokeServerMethod(theResourceProvider, theMethodParams);
|
||||
|
||||
return toResourceList(response);
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -37,7 +37,6 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
|||
import ca.uhn.fhir.rest.client.GetClientInvocation;
|
||||
import ca.uhn.fhir.rest.param.BaseQueryParameter;
|
||||
import ca.uhn.fhir.rest.param.IParameter;
|
||||
import ca.uhn.fhir.rest.param.ParameterUtil;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
@ -49,12 +48,10 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
|
||||
|
||||
private Class<?> myDeclaredResourceType;
|
||||
private List<IParameter> myParameters;
|
||||
private String myQueryName;
|
||||
|
||||
public SearchMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, String theQueryName, FhirContext theContext, Object theProvider) {
|
||||
super(theReturnResourceType, theMethod, theContext, theProvider);
|
||||
this.myParameters = ParameterUtil.getResourceParameters(theMethod);
|
||||
this.myQueryName = StringUtils.defaultIfBlank(theQueryName, null);
|
||||
this.myDeclaredResourceType = theMethod.getReturnType();
|
||||
}
|
||||
|
@ -63,10 +60,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return myDeclaredResourceType.getClass();
|
||||
}
|
||||
|
||||
public List<IParameter> getParameters() {
|
||||
return myParameters;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RestfulOperationTypeEnum getResourceOperationType() {
|
||||
return RestfulOperationTypeEnum.SEARCH_TYPE;
|
||||
|
@ -84,7 +77,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
|
||||
@Override
|
||||
public GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
|
||||
assert (myQueryName == null || ((theArgs != null ? theArgs.length : 0) == myParameters.size())) : "Wrong number of arguments: " + (theArgs!=null?theArgs.length:"null");
|
||||
assert (myQueryName == null || ((theArgs != null ? theArgs.length : 0) == getParameters().size())) : "Wrong number of arguments: " + (theArgs != null ? theArgs.length : "null");
|
||||
|
||||
Map<String, List<String>> queryStringArgs = new LinkedHashMap<String, List<String>>();
|
||||
|
||||
|
@ -94,7 +87,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
|
||||
if (theArgs != null) {
|
||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||
IParameter nextParam = myParameters.get(idx);
|
||||
IParameter nextParam = getParameters().get(idx);
|
||||
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], queryStringArgs);
|
||||
}
|
||||
}
|
||||
|
@ -103,8 +96,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
@Override
|
||||
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException,
|
||||
InternalErrorException {
|
||||
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
|
||||
assert theRequest.getId() == null;
|
||||
assert theRequest.getVersion() == null;
|
||||
|
||||
|
@ -138,11 +130,11 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
}
|
||||
|
||||
Set<String> methodParamsTemp = new HashSet<String>();
|
||||
for (int i = 0; i < this.myParameters.size(); i++) {
|
||||
if (!(myParameters.get(i) instanceof BaseQueryParameter)) {
|
||||
for (int i = 0; i < this.getParameters().size(); i++) {
|
||||
if (!(getParameters().get(i) instanceof BaseQueryParameter)) {
|
||||
continue;
|
||||
}
|
||||
BaseQueryParameter temp = (BaseQueryParameter) myParameters.get(i);
|
||||
BaseQueryParameter temp = (BaseQueryParameter) getParameters().get(i);
|
||||
methodParamsTemp.add(temp.getName());
|
||||
if (temp.isRequired() && !theRequest.getParameters().containsKey(temp.getName())) {
|
||||
ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), temp.getName());
|
||||
|
@ -176,10 +168,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
|||
return retVal;
|
||||
}
|
||||
|
||||
public void setParameters(List<IParameter> parameters) {
|
||||
this.myParameters = parameters;
|
||||
}
|
||||
|
||||
public void setResourceType(Class<?> resourceType) {
|
||||
this.myDeclaredResourceType = resourceType;
|
||||
}
|
||||
|
|
|
@ -27,18 +27,16 @@ import java.net.URLDecoder;
|
|||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.rest.annotation.Count;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
*/
|
||||
class Util {
|
||||
public static Integer findCountParameterIndex(Method theMethod) {
|
||||
return findParamIndex(theMethod, Count.class);
|
||||
}
|
||||
// public static Integer findCountParameterIndex(Method theMethod) {
|
||||
// return findParamIndex(theMethod, Count.class);
|
||||
// }
|
||||
|
||||
public static Integer findIdParameterIndex(Method theMethod) {
|
||||
return findParamIndex(theMethod, IdParam.class);
|
||||
|
@ -59,9 +57,9 @@ class Util {
|
|||
return null;
|
||||
}
|
||||
|
||||
public static Integer findSinceParameterIndex(Method theMethod) {
|
||||
return findParamIndex(theMethod, Since.class);
|
||||
}
|
||||
// public static Integer findSinceParameterIndex(Method theMethod) {
|
||||
// return findParamIndex(theMethod, Since.class);
|
||||
// }
|
||||
|
||||
public static Integer findVersionIdParameterIndex(Method theMethod) {
|
||||
return findParamIndex(theMethod, VersionIdParam.class);
|
||||
|
|
|
@ -20,7 +20,9 @@ package ca.uhn.fhir.rest.param;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -98,5 +100,9 @@ public abstract class BaseQueryParameter implements IParameter {
|
|||
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// ignore for now
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package ca.uhn.fhir.rest.param;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Library
|
||||
* %%
|
||||
* Copyright (C) 2014 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.method.Request;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class CountParameter implements IParameter {
|
||||
|
||||
private Class<?> myType;
|
||||
|
||||
@Override
|
||||
public void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
||||
if (theSourceClientArgument != null) {
|
||||
IntegerDt since = ParameterUtil.toInteger(theSourceClientArgument);
|
||||
if (since.isEmpty() == false) {
|
||||
theTargetQueryArguments.put(Constants.PARAM_COUNT, Collections.singletonList(since.getValueAsString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
|
||||
String[] sinceParams = theRequest.getParameters().remove(Constants.PARAM_COUNT);
|
||||
if (sinceParams != null) {
|
||||
if (sinceParams.length > 0) {
|
||||
if (StringUtils.isNotBlank(sinceParams[0])) {
|
||||
try {
|
||||
IntegerDt since = new IntegerDt(sinceParams[0]);
|
||||
return ParameterUtil.fromInteger(myType, since);
|
||||
} catch (DataFormatException e) {
|
||||
throw new InvalidRequestException("Invalid " + Constants.PARAM_COUNT + " value: " + sinceParams[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ParameterUtil.fromInteger(myType, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
if (theOuterCollectionType != null) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Since.class.getName() + " but can not be of collection type");
|
||||
}
|
||||
if (!ParameterUtil.getBindableIntegerTypes().contains(theParameterType)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Since.class.getName() + " but type '" + theParameterType + "' is an invalid type, must be one of: " + ParameterUtil.getBindableInstantTypes());
|
||||
}
|
||||
myType = theParameterType;
|
||||
}
|
||||
|
||||
}
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -45,4 +47,6 @@ public interface IParameter {
|
|||
*/
|
||||
Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException;
|
||||
|
||||
void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType);
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.param;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
@ -110,4 +111,5 @@ public class IncludeParameter extends BaseQueryParameter {
|
|||
return true;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package ca.uhn.fhir.rest.param;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Library
|
||||
* %%
|
||||
* Copyright (C) 2014 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import ca.uhn.fhir.rest.method.Request;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
class NullParameter implements IParameter {
|
||||
|
||||
@Override
|
||||
public void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
||||
//nothing
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
|
||||
// nothing
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// nothing
|
||||
}
|
||||
|
||||
}
|
|
@ -23,63 +23,71 @@ package ca.uhn.fhir.rest.param;
|
|||
import java.lang.annotation.Annotation;
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.servlet.ServletRequest;
|
||||
import javax.servlet.ServletResponse;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.PathSpecification;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
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.ServerBase;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
||||
import ca.uhn.fhir.util.ReflectionUtil;
|
||||
|
||||
public class ParameterUtil {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static List<IParameter> getResourceParameters(Method method) {
|
||||
public static List<IParameter> getResourceParameters(Method theMethod) {
|
||||
List<IParameter> parameters = new ArrayList<IParameter>();
|
||||
|
||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
||||
Class<?>[] parameterTypes = theMethod.getParameterTypes();
|
||||
int paramIndex = 0;
|
||||
for (Annotation[] annotations : method.getParameterAnnotations()) {
|
||||
boolean haveHandledMethod = false;
|
||||
for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
|
||||
|
||||
IParameter param = null;
|
||||
Class<?> parameterType = parameterTypes[paramIndex];
|
||||
if (parameterType.equals(HttpServletRequest.class) || parameterType.equals(ServletRequest.class)) {
|
||||
ServletRequestParameter param = new ServletRequestParameter();
|
||||
parameters.add(param);
|
||||
} else if (parameterType.equals(HttpServletResponse.class) || parameterType.equals(ServletResponse.class)) {
|
||||
ServletResponseParameter param = new ServletResponseParameter();
|
||||
parameters.add(param);
|
||||
} else {
|
||||
for (int i = 0; i < annotations.length; i++) {
|
||||
Annotation nextAnnotation = annotations[i];
|
||||
|
||||
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);
|
||||
parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(parameterType)) {
|
||||
outerCollectionType = innerCollectionType;
|
||||
innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
|
||||
parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(method, paramIndex);
|
||||
parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
|
||||
}
|
||||
if (Collection.class.isAssignableFrom(parameterType)) {
|
||||
throw new ConfigurationException("Argument #" + paramIndex + " of Method '" + theMethod.getName() + "' in type '" + theMethod.getDeclaringClass().getCanonicalName() + "' is of an invalid generic type (can not be a collection of a collection of a collection)");
|
||||
}
|
||||
|
||||
IParameter param;
|
||||
if (parameterType.equals(HttpServletRequest.class) || parameterType.equals(ServletRequest.class)) {
|
||||
param = new ServletRequestParameter();
|
||||
} else if (parameterType.equals(HttpServletResponse.class) || parameterType.equals(ServletResponse.class)) {
|
||||
param = new ServletResponseParameter();
|
||||
} else {
|
||||
for (int i = 0; i < annotations.length && param == null; i++) {
|
||||
Annotation nextAnnotation = annotations[i];
|
||||
|
||||
if (nextAnnotation instanceof RequiredParam) {
|
||||
SearchParameter parameter = new SearchParameter();
|
||||
parameter.setName(((RequiredParam) nextAnnotation).name());
|
||||
|
@ -94,43 +102,121 @@ public class ParameterUtil {
|
|||
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 '" + theMethod.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 '" + theMethod.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 '" + theMethod.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) {
|
||||
param = null;
|
||||
param = new NullParameter();
|
||||
} else if (nextAnnotation instanceof ServerBase) {
|
||||
param = new ServerBaseParameter();
|
||||
} else if (nextAnnotation instanceof Since) {
|
||||
param = new SinceParameter();
|
||||
} else if (nextAnnotation instanceof Count) {
|
||||
param = new CountParameter();
|
||||
} else {
|
||||
continue;
|
||||
}
|
||||
|
||||
haveHandledMethod = true;
|
||||
parameters.add(param);
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if (!haveHandledMethod) {
|
||||
throw new ConfigurationException("Parameter #" + paramIndex + " of method '" + method.getName() + "' on type '" + method.getDeclaringClass().getCanonicalName()
|
||||
if (param == null) {
|
||||
throw new ConfigurationException("Parameter #" + paramIndex + " of method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName()
|
||||
+ "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
|
||||
}
|
||||
}
|
||||
|
||||
param.initializeTypes(theMethod, outerCollectionType, innerCollectionType, parameterType);
|
||||
parameters.add(param);
|
||||
|
||||
paramIndex++;
|
||||
}
|
||||
return parameters;
|
||||
}
|
||||
|
||||
public static InstantDt toInstant(Object theArgument) {
|
||||
if (theArgument instanceof InstantDt) {
|
||||
return (InstantDt) theArgument;
|
||||
}
|
||||
if (theArgument instanceof Date) {
|
||||
return new InstantDt((Date) theArgument);
|
||||
}
|
||||
if (theArgument instanceof Calendar) {
|
||||
return new InstantDt((Calendar) theArgument);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Set<Class<?>> getBindableInstantTypes() {
|
||||
// TODO: make this constant
|
||||
HashSet<Class<?>> retVal = new HashSet<Class<?>>();
|
||||
retVal.add(InstantDt.class);
|
||||
retVal.add(Date.class);
|
||||
retVal.add(Calendar.class);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static Object fromInstant(Class<?> theType, InstantDt theArgument) {
|
||||
if (theType.equals(InstantDt.class)) {
|
||||
if (theArgument == null) {
|
||||
return new InstantDt();
|
||||
}
|
||||
return theArgument;
|
||||
}
|
||||
if (theType.equals(Date.class)) {
|
||||
if (theArgument == null) {
|
||||
return null;
|
||||
}
|
||||
return theArgument.getValue();
|
||||
}
|
||||
if (theType.equals(Calendar.class)) {
|
||||
if (theArgument == null) {
|
||||
return null;
|
||||
}
|
||||
return DateUtils.toCalendar(theArgument.getValue());
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid instant type:" + theType);
|
||||
}
|
||||
|
||||
public static IntegerDt toInteger(Object theArgument) {
|
||||
if (theArgument instanceof IntegerDt) {
|
||||
return (IntegerDt) theArgument;
|
||||
}
|
||||
if (theArgument instanceof Integer) {
|
||||
return new IntegerDt((Integer) theArgument);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public static Set<Class<?>> getBindableIntegerTypes() {
|
||||
// TODO: make this constant
|
||||
HashSet<Class<?>> retVal = new HashSet<Class<?>>();
|
||||
retVal.add(IntegerDt.class);
|
||||
retVal.add(Integer.class);
|
||||
return retVal;
|
||||
}
|
||||
|
||||
public static Object fromInteger(Class<?> theType, IntegerDt theArgument) {
|
||||
if (theType.equals(IntegerDt.class)) {
|
||||
if (theArgument == null) {
|
||||
return new IntegerDt();
|
||||
}
|
||||
return theArgument;
|
||||
}
|
||||
if (theType.equals(Integer.class)) {
|
||||
if (theArgument == null) {
|
||||
return null;
|
||||
}
|
||||
return theArgument.getValue();
|
||||
}
|
||||
throw new IllegalArgumentException("Invalid Integer type:" + theType);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -52,6 +54,9 @@ public class ResourceParameter implements IParameter {
|
|||
return myResourceName;
|
||||
}
|
||||
|
||||
// public IResource
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// ignore for now
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -35,7 +37,7 @@ class ServerBaseParameter implements IParameter {
|
|||
/*
|
||||
* Does nothing, since we just ignore serverbase arguments
|
||||
*/
|
||||
ourLog.trace("Ignoring HttpServletRequest argument: {}", theSourceClientArgument);
|
||||
ourLog.trace("Ignoring server base argument: {}", theSourceClientArgument);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -43,4 +45,9 @@ class ServerBaseParameter implements IParameter {
|
|||
return theRequest.getFhirServerBase();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// ignore for now
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -43,4 +45,9 @@ class ServletRequestParameter implements IParameter {
|
|||
return theRequest.getServletRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
|
@ -43,4 +45,10 @@ class ServletResponseParameter implements IParameter {
|
|||
return theRequest.getServletResponse();
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,83 @@
|
|||
package ca.uhn.fhir.rest.param;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Library
|
||||
* %%
|
||||
* Copyright (C) 2014 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.method.Request;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
|
||||
public class SinceParameter implements IParameter {
|
||||
|
||||
private Class<?> myType;
|
||||
|
||||
@Override
|
||||
public void translateClientArgumentIntoQueryArgument(Object theSourceClientArgument, Map<String, List<String>> theTargetQueryArguments) throws InternalErrorException {
|
||||
if (theSourceClientArgument != null) {
|
||||
InstantDt since = ParameterUtil.toInstant(theSourceClientArgument);
|
||||
if (since.isEmpty() == false) {
|
||||
theTargetQueryArguments.put(Constants.PARAM_SINCE, Collections.singletonList(since.getValueAsString()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException {
|
||||
String[] sinceParams = theRequest.getParameters().remove(Constants.PARAM_SINCE);
|
||||
if (sinceParams != null) {
|
||||
if (sinceParams.length > 0) {
|
||||
if (StringUtils.isNotBlank(sinceParams[0])) {
|
||||
try {
|
||||
InstantDt since = new InstantDt(sinceParams[0]);
|
||||
return ParameterUtil.fromInstant(myType, since);
|
||||
} catch (DataFormatException e) {
|
||||
throw new InvalidRequestException("Invalid " + Constants.PARAM_SINCE + " value: " + sinceParams[0]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return ParameterUtil.fromInstant(myType, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
|
||||
if (theOuterCollectionType != null) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Since.class.getName() + " but can not be of collection type");
|
||||
}
|
||||
if (!ParameterUtil.getBindableInstantTypes().contains(theParameterType)) {
|
||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type '" + "' is annotated with @" + Since.class.getName() + " but cis an invalid type, must be one of: " + ParameterUtil.getBindableInstantTypes());
|
||||
}
|
||||
myType = theParameterType;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.rest.server.exceptions;
|
||||
package ca.uhn.fhir.rest.server;
|
||||
|
||||
/*
|
||||
* #%L
|
|
@ -48,10 +48,9 @@ import ca.uhn.fhir.rest.method.Request;
|
|||
import ca.uhn.fhir.rest.method.SearchMethodBinding;
|
||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ConfigurationException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.MethodNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
|
||||
import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider;
|
||||
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
|
||||
import ca.uhn.fhir.util.VersionUtil;
|
||||
|
@ -63,7 +62,7 @@ public class RestfulServer extends HttpServlet {
|
|||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private FhirContext myFhirContext;
|
||||
private Collection<Object> myProviders;
|
||||
private Collection<Object> myPlainProviders;
|
||||
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
|
||||
private Collection<IResourceProvider> myResourceProviders;
|
||||
private ISecurityManager mySecurityManager;
|
||||
|
@ -73,16 +72,34 @@ public class RestfulServer extends HttpServlet {
|
|||
private String myServerVersion = VersionUtil.getVersion(); // defaults to
|
||||
// HAPI version
|
||||
private boolean myUseBrowserFriendlyContentTypes;
|
||||
private ResourceBinding myNullResourceBinding = new ResourceBinding();
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public RestfulServer() {
|
||||
myFhirContext = new FhirContext();
|
||||
myServerConformanceProvider = new ServerConformanceProvider(this);
|
||||
}
|
||||
|
||||
public void addHapiHeader(HttpServletResponse theHttpResponse) {
|
||||
theHttpResponse.addHeader("X-CatchingFhir", "Powered by HAPI FHIR " + VersionUtil.getVersion());
|
||||
/**
|
||||
* This method is called prior to sending a response to incoming requests. It is
|
||||
* used to add custom headers.
|
||||
* <p>
|
||||
* Use caution if overriding this method: it is recommended to call
|
||||
* <code>super.addHeadersToResponse</code> to avoid inadvertantly
|
||||
* disabling functionality.
|
||||
* </p>
|
||||
*/
|
||||
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
|
||||
theHttpResponse.addHeader("X-PoweredBy", "HAPI FHIR " + VersionUtil.getVersion() + " RESTful Server");
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the {@link FhirContext} associated with this server. For efficient processing,
|
||||
* resource providers and plain providers should generally use this context
|
||||
* if one is needed, as opposed to creating their own.
|
||||
*/
|
||||
public FhirContext getFhirContext() {
|
||||
return myFhirContext;
|
||||
}
|
||||
|
@ -93,8 +110,8 @@ public class RestfulServer extends HttpServlet {
|
|||
*
|
||||
* @see #getResourceProviders()
|
||||
*/
|
||||
public Collection<Object> getProviders() {
|
||||
return myProviders;
|
||||
public Collection<Object> getPlainProviders() {
|
||||
return myPlainProviders;
|
||||
}
|
||||
|
||||
public Collection<ResourceBinding> getResourceBindings() {
|
||||
|
@ -152,6 +169,12 @@ public class RestfulServer extends HttpServlet {
|
|||
return myServerVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Initializes the server. Note that this method is final to avoid accidentally
|
||||
* introducing bugs in implementations, but subclasses may put initialization code in
|
||||
* {@link #initialize()}, which is called immediately before beginning initialization of
|
||||
* the restful server's internal init.
|
||||
*/
|
||||
@Override
|
||||
public final void init() throws ServletException {
|
||||
initialize();
|
||||
|
@ -183,7 +206,7 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
}
|
||||
|
||||
Collection<Object> providers = getProviders();
|
||||
Collection<Object> providers = getPlainProviders();
|
||||
if (providers != null) {
|
||||
for (Object next : providers) {
|
||||
assertProviderIsValid(next);
|
||||
|
@ -214,12 +237,12 @@ public class RestfulServer extends HttpServlet {
|
|||
|
||||
/**
|
||||
* Sets the non-resource specific providers which implement method calls on
|
||||
* this server
|
||||
* this server.
|
||||
*
|
||||
* @see #setResourceProviders(Collection)
|
||||
*/
|
||||
public void setProviders(Collection<Object> theProviders) {
|
||||
myProviders = theProviders;
|
||||
public void setPlainProviders(Collection<Object> theProviders) {
|
||||
myPlainProviders = theProviders;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -229,7 +252,7 @@ public class RestfulServer extends HttpServlet {
|
|||
* @see #setResourceProviders(Collection)
|
||||
*/
|
||||
public void setProviders(Object... theProviders) {
|
||||
myProviders = Arrays.asList(theProviders);
|
||||
myPlainProviders = Arrays.asList(theProviders);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -317,16 +340,20 @@ public class RestfulServer extends HttpServlet {
|
|||
BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, myFhirContext, theProvider);
|
||||
if (foundMethodBinding != null) {
|
||||
|
||||
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(foundMethodBinding.getResourceName());
|
||||
String resourceName = foundMethodBinding.getResourceName();
|
||||
ResourceBinding resourceBinding;
|
||||
if (resourceName == null) {
|
||||
resourceBinding = myNullResourceBinding;
|
||||
} else {
|
||||
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceName);
|
||||
if (myResourceNameToProvider.containsKey(definition.getName())) {
|
||||
resourceBinding = myResourceNameToProvider.get(definition.getName());
|
||||
} else {
|
||||
resourceBinding = new ResourceBinding();
|
||||
String resourceName = definition.getName();
|
||||
resourceBinding.setResourceName(resourceName);
|
||||
myResourceNameToProvider.put(resourceName, resourceBinding);
|
||||
}
|
||||
}
|
||||
|
||||
resourceBinding.addMethod(foundMethodBinding);
|
||||
ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
|
||||
|
@ -453,18 +480,24 @@ public class RestfulServer extends HttpServlet {
|
|||
|
||||
StringTokenizer tok = new StringTokenizer(requestPath, "/");
|
||||
if (!tok.hasMoreTokens()) {
|
||||
throw new MethodNotFoundException("No resource name specified");
|
||||
throw new ResourceNotFoundException("No resource name specified");
|
||||
}
|
||||
resourceName = tok.nextToken();
|
||||
if (resourceName.startsWith("_")) {
|
||||
operation = resourceName;
|
||||
resourceName = null;
|
||||
}
|
||||
|
||||
ResourceBinding resourceBinding = null;
|
||||
BaseMethodBinding resourceMethod = null;
|
||||
if ("metadata".equals(resourceName)) {
|
||||
resourceMethod = myServerConformanceMethod;
|
||||
} else if (resourceName == null) {
|
||||
resourceBinding = myNullResourceBinding;
|
||||
} else {
|
||||
resourceBinding = myResourceNameToProvider.get(resourceName);
|
||||
if (resourceBinding == null) {
|
||||
throw new MethodNotFoundException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToProvider.keySet());
|
||||
throw new ResourceNotFoundException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToProvider.keySet());
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -515,14 +548,14 @@ public class RestfulServer extends HttpServlet {
|
|||
resourceMethod = resourceBinding.getMethod(r);
|
||||
}
|
||||
if (null == resourceMethod) {
|
||||
throw new MethodNotFoundException("No resource method available for the supplied parameters " + params);
|
||||
throw new ResourceNotFoundException("No resource method available for the supplied parameters " + params);
|
||||
}
|
||||
|
||||
resourceMethod.invokeServer(this, r, theResponse);
|
||||
|
||||
} catch (AuthenticationException e) {
|
||||
theResponse.setStatus(e.getStatusCode());
|
||||
addHapiHeader(theResponse);
|
||||
addHeadersToResponse(theResponse);
|
||||
theResponse.setContentType("text/plain");
|
||||
theResponse.setCharacterEncoding("UTF-8");
|
||||
theResponse.getWriter().write(e.getMessage());
|
||||
|
@ -535,7 +568,7 @@ public class RestfulServer extends HttpServlet {
|
|||
}
|
||||
|
||||
theResponse.setStatus(e.getStatusCode());
|
||||
addHapiHeader(theResponse);
|
||||
addHeadersToResponse(theResponse);
|
||||
theResponse.setContentType("text/plain");
|
||||
theResponse.setCharacterEncoding("UTF-8");
|
||||
theResponse.getWriter().append(e.getMessage());
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ca.uhn.fhir.rest.server.exceptions;
|
||||
|
||||
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Library
|
||||
|
@ -22,7 +24,8 @@ package ca.uhn.fhir.rest.server.exceptions;
|
|||
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 3/10/2014.
|
||||
* Represents an <b>HTTP 401 Client Unauthorized</b> response, which means that
|
||||
* the client needs to provide credentials, or has provided invalid credentials.
|
||||
*/
|
||||
public class AuthenticationException extends BaseServerResponseException {
|
||||
|
||||
|
@ -36,7 +39,4 @@ public class AuthenticationException extends BaseServerResponseException {
|
|||
super(401, theMessage);
|
||||
}
|
||||
|
||||
public AuthenticationException(int theStatusCode, String theMessage) {
|
||||
super(theStatusCode, theMessage);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -20,6 +20,9 @@ package ca.uhn.fhir.rest.server.exceptions;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
/**
|
||||
* TODO: javadoc this
|
||||
*/
|
||||
public class InternalErrorException extends BaseServerResponseException {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
|
|
@ -21,7 +21,7 @@ package ca.uhn.fhir.rest.server.exceptions;
|
|||
*/
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/27/2014.
|
||||
* TODO: javadoc this
|
||||
*/
|
||||
public class MethodNotAllowedException extends BaseServerResponseException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
|
|
@ -1,32 +0,0 @@
|
|||
package ca.uhn.fhir.rest.server.exceptions;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR Library
|
||||
* %%
|
||||
* Copyright (C) 2014 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/27/2014.
|
||||
*/
|
||||
public class MethodNotFoundException extends BaseServerResponseException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
public MethodNotFoundException(String error) {
|
||||
super(404, error);
|
||||
}
|
||||
}
|
|
@ -24,6 +24,10 @@ import ca.uhn.fhir.model.api.IResource;
|
|||
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
|
||||
/**
|
||||
* Represents an <b>HTTP 404 Resource Not Found</b> response, which means that
|
||||
* the request is pointing to a resource that does not exist.
|
||||
*/
|
||||
public class ResourceNotFoundException extends BaseServerResponseException {
|
||||
|
||||
public ResourceNotFoundException(IdDt theId) {
|
||||
|
|
|
@ -20,10 +20,14 @@ package ca.uhn.fhir.rest.server.exceptions;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.annotation.Delete;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/27/2014.
|
||||
* Represents an <b>HTTP 409 Conflict</b> response. This exception should be
|
||||
* thrown in methods which accept a version (e.g. {@link Update}, {@link Delete})
|
||||
* when the operation fails because of a version conflict as specified in the FHIR specification.
|
||||
*/
|
||||
public class ResourceVersionConflictException extends BaseServerResponseException {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
|
|
@ -20,10 +20,12 @@ package ca.uhn.fhir.rest.server.exceptions;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
import ca.uhn.fhir.rest.server.Constants;
|
||||
|
||||
/**
|
||||
* Thrown for an Update operation if that operation requires a version to
|
||||
* Represents an <b>HTTP 412 Precondition Failed</b> response. This exception
|
||||
* should be thrown for an {@link Update} operation if that operation requires a version to
|
||||
* be specified in an HTTP header, and none was.
|
||||
*/
|
||||
public class ResourceVersionNotSpecifiedException extends BaseServerResponseException {
|
||||
|
|
|
@ -22,7 +22,9 @@ package ca.uhn.fhir.rest.server.exceptions;
|
|||
|
||||
/**
|
||||
* Exception for use when a response is received or being sent that
|
||||
* does not correspond to any other exception type
|
||||
* does not correspond to any other exception type. An HTTP status code
|
||||
* must be provided, and will be provided to the caller in the case of a
|
||||
* server implementation.
|
||||
*/
|
||||
public class UnclassifiedServerFailureException extends BaseServerResponseException {
|
||||
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
package example;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
|
||||
@SuppressWarnings(value= {"serial","unused"})
|
||||
public class ExampleProviders {
|
||||
|
||||
|
||||
//START SNIPPET: plainProvider
|
||||
public class PlainProvider {
|
||||
|
||||
/**
|
||||
* This method is a Patient search, but HAPI can not automatically
|
||||
* determine the resource type so it must be explicitly stated.
|
||||
*/
|
||||
@Search(type=Patient.class)
|
||||
public Bundle searchForPatients(@RequiredParam(name="name") StringDt theName) {
|
||||
Bundle retVal = new Bundle();
|
||||
// perform search
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
//END SNIPPET: plainProvider
|
||||
|
||||
|
||||
//START SNIPPET: plainProviderServer
|
||||
public class ExampleServlet extends RestfulServer {
|
||||
|
||||
public ExampleServlet() {
|
||||
/*
|
||||
* Plain providers are passed to the server in the same way
|
||||
* as resource providers. You may pass both resource providers
|
||||
* and and plain providers to the same server if you like.
|
||||
*/
|
||||
List<Object> plainProviders=new ArrayList<Object>();
|
||||
plainProviders.add(new PlainProvider());
|
||||
setPlainProviders(plainProviders);
|
||||
|
||||
List<IResourceProvider> resourceProviders = new ArrayList<IResourceProvider>();
|
||||
// ...add some resource providers...
|
||||
setResourceProviders(resourceProviders);
|
||||
}
|
||||
|
||||
}
|
||||
//END SNIPPET: plainProviderServer
|
||||
|
||||
|
||||
}
|
|
@ -1,7 +1,6 @@
|
|||
package example;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.annotation.WebServlet;
|
||||
|
@ -21,17 +20,18 @@ public class ExampleRestfulServlet extends RestfulServer {
|
|||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* Restful servers must provide an implementation of this method, which
|
||||
* returns all resource providers to be used by this server. In the example
|
||||
* below, we are creating a RESTful server which is able to serve
|
||||
* Patient and Observation resources.
|
||||
* Constructor
|
||||
*/
|
||||
@Override
|
||||
public Collection<IResourceProvider> getResourceProviders() {
|
||||
List<IResourceProvider> retVal = new ArrayList<IResourceProvider>();
|
||||
retVal.add(new RestfulPatientResourceProvider());
|
||||
retVal.add(new RestfulObservationResourceProvider());
|
||||
return retVal;
|
||||
public ExampleRestfulServlet() {
|
||||
/*
|
||||
* The servlet defines any number of resource providers, and
|
||||
* configures itself to use them by calling
|
||||
* setResourceProviders()
|
||||
*/
|
||||
List<IResourceProvider> resourceProviders = new ArrayList<IResourceProvider>();
|
||||
resourceProviders.add(new RestfulPatientResourceProvider());
|
||||
resourceProviders.add(new RestfulObservationResourceProvider());
|
||||
setResourceProviders(resourceProviders);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -68,6 +68,22 @@ System.out.println(encoded);
|
|||
|
||||
}
|
||||
|
||||
|
||||
public void fluent() throws DataFormatException, IOException {
|
||||
FhirContext ctx = new FhirContext(Patient.class, Observation.class);
|
||||
String encoded;
|
||||
//START SNIPPET: encodeMsgFluent
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier().setSystem("http://example.com/fictitious-mrns").setValue("MRN001");
|
||||
patient.addName().setUse(NameUseEnum.OFFICIAL).addFamily("Tester").addGiven("John").addGiven("Q");
|
||||
|
||||
encoded = ctx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
|
||||
System.out.println(encoded);
|
||||
//END SNIPPET: encodeMsgFluent
|
||||
|
||||
}
|
||||
|
||||
|
||||
public static void parseMsg() {
|
||||
FhirContext ctx = new FhirContext(Patient.class, Observation.class);
|
||||
|
||||
|
|
|
@ -28,6 +28,7 @@ import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
|
|||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.parser.DataFormatException;
|
||||
import ca.uhn.fhir.rest.annotation.Count;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.History;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
|
@ -38,11 +39,13 @@ import ca.uhn.fhir.rest.annotation.Read;
|
|||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
import ca.uhn.fhir.rest.annotation.Validate;
|
||||
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.ITestClient;
|
||||
import ca.uhn.fhir.rest.client.api.IBasicClient;
|
||||
import ca.uhn.fhir.rest.client.api.IRestfulClient;
|
||||
import ca.uhn.fhir.rest.param.CodingListParam;
|
||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||
|
@ -443,22 +446,29 @@ public interface MetadataClient extends IRestfulClient {
|
|||
}
|
||||
//END SNIPPET: metadataClient
|
||||
|
||||
public interface HistoryClient {
|
||||
//START SNIPPET: historyClient
|
||||
// Server level (history of ALL resources)
|
||||
public interface HistoryClient extends IBasicClient {
|
||||
/** Server level (history of ALL resources) */
|
||||
@History
|
||||
Bundle getHistoryServer();
|
||||
|
||||
// Type level (history of all resources of a given type)
|
||||
/** Type level (history of all resources of a given type) */
|
||||
@History(type=Patient.class)
|
||||
Bundle getHistoryPatientType();
|
||||
|
||||
// Instance level (history of a specific resource instance by type and ID)
|
||||
/** Instance level (history of a specific resource instance by type and ID) */
|
||||
@History(type=Patient.class)
|
||||
Bundle getHistoryPatientInstance(@IdParam IdDt theId);
|
||||
//END SNIPPET: historyClient
|
||||
|
||||
/**
|
||||
* Either (or both) of the "since" and "count" paramaters can
|
||||
* also be included in any of the methods above.
|
||||
*/
|
||||
@History
|
||||
Bundle getHistoryServerWithCriteria(@Since Date theDate, @Count int theCount);
|
||||
|
||||
}
|
||||
//END SNIPPET: historyClient
|
||||
|
||||
|
||||
public void bbbbb() throws DataFormatException, IOException {
|
||||
|
|
|
@ -110,6 +110,9 @@
|
|||
<param name="file" value="src/site/example/java/example/FhirContextIntro.java" />
|
||||
</macro>
|
||||
|
||||
<!-- ****** The section below on fluent references the snippet above
|
||||
****** so be careful about any reordering! -->
|
||||
|
||||
<p>
|
||||
This code gives the following output:
|
||||
</p>
|
||||
|
@ -129,6 +132,26 @@
|
|||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Fluent Programming">
|
||||
|
||||
<p>
|
||||
Much of the HAPI FHIR API is designed using a fluent style,
|
||||
where method calls can be chained in a natural way. This
|
||||
leads to tighter and easier-to-read code.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The following snippet is functionally identical to the
|
||||
example above:
|
||||
</p>
|
||||
|
||||
<macro name="snippet">
|
||||
<param name="id" value="encodeMsgFluent" />
|
||||
<param name="file" value="src/site/example/java/example/FhirContextIntro.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="JSON Encoding">
|
||||
|
||||
<p>
|
||||
|
|
|
@ -10,6 +10,14 @@
|
|||
|
||||
<section name="Implementing Resource Provider Operations">
|
||||
|
||||
<p>
|
||||
Jump To...
|
||||
</p>
|
||||
<ul>
|
||||
<li><a href="#operations">Operations</a></li>
|
||||
<li><a href="#exceptions">Exceptions</a></li>
|
||||
</ul>
|
||||
|
||||
<p>
|
||||
RESTful Clients and Servers both share the same
|
||||
method pattern, with one key difference: A client
|
||||
|
@ -26,6 +34,10 @@
|
|||
implementations, but client methods will follow the same patterns.
|
||||
</p>
|
||||
|
||||
<a name="operations"/>
|
||||
</section>
|
||||
|
||||
<section name="Operations">
|
||||
<p>
|
||||
The following table lists the operations supported by
|
||||
HAPI FHIR RESTful Servers and Clients.
|
||||
|
@ -81,7 +93,7 @@
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="#type_create">Type - Create</a>
|
||||
<a href="#type_search">Type - Create</a>
|
||||
</td>
|
||||
<td>
|
||||
Create a new resource with a server assigned id
|
||||
|
@ -99,14 +111,6 @@
|
|||
Search the resource type based on some filter criteria
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="#type_search">Type - Search</a>
|
||||
</td>
|
||||
<td>
|
||||
Search the resource type based on some filter criteria
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<a href="#history">Type - History</a>
|
||||
|
@ -892,7 +896,7 @@
|
|||
<section name="System Level - Transaction">
|
||||
|
||||
<p>
|
||||
Not yet implemented
|
||||
Not yet implemented - Get in touch if you would like to help!
|
||||
</p>
|
||||
|
||||
<a name="system_search" />
|
||||
|
@ -905,7 +909,7 @@
|
|||
<section name="System Level - Search">
|
||||
|
||||
<p>
|
||||
Not yet implemented
|
||||
Not yet implemented - Get in touch if you would like to help!
|
||||
</p>
|
||||
|
||||
<a name="history" />
|
||||
|
@ -945,18 +949,38 @@
|
|||
annotated with the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
|
||||
annotation, indicating the ID of the resource for which to return history.
|
||||
<ul><li>
|
||||
For a server
|
||||
implementation, the method must either be defined in a
|
||||
<a href="./doc_rest_server.html#resource_providers">resource provider</a>
|
||||
or have a <code>type()</code> value in the @History annotation if it is
|
||||
defined in a
|
||||
<a href="./doc_rest_server.html#plain_providers">plain provider</a>.
|
||||
</li></ul>
|
||||
</li>
|
||||
<li>
|
||||
For an
|
||||
For a
|
||||
<b>Type History</b>
|
||||
method, the method must not have any @IdParam parameter.
|
||||
<ul><li>
|
||||
For a server
|
||||
implementation, the method must either be defined in a
|
||||
<a href="./doc_rest_server.html#resource_providers">resource provider</a>
|
||||
or have a <code>type()</code> value in the @History annotation if it is
|
||||
defined in a
|
||||
<a href="./doc_rest_server.html#plain_providers">plain provider</a>.
|
||||
</li></ul>
|
||||
</li>
|
||||
<li>
|
||||
For an
|
||||
For a
|
||||
<b>Server History</b>
|
||||
method, the method must not have any @IdParam parameter
|
||||
and must not be found in a ResourceProvider definition.
|
||||
<!-- TODO: make ResourceProvider a link to a defintion of these on the RESTFul server page -->
|
||||
method, the method must not have any @IdParam parameter, and
|
||||
must not have a <code>type()</code> value specified in
|
||||
the @History annotation.
|
||||
<ul><li>
|
||||
In a server implementation, the method must
|
||||
be defined in a <a href="./doc_rest_server.html#plain_providers">plain provider</a>.
|
||||
</li></ul>
|
||||
</li>
|
||||
</ul>
|
||||
<p>
|
||||
|
@ -977,6 +1001,32 @@
|
|||
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
|
||||
</macro>
|
||||
|
||||
<a name="exceptions"/>
|
||||
</section>
|
||||
|
||||
<section name="Exceptions">
|
||||
|
||||
<p>
|
||||
When implementing a server operation, there are a number of failure conditions
|
||||
specified. For example, an
|
||||
<a href="#instance_read">Instance Read</a> request might specify an unknown
|
||||
resource ID, or a <a href="#type_create">Type Create</a> request might contain an
|
||||
invalid resource which can not be created.
|
||||
</p>
|
||||
<p>
|
||||
In these cases, an appropriate exception should be thrown. The HAPI RESTful
|
||||
API includes a set of exceptions extending
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>
|
||||
which represent specific HTTP failure codes.
|
||||
</p>
|
||||
<p>
|
||||
See the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/package-summary.html">Exceptions List</a>
|
||||
for a complete list of these exceptions. Note that these exceptions are all <b>unchecked</b>
|
||||
exceptions, so they do not need to ne explicitly declared in the method
|
||||
signature.
|
||||
</p>
|
||||
|
||||
</section>
|
||||
|
||||
</body>
|
||||
|
|
|
@ -26,6 +26,7 @@
|
|||
be possible to create a FHIR compliant server quickly and easily.
|
||||
</p>
|
||||
|
||||
<a name="resource_providers"/>
|
||||
<subsection name="Defining Resource Providers">
|
||||
|
||||
<p>
|
||||
|
@ -90,6 +91,54 @@
|
|||
<param name="file" value="src/site/example/java/example/ExampleRestfulServlet.java" />
|
||||
</macro>
|
||||
|
||||
<a name="plain_providers"/>
|
||||
</subsection>
|
||||
|
||||
<!-- NB there is an anchor for this section above -->
|
||||
<subsection name="Plain Providers (non-resource specific)">
|
||||
|
||||
<p>
|
||||
Defining one provider per resource is a good strategy to keep
|
||||
code readable and maintainable, but it is also possible to put
|
||||
methods for multiple resource types in a provider class. Providers
|
||||
which do not implement the
|
||||
<a href="./apidocs/ca/uhn/fhir/rest/server/IResourceProvider.html">IResourceProvider</a>
|
||||
(and therefore are not bound to one specific resource type) are known as
|
||||
<b>Plain Providers</b>.
|
||||
</p>
|
||||
<p>
|
||||
A plain provider may implement any
|
||||
<a href="./doc_rest_operations.html">RESTful operation</a>, but will generally
|
||||
need to explicitly state what type of resource it applies to. If the method directly
|
||||
returns a resource or a collection of resources (as in an
|
||||
<a href="./doc_rest_operations.html#instance_read">instance read</a> or
|
||||
<a href="./doc_rest_operations.html#type_search">type search</a> operation)
|
||||
the resource type will be inferred automatically. If the method returns a
|
||||
<a href="./apidocs/ca/uhn/fhir/model/api/Bundle.html">Bundle</a>
|
||||
resource, it is necessary to explicitly specify the resource type
|
||||
in the method annotation. The following example shows this:
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="plainProvider" />
|
||||
<param name="file" value="src/site/example/java/example/ExampleProviders.java" />
|
||||
</macro>
|
||||
|
||||
<p>
|
||||
In addition, some methods are not resource specific. For example, the
|
||||
<a href="./doc_rest_operations.html#history">system history</a> operation
|
||||
returns historical versions of <b>all resource types</b> on a server,
|
||||
so it needs to be defined in a plain provider.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Once you have defined your resource providers, they are passed to the
|
||||
server in a similar way to the resource providers.
|
||||
</p>
|
||||
<macro name="snippet">
|
||||
<param name="id" value="plainProviderServer" />
|
||||
<param name="file" value="src/site/example/java/example/ExampleProviders.java" />
|
||||
</macro>
|
||||
|
||||
</subsection>
|
||||
|
||||
<subsection name="Deploy">
|
||||
|
@ -100,6 +149,12 @@
|
|||
any JEE container (Tomcat, Websphere, Glassfish, etc).
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Bundling a servlet into a WAR file and deploying it to an application server
|
||||
is beyond the scope of this page, but there are many good tutorials on how
|
||||
to do this.
|
||||
</p>
|
||||
|
||||
</subsection>
|
||||
|
||||
</section>
|
||||
|
@ -167,6 +222,22 @@
|
|||
</section>
|
||||
-->
|
||||
|
||||
|
||||
<section name="Using the Server">
|
||||
|
||||
<p>
|
||||
Your RESTful server should now support the methods you have declared. Here are a
|
||||
few helpful tricks for interacting with the server:
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<b>Pretty Printing:</b> The HAPI RESTful server supports a non-standard parameter called
|
||||
<code>_pretty</code>, which can be used to request that responses be pretty-printed (indented for
|
||||
easy reading by humans) by setting the value to <code>true</code>. This can be useful in testing. An example URL for this might be:<br/>
|
||||
<code>http://example.com/fhir/Patient/_search?name=TESTING&_pretty=true</code>
|
||||
</p>
|
||||
|
||||
</section>
|
||||
</body>
|
||||
|
||||
</document>
|
||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.rest.client;
|
|||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.io.InputStream;
|
||||
import java.io.StringReader;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Arrays;
|
||||
|
@ -27,6 +28,8 @@ import org.junit.Before;
|
|||
import org.junit.Test;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
||||
import org.mockito.invocation.InvocationOnMock;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
|
@ -41,6 +44,7 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
|
|||
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.param.CodingListParam;
|
||||
|
@ -249,6 +253,50 @@ public class ClientTest {
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testHistoryWithParams() throws Exception {
|
||||
|
||||
//@formatter:off
|
||||
final String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
|
||||
//@formatter:on
|
||||
|
||||
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
|
||||
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
|
||||
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
|
||||
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_ATOM_XML + "; charset=UTF-8"));
|
||||
when(httpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
|
||||
@Override
|
||||
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
|
||||
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
|
||||
|
||||
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt(12));
|
||||
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02&_count=12", capt.getAllValues().get(0).getURI().toString());
|
||||
|
||||
String expectedDateString= new InstantDt(new InstantDt("2012-01-02T00:01:02").getValue()).getValueAsString(); // ensures the local timezone
|
||||
expectedDateString=expectedDateString.replace(":", "%3A");
|
||||
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02").getValue(), new IntegerDt(12).getValue());
|
||||
assertEquals("http://foo/Patient/111/_history?_since="+expectedDateString+"&_count=12", capt.getAllValues().get(1).getURI().toString());
|
||||
|
||||
client.getHistoryPatientInstance(new IdDt("111"), null, new IntegerDt(12));
|
||||
assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString());
|
||||
|
||||
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), null);
|
||||
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02", capt.getAllValues().get(3).getURI().toString());
|
||||
|
||||
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt(), new IntegerDt(12));
|
||||
assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString());
|
||||
|
||||
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt());
|
||||
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02", capt.getAllValues().get(3).getURI().toString());
|
||||
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testHistoryResourceType() throws Exception {
|
||||
|
||||
|
@ -408,7 +456,7 @@ public class ClientTest {
|
|||
DateRangeParam param = new DateRangeParam();
|
||||
param.setLowerBound(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-01"));
|
||||
param.setUpperBound(new QualifiedDateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, "2021-01-01"));
|
||||
List<Patient> response = client.getPatientByDateRange(param);
|
||||
client.getPatientByDateRange(param);
|
||||
|
||||
assertEquals("http://foo/Patient?dateRange=%3E%3D2011-01-01&dateRange=%3C%3D2021-01-01", capt.getValue().getURI().toString());
|
||||
|
||||
|
@ -467,7 +515,7 @@ public class ClientTest {
|
|||
CodingListParam identifiers = new CodingListParam();
|
||||
identifiers.add(new CodingDt("foo", "bar"));
|
||||
identifiers.add(new CodingDt("baz", "boz"));
|
||||
List<Patient> response = client.getPatientMultipleIdentifiers(identifiers);
|
||||
client.getPatientMultipleIdentifiers(identifiers);
|
||||
|
||||
assertEquals("http://foo/Patient?ids=foo%7Cbar%2Cbaz%7Cboz", capt.getValue().getURI().toString());
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package ca.uhn.fhir.rest.client;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
|
@ -8,7 +9,10 @@ import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
|
|||
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
import ca.uhn.fhir.rest.annotation.Count;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.Delete;
|
||||
import ca.uhn.fhir.rest.annotation.History;
|
||||
|
@ -19,6 +23,7 @@ import ca.uhn.fhir.rest.annotation.Read;
|
|||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
|
@ -71,6 +76,12 @@ public interface ITestClient extends IBasicClient {
|
|||
@History(type=Patient.class)
|
||||
Bundle getHistoryPatientInstance(@IdParam IdDt theId);
|
||||
|
||||
@History(type=Patient.class)
|
||||
Bundle getHistoryPatientInstance(@IdParam IdDt theId, @Since InstantDt theSince, @Count IntegerDt theCount);
|
||||
|
||||
@History(type=Patient.class)
|
||||
Bundle getHistoryPatientInstance(@IdParam IdDt theId, @Since Date theSince, @Count Integer theCount);
|
||||
|
||||
@History(type=Patient.class)
|
||||
Bundle getHistoryPatientType();
|
||||
|
||||
|
|
|
@ -2,7 +2,9 @@ package ca.uhn.fhir.rest.server;
|
|||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
|
@ -15,69 +17,83 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
|
|||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.hamcrest.core.IsEqual;
|
||||
import org.hamcrest.core.StringStartsWith;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.model.api.Bundle;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
|
||||
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
||||
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
|
||||
import ca.uhn.fhir.model.dstu.resource.Organization;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
||||
import ca.uhn.fhir.model.primitive.IntegerDt;
|
||||
import ca.uhn.fhir.model.primitive.UriDt;
|
||||
import ca.uhn.fhir.rest.annotation.Count;
|
||||
import ca.uhn.fhir.rest.annotation.History;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.annotation.Read;
|
||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||
import ca.uhn.fhir.rest.annotation.Search;
|
||||
import ca.uhn.fhir.rest.annotation.Since;
|
||||
import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
||||
|
||||
public class NonResourceProviderServerTest {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(NonResourceProviderServerTest.class);
|
||||
private static int ourPort;
|
||||
private static Server ourServer;
|
||||
private static CloseableHttpClient ourClient;
|
||||
private static FhirContext ourCtx;
|
||||
private int myPort;
|
||||
private Server myServer;
|
||||
private CloseableHttpClient myClient;
|
||||
private FhirContext myCtx;
|
||||
private RestfulServer myRestfulServer;
|
||||
|
||||
@BeforeClass
|
||||
public static void beforeClass() throws Exception {
|
||||
ourPort = RandomServerPortProvider.findFreePort();
|
||||
ourServer = new Server(ourPort);
|
||||
ourCtx = new FhirContext(Patient.class);
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
myPort = RandomServerPortProvider.findFreePort();
|
||||
myServer = new Server(myPort);
|
||||
myCtx = new FhirContext(Patient.class);
|
||||
|
||||
ServletHandler proxyHandler = new ServletHandler();
|
||||
ServletHolder servletHolder = new ServletHolder(new DummyRestfulServer());
|
||||
ServletHolder servletHolder = new ServletHolder();
|
||||
myRestfulServer = new RestfulServer();
|
||||
servletHolder.setServlet(myRestfulServer);
|
||||
proxyHandler.addServletWithMapping(servletHolder, "/fhir/context/*");
|
||||
ourServer.setHandler(proxyHandler);
|
||||
ourServer.start();
|
||||
myServer.setHandler(proxyHandler);
|
||||
|
||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||
builder.setConnectionManager(connectionManager);
|
||||
ourClient = builder.build();
|
||||
myClient = builder.build();
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void afterClass() throws Exception {
|
||||
ourServer.stop();
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
myServer.stop();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSearchByParamIdentifier() throws Exception {
|
||||
myRestfulServer.setProviders(new SearchProvider());
|
||||
myServer.start();
|
||||
|
||||
String baseUri = "http://localhost:" + ourPort + "/fhir/context";
|
||||
String baseUri = "http://localhost:" + myPort + "/fhir/context";
|
||||
String uri = baseUri + "/Patient?identifier=urn:hapitest:mrns%7C00001";
|
||||
HttpGet httpGet = new HttpGet(uri);
|
||||
HttpResponse status = ourClient.execute(httpGet);
|
||||
HttpResponse status = myClient.execute(httpGet);
|
||||
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
ourLog.info("Response was:\n{}", responseContent);
|
||||
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
||||
Bundle bundle = myCtx.newXmlParser().parseBundle(responseContent);
|
||||
|
||||
assertEquals(1, bundle.getEntries().size());
|
||||
|
||||
|
@ -88,33 +104,59 @@ public class NonResourceProviderServerTest {
|
|||
assertEquals(baseUri, bundle.getLinkBase().getValue());
|
||||
}
|
||||
|
||||
public static class DummyRestfulServer extends RestfulServer {
|
||||
@Test
|
||||
public void testGlobalHistory() throws Exception {
|
||||
GlobalHistoryProvider provider = new GlobalHistoryProvider();
|
||||
myRestfulServer.setProviders(provider);
|
||||
myServer.start();
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
String baseUri = "http://localhost:" + myPort + "/fhir/context";
|
||||
HttpResponse status = myClient.execute(new HttpGet(baseUri + "/_history?_since=2012-01-02T00%3A01%3A02&_count=12"));
|
||||
|
||||
public DummyRestfulServer() {
|
||||
setProviders(new DummyProvider());
|
||||
}
|
||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
ourLog.info("Response was:\n{}", responseContent);
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
Bundle bundle = myCtx.newXmlParser().parseBundle(responseContent);
|
||||
assertEquals(3, bundle.getEntries().size());
|
||||
|
||||
assertThat(provider.myLastSince.getValueAsString(), StringStartsWith.startsWith("2012-01-02T00:01:02"));
|
||||
assertThat(provider.myLastCount.getValueAsString(), IsEqual.equalTo("12"));
|
||||
|
||||
status = myClient.execute(new HttpGet(baseUri + "/_history?&_count=12"));
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
bundle = myCtx.newXmlParser().parseBundle(responseContent);
|
||||
assertEquals(3, bundle.getEntries().size());
|
||||
assertNull(provider.myLastSince.getValueAsString());
|
||||
assertThat(provider.myLastCount.getValueAsString(), IsEqual.equalTo("12"));
|
||||
|
||||
status =myClient.execute(new HttpGet(baseUri + "/_history?_since=2012-01-02T00%3A01%3A02"));
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
bundle = myCtx.newXmlParser().parseBundle(responseContent);
|
||||
assertEquals(3, bundle.getEntries().size());
|
||||
assertThat(provider.myLastSince.getValueAsString(), StringStartsWith.startsWith("2012-01-02T00:01:02"));
|
||||
assertNull(provider.myLastCount.getValueAsString());
|
||||
|
||||
status =myClient.execute(new HttpGet(baseUri + "/_history"));
|
||||
responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||
bundle = myCtx.newXmlParser().parseBundle(responseContent);
|
||||
assertEquals(3, bundle.getEntries().size());
|
||||
assertNull(provider.myLastSince.getValueAsString());
|
||||
assertNull(provider.myLastCount.getValueAsString());
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Created by dsotnikov on 2/25/2014.
|
||||
*/
|
||||
public static class DummyProvider {
|
||||
public static class SearchProvider {
|
||||
|
||||
public Map<String, Patient> getIdToPatient() {
|
||||
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
|
||||
{
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier();
|
||||
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
|
||||
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
|
||||
patient.getIdentifier().get(0).setValue("00001");
|
||||
patient.addName();
|
||||
patient.getName().get(0).addFamily("Test");
|
||||
patient.getName().get(0).addGiven("PatientOne");
|
||||
patient.getGender().setText("M");
|
||||
Patient patient = createPatient();
|
||||
idToPatient.put("1", patient);
|
||||
}
|
||||
{
|
||||
|
@ -158,4 +200,64 @@ public class NonResourceProviderServerTest {
|
|||
|
||||
}
|
||||
|
||||
public static class GlobalHistoryProvider {
|
||||
|
||||
private InstantDt myLastSince;
|
||||
private IntegerDt myLastCount;
|
||||
|
||||
@History
|
||||
public List<IResource> getGlobalHistory(@Since InstantDt theSince, @Count IntegerDt theCount) {
|
||||
myLastSince = theSince;
|
||||
myLastCount = theCount;
|
||||
ArrayList<IResource> retVal = new ArrayList<IResource>();
|
||||
|
||||
IResource p = createPatient();
|
||||
p.setId(new IdDt("1"));
|
||||
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("A"));
|
||||
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
|
||||
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2012-01-01T01:00:01"));
|
||||
retVal.add(p);
|
||||
|
||||
p = createPatient();
|
||||
p.setId(new IdDt("1"));
|
||||
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("B"));
|
||||
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
|
||||
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2012-01-01T01:00:03"));
|
||||
retVal.add(p);
|
||||
|
||||
p = createOrganization();
|
||||
p.setId(new IdDt("1"));
|
||||
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("A"));
|
||||
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2013-01-01T00:00:01"));
|
||||
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2013-01-01T01:00:01"));
|
||||
retVal.add(p);
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static Patient createPatient() {
|
||||
Patient patient = new Patient();
|
||||
patient.addIdentifier();
|
||||
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
|
||||
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
|
||||
patient.getIdentifier().get(0).setValue("00001");
|
||||
patient.addName();
|
||||
patient.getName().get(0).addFamily("Test");
|
||||
patient.getName().get(0).addGiven("PatientOne");
|
||||
patient.getGender().setText("M");
|
||||
return patient;
|
||||
}
|
||||
|
||||
private static Organization createOrganization() {
|
||||
Organization retVal = new Organization();
|
||||
retVal.addIdentifier();
|
||||
retVal.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
|
||||
retVal.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
|
||||
retVal.getIdentifier().get(0).setValue("00001");
|
||||
retVal.getName().setValue("Test Org");
|
||||
return retVal;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,11 +5,6 @@
|
|||
<classpathentry kind="var" path="M2_REPO/javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2.jar" sourcepath="M2_REPO/javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2-sources.jar"/>
|
||||
<classpathentry kind="var" path="M2_REPO/javax/servlet/javax.servlet-api/3.0.1/javax.servlet-api-3.0.1.jar" sourcepath="M2_REPO/javax/servlet/javax.servlet-api/3.0.1/javax.servlet-api-3.0.1-sources.jar"/>
|
||||
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
|
||||
<classpathentry kind="var" path="M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-base/0.3/hapi-fhir-base-0.3.jar" sourcepath="M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-base/0.1/hapi-fhir-base-0.1-sources.jar">
|
||||
<attributes>
|
||||
<attribute name="javadoc_location" value="jar:file:/home/t3903uhn/.m2/repository/ca/uhn/hapi/fhir/hapi-fhir-base/0.1/hapi-fhir-base-0.1-javadoc.jar!/"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry kind="var" path="M2_REPO/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar" sourcepath="M2_REPO/org/glassfish/javax.json/1.0.4/javax.json-1.0.4-sources.jar"/>
|
||||
<classpathentry kind="var" path="M2_REPO/org/codehaus/woodstox/woodstox-core-asl/4.2.0/woodstox-core-asl-4.2.0.jar" sourcepath="M2_REPO/org/codehaus/woodstox/woodstox-core-asl/4.2.0/woodstox-core-asl-4.2.0-sources.jar">
|
||||
<attributes>
|
||||
|
@ -42,5 +37,6 @@
|
|||
<attribute name="javadoc_location" value="jar:file:/home/t3903uhn/.m2/repository/ch/qos/logback/logback-core/1.1.1/logback-core-1.1.1-javadoc.jar!/"/>
|
||||
</attributes>
|
||||
</classpathentry>
|
||||
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-base"/>
|
||||
<classpathentry kind="output" path="target/classes"/>
|
||||
</classpath>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package ca.uhn.example.rest;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -10,7 +9,6 @@ import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
|
|||
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
|
||||
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
||||
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.model.primitive.StringDt;
|
||||
|
|
Loading…
Reference in New Issue