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>
|
<artifactId>maven-jxr-plugin</artifactId>
|
||||||
<version>2.4</version>
|
<version>2.4</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
<!--
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.codehaus.mojo</groupId>
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
<artifactId>findbugs-maven-plugin</artifactId>
|
<artifactId>findbugs-maven-plugin</artifactId>
|
||||||
<version>2.5.3</version>
|
<version>2.5.3</version>
|
||||||
</plugin>
|
</plugin>
|
||||||
|
-->
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-project-info-reports-plugin</artifactId>
|
<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 final Method myAccessorMethod;
|
||||||
|
|
||||||
private ListAccessor(Method theAccessor) {
|
private ListAccessor(Method theAccessor) {
|
||||||
|
|
|
@ -27,6 +27,8 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.Validate;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.IElement;
|
import ca.uhn.fhir.model.api.IElement;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||||
|
@ -117,6 +119,8 @@ public class FhirContext {
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
|
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
|
||||||
|
Validate.notBlank(theResourceName, "Resource name must not be blank");
|
||||||
|
|
||||||
RuntimeResourceDefinition retVal = myNameToElementDefinition.get(theResourceName);
|
RuntimeResourceDefinition retVal = myNameToElementDefinition.get(theResourceName);
|
||||||
|
|
||||||
if (retVal == null) {
|
if (retVal == null) {
|
||||||
|
|
|
@ -41,12 +41,15 @@ public abstract class BasePrimitive<T> extends BaseElement implements IPrimitive
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object obj) {
|
public boolean equals(Object theObj) {
|
||||||
if (!(obj.getClass() == getClass())) {
|
if (theObj == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!(theObj.getClass() == getClass())) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
BasePrimitive<?> o = (BasePrimitive<?>)obj;
|
BasePrimitive<?> o = (BasePrimitive<?>) theObj;
|
||||||
|
|
||||||
EqualsBuilder b = new EqualsBuilder();
|
EqualsBuilder b = new EqualsBuilder();
|
||||||
b.append(getValue(), o.getValue());
|
b.append(getValue(), o.getValue());
|
||||||
|
|
|
@ -26,8 +26,6 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.IElement;
|
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)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(value= {ElementType.FIELD})
|
@Target(value= {ElementType.FIELD})
|
||||||
|
|
|
@ -20,6 +20,7 @@ package ca.uhn.fhir.model.primitive;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.TimeZone;
|
import java.util.TimeZone;
|
||||||
|
|
||||||
|
@ -67,6 +68,15 @@ public class InstantDt extends BaseDateTimeDt {
|
||||||
setTimeZone(TimeZone.getDefault());
|
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
|
* Create a new InstantDt from a string value
|
||||||
*
|
*
|
||||||
|
|
|
@ -26,16 +26,26 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
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)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
public @interface Read {
|
public @interface Read {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The return type for this search method. This generally does not need
|
* The return type for this method. This generally does not need
|
||||||
* to be populated for a server implementation, since servers will return
|
* to be populated for {@link IResourceProvider resource providers} in a server implementation,
|
||||||
* only one resource per class, but generally does need to be populated
|
* but often does need to be populated in client implementations using {@link IBasicClient} or
|
||||||
* for client implementations.
|
* {@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, so update the javadocs everywhere
|
||||||
Class<? extends IResource> type() default IResource.class;
|
Class<? extends IResource> type() default IResource.class;
|
||||||
|
|
|
@ -26,6 +26,9 @@ import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
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 "";
|
String queryName() default "";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The return type for this search method. This generally does not need
|
* The return type for this method. This generally does not need
|
||||||
* to be populated for a server implementation, since servers will return
|
* to be populated for {@link IResourceProvider resource providers} in a server implementation,
|
||||||
* only one resource per class, but generally does need to be populated
|
* but often does need to be populated in client implementations using {@link IBasicClient} or
|
||||||
* for client implementations.
|
* {@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;
|
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.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.client.BaseClientInvocation;
|
import ca.uhn.fhir.rest.client.BaseClientInvocation;
|
||||||
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
|
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.Constants;
|
||||||
import ca.uhn.fhir.rest.server.EncodingUtil;
|
import ca.uhn.fhir.rest.server.EncodingUtil;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
|
@ -65,8 +67,9 @@ import ca.uhn.fhir.util.ReflectionUtil;
|
||||||
|
|
||||||
public abstract class BaseMethodBinding {
|
public abstract class BaseMethodBinding {
|
||||||
|
|
||||||
private Method myMethod;
|
|
||||||
private FhirContext myContext;
|
private FhirContext myContext;
|
||||||
|
private Method myMethod;
|
||||||
|
private List<IParameter> myParameters;
|
||||||
private Object myProvider;
|
private Object myProvider;
|
||||||
|
|
||||||
public BaseMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
|
public BaseMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
|
||||||
|
@ -76,31 +79,7 @@ public abstract class BaseMethodBinding {
|
||||||
myMethod = theMethod;
|
myMethod = theMethod;
|
||||||
myContext = theConetxt;
|
myContext = theConetxt;
|
||||||
myProvider = theProvider;
|
myProvider = theProvider;
|
||||||
}
|
myParameters = ParameterUtil.getResourceParameters(theMethod);
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the name of the resource this method handles, or <code>null</code> if this
|
|
||||||
* method is not resource specific
|
|
||||||
*/
|
|
||||||
public abstract String getResourceName();
|
|
||||||
|
|
||||||
protected Object invokeServerMethod(Object theResourceProvider, Object[] theMethodParams) {
|
|
||||||
try {
|
|
||||||
return getMethod().invoke(theResourceProvider, theMethodParams);
|
|
||||||
} catch (InvocationTargetException e) {
|
|
||||||
if (e.getCause() instanceof BaseServerResponseException) {
|
|
||||||
throw (BaseServerResponseException)e.getCause();
|
|
||||||
} else {
|
|
||||||
throw new InternalErrorException("Failed to call access method", e);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new InternalErrorException("Failed to call access method", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public Object getProvider() {
|
|
||||||
return myProvider;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public FhirContext getContext() {
|
public FhirContext getContext() {
|
||||||
|
@ -111,12 +90,26 @@ public abstract class BaseMethodBinding {
|
||||||
return myMethod;
|
return myMethod;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
|
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
|
||||||
|
*/
|
||||||
|
public abstract String getResourceName();
|
||||||
|
|
||||||
public abstract RestfulOperationTypeEnum getResourceOperationType();
|
public abstract RestfulOperationTypeEnum getResourceOperationType();
|
||||||
|
|
||||||
public abstract RestfulOperationSystemEnum getSystemOperationType();
|
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);
|
public abstract boolean matches(Request theRequest);
|
||||||
|
|
||||||
protected IParser createAppropriateParser(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException {
|
protected IParser createAppropriateParser(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException {
|
||||||
|
@ -131,6 +124,29 @@ public abstract class BaseMethodBinding {
|
||||||
return parser;
|
return parser;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public List<IParameter> getParameters() {
|
||||||
|
return myParameters;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Object invokeServerMethod(Object theResourceProvider, Object[] theMethodParams) {
|
||||||
|
try {
|
||||||
|
return getMethod().invoke(theResourceProvider, theMethodParams);
|
||||||
|
} catch (InvocationTargetException e) {
|
||||||
|
if (e.getCause() instanceof BaseServerResponseException) {
|
||||||
|
throw (BaseServerResponseException) e.getCause();
|
||||||
|
} else {
|
||||||
|
throw new InternalErrorException("Failed to call access method", e);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new InternalErrorException("Failed to call access method", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** For unit tests only */
|
||||||
|
public void setParameters(List<IParameter> theParameters) {
|
||||||
|
myParameters = theParameters;
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static BaseMethodBinding bindMethod(Method theMethod, FhirContext theContext, Object theProvider) {
|
public static BaseMethodBinding bindMethod(Method theMethod, FhirContext theContext, Object theProvider) {
|
||||||
Read read = theMethod.getAnnotation(Read.class);
|
Read read = theMethod.getAnnotation(Read.class);
|
||||||
|
@ -152,28 +168,27 @@ public abstract class BaseMethodBinding {
|
||||||
if (theProvider instanceof IResourceProvider) {
|
if (theProvider instanceof IResourceProvider) {
|
||||||
returnTypeFromRp = ((IResourceProvider) theProvider).getResourceType();
|
returnTypeFromRp = ((IResourceProvider) theProvider).getResourceType();
|
||||||
if (!verifyIsValidResourceReturnType(returnTypeFromRp)) {
|
if (!verifyIsValidResourceReturnType(returnTypeFromRp)) {
|
||||||
throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned "
|
throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned " + toLogString(returnTypeFromRp) + " - Must return a resource type");
|
||||||
+ toLogString(returnTypeFromRp) + " - Must return a resource type");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Class<?> returnTypeFromMethod = theMethod.getReturnType();
|
Class<?> returnTypeFromMethod = theMethod.getReturnType();
|
||||||
if (MethodOutcome.class.equals(returnTypeFromMethod)) {
|
if (MethodOutcome.class.equals(returnTypeFromMethod)) {
|
||||||
// returns a method outcome
|
// returns a method outcome
|
||||||
}else if (Bundle.class.equals(returnTypeFromMethod)) {
|
} else if (Bundle.class.equals(returnTypeFromMethod)) {
|
||||||
// returns a bundle
|
// returns a bundle
|
||||||
}else if (void.class.equals(returnTypeFromMethod)) {
|
} else if (void.class.equals(returnTypeFromMethod)) {
|
||||||
// returns a bundle
|
// returns a bundle
|
||||||
} else if (Collection.class.isAssignableFrom(returnTypeFromMethod)) {
|
} else if (Collection.class.isAssignableFrom(returnTypeFromMethod)) {
|
||||||
returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
|
returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
|
||||||
if (!verifyIsValidResourceReturnType(returnTypeFromMethod)) {
|
if (!verifyIsValidResourceReturnType(returnTypeFromMethod) && !IResource.class.equals(returnTypeFromMethod)) {
|
||||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
|
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns a collection with generic type " + toLogString(returnTypeFromMethod)
|
||||||
+ " returns a collection with generic type " + toLogString(returnTypeFromMethod) + " - Must return a resource type or a collection (List, Set) of a resource type");
|
+ " - Must return a resource type or a collection (List, Set) of a resource type");
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (!verifyIsValidResourceReturnType(returnTypeFromMethod)) {
|
if (!verifyIsValidResourceReturnType(returnTypeFromMethod)) {
|
||||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
|
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromMethod)
|
||||||
+ " returns " + toLogString(returnTypeFromMethod) + " - Must return a resource type");
|
+ " - Must return a resource type");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -197,13 +212,12 @@ public abstract class BaseMethodBinding {
|
||||||
if (returnTypeFromRp != null) {
|
if (returnTypeFromRp != null) {
|
||||||
if (returnTypeFromAnnotation != null && returnTypeFromAnnotation != IResource.class) {
|
if (returnTypeFromAnnotation != null && returnTypeFromAnnotation != IResource.class) {
|
||||||
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
|
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
|
||||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type "
|
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type " + returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName()
|
||||||
+ returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
|
+ " (or a subclass of it) per IResourceProvider contract");
|
||||||
}
|
}
|
||||||
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
|
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
|
||||||
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type "
|
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type " + returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return "
|
||||||
+ returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return " + returnTypeFromRp.getCanonicalName()
|
+ returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
|
||||||
+ " (or a subclass of it) per IResourceProvider contract");
|
|
||||||
}
|
}
|
||||||
returnType = returnTypeFromAnnotation;
|
returnType = returnTypeFromAnnotation;
|
||||||
} else {
|
} else {
|
||||||
|
@ -212,10 +226,10 @@ public abstract class BaseMethodBinding {
|
||||||
} else {
|
} else {
|
||||||
if (returnTypeFromAnnotation != IResource.class) {
|
if (returnTypeFromAnnotation != IResource.class) {
|
||||||
if (!verifyIsValidResourceReturnType(returnTypeFromAnnotation)) {
|
if (!verifyIsValidResourceReturnType(returnTypeFromAnnotation)) {
|
||||||
throw new ConfigurationException("Method '"+theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns "
|
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromAnnotation)
|
||||||
+ toLogString(returnTypeFromAnnotation) + " according to annotation - Must return a resource type");
|
+ " according to annotation - Must return a resource type");
|
||||||
}
|
}
|
||||||
returnType=returnTypeFromAnnotation;
|
returnType = returnTypeFromAnnotation;
|
||||||
} else {
|
} else {
|
||||||
returnType = (Class<? extends IResource>) returnTypeFromMethod;
|
returnType = (Class<? extends IResource>) returnTypeFromMethod;
|
||||||
}
|
}
|
||||||
|
@ -264,63 +278,6 @@ public abstract class BaseMethodBinding {
|
||||||
// return sm;
|
// 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) {
|
public static EncodingUtil determineResponseEncoding(HttpServletRequest theRequest, Map<String, String[]> theParams) {
|
||||||
String[] format = theParams.remove(Constants.PARAM_FORMAT);
|
String[] format = theParams.remove(Constants.PARAM_FORMAT);
|
||||||
if (format != null) {
|
if (format != null) {
|
||||||
|
@ -344,9 +301,61 @@ public abstract class BaseMethodBinding {
|
||||||
return EncodingUtil.XML;
|
return EncodingUtil.XML;
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException;
|
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.");
|
||||||
|
}
|
||||||
|
|
||||||
public abstract Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException,
|
}
|
||||||
BaseServerResponseException;
|
}
|
||||||
|
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.client.BaseClientInvocation;
|
||||||
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
|
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
|
||||||
import ca.uhn.fhir.rest.param.IParameter;
|
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.Constants;
|
||||||
import ca.uhn.fhir.rest.server.EncodingUtil;
|
import ca.uhn.fhir.rest.server.EncodingUtil;
|
||||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||||
|
@ -59,12 +58,10 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||||
|
|
||||||
abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
|
||||||
private List<IParameter> myParameters;
|
|
||||||
private boolean myReturnVoid;
|
private boolean myReturnVoid;
|
||||||
|
|
||||||
public BaseOutcomeReturningMethodBinding(Method theMethod, FhirContext theContext, Class<?> theMethodAnnotation, Object theProvider) {
|
public BaseOutcomeReturningMethodBinding(Method theMethod, FhirContext theContext, Class<?> theMethodAnnotation, Object theProvider) {
|
||||||
super(theMethod, theContext, theProvider);
|
super(theMethod, theContext, theProvider);
|
||||||
myParameters = ParameterUtil.getResourceParameters(theMethod);
|
|
||||||
|
|
||||||
if (!theMethod.getReturnType().equals(MethodOutcome.class)) {
|
if (!theMethod.getReturnType().equals(MethodOutcome.class)) {
|
||||||
if (!allowVoidReturnType()) {
|
if (!allowVoidReturnType()) {
|
||||||
|
@ -143,9 +140,9 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
|
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
|
||||||
Object[] params = new Object[myParameters.size()];
|
Object[] params = new Object[getParameters().size()];
|
||||||
for (int i = 0; i < myParameters.size(); i++) {
|
for (int i = 0; i < getParameters().size(); i++) {
|
||||||
IParameter param = myParameters.get(i);
|
IParameter param = getParameters().get(i);
|
||||||
if (param != null) {
|
if (param != null) {
|
||||||
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null);
|
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null);
|
||||||
}
|
}
|
||||||
|
@ -182,7 +179,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
||||||
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
|
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
theServer.addHapiHeader(theResponse);
|
theServer.addHeadersToResponse(theResponse);
|
||||||
|
|
||||||
Writer writer = theResponse.getWriter();
|
Writer writer = theResponse.getWriter();
|
||||||
try {
|
try {
|
||||||
|
@ -243,10 +240,6 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
|
||||||
|
|
||||||
protected abstract BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName);
|
protected abstract BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName);
|
||||||
|
|
||||||
protected List<IParameter> getParameters() {
|
|
||||||
return myParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void parseContentLocation(MethodOutcome theOutcomeToPopulate, String theLocationHeader) {
|
protected void parseContentLocation(MethodOutcome theOutcomeToPopulate, String theLocationHeader) {
|
||||||
String resourceNamePart = "/" + getResourceName() + "/";
|
String resourceNamePart = "/" + getResourceName() + "/";
|
||||||
int resourceIndex = theLocationHeader.lastIndexOf(resourceNamePart);
|
int resourceIndex = theLocationHeader.lastIndexOf(resourceNamePart);
|
||||||
|
|
|
@ -125,7 +125,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
|
||||||
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
|
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
|
||||||
}
|
}
|
||||||
|
|
||||||
theServer.addHapiHeader(theResponse);
|
theServer.addHeadersToResponse(theResponse);
|
||||||
|
|
||||||
if (response != null && response.getOperationOutcome() != null) {
|
if (response != null && response.getOperationOutcome() != null) {
|
||||||
theResponse.setContentType(encoding.getResourceContentType());
|
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 {
|
private void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingUtil theEncoding, HttpServletResponse theResponse) throws IOException {
|
||||||
theResponse.setStatus(theE.getStatusCode());
|
theResponse.setStatus(theE.getStatusCode());
|
||||||
|
|
||||||
theServer.addHapiHeader(theResponse);
|
theServer.addHeadersToResponse(theResponse);
|
||||||
|
|
||||||
if (theE.getOperationOutcome() != null) {
|
if (theE.getOperationOutcome() != null) {
|
||||||
theResponse.setContentType(theEncoding.getResourceContentType());
|
theResponse.setContentType(theEncoding.getResourceContentType());
|
||||||
|
|
|
@ -77,13 +77,11 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
||||||
|
|
||||||
private MethodReturnTypeEnum myMethodReturnType;
|
private MethodReturnTypeEnum myMethodReturnType;
|
||||||
private String myResourceName;
|
private String myResourceName;
|
||||||
private List<IParameter> myParameters;
|
|
||||||
|
|
||||||
public BaseResourceReturningMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, FhirContext theConetxt, Object theProvider) {
|
public BaseResourceReturningMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, FhirContext theConetxt, Object theProvider) {
|
||||||
super(theMethod, theConetxt, theProvider);
|
super(theMethod, theConetxt, theProvider);
|
||||||
|
|
||||||
myParameters = ParameterUtil.getResourceParameters(theMethod);
|
|
||||||
|
|
||||||
Class<?> methodReturnType = theMethod.getReturnType();
|
Class<?> methodReturnType = theMethod.getReturnType();
|
||||||
if (Collection.class.isAssignableFrom(methodReturnType)) {
|
if (Collection.class.isAssignableFrom(methodReturnType)) {
|
||||||
myMethodReturnType = MethodReturnTypeEnum.LIST_OF_RESOURCES;
|
myMethodReturnType = MethodReturnTypeEnum.LIST_OF_RESOURCES;
|
||||||
|
@ -92,8 +90,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
||||||
} else if (Bundle.class.isAssignableFrom(methodReturnType)) {
|
} else if (Bundle.class.isAssignableFrom(methodReturnType)) {
|
||||||
myMethodReturnType = MethodReturnTypeEnum.BUNDLE;
|
myMethodReturnType = MethodReturnTypeEnum.BUNDLE;
|
||||||
} else {
|
} else {
|
||||||
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: "
|
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
|
||||||
+ theMethod.getDeclaringClass().getCanonicalName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (theReturnResourceType != null) {
|
if (theReturnResourceType != null) {
|
||||||
|
@ -200,9 +197,9 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Method params
|
// Method params
|
||||||
Object[] params = new Object[myParameters.size()];
|
Object[] params = new Object[getParameters().size()];
|
||||||
for (int i = 0; i < myParameters.size(); i++) {
|
for (int i = 0; i < getParameters().size(); i++) {
|
||||||
IParameter param = myParameters.get(i);
|
IParameter param = getParameters().get(i);
|
||||||
if (param != null) {
|
if (param != null) {
|
||||||
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null);
|
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null);
|
||||||
}
|
}
|
||||||
|
@ -241,8 +238,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
||||||
return (IdDt) retValObj;
|
return (IdDt) retValObj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
|
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName());
|
||||||
+ IdDt.class.getCanonicalName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
|
private InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
|
||||||
|
@ -258,8 +254,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
||||||
return (InstantDt) retValObj;
|
return (InstantDt) retValObj;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
|
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + InstantDt.class.getCanonicalName());
|
||||||
+ InstantDt.class.getCanonicalName());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private IParser getNewParser(EncodingUtil theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) {
|
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);
|
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingUtil theResponseEncoding, String theServerBase,
|
private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingUtil theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser,
|
||||||
String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
|
NarrativeModeEnum theNarrativeMode) throws IOException {
|
||||||
assert !theServerBase.endsWith("/");
|
assert !theServerBase.endsWith("/");
|
||||||
|
|
||||||
theHttpResponse.setStatus(200);
|
theHttpResponse.setStatus(200);
|
||||||
|
@ -292,7 +287,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
||||||
|
|
||||||
theHttpResponse.setCharacterEncoding("UTF-8");
|
theHttpResponse.setCharacterEncoding("UTF-8");
|
||||||
|
|
||||||
theServer.addHapiHeader(theHttpResponse);
|
theServer.addHeadersToResponse(theHttpResponse);
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.getAuthorName().setValue(getClass().getCanonicalName());
|
bundle.getAuthorName().setValue(getClass().getCanonicalName());
|
||||||
|
@ -322,10 +317,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
||||||
b.append(resId);
|
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
|
if (getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_INSTANCE || getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_TYPE || getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) {
|
||||||
|| getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) {
|
|
||||||
IdDt versionId = getIdFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.VERSION_ID);
|
IdDt versionId = getIdFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.VERSION_ID);
|
||||||
if (versionId != null) {
|
if (versionId != null) {
|
||||||
b.append('/');
|
b.append('/');
|
||||||
|
@ -387,8 +382,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint,
|
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
|
||||||
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
|
|
||||||
|
|
||||||
theHttpResponse.setStatus(200);
|
theHttpResponse.setStatus(200);
|
||||||
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
|
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {
|
||||||
|
|
|
@ -20,26 +20,23 @@ package ca.uhn.fhir.rest.method;
|
||||||
* #L%
|
* #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.lang.reflect.Method;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import org.apache.commons.lang3.ObjectUtils;
|
import org.apache.commons.lang3.ObjectUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
|
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
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.annotation.History;
|
||||||
import ca.uhn.fhir.rest.client.BaseClientInvocation;
|
import ca.uhn.fhir.rest.client.BaseClientInvocation;
|
||||||
import ca.uhn.fhir.rest.client.GetClientInvocation;
|
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.Constants;
|
||||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
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 {
|
public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
private final Integer myIdParamIndex;
|
private final Integer myIdParamIndex;
|
||||||
|
private String myResourceName;
|
||||||
private final RestfulOperationTypeEnum myResourceOperationType;
|
private final RestfulOperationTypeEnum myResourceOperationType;
|
||||||
private final RestfulOperationSystemEnum mySystemOperationType;
|
private final RestfulOperationSystemEnum mySystemOperationType;
|
||||||
private String myResourceName;
|
|
||||||
private Integer mySinceParamIndex;
|
|
||||||
private Integer myCountParamIndex;
|
|
||||||
|
|
||||||
public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
|
public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
|
||||||
super(toReturnType(theMethod, theProvider), theMethod, theConetxt, theProvider);
|
super(toReturnType(theMethod, theProvider), theMethod, theConetxt, theProvider);
|
||||||
|
|
||||||
myIdParamIndex = Util.findIdParameterIndex(theMethod);
|
myIdParamIndex = Util.findIdParameterIndex(theMethod);
|
||||||
mySinceParamIndex = Util.findSinceParameterIndex(theMethod);
|
|
||||||
myCountParamIndex = Util.findCountParameterIndex(theMethod);
|
|
||||||
|
|
||||||
History historyAnnotation = theMethod.getAnnotation(History.class);
|
History historyAnnotation = theMethod.getAnnotation(History.class);
|
||||||
Class<? extends IResource> type = historyAnnotation.type();
|
Class<? extends IResource> type = historyAnnotation.type();
|
||||||
|
@ -93,6 +86,89 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestfulOperationTypeEnum getResourceOperationType() {
|
||||||
|
return myResourceOperationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ReturnTypeEnum getReturnType() {
|
||||||
|
return ReturnTypeEnum.BUNDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public RestfulOperationSystemEnum getSystemOperationType() {
|
||||||
|
return mySystemOperationType;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
|
||||||
|
StringBuilder b = new StringBuilder();
|
||||||
|
if (myResourceName != null) {
|
||||||
|
b.append(myResourceName);
|
||||||
|
if (myIdParamIndex != null) {
|
||||||
|
IdDt id = (IdDt) theArgs[myIdParamIndex];
|
||||||
|
if (id == null || isBlank(id.getValue())) {
|
||||||
|
throw new NullPointerException("ID can not be null");
|
||||||
|
}
|
||||||
|
b.append('/');
|
||||||
|
b.append(id.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (b.length() > 0) {
|
||||||
|
b.append('/');
|
||||||
|
}
|
||||||
|
b.append(Constants.PARAM_HISTORY);
|
||||||
|
|
||||||
|
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());
|
||||||
|
}
|
||||||
|
|
||||||
|
@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())) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (theRequest.getResourceName() == null) {
|
||||||
|
return mySystemOperationType == RestfulOperationSystemEnum.HISTORY_SYSTEM;
|
||||||
|
}
|
||||||
|
if (!ObjectUtils.equals(theRequest.getResourceName(), myResourceName)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean haveIdParam = theRequest.getId() != null && !theRequest.getId().isEmpty();
|
||||||
|
boolean wantIdParam = myIdParamIndex != null;
|
||||||
|
if (haveIdParam != wantIdParam) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (theRequest.getVersion() != null && !theRequest.getVersion().isEmpty()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
private static Class<? extends IResource> toReturnType(Method theMethod, Object theProvider) {
|
private static Class<? extends IResource> toReturnType(Method theMethod, Object theProvider) {
|
||||||
if (theProvider instanceof IResourceProvider) {
|
if (theProvider instanceof IResourceProvider) {
|
||||||
return ((IResourceProvider) theProvider).getResourceType();
|
return ((IResourceProvider) theProvider).getResourceType();
|
||||||
|
@ -105,100 +181,4 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public RestfulOperationTypeEnum getResourceOperationType() {
|
|
||||||
return myResourceOperationType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RestfulOperationSystemEnum getSystemOperationType() {
|
|
||||||
return mySystemOperationType;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
|
|
||||||
StringBuilder b = new StringBuilder();
|
|
||||||
if (myResourceName!=null) {
|
|
||||||
b.append(myResourceName);
|
|
||||||
if (myIdParamIndex!=null) {
|
|
||||||
IdDt id = (IdDt)theArgs[myIdParamIndex];
|
|
||||||
if (id==null||isBlank(id.getValue())) {
|
|
||||||
throw new NullPointerException("ID can not be null");
|
|
||||||
}
|
|
||||||
b.append('/');
|
|
||||||
b.append(id.getValue());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (b.length()>0) {
|
|
||||||
b.append('/');
|
|
||||||
}
|
|
||||||
b.append(Constants.PARAM_HISTORY);
|
|
||||||
|
|
||||||
return new GetClientInvocation(b.toString());
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") // ObjectUtils.equals is replaced by a JDK7 method..
|
|
||||||
@Override
|
|
||||||
public boolean matches(Request theRequest) {
|
|
||||||
if (!Constants.PARAM_HISTORY.equals(theRequest.getOperation())) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (theRequest.getResourceName() == null) {
|
|
||||||
return mySystemOperationType == RestfulOperationSystemEnum.HISTORY_SYSTEM;
|
|
||||||
}
|
|
||||||
if (!ObjectUtils.equals(theRequest.getResourceName(),myResourceName)) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean haveIdParam= theRequest.getId() != null && !theRequest.getId().isEmpty();
|
|
||||||
boolean wantIdParam = myIdParamIndex != null;
|
|
||||||
if (haveIdParam!=wantIdParam) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (theRequest.getVersion() != null && !theRequest.getVersion().isEmpty()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ReturnTypeEnum getReturnType() {
|
|
||||||
return ReturnTypeEnum.BUNDLE;
|
|
||||||
}
|
|
||||||
|
|
||||||
@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]);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,7 +37,6 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
|
||||||
import ca.uhn.fhir.rest.client.GetClientInvocation;
|
import ca.uhn.fhir.rest.client.GetClientInvocation;
|
||||||
import ca.uhn.fhir.rest.param.BaseQueryParameter;
|
import ca.uhn.fhir.rest.param.BaseQueryParameter;
|
||||||
import ca.uhn.fhir.rest.param.IParameter;
|
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.Constants;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
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 static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
|
||||||
|
|
||||||
private Class<?> myDeclaredResourceType;
|
private Class<?> myDeclaredResourceType;
|
||||||
private List<IParameter> myParameters;
|
|
||||||
private String myQueryName;
|
private String myQueryName;
|
||||||
|
|
||||||
public SearchMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, String theQueryName, FhirContext theContext, Object theProvider) {
|
public SearchMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, String theQueryName, FhirContext theContext, Object theProvider) {
|
||||||
super(theReturnResourceType, theMethod, theContext, theProvider);
|
super(theReturnResourceType, theMethod, theContext, theProvider);
|
||||||
this.myParameters = ParameterUtil.getResourceParameters(theMethod);
|
|
||||||
this.myQueryName = StringUtils.defaultIfBlank(theQueryName, null);
|
this.myQueryName = StringUtils.defaultIfBlank(theQueryName, null);
|
||||||
this.myDeclaredResourceType = theMethod.getReturnType();
|
this.myDeclaredResourceType = theMethod.getReturnType();
|
||||||
}
|
}
|
||||||
|
@ -63,10 +60,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
return myDeclaredResourceType.getClass();
|
return myDeclaredResourceType.getClass();
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<IParameter> getParameters() {
|
|
||||||
return myParameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RestfulOperationTypeEnum getResourceOperationType() {
|
public RestfulOperationTypeEnum getResourceOperationType() {
|
||||||
return RestfulOperationTypeEnum.SEARCH_TYPE;
|
return RestfulOperationTypeEnum.SEARCH_TYPE;
|
||||||
|
@ -84,7 +77,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
|
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>>();
|
Map<String, List<String>> queryStringArgs = new LinkedHashMap<String, List<String>>();
|
||||||
|
|
||||||
|
@ -94,7 +87,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
|
|
||||||
if (theArgs != null) {
|
if (theArgs != null) {
|
||||||
for (int idx = 0; idx < theArgs.length; idx++) {
|
for (int idx = 0; idx < theArgs.length; idx++) {
|
||||||
IParameter nextParam = myParameters.get(idx);
|
IParameter nextParam = getParameters().get(idx);
|
||||||
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], queryStringArgs);
|
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], queryStringArgs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -103,8 +96,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException,
|
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
|
||||||
InternalErrorException {
|
|
||||||
assert theRequest.getId() == null;
|
assert theRequest.getId() == null;
|
||||||
assert theRequest.getVersion() == null;
|
assert theRequest.getVersion() == null;
|
||||||
|
|
||||||
|
@ -138,11 +130,11 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> methodParamsTemp = new HashSet<String>();
|
Set<String> methodParamsTemp = new HashSet<String>();
|
||||||
for (int i = 0; i < this.myParameters.size(); i++) {
|
for (int i = 0; i < this.getParameters().size(); i++) {
|
||||||
if (!(myParameters.get(i) instanceof BaseQueryParameter)) {
|
if (!(getParameters().get(i) instanceof BaseQueryParameter)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
BaseQueryParameter temp = (BaseQueryParameter) myParameters.get(i);
|
BaseQueryParameter temp = (BaseQueryParameter) getParameters().get(i);
|
||||||
methodParamsTemp.add(temp.getName());
|
methodParamsTemp.add(temp.getName());
|
||||||
if (temp.isRequired() && !theRequest.getParameters().containsKey(temp.getName())) {
|
if (temp.isRequired() && !theRequest.getParameters().containsKey(temp.getName())) {
|
||||||
ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), 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;
|
return retVal;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setParameters(List<IParameter> parameters) {
|
|
||||||
this.myParameters = parameters;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setResourceType(Class<?> resourceType) {
|
public void setResourceType(Class<?> resourceType) {
|
||||||
this.myDeclaredResourceType = resourceType;
|
this.myDeclaredResourceType = resourceType;
|
||||||
}
|
}
|
||||||
|
|
|
@ -27,18 +27,16 @@ import java.net.URLDecoder;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import ca.uhn.fhir.rest.annotation.Count;
|
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Since;
|
|
||||||
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Created by dsotnikov on 2/25/2014.
|
* Created by dsotnikov on 2/25/2014.
|
||||||
*/
|
*/
|
||||||
class Util {
|
class Util {
|
||||||
public static Integer findCountParameterIndex(Method theMethod) {
|
// public static Integer findCountParameterIndex(Method theMethod) {
|
||||||
return findParamIndex(theMethod, Count.class);
|
// return findParamIndex(theMethod, Count.class);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public static Integer findIdParameterIndex(Method theMethod) {
|
public static Integer findIdParameterIndex(Method theMethod) {
|
||||||
return findParamIndex(theMethod, IdParam.class);
|
return findParamIndex(theMethod, IdParam.class);
|
||||||
|
@ -59,9 +57,9 @@ class Util {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Integer findSinceParameterIndex(Method theMethod) {
|
// public static Integer findSinceParameterIndex(Method theMethod) {
|
||||||
return findParamIndex(theMethod, Since.class);
|
// return findParamIndex(theMethod, Since.class);
|
||||||
}
|
// }
|
||||||
|
|
||||||
public static Integer findVersionIdParameterIndex(Method theMethod) {
|
public static Integer findVersionIdParameterIndex(Method theMethod) {
|
||||||
return findParamIndex(theMethod, VersionIdParam.class);
|
return findParamIndex(theMethod, VersionIdParam.class);
|
||||||
|
|
|
@ -20,7 +20,9 @@ package ca.uhn.fhir.rest.param;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -45,4 +47,6 @@ public interface IParameter {
|
||||||
*/
|
*/
|
||||||
Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException;
|
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%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
@ -110,4 +111,5 @@ public class IncludeParameter extends BaseQueryParameter {
|
||||||
return true;
|
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.annotation.Annotation;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Calendar;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.Date;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import javax.servlet.ServletRequest;
|
import javax.servlet.ServletRequest;
|
||||||
import javax.servlet.ServletResponse;
|
import javax.servlet.ServletResponse;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
|
|
||||||
|
import org.apache.commons.lang3.time.DateUtils;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.ConfigurationException;
|
import ca.uhn.fhir.context.ConfigurationException;
|
||||||
import ca.uhn.fhir.model.api.IResource;
|
import ca.uhn.fhir.model.api.IResource;
|
||||||
import ca.uhn.fhir.model.api.PathSpecification;
|
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.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.IncludeParam;
|
import ca.uhn.fhir.rest.annotation.IncludeParam;
|
||||||
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
import ca.uhn.fhir.rest.annotation.OptionalParam;
|
||||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
import ca.uhn.fhir.rest.annotation.ServerBase;
|
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.rest.annotation.VersionIdParam;
|
||||||
import ca.uhn.fhir.util.ReflectionUtil;
|
import ca.uhn.fhir.util.ReflectionUtil;
|
||||||
|
|
||||||
public class ParameterUtil {
|
public class ParameterUtil {
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
@SuppressWarnings("unchecked")
|
||||||
public static List<IParameter> getResourceParameters(Method method) {
|
public static List<IParameter> getResourceParameters(Method theMethod) {
|
||||||
List<IParameter> parameters = new ArrayList<IParameter>();
|
List<IParameter> parameters = new ArrayList<IParameter>();
|
||||||
|
|
||||||
Class<?>[] parameterTypes = method.getParameterTypes();
|
Class<?>[] parameterTypes = theMethod.getParameterTypes();
|
||||||
int paramIndex = 0;
|
int paramIndex = 0;
|
||||||
for (Annotation[] annotations : method.getParameterAnnotations()) {
|
for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
|
||||||
boolean haveHandledMethod = false;
|
|
||||||
|
|
||||||
|
IParameter param = null;
|
||||||
Class<?> parameterType = parameterTypes[paramIndex];
|
Class<?> parameterType = parameterTypes[paramIndex];
|
||||||
|
Class<? extends java.util.Collection<?>> outerCollectionType = null;
|
||||||
|
Class<? extends java.util.Collection<?>> innerCollectionType = null;
|
||||||
|
if (Collection.class.isAssignableFrom(parameterType)) {
|
||||||
|
innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
|
||||||
|
parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(theMethod, paramIndex);
|
||||||
|
}
|
||||||
|
if (Collection.class.isAssignableFrom(parameterType)) {
|
||||||
|
outerCollectionType = innerCollectionType;
|
||||||
|
innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
|
||||||
|
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)");
|
||||||
|
}
|
||||||
|
|
||||||
if (parameterType.equals(HttpServletRequest.class) || parameterType.equals(ServletRequest.class)) {
|
if (parameterType.equals(HttpServletRequest.class) || parameterType.equals(ServletRequest.class)) {
|
||||||
ServletRequestParameter param = new ServletRequestParameter();
|
param = new ServletRequestParameter();
|
||||||
parameters.add(param);
|
|
||||||
} else if (parameterType.equals(HttpServletResponse.class) || parameterType.equals(ServletResponse.class)) {
|
} else if (parameterType.equals(HttpServletResponse.class) || parameterType.equals(ServletResponse.class)) {
|
||||||
ServletResponseParameter param = new ServletResponseParameter();
|
param = new ServletResponseParameter();
|
||||||
parameters.add(param);
|
|
||||||
} else {
|
} else {
|
||||||
for (int i = 0; i < annotations.length; i++) {
|
for (int i = 0; i < annotations.length && param == null; i++) {
|
||||||
Annotation nextAnnotation = annotations[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);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Collection.class.isAssignableFrom(parameterType)) {
|
|
||||||
outerCollectionType = innerCollectionType;
|
|
||||||
innerCollectionType = (Class<? extends java.util.Collection<?>>) parameterType;
|
|
||||||
parameterType = ReflectionUtil.getGenericCollectionTypeOfMethodParameter(method, paramIndex);
|
|
||||||
}
|
|
||||||
|
|
||||||
IParameter param;
|
|
||||||
if (nextAnnotation instanceof RequiredParam) {
|
if (nextAnnotation instanceof RequiredParam) {
|
||||||
SearchParameter parameter = new SearchParameter();
|
SearchParameter parameter = new SearchParameter();
|
||||||
parameter.setName(((RequiredParam) nextAnnotation).name());
|
parameter.setName(((RequiredParam) nextAnnotation).name());
|
||||||
|
@ -94,43 +102,121 @@ public class ParameterUtil {
|
||||||
param = parameter;
|
param = parameter;
|
||||||
} else if (nextAnnotation instanceof IncludeParam) {
|
} else if (nextAnnotation instanceof IncludeParam) {
|
||||||
if (parameterType != PathSpecification.class || innerCollectionType == null || outerCollectionType != null) {
|
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<"
|
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" + PathSpecification.class.getSimpleName() + ">");
|
||||||
+ PathSpecification.class.getSimpleName() + ">");
|
|
||||||
}
|
}
|
||||||
Class<? extends Collection<PathSpecification>> instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(
|
Class<? extends Collection<PathSpecification>> instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'");
|
||||||
innerCollectionType, "Method '" + method.getName() + "'");
|
|
||||||
|
|
||||||
param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType);
|
param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType);
|
||||||
} else if (nextAnnotation instanceof ResourceParam) {
|
} else if (nextAnnotation instanceof ResourceParam) {
|
||||||
if (!IResource.class.isAssignableFrom(parameterType)) {
|
if (!IResource.class.isAssignableFrom(parameterType)) {
|
||||||
throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName()
|
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());
|
||||||
+ " but has a type that is not an implemtation of " + IResource.class.getCanonicalName());
|
|
||||||
}
|
}
|
||||||
param = new ResourceParameter((Class<? extends IResource>) parameterType);
|
param = new ResourceParameter((Class<? extends IResource>) parameterType);
|
||||||
} else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) {
|
} else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) {
|
||||||
param = null;
|
param = new NullParameter();
|
||||||
} else if (nextAnnotation instanceof ServerBase) {
|
} else if (nextAnnotation instanceof ServerBase) {
|
||||||
param = new ServerBaseParameter();
|
param = new ServerBaseParameter();
|
||||||
|
} else if (nextAnnotation instanceof Since) {
|
||||||
|
param = new SinceParameter();
|
||||||
|
} else if (nextAnnotation instanceof Count) {
|
||||||
|
param = new CountParameter();
|
||||||
} else {
|
} else {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
haveHandledMethod = true;
|
|
||||||
parameters.add(param);
|
|
||||||
break;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!haveHandledMethod) {
|
|
||||||
throw new ConfigurationException("Parameter #" + paramIndex + " of method '" + method.getName() + "' on type '" + method.getDeclaringClass().getCanonicalName()
|
|
||||||
+ "' has no recognized FHIR interface parameter annotations. Don't know how to handle this parameter");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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++;
|
paramIndex++;
|
||||||
}
|
}
|
||||||
return parameters;
|
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%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -52,6 +54,9 @@ public class ResourceParameter implements IParameter {
|
||||||
return myResourceName;
|
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%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -35,7 +37,7 @@ class ServerBaseParameter implements IParameter {
|
||||||
/*
|
/*
|
||||||
* Does nothing, since we just ignore serverbase arguments
|
* Does nothing, since we just ignore serverbase arguments
|
||||||
*/
|
*/
|
||||||
ourLog.trace("Ignoring HttpServletRequest argument: {}", theSourceClientArgument);
|
ourLog.trace("Ignoring server base argument: {}", theSourceClientArgument);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -43,4 +45,9 @@ class ServerBaseParameter implements IParameter {
|
||||||
return theRequest.getFhirServerBase();
|
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%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -43,4 +45,9 @@ class ServletRequestParameter implements IParameter {
|
||||||
return theRequest.getServletRequest();
|
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%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import java.util.Collection;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
@ -43,4 +45,10 @@ class ServletResponseParameter implements IParameter {
|
||||||
return theRequest.getServletResponse();
|
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
|
* #%L
|
|
@ -48,10 +48,9 @@ import ca.uhn.fhir.rest.method.Request;
|
||||||
import ca.uhn.fhir.rest.method.SearchMethodBinding;
|
import ca.uhn.fhir.rest.method.SearchMethodBinding;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
|
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.InternalErrorException;
|
||||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
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.ServerConformanceProvider;
|
||||||
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
|
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
|
||||||
import ca.uhn.fhir.util.VersionUtil;
|
import ca.uhn.fhir.util.VersionUtil;
|
||||||
|
@ -63,7 +62,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
private FhirContext myFhirContext;
|
private FhirContext myFhirContext;
|
||||||
private Collection<Object> myProviders;
|
private Collection<Object> myPlainProviders;
|
||||||
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
|
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
|
||||||
private Collection<IResourceProvider> myResourceProviders;
|
private Collection<IResourceProvider> myResourceProviders;
|
||||||
private ISecurityManager mySecurityManager;
|
private ISecurityManager mySecurityManager;
|
||||||
|
@ -73,16 +72,34 @@ public class RestfulServer extends HttpServlet {
|
||||||
private String myServerVersion = VersionUtil.getVersion(); // defaults to
|
private String myServerVersion = VersionUtil.getVersion(); // defaults to
|
||||||
// HAPI version
|
// HAPI version
|
||||||
private boolean myUseBrowserFriendlyContentTypes;
|
private boolean myUseBrowserFriendlyContentTypes;
|
||||||
|
private ResourceBinding myNullResourceBinding = new ResourceBinding();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
public RestfulServer() {
|
public RestfulServer() {
|
||||||
myFhirContext = new FhirContext();
|
myFhirContext = new FhirContext();
|
||||||
myServerConformanceProvider = new ServerConformanceProvider(this);
|
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() {
|
public FhirContext getFhirContext() {
|
||||||
return myFhirContext;
|
return myFhirContext;
|
||||||
}
|
}
|
||||||
|
@ -93,8 +110,8 @@ public class RestfulServer extends HttpServlet {
|
||||||
*
|
*
|
||||||
* @see #getResourceProviders()
|
* @see #getResourceProviders()
|
||||||
*/
|
*/
|
||||||
public Collection<Object> getProviders() {
|
public Collection<Object> getPlainProviders() {
|
||||||
return myProviders;
|
return myPlainProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Collection<ResourceBinding> getResourceBindings() {
|
public Collection<ResourceBinding> getResourceBindings() {
|
||||||
|
@ -152,6 +169,12 @@ public class RestfulServer extends HttpServlet {
|
||||||
return myServerVersion;
|
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
|
@Override
|
||||||
public final void init() throws ServletException {
|
public final void init() throws ServletException {
|
||||||
initialize();
|
initialize();
|
||||||
|
@ -183,7 +206,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Collection<Object> providers = getProviders();
|
Collection<Object> providers = getPlainProviders();
|
||||||
if (providers != null) {
|
if (providers != null) {
|
||||||
for (Object next : providers) {
|
for (Object next : providers) {
|
||||||
assertProviderIsValid(next);
|
assertProviderIsValid(next);
|
||||||
|
@ -214,12 +237,12 @@ public class RestfulServer extends HttpServlet {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the non-resource specific providers which implement method calls on
|
* Sets the non-resource specific providers which implement method calls on
|
||||||
* this server
|
* this server.
|
||||||
*
|
*
|
||||||
* @see #setResourceProviders(Collection)
|
* @see #setResourceProviders(Collection)
|
||||||
*/
|
*/
|
||||||
public void setProviders(Collection<Object> theProviders) {
|
public void setPlainProviders(Collection<Object> theProviders) {
|
||||||
myProviders = theProviders;
|
myPlainProviders = theProviders;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -229,7 +252,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
* @see #setResourceProviders(Collection)
|
* @see #setResourceProviders(Collection)
|
||||||
*/
|
*/
|
||||||
public void setProviders(Object... theProviders) {
|
public void setProviders(Object... theProviders) {
|
||||||
myProviders = Arrays.asList(theProviders);
|
myPlainProviders = Arrays.asList(theProviders);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -317,15 +340,19 @@ public class RestfulServer extends HttpServlet {
|
||||||
BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, myFhirContext, theProvider);
|
BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, myFhirContext, theProvider);
|
||||||
if (foundMethodBinding != null) {
|
if (foundMethodBinding != null) {
|
||||||
|
|
||||||
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(foundMethodBinding.getResourceName());
|
String resourceName = foundMethodBinding.getResourceName();
|
||||||
ResourceBinding resourceBinding;
|
ResourceBinding resourceBinding;
|
||||||
if (myResourceNameToProvider.containsKey(definition.getName())) {
|
if (resourceName == null) {
|
||||||
resourceBinding = myResourceNameToProvider.get(definition.getName());
|
resourceBinding = myNullResourceBinding;
|
||||||
} else {
|
} else {
|
||||||
resourceBinding = new ResourceBinding();
|
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceName);
|
||||||
String resourceName = definition.getName();
|
if (myResourceNameToProvider.containsKey(definition.getName())) {
|
||||||
resourceBinding.setResourceName(resourceName);
|
resourceBinding = myResourceNameToProvider.get(definition.getName());
|
||||||
myResourceNameToProvider.put(resourceName, resourceBinding);
|
} else {
|
||||||
|
resourceBinding = new ResourceBinding();
|
||||||
|
resourceBinding.setResourceName(resourceName);
|
||||||
|
myResourceNameToProvider.put(resourceName, resourceBinding);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
resourceBinding.addMethod(foundMethodBinding);
|
resourceBinding.addMethod(foundMethodBinding);
|
||||||
|
@ -453,18 +480,24 @@ public class RestfulServer extends HttpServlet {
|
||||||
|
|
||||||
StringTokenizer tok = new StringTokenizer(requestPath, "/");
|
StringTokenizer tok = new StringTokenizer(requestPath, "/");
|
||||||
if (!tok.hasMoreTokens()) {
|
if (!tok.hasMoreTokens()) {
|
||||||
throw new MethodNotFoundException("No resource name specified");
|
throw new ResourceNotFoundException("No resource name specified");
|
||||||
}
|
}
|
||||||
resourceName = tok.nextToken();
|
resourceName = tok.nextToken();
|
||||||
|
if (resourceName.startsWith("_")) {
|
||||||
|
operation = resourceName;
|
||||||
|
resourceName = null;
|
||||||
|
}
|
||||||
|
|
||||||
ResourceBinding resourceBinding = null;
|
ResourceBinding resourceBinding = null;
|
||||||
BaseMethodBinding resourceMethod = null;
|
BaseMethodBinding resourceMethod = null;
|
||||||
if ("metadata".equals(resourceName)) {
|
if ("metadata".equals(resourceName)) {
|
||||||
resourceMethod = myServerConformanceMethod;
|
resourceMethod = myServerConformanceMethod;
|
||||||
|
} else if (resourceName == null) {
|
||||||
|
resourceBinding = myNullResourceBinding;
|
||||||
} else {
|
} else {
|
||||||
resourceBinding = myResourceNameToProvider.get(resourceName);
|
resourceBinding = myResourceNameToProvider.get(resourceName);
|
||||||
if (resourceBinding == null) {
|
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);
|
resourceMethod = resourceBinding.getMethod(r);
|
||||||
}
|
}
|
||||||
if (null == resourceMethod) {
|
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);
|
resourceMethod.invokeServer(this, r, theResponse);
|
||||||
|
|
||||||
} catch (AuthenticationException e) {
|
} catch (AuthenticationException e) {
|
||||||
theResponse.setStatus(e.getStatusCode());
|
theResponse.setStatus(e.getStatusCode());
|
||||||
addHapiHeader(theResponse);
|
addHeadersToResponse(theResponse);
|
||||||
theResponse.setContentType("text/plain");
|
theResponse.setContentType("text/plain");
|
||||||
theResponse.setCharacterEncoding("UTF-8");
|
theResponse.setCharacterEncoding("UTF-8");
|
||||||
theResponse.getWriter().write(e.getMessage());
|
theResponse.getWriter().write(e.getMessage());
|
||||||
|
@ -535,7 +568,7 @@ public class RestfulServer extends HttpServlet {
|
||||||
}
|
}
|
||||||
|
|
||||||
theResponse.setStatus(e.getStatusCode());
|
theResponse.setStatus(e.getStatusCode());
|
||||||
addHapiHeader(theResponse);
|
addHeadersToResponse(theResponse);
|
||||||
theResponse.setContentType("text/plain");
|
theResponse.setContentType("text/plain");
|
||||||
theResponse.setCharacterEncoding("UTF-8");
|
theResponse.setCharacterEncoding("UTF-8");
|
||||||
theResponse.getWriter().append(e.getMessage());
|
theResponse.getWriter().append(e.getMessage());
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package ca.uhn.fhir.rest.server.exceptions;
|
package ca.uhn.fhir.rest.server.exceptions;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* #%L
|
* #%L
|
||||||
* HAPI FHIR Library
|
* 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 {
|
public class AuthenticationException extends BaseServerResponseException {
|
||||||
|
|
||||||
|
@ -36,7 +39,4 @@ public class AuthenticationException extends BaseServerResponseException {
|
||||||
super(401, theMessage);
|
super(401, theMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public AuthenticationException(int theStatusCode, String theMessage) {
|
|
||||||
super(theStatusCode, theMessage);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,6 +20,9 @@ package ca.uhn.fhir.rest.server.exceptions;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* TODO: javadoc this
|
||||||
|
*/
|
||||||
public class InternalErrorException extends BaseServerResponseException {
|
public class InternalErrorException extends BaseServerResponseException {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
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 {
|
public class MethodNotAllowedException extends BaseServerResponseException {
|
||||||
private static final long serialVersionUID = 1L;
|
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.dstu.composite.IdentifierDt;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
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 class ResourceNotFoundException extends BaseServerResponseException {
|
||||||
|
|
||||||
public ResourceNotFoundException(IdDt theId) {
|
public ResourceNotFoundException(IdDt theId) {
|
||||||
|
|
|
@ -20,10 +20,14 @@ package ca.uhn.fhir.rest.server.exceptions;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.annotation.Delete;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Update;
|
||||||
import ca.uhn.fhir.rest.server.Constants;
|
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 {
|
public class ResourceVersionConflictException extends BaseServerResponseException {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
|
@ -20,10 +20,12 @@ package ca.uhn.fhir.rest.server.exceptions;
|
||||||
* #L%
|
* #L%
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import ca.uhn.fhir.rest.annotation.Update;
|
||||||
import ca.uhn.fhir.rest.server.Constants;
|
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.
|
* be specified in an HTTP header, and none was.
|
||||||
*/
|
*/
|
||||||
public class ResourceVersionNotSpecifiedException extends BaseServerResponseException {
|
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
|
* 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 {
|
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;
|
package example;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import javax.servlet.annotation.WebServlet;
|
import javax.servlet.annotation.WebServlet;
|
||||||
|
@ -21,17 +20,18 @@ public class ExampleRestfulServlet extends RestfulServer {
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Restful servers must provide an implementation of this method, which
|
* Constructor
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
@Override
|
public ExampleRestfulServlet() {
|
||||||
public Collection<IResourceProvider> getResourceProviders() {
|
/*
|
||||||
List<IResourceProvider> retVal = new ArrayList<IResourceProvider>();
|
* The servlet defines any number of resource providers, and
|
||||||
retVal.add(new RestfulPatientResourceProvider());
|
* configures itself to use them by calling
|
||||||
retVal.add(new RestfulObservationResourceProvider());
|
* setResourceProviders()
|
||||||
return retVal;
|
*/
|
||||||
|
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() {
|
public static void parseMsg() {
|
||||||
FhirContext ctx = new FhirContext(Patient.class, Observation.class);
|
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.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.parser.DataFormatException;
|
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.Create;
|
||||||
import ca.uhn.fhir.rest.annotation.History;
|
import ca.uhn.fhir.rest.annotation.History;
|
||||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
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.RequiredParam;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
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.Update;
|
||||||
import ca.uhn.fhir.rest.annotation.Validate;
|
import ca.uhn.fhir.rest.annotation.Validate;
|
||||||
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.client.ITestClient;
|
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.client.api.IRestfulClient;
|
||||||
import ca.uhn.fhir.rest.param.CodingListParam;
|
import ca.uhn.fhir.rest.param.CodingListParam;
|
||||||
import ca.uhn.fhir.rest.param.DateRangeParam;
|
import ca.uhn.fhir.rest.param.DateRangeParam;
|
||||||
|
@ -443,22 +446,29 @@ public interface MetadataClient extends IRestfulClient {
|
||||||
}
|
}
|
||||||
//END SNIPPET: metadataClient
|
//END SNIPPET: metadataClient
|
||||||
|
|
||||||
public interface HistoryClient {
|
|
||||||
//START SNIPPET: historyClient
|
//START SNIPPET: historyClient
|
||||||
// Server level (history of ALL resources)
|
public interface HistoryClient extends IBasicClient {
|
||||||
@History
|
/** Server level (history of ALL resources) */
|
||||||
Bundle getHistoryServer();
|
@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)
|
@History(type=Patient.class)
|
||||||
Bundle getHistoryPatientType();
|
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)
|
@History(type=Patient.class)
|
||||||
Bundle getHistoryPatientInstance(@IdParam IdDt theId);
|
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 {
|
public void bbbbb() throws DataFormatException, IOException {
|
||||||
|
|
|
@ -110,6 +110,9 @@
|
||||||
<param name="file" value="src/site/example/java/example/FhirContextIntro.java" />
|
<param name="file" value="src/site/example/java/example/FhirContextIntro.java" />
|
||||||
</macro>
|
</macro>
|
||||||
|
|
||||||
|
<!-- ****** The section below on fluent references the snippet above
|
||||||
|
****** so be careful about any reordering! -->
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
This code gives the following output:
|
This code gives the following output:
|
||||||
</p>
|
</p>
|
||||||
|
@ -129,6 +132,26 @@
|
||||||
|
|
||||||
</subsection>
|
</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">
|
<subsection name="JSON Encoding">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
|
|
@ -10,6 +10,14 @@
|
||||||
|
|
||||||
<section name="Implementing Resource Provider Operations">
|
<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>
|
<p>
|
||||||
RESTful Clients and Servers both share the same
|
RESTful Clients and Servers both share the same
|
||||||
method pattern, with one key difference: A client
|
method pattern, with one key difference: A client
|
||||||
|
@ -26,6 +34,10 @@
|
||||||
implementations, but client methods will follow the same patterns.
|
implementations, but client methods will follow the same patterns.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<a name="operations"/>
|
||||||
|
</section>
|
||||||
|
|
||||||
|
<section name="Operations">
|
||||||
<p>
|
<p>
|
||||||
The following table lists the operations supported by
|
The following table lists the operations supported by
|
||||||
HAPI FHIR RESTful Servers and Clients.
|
HAPI FHIR RESTful Servers and Clients.
|
||||||
|
@ -81,7 +93,7 @@
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="#type_create">Type - Create</a>
|
<a href="#type_search">Type - Create</a>
|
||||||
</td>
|
</td>
|
||||||
<td>
|
<td>
|
||||||
Create a new resource with a server assigned id
|
Create a new resource with a server assigned id
|
||||||
|
@ -99,14 +111,6 @@
|
||||||
Search the resource type based on some filter criteria
|
Search the resource type based on some filter criteria
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
|
||||||
<td>
|
|
||||||
<a href="#type_search">Type - Search</a>
|
|
||||||
</td>
|
|
||||||
<td>
|
|
||||||
Search the resource type based on some filter criteria
|
|
||||||
</td>
|
|
||||||
</tr>
|
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<td>
|
||||||
<a href="#history">Type - History</a>
|
<a href="#history">Type - History</a>
|
||||||
|
@ -892,7 +896,7 @@
|
||||||
<section name="System Level - Transaction">
|
<section name="System Level - Transaction">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Not yet implemented
|
Not yet implemented - Get in touch if you would like to help!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a name="system_search" />
|
<a name="system_search" />
|
||||||
|
@ -905,7 +909,7 @@
|
||||||
<section name="System Level - Search">
|
<section name="System Level - Search">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
Not yet implemented
|
Not yet implemented - Get in touch if you would like to help!
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<a name="history" />
|
<a name="history" />
|
||||||
|
@ -945,18 +949,38 @@
|
||||||
annotated with the
|
annotated with the
|
||||||
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
|
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
|
||||||
annotation, indicating the ID of the resource for which to return history.
|
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>
|
||||||
<li>
|
<li>
|
||||||
For an
|
For a
|
||||||
<b>Type History</b>
|
<b>Type History</b>
|
||||||
method, the method must not have any @IdParam parameter.
|
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>
|
||||||
<li>
|
<li>
|
||||||
For an
|
For a
|
||||||
<b>Server History</b>
|
<b>Server History</b>
|
||||||
method, the method must not have any @IdParam parameter
|
method, the method must not have any @IdParam parameter, and
|
||||||
and must not be found in a ResourceProvider definition.
|
must not have a <code>type()</code> value specified in
|
||||||
<!-- TODO: make ResourceProvider a link to a defintion of these on the RESTFul server page -->
|
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>
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<p>
|
<p>
|
||||||
|
@ -977,6 +1001,32 @@
|
||||||
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
|
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
|
||||||
</macro>
|
</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>
|
</section>
|
||||||
|
|
||||||
</body>
|
</body>
|
||||||
|
|
|
@ -26,6 +26,7 @@
|
||||||
be possible to create a FHIR compliant server quickly and easily.
|
be possible to create a FHIR compliant server quickly and easily.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
<a name="resource_providers"/>
|
||||||
<subsection name="Defining Resource Providers">
|
<subsection name="Defining Resource Providers">
|
||||||
|
|
||||||
<p>
|
<p>
|
||||||
|
@ -90,6 +91,54 @@
|
||||||
<param name="file" value="src/site/example/java/example/ExampleRestfulServlet.java" />
|
<param name="file" value="src/site/example/java/example/ExampleRestfulServlet.java" />
|
||||||
</macro>
|
</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>
|
||||||
|
|
||||||
<subsection name="Deploy">
|
<subsection name="Deploy">
|
||||||
|
@ -100,6 +149,12 @@
|
||||||
any JEE container (Tomcat, Websphere, Glassfish, etc).
|
any JEE container (Tomcat, Websphere, Glassfish, etc).
|
||||||
</p>
|
</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>
|
</subsection>
|
||||||
|
|
||||||
</section>
|
</section>
|
||||||
|
@ -167,6 +222,22 @@
|
||||||
</section>
|
</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>
|
</body>
|
||||||
|
|
||||||
</document>
|
</document>
|
||||||
|
|
|
@ -3,6 +3,7 @@ package ca.uhn.fhir.rest.client;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Mockito.*;
|
import static org.mockito.Mockito.*;
|
||||||
|
|
||||||
|
import java.io.InputStream;
|
||||||
import java.io.StringReader;
|
import java.io.StringReader;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
@ -27,6 +28,8 @@ import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
|
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.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
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.dstu.valueset.QuantityCompararatorEnum;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.InstantDt;
|
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.model.primitive.StringDt;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
import ca.uhn.fhir.rest.param.CodingListParam;
|
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
|
@Test
|
||||||
public void testHistoryResourceType() throws Exception {
|
public void testHistoryResourceType() throws Exception {
|
||||||
|
|
||||||
|
@ -408,7 +456,7 @@ public class ClientTest {
|
||||||
DateRangeParam param = new DateRangeParam();
|
DateRangeParam param = new DateRangeParam();
|
||||||
param.setLowerBound(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-01"));
|
param.setLowerBound(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-01"));
|
||||||
param.setUpperBound(new QualifiedDateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, "2021-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());
|
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();
|
CodingListParam identifiers = new CodingListParam();
|
||||||
identifiers.add(new CodingDt("foo", "bar"));
|
identifiers.add(new CodingDt("foo", "bar"));
|
||||||
identifiers.add(new CodingDt("baz", "boz"));
|
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());
|
assertEquals("http://foo/Patient?ids=foo%7Cbar%2Cbaz%7Cboz", capt.getValue().getURI().toString());
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ca.uhn.fhir.rest.client;
|
package ca.uhn.fhir.rest.client;
|
||||||
|
|
||||||
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
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.DiagnosticReport;
|
||||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
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.model.primitive.StringDt;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Count;
|
||||||
import ca.uhn.fhir.rest.annotation.Create;
|
import ca.uhn.fhir.rest.annotation.Create;
|
||||||
import ca.uhn.fhir.rest.annotation.Delete;
|
import ca.uhn.fhir.rest.annotation.Delete;
|
||||||
import ca.uhn.fhir.rest.annotation.History;
|
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.RequiredParam;
|
||||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
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.Update;
|
||||||
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
import ca.uhn.fhir.rest.annotation.VersionIdParam;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
|
@ -71,6 +76,12 @@ public interface ITestClient extends IBasicClient {
|
||||||
@History(type=Patient.class)
|
@History(type=Patient.class)
|
||||||
Bundle getHistoryPatientInstance(@IdParam IdDt theId);
|
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)
|
@History(type=Patient.class)
|
||||||
Bundle getHistoryPatientType();
|
Bundle getHistoryPatientType();
|
||||||
|
|
||||||
|
|
|
@ -2,7 +2,9 @@ package ca.uhn.fhir.rest.server;
|
||||||
|
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.concurrent.TimeUnit;
|
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.server.Server;
|
||||||
import org.eclipse.jetty.servlet.ServletHandler;
|
import org.eclipse.jetty.servlet.ServletHandler;
|
||||||
import org.eclipse.jetty.servlet.ServletHolder;
|
import org.eclipse.jetty.servlet.ServletHolder;
|
||||||
import org.junit.AfterClass;
|
import org.hamcrest.core.IsEqual;
|
||||||
import org.junit.BeforeClass;
|
import org.hamcrest.core.StringStartsWith;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.model.api.Bundle;
|
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.HumanNameDt;
|
||||||
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
|
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.resource.Patient;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
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.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.IdParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Read;
|
import ca.uhn.fhir.rest.annotation.Read;
|
||||||
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
import ca.uhn.fhir.rest.annotation.RequiredParam;
|
||||||
import ca.uhn.fhir.rest.annotation.Search;
|
import ca.uhn.fhir.rest.annotation.Search;
|
||||||
|
import ca.uhn.fhir.rest.annotation.Since;
|
||||||
import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
import ca.uhn.fhir.testutil.RandomServerPortProvider;
|
||||||
|
|
||||||
public class NonResourceProviderServerTest {
|
public class NonResourceProviderServerTest {
|
||||||
|
|
||||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(NonResourceProviderServerTest.class);
|
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(NonResourceProviderServerTest.class);
|
||||||
private static int ourPort;
|
private int myPort;
|
||||||
private static Server ourServer;
|
private Server myServer;
|
||||||
private static CloseableHttpClient ourClient;
|
private CloseableHttpClient myClient;
|
||||||
private static FhirContext ourCtx;
|
private FhirContext myCtx;
|
||||||
|
private RestfulServer myRestfulServer;
|
||||||
|
|
||||||
@BeforeClass
|
@Before
|
||||||
public static void beforeClass() throws Exception {
|
public void before() throws Exception {
|
||||||
ourPort = RandomServerPortProvider.findFreePort();
|
myPort = RandomServerPortProvider.findFreePort();
|
||||||
ourServer = new Server(ourPort);
|
myServer = new Server(myPort);
|
||||||
ourCtx = new FhirContext(Patient.class);
|
myCtx = new FhirContext(Patient.class);
|
||||||
|
|
||||||
ServletHandler proxyHandler = new ServletHandler();
|
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/*");
|
proxyHandler.addServletWithMapping(servletHolder, "/fhir/context/*");
|
||||||
ourServer.setHandler(proxyHandler);
|
myServer.setHandler(proxyHandler);
|
||||||
ourServer.start();
|
|
||||||
|
|
||||||
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
|
||||||
HttpClientBuilder builder = HttpClientBuilder.create();
|
HttpClientBuilder builder = HttpClientBuilder.create();
|
||||||
builder.setConnectionManager(connectionManager);
|
builder.setConnectionManager(connectionManager);
|
||||||
ourClient = builder.build();
|
myClient = builder.build();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterClass
|
@After
|
||||||
public static void afterClass() throws Exception {
|
public void after() throws Exception {
|
||||||
ourServer.stop();
|
myServer.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSearchByParamIdentifier() throws Exception {
|
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";
|
String uri = baseUri + "/Patient?identifier=urn:hapitest:mrns%7C00001";
|
||||||
HttpGet httpGet = new HttpGet(uri);
|
HttpGet httpGet = new HttpGet(uri);
|
||||||
HttpResponse status = ourClient.execute(httpGet);
|
HttpResponse status = myClient.execute(httpGet);
|
||||||
|
|
||||||
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
ourLog.info("Response was:\n{}", responseContent);
|
ourLog.info("Response was:\n{}", responseContent);
|
||||||
|
|
||||||
assertEquals(200, status.getStatusLine().getStatusCode());
|
assertEquals(200, status.getStatusLine().getStatusCode());
|
||||||
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
|
Bundle bundle = myCtx.newXmlParser().parseBundle(responseContent);
|
||||||
|
|
||||||
assertEquals(1, bundle.getEntries().size());
|
assertEquals(1, bundle.getEntries().size());
|
||||||
|
|
||||||
|
@ -88,33 +104,59 @@ public class NonResourceProviderServerTest {
|
||||||
assertEquals(baseUri, bundle.getLinkBase().getValue());
|
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() {
|
String responseContent = IOUtils.toString(status.getEntity().getContent());
|
||||||
setProviders(new DummyProvider());
|
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.
|
* Created by dsotnikov on 2/25/2014.
|
||||||
*/
|
*/
|
||||||
public static class DummyProvider {
|
public static class SearchProvider {
|
||||||
|
|
||||||
public Map<String, Patient> getIdToPatient() {
|
public Map<String, Patient> getIdToPatient() {
|
||||||
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
|
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
|
||||||
{
|
{
|
||||||
Patient patient = new Patient();
|
Patient patient = createPatient();
|
||||||
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");
|
|
||||||
idToPatient.put("1", patient);
|
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/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="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="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/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">
|
<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>
|
<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!/"/>
|
<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>
|
</attributes>
|
||||||
</classpathentry>
|
</classpathentry>
|
||||||
|
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-base"/>
|
||||||
<classpathentry kind="output" path="target/classes"/>
|
<classpathentry kind="output" path="target/classes"/>
|
||||||
</classpath>
|
</classpath>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
package ca.uhn.example.rest;
|
package ca.uhn.example.rest;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
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.OperationOutcome;
|
||||||
import ca.uhn.fhir.model.dstu.resource.Patient;
|
import ca.uhn.fhir.model.dstu.resource.Patient;
|
||||||
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
|
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.dstu.valueset.IssueSeverityEnum;
|
||||||
import ca.uhn.fhir.model.primitive.IdDt;
|
import ca.uhn.fhir.model.primitive.IdDt;
|
||||||
import ca.uhn.fhir.model.primitive.StringDt;
|
import ca.uhn.fhir.model.primitive.StringDt;
|
||||||
|
|
Loading…
Reference in New Issue