Lots of documentation (site and javadoc) updates, and make since and count params work for history operation

This commit is contained in:
jamesagnew 2014-04-27 22:17:27 -04:00
parent 6877c0628e
commit e69464f34d
49 changed files with 1225 additions and 485 deletions

View File

@ -248,11 +248,13 @@
<artifactId>maven-jxr-plugin</artifactId>
<version>2.4</version>
</plugin>
<!--
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>findbugs-maven-plugin</artifactId>
<version>2.5.3</version>
</plugin>
-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-project-info-reports-plugin</artifactId>

View File

@ -160,7 +160,7 @@ public abstract class BaseRuntimeDeclaredChildDefinition extends BaseRuntimeChil
}
}
private final class ListAccessor implements IAccessor {
private final static class ListAccessor implements IAccessor {
private final Method myAccessorMethod;
private ListAccessor(Method theAccessor) {

View File

@ -27,6 +27,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang3.Validate;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.resource.Patient;
@ -117,6 +119,8 @@ public class FhirContext {
*/
@SuppressWarnings("unchecked")
public RuntimeResourceDefinition getResourceDefinition(String theResourceName) {
Validate.notBlank(theResourceName, "Resource name must not be blank");
RuntimeResourceDefinition retVal = myNameToElementDefinition.get(theResourceName);
if (retVal == null) {

View File

@ -41,13 +41,16 @@ public abstract class BasePrimitive<T> extends BaseElement implements IPrimitive
}
@Override
public boolean equals(Object obj) {
if (!(obj.getClass() == getClass())) {
public boolean equals(Object theObj) {
if (theObj == null) {
return false;
}
if (!(theObj.getClass() == getClass())) {
return false;
}
BasePrimitive<?> o = (BasePrimitive<?>)obj;
BasePrimitive<?> o = (BasePrimitive<?>) theObj;
EqualsBuilder b = new EqualsBuilder();
b.append(getValue(), o.getValue());
return b.isEquals();

View File

@ -26,8 +26,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.IElement;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
@Retention(RetentionPolicy.RUNTIME)
@Target(value= {ElementType.FIELD})

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.model.primitive;
* #L%
*/
import java.util.Calendar;
import java.util.Date;
import java.util.TimeZone;
@ -67,6 +68,15 @@ public class InstantDt extends BaseDateTimeDt {
setTimeZone(TimeZone.getDefault());
}
/**
* Create a new DateTimeDt
*/
public InstantDt(Calendar theCalendar) {
setValue(theCalendar.getTime());
setPrecision(DEFAULT_PRECISION);
setTimeZone(theCalendar.getTimeZone());
}
/**
* Create a new InstantDt from a string value
*

View File

@ -26,16 +26,26 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.server.IResourceProvider;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Read {
/**
* The return type for this search method. This generally does not need
* to be populated for a server implementation, since servers will return
* only one resource per class, but generally does need to be populated
* for client implementations.
* The return type for this method. This generally does not need
* to be populated for {@link IResourceProvider resource providers} in a server implementation,
* but often does need to be populated in client implementations using {@link IBasicClient} or
* {@link IRestfulClient}, or in plain providers on a server.
* <p>
* This value also does not need to be populated if the return type for a method annotated with
* this annotation is sufficient to determine the type of resource provided. E.g. if the
* method returns <code>Patient</code> or <code>List&lt;Patient&gt;</code>, the server/client
* will automatically determine that the Patient resource is the return type, and this value
* may be left blank.
* </p>
*/
// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
Class<? extends IResource> type() default IResource.class;

View File

@ -26,6 +26,9 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.server.IResourceProvider;
/**
@ -49,12 +52,19 @@ public @interface Search {
String queryName() default "";
/**
* The return type for this search method. This generally does not need
* to be populated for a server implementation, since servers will return
* only one resource per class, but generally does need to be populated
* for client implementations.
* The return type for this method. This generally does not need
* to be populated for {@link IResourceProvider resource providers} in a server implementation,
* but often does need to be populated in client implementations using {@link IBasicClient} or
* {@link IRestfulClient}, or in plain providers on a server.
* <p>
* This value also does not need to be populated if the return type for a method annotated with
* this annotation is sufficient to determine the type of resource provided. E.g. if the
* method returns <code>Patient</code> or <code>List&lt;Patient&gt;</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;
}

View File

@ -27,7 +27,7 @@ import java.lang.annotation.Target;
/**
* Parameter annotation for the _since parameter, which indicates to the
* server that only results dated since the given instant will be returned.
* server that only results dated since the given instant will be returned.
*
* @see History
*/

View File

@ -55,6 +55,8 @@ import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.IResourceProvider;
@ -65,8 +67,9 @@ import ca.uhn.fhir.util.ReflectionUtil;
public abstract class BaseMethodBinding {
private Method myMethod;
private FhirContext myContext;
private Method myMethod;
private List<IParameter> myParameters;
private Object myProvider;
public BaseMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
@ -76,31 +79,7 @@ public abstract class BaseMethodBinding {
myMethod = theMethod;
myContext = theConetxt;
myProvider = theProvider;
}
/**
* 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;
myParameters = ParameterUtil.getResourceParameters(theMethod);
}
public FhirContext getContext() {
@ -111,12 +90,26 @@ public abstract class BaseMethodBinding {
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 RestfulOperationSystemEnum getSystemOperationType();
public abstract BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException;
public abstract Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map<String, List<String>> theHeaders) throws IOException, BaseServerResponseException;
public abstract void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException;
public abstract boolean matches(Request theRequest);
protected IParser createAppropriateParser(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) throws IOException {
@ -131,6 +124,29 @@ public abstract class BaseMethodBinding {
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")
public static BaseMethodBinding bindMethod(Method theMethod, FhirContext theContext, Object theProvider) {
Read read = theMethod.getAnnotation(Read.class);
@ -152,28 +168,27 @@ public abstract class BaseMethodBinding {
if (theProvider instanceof IResourceProvider) {
returnTypeFromRp = ((IResourceProvider) theProvider).getResourceType();
if (!verifyIsValidResourceReturnType(returnTypeFromRp)) {
throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned "
+ toLogString(returnTypeFromRp) + " - Must return a resource type");
throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned " + toLogString(returnTypeFromRp) + " - Must return a resource type");
}
}
Class<?> returnTypeFromMethod = theMethod.getReturnType();
if (MethodOutcome.class.equals(returnTypeFromMethod)) {
// returns a method outcome
}else if (Bundle.class.equals(returnTypeFromMethod)) {
} else if (Bundle.class.equals(returnTypeFromMethod)) {
// returns a bundle
}else if (void.class.equals(returnTypeFromMethod)) {
} else if (void.class.equals(returnTypeFromMethod)) {
// returns a bundle
} else if (Collection.class.isAssignableFrom(returnTypeFromMethod)) {
returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod);
if (!verifyIsValidResourceReturnType(returnTypeFromMethod)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
+ " returns a collection with generic type " + toLogString(returnTypeFromMethod) + " - Must return a resource type or a collection (List, Set) of a resource type");
if (!verifyIsValidResourceReturnType(returnTypeFromMethod) && !IResource.class.equals(returnTypeFromMethod)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns a collection with generic type " + toLogString(returnTypeFromMethod)
+ " - Must return a resource type or a collection (List, Set) of a resource type");
}
} else {
if (!verifyIsValidResourceReturnType(returnTypeFromMethod)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName()
+ " returns " + toLogString(returnTypeFromMethod) + " - Must return a resource type");
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromMethod)
+ " - Must return a resource type");
}
}
@ -197,13 +212,12 @@ public abstract class BaseMethodBinding {
if (returnTypeFromRp != null) {
if (returnTypeFromAnnotation != null && returnTypeFromAnnotation != IResource.class) {
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type "
+ returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type " + returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName()
+ " (or a subclass of it) per IResourceProvider contract");
}
if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) {
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type "
+ returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return " + returnTypeFromRp.getCanonicalName()
+ " (or a subclass of it) per IResourceProvider contract");
throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type " + returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return "
+ returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract");
}
returnType = returnTypeFromAnnotation;
} else {
@ -212,10 +226,10 @@ public abstract class BaseMethodBinding {
} else {
if (returnTypeFromAnnotation != IResource.class) {
if (!verifyIsValidResourceReturnType(returnTypeFromAnnotation)) {
throw new ConfigurationException("Method '"+theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns "
+ toLogString(returnTypeFromAnnotation) + " according to annotation - Must return a resource type");
throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromAnnotation)
+ " according to annotation - Must return a resource type");
}
returnType=returnTypeFromAnnotation;
returnType = returnTypeFromAnnotation;
} else {
returnType = (Class<? extends IResource>) returnTypeFromMethod;
}
@ -264,63 +278,6 @@ public abstract class BaseMethodBinding {
// return sm;
}
private static boolean verifyIsValidResourceReturnType(Class<?> theReturnType) {
if (theReturnType == null) {
return false;
}
if (!IResource.class.isAssignableFrom(theReturnType)) {
return false;
}
boolean retVal = Modifier.isAbstract(theReturnType.getModifiers()) == false;
return retVal;
}
private static String toLogString(Class<?> theType) {
if (theType == null) {
return null;
}
return theType.getCanonicalName();
}
public static boolean verifyMethodHasZeroOrOneOperationAnnotation(Method theNextMethod, Object... theAnnotations) {
Object obj1 = null;
for (Object object : theAnnotations) {
if (object != null) {
if (obj1 == null) {
obj1 = object;
} else {
throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @"
+ obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName() + ". Can not have both.");
}
}
}
if (obj1 == null) {
return false;
// throw new ConfigurationException("Method '" +
// theNextMethod.getName() + "' on type '" +
// theNextMethod.getDeclaringClass().getSimpleName() +
// " has no FHIR method annotations.");
}
return true;
}
protected static List<IResource> toResourceList(Object response) throws InternalErrorException {
if (response == null) {
return Collections.emptyList();
} else if (response instanceof IResource) {
return Collections.singletonList((IResource) response);
} else if (response instanceof Collection) {
List<IResource> retVal = new ArrayList<IResource>();
for (Object next : ((Collection<?>) response)) {
retVal.add((IResource) next);
}
return retVal;
} else {
throw new InternalErrorException("Unexpected return type: " + response.getClass().getCanonicalName());
}
}
public static EncodingUtil determineResponseEncoding(HttpServletRequest theRequest, Map<String, String[]> theParams) {
String[] format = theParams.remove(Constants.PARAM_FORMAT);
if (format != null) {
@ -344,9 +301,61 @@ public abstract class BaseMethodBinding {
return EncodingUtil.XML;
}
public abstract void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException;
public 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());
}
}
}

View File

@ -44,7 +44,6 @@ import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.EncodingUtil;
import ca.uhn.fhir.rest.server.RestfulServer;
@ -59,12 +58,10 @@ import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseOutcomeReturningMethodBinding.class);
private List<IParameter> myParameters;
private boolean myReturnVoid;
public BaseOutcomeReturningMethodBinding(Method theMethod, FhirContext theContext, Class<?> theMethodAnnotation, Object theProvider) {
super(theMethod, theContext, theProvider);
myParameters = ParameterUtil.getResourceParameters(theMethod);
if (!theMethod.getReturnType().equals(MethodOutcome.class)) {
if (!allowVoidReturnType()) {
@ -143,9 +140,9 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
@Override
public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException {
Object[] params = new Object[myParameters.size()];
for (int i = 0; i < myParameters.size(); i++) {
IParameter param = myParameters.get(i);
Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) {
IParameter param = getParameters().get(i);
if (param != null) {
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null);
}
@ -182,7 +179,7 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
}
theServer.addHapiHeader(theResponse);
theServer.addHeadersToResponse(theResponse);
Writer writer = theResponse.getWriter();
try {
@ -243,10 +240,6 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding {
protected abstract BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName);
protected List<IParameter> getParameters() {
return myParameters;
}
protected void parseContentLocation(MethodOutcome theOutcomeToPopulate, String theLocationHeader) {
String resourceNamePart = "/" + getResourceName() + "/";
int resourceIndex = theLocationHeader.lastIndexOf(resourceNamePart);

View File

@ -125,7 +125,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT);
}
theServer.addHapiHeader(theResponse);
theServer.addHeadersToResponse(theResponse);
if (response != null && response.getOperationOutcome() != null) {
theResponse.setContentType(encoding.getResourceContentType());
@ -147,7 +147,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu
private void streamOperationOutcome(BaseServerResponseException theE, RestfulServer theServer, EncodingUtil theEncoding, HttpServletResponse theResponse) throws IOException {
theResponse.setStatus(theE.getStatusCode());
theServer.addHapiHeader(theResponse);
theServer.addHeadersToResponse(theResponse);
if (theE.getOperationOutcome() != null) {
theResponse.setContentType(theEncoding.getResourceContentType());

View File

@ -77,13 +77,11 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
private MethodReturnTypeEnum myMethodReturnType;
private String myResourceName;
private List<IParameter> myParameters;
public BaseResourceReturningMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, FhirContext theConetxt, Object theProvider) {
super(theMethod, theConetxt, theProvider);
myParameters = ParameterUtil.getResourceParameters(theMethod);
Class<?> methodReturnType = theMethod.getReturnType();
if (Collection.class.isAssignableFrom(methodReturnType)) {
myMethodReturnType = MethodReturnTypeEnum.LIST_OF_RESOURCES;
@ -92,8 +90,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
} else if (Bundle.class.isAssignableFrom(methodReturnType)) {
myMethodReturnType = MethodReturnTypeEnum.BUNDLE;
} else {
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: "
+ theMethod.getDeclaringClass().getCanonicalName());
throw new ConfigurationException("Invalid return type '" + methodReturnType.getCanonicalName() + "' on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
}
if (theReturnResourceType != null) {
@ -200,9 +197,9 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
}
// Method params
Object[] params = new Object[myParameters.size()];
for (int i = 0; i < myParameters.size(); i++) {
IParameter param = myParameters.get(i);
Object[] params = new Object[getParameters().size()];
for (int i = 0; i < getParameters().size(); i++) {
IParameter param = getParameters().get(i);
if (param != null) {
params[i] = param.translateQueryParametersIntoServerArgument(theRequest, null);
}
@ -241,8 +238,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
return (IdDt) retValObj;
}
}
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
+ IdDt.class.getCanonicalName());
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + IdDt.class.getCanonicalName());
}
private InstantDt getInstantFromMetadataOrNullIfNone(Map<ResourceMetadataKeyEnum, Object> theResourceMetadata, ResourceMetadataKeyEnum theKey) {
@ -258,8 +254,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
return (InstantDt) retValObj;
}
}
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected "
+ InstantDt.class.getCanonicalName());
throw new InternalErrorException("Found an object of type '" + retValObj.getClass().getCanonicalName() + "' in resource metadata for key " + theKey.name() + " - Expected " + InstantDt.class.getCanonicalName());
}
private IParser getNewParser(EncodingUtil theResponseEncoding, boolean thePrettyPrint, NarrativeModeEnum theNarrativeMode) {
@ -276,8 +271,8 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
return parser.setPrettyPrint(thePrettyPrint).setSuppressNarratives(theNarrativeMode == NarrativeModeEnum.SUPPRESS);
}
private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingUtil theResponseEncoding, String theServerBase,
String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
private void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, List<IResource> theResult, EncodingUtil theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, boolean theRequestIsBrowser,
NarrativeModeEnum theNarrativeMode) throws IOException {
assert !theServerBase.endsWith("/");
theHttpResponse.setStatus(200);
@ -292,7 +287,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
theHttpResponse.setCharacterEncoding("UTF-8");
theServer.addHapiHeader(theHttpResponse);
theServer.addHeadersToResponse(theHttpResponse);
Bundle bundle = new Bundle();
bundle.getAuthorName().setValue(getClass().getCanonicalName());
@ -322,10 +317,10 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
b.append(resId);
/*
* If this is a history operation, we add the version of the resource to the self link to indicate the version
* If this is a history operation, we add the version of the
* resource to the self link to indicate the version
*/
if (getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_INSTANCE || getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_TYPE
|| getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) {
if (getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_INSTANCE || getResourceOperationType() == RestfulOperationTypeEnum.HISTORY_TYPE || getSystemOperationType() == RestfulOperationSystemEnum.HISTORY_SYSTEM) {
IdDt versionId = getIdFromMetadataOrNullIfNone(next.getResourceMetadata(), ResourceMetadataKeyEnum.VERSION_ID);
if (versionId != null) {
b.append('/');
@ -387,8 +382,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding {
}
}
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint,
boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
private void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IResource theResource, EncodingUtil theResponseEncoding, boolean thePrettyPrint, boolean theRequestIsBrowser, NarrativeModeEnum theNarrativeMode) throws IOException {
theHttpResponse.setStatus(200);
if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) {

View File

@ -20,26 +20,23 @@ package ca.uhn.fhir.rest.method;
* #L%
*/
import static org.apache.commons.lang3.StringUtils.isBlank;
import static org.apache.commons.lang3.StringUtils.*;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.LinkedHashMap;
import java.util.List;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum;
import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.client.BaseClientInvocation;
import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
@ -48,18 +45,14 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
private final Integer myIdParamIndex;
private String myResourceName;
private final RestfulOperationTypeEnum myResourceOperationType;
private final RestfulOperationSystemEnum mySystemOperationType;
private String myResourceName;
private Integer mySinceParamIndex;
private Integer myCountParamIndex;
public HistoryMethodBinding(Method theMethod, FhirContext theConetxt, Object theProvider) {
super(toReturnType(theMethod, theProvider), theMethod, theConetxt, theProvider);
myIdParamIndex = Util.findIdParameterIndex(theMethod);
mySinceParamIndex = Util.findSinceParameterIndex(theMethod);
myCountParamIndex = Util.findCountParameterIndex(theMethod);
History historyAnnotation = theMethod.getAnnotation(History.class);
Class<? extends IResource> type = historyAnnotation.type();
@ -93,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) {
if (theProvider instanceof IResourceProvider) {
return ((IResourceProvider) theProvider).getResourceType();
@ -105,100 +181,4 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding {
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);
}
}

View File

@ -37,7 +37,6 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum;
import ca.uhn.fhir.rest.client.GetClientInvocation;
import ca.uhn.fhir.rest.param.BaseQueryParameter;
import ca.uhn.fhir.rest.param.IParameter;
import ca.uhn.fhir.rest.param.ParameterUtil;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
@ -49,12 +48,10 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
private Class<?> myDeclaredResourceType;
private List<IParameter> myParameters;
private String myQueryName;
public SearchMethodBinding(Class<? extends IResource> theReturnResourceType, Method theMethod, String theQueryName, FhirContext theContext, Object theProvider) {
super(theReturnResourceType, theMethod, theContext, theProvider);
this.myParameters = ParameterUtil.getResourceParameters(theMethod);
this.myQueryName = StringUtils.defaultIfBlank(theQueryName, null);
this.myDeclaredResourceType = theMethod.getReturnType();
}
@ -63,10 +60,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
return myDeclaredResourceType.getClass();
}
public List<IParameter> getParameters() {
return myParameters;
}
@Override
public RestfulOperationTypeEnum getResourceOperationType() {
return RestfulOperationTypeEnum.SEARCH_TYPE;
@ -84,7 +77,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
@Override
public GetClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException {
assert (myQueryName == null || ((theArgs != null ? theArgs.length : 0) == myParameters.size())) : "Wrong number of arguments: " + (theArgs!=null?theArgs.length:"null");
assert (myQueryName == null || ((theArgs != null ? theArgs.length : 0) == getParameters().size())) : "Wrong number of arguments: " + (theArgs != null ? theArgs.length : "null");
Map<String, List<String>> queryStringArgs = new LinkedHashMap<String, List<String>>();
@ -94,7 +87,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
if (theArgs != null) {
for (int idx = 0; idx < theArgs.length; idx++) {
IParameter nextParam = myParameters.get(idx);
IParameter nextParam = getParameters().get(idx);
nextParam.translateClientArgumentIntoQueryArgument(theArgs[idx], queryStringArgs);
}
}
@ -103,8 +96,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
}
@Override
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException,
InternalErrorException {
public List<IResource> invokeServer(Object theResourceProvider, Request theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException {
assert theRequest.getId() == null;
assert theRequest.getVersion() == null;
@ -138,11 +130,11 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
}
Set<String> methodParamsTemp = new HashSet<String>();
for (int i = 0; i < this.myParameters.size(); i++) {
if (!(myParameters.get(i) instanceof BaseQueryParameter)) {
for (int i = 0; i < this.getParameters().size(); i++) {
if (!(getParameters().get(i) instanceof BaseQueryParameter)) {
continue;
}
BaseQueryParameter temp = (BaseQueryParameter) myParameters.get(i);
BaseQueryParameter temp = (BaseQueryParameter) getParameters().get(i);
methodParamsTemp.add(temp.getName());
if (temp.isRequired() && !theRequest.getParameters().containsKey(temp.getName())) {
ourLog.trace("Method {} doesn't match param '{}' is not present", getMethod().getName(), temp.getName());
@ -176,10 +168,6 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding {
return retVal;
}
public void setParameters(List<IParameter> parameters) {
this.myParameters = parameters;
}
public void setResourceType(Class<?> resourceType) {
this.myDeclaredResourceType = resourceType;
}

View File

@ -27,18 +27,16 @@ import java.net.URLDecoder;
import java.util.HashMap;
import java.util.Map;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
/**
* Created by dsotnikov on 2/25/2014.
*/
class Util {
public static Integer findCountParameterIndex(Method theMethod) {
return findParamIndex(theMethod, Count.class);
}
// public static Integer findCountParameterIndex(Method theMethod) {
// return findParamIndex(theMethod, Count.class);
// }
public static Integer findIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, IdParam.class);
@ -59,9 +57,9 @@ class Util {
return null;
}
public static Integer findSinceParameterIndex(Method theMethod) {
return findParamIndex(theMethod, Since.class);
}
// public static Integer findSinceParameterIndex(Method theMethod) {
// return findParamIndex(theMethod, Since.class);
// }
public static Integer findVersionIdParameterIndex(Method theMethod) {
return findParamIndex(theMethod, VersionIdParam.class);

View File

@ -20,7 +20,9 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@ -98,5 +100,9 @@ public abstract class BaseQueryParameter implements IParameter {
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
// ignore for now
}
}

View File

@ -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;
}
}

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -45,4 +47,6 @@ public interface IParameter {
*/
Object translateQueryParametersIntoServerArgument(Request theRequest, Object theRequestContents) throws InternalErrorException, InvalidRequestException;
void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType);
}

View File

@ -20,6 +20,7 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
@ -110,4 +111,5 @@ public class IncludeParameter extends BaseQueryParameter {
return true;
}
}

View File

@ -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
}
}

View File

@ -23,63 +23,71 @@ package ca.uhn.fhir.rest.param;
import java.lang.annotation.Annotation;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.time.DateUtils;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.PathSpecification;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.IncludeParam;
import ca.uhn.fhir.rest.annotation.OptionalParam;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.ServerBase;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.util.ReflectionUtil;
public class ParameterUtil {
@SuppressWarnings("unchecked")
public static List<IParameter> getResourceParameters(Method method) {
public static List<IParameter> getResourceParameters(Method theMethod) {
List<IParameter> parameters = new ArrayList<IParameter>();
Class<?>[] parameterTypes = method.getParameterTypes();
Class<?>[] parameterTypes = theMethod.getParameterTypes();
int paramIndex = 0;
for (Annotation[] annotations : method.getParameterAnnotations()) {
boolean haveHandledMethod = false;
for (Annotation[] annotations : theMethod.getParameterAnnotations()) {
IParameter param = null;
Class<?> parameterType = parameterTypes[paramIndex];
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)) {
ServletRequestParameter param = new ServletRequestParameter();
parameters.add(param);
param = new ServletRequestParameter();
} else if (parameterType.equals(HttpServletResponse.class) || parameterType.equals(ServletResponse.class)) {
ServletResponseParameter param = new ServletResponseParameter();
parameters.add(param);
param = new ServletResponseParameter();
} else {
for (int i = 0; i < annotations.length; i++) {
for (int i = 0; i < annotations.length && param == null; 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) {
SearchParameter parameter = new SearchParameter();
parameter.setName(((RequiredParam) nextAnnotation).name());
@ -94,43 +102,121 @@ public class ParameterUtil {
param = parameter;
} else if (nextAnnotation instanceof IncludeParam) {
if (parameterType != PathSpecification.class || innerCollectionType == null || outerCollectionType != null) {
throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<"
+ PathSpecification.class.getSimpleName() + ">");
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + IncludeParam.class.getSimpleName() + " but has a type other than Collection<" + PathSpecification.class.getSimpleName() + ">");
}
Class<? extends Collection<PathSpecification>> instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(
innerCollectionType, "Method '" + method.getName() + "'");
Class<? extends Collection<PathSpecification>> instantiableCollectionType = (Class<? extends Collection<PathSpecification>>) CollectionBinder.getInstantiableCollectionType(innerCollectionType, "Method '" + theMethod.getName() + "'");
param = new IncludeParameter((IncludeParam) nextAnnotation, instantiableCollectionType);
} else if (nextAnnotation instanceof ResourceParam) {
if (!IResource.class.isAssignableFrom(parameterType)) {
throw new ConfigurationException("Method '" + method.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName()
+ " but has a type that is not an implemtation of " + IResource.class.getCanonicalName());
throw new ConfigurationException("Method '" + theMethod.getName() + "' is annotated with @" + ResourceParam.class.getSimpleName() + " but has a type that is not an implemtation of " + IResource.class.getCanonicalName());
}
param = new ResourceParameter((Class<? extends IResource>) parameterType);
} else if (nextAnnotation instanceof IdParam || nextAnnotation instanceof VersionIdParam) {
param = null;
param = new NullParameter();
} else if (nextAnnotation instanceof ServerBase) {
param = new ServerBaseParameter();
} else if (nextAnnotation instanceof Since) {
param = new SinceParameter();
} else if (nextAnnotation instanceof Count) {
param = new CountParameter();
} else {
continue;
}
haveHandledMethod = true;
parameters.add(param);
break;
}
if (!haveHandledMethod) {
throw new ConfigurationException("Parameter #" + paramIndex + " of method '" + method.getName() + "' on type '" + method.getDeclaringClass().getCanonicalName()
+ "' 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++;
}
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);
}
}

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -52,6 +54,9 @@ public class ResourceParameter implements IParameter {
return myResourceName;
}
// public IResource
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
// ignore for now
}
}

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -35,7 +37,7 @@ class ServerBaseParameter implements IParameter {
/*
* Does nothing, since we just ignore serverbase arguments
*/
ourLog.trace("Ignoring HttpServletRequest argument: {}", theSourceClientArgument);
ourLog.trace("Ignoring server base argument: {}", theSourceClientArgument);
}
@Override
@ -43,4 +45,9 @@ class ServerBaseParameter implements IParameter {
return theRequest.getFhirServerBase();
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
// ignore for now
}
}

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -43,4 +45,9 @@ class ServletRequestParameter implements IParameter {
return theRequest.getServletRequest();
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
// ignore
}
}

View File

@ -20,6 +20,8 @@ package ca.uhn.fhir.rest.param;
* #L%
*/
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.List;
import java.util.Map;
@ -43,4 +45,10 @@ class ServletResponseParameter implements IParameter {
return theRequest.getServletResponse();
}
@Override
public void initializeTypes(Method theMethod, Class<? extends Collection<?>> theOuterCollectionType, Class<? extends Collection<?>> theInnerCollectionType, Class<?> theParameterType) {
// ignore
}
}

View File

@ -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;
}
}

View File

@ -48,10 +48,9 @@ import ca.uhn.fhir.rest.method.Request;
import ca.uhn.fhir.rest.method.SearchMethodBinding;
import ca.uhn.fhir.rest.server.exceptions.AuthenticationException;
import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException;
import ca.uhn.fhir.rest.server.exceptions.ConfigurationException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.provider.ServerConformanceProvider;
import ca.uhn.fhir.rest.server.provider.ServerProfileProvider;
import ca.uhn.fhir.util.VersionUtil;
@ -63,7 +62,7 @@ public class RestfulServer extends HttpServlet {
private static final long serialVersionUID = 1L;
private FhirContext myFhirContext;
private Collection<Object> myProviders;
private Collection<Object> myPlainProviders;
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>();
private Collection<IResourceProvider> myResourceProviders;
private ISecurityManager mySecurityManager;
@ -73,16 +72,34 @@ public class RestfulServer extends HttpServlet {
private String myServerVersion = VersionUtil.getVersion(); // defaults to
// HAPI version
private boolean myUseBrowserFriendlyContentTypes;
private ResourceBinding myNullResourceBinding = new ResourceBinding();
/**
* Constructor
*/
public RestfulServer() {
myFhirContext = new FhirContext();
myServerConformanceProvider = new ServerConformanceProvider(this);
}
public void addHapiHeader(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-CatchingFhir", "Powered by HAPI FHIR " + VersionUtil.getVersion());
/**
* This method is called prior to sending a response to incoming requests. It is
* used to add custom headers.
* <p>
* Use caution if overriding this method: it is recommended to call
* <code>super.addHeadersToResponse</code> to avoid inadvertantly
* disabling functionality.
* </p>
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
theHttpResponse.addHeader("X-PoweredBy", "HAPI FHIR " + VersionUtil.getVersion() + " RESTful Server");
}
/**
* Gets the {@link FhirContext} associated with this server. For efficient processing,
* resource providers and plain providers should generally use this context
* if one is needed, as opposed to creating their own.
*/
public FhirContext getFhirContext() {
return myFhirContext;
}
@ -93,8 +110,8 @@ public class RestfulServer extends HttpServlet {
*
* @see #getResourceProviders()
*/
public Collection<Object> getProviders() {
return myProviders;
public Collection<Object> getPlainProviders() {
return myPlainProviders;
}
public Collection<ResourceBinding> getResourceBindings() {
@ -152,6 +169,12 @@ public class RestfulServer extends HttpServlet {
return myServerVersion;
}
/**
* Initializes the server. Note that this method is final to avoid accidentally
* introducing bugs in implementations, but subclasses may put initialization code in
* {@link #initialize()}, which is called immediately before beginning initialization of
* the restful server's internal init.
*/
@Override
public final void init() throws ServletException {
initialize();
@ -183,7 +206,7 @@ public class RestfulServer extends HttpServlet {
}
}
Collection<Object> providers = getProviders();
Collection<Object> providers = getPlainProviders();
if (providers != null) {
for (Object next : providers) {
assertProviderIsValid(next);
@ -214,12 +237,12 @@ public class RestfulServer extends HttpServlet {
/**
* Sets the non-resource specific providers which implement method calls on
* this server
* this server.
*
* @see #setResourceProviders(Collection)
*/
public void setProviders(Collection<Object> theProviders) {
myProviders = theProviders;
public void setPlainProviders(Collection<Object> theProviders) {
myPlainProviders = theProviders;
}
/**
@ -229,7 +252,7 @@ public class RestfulServer extends HttpServlet {
* @see #setResourceProviders(Collection)
*/
public void setProviders(Object... theProviders) {
myProviders = Arrays.asList(theProviders);
myPlainProviders = Arrays.asList(theProviders);
}
/**
@ -316,16 +339,20 @@ public class RestfulServer extends HttpServlet {
BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, myFhirContext, theProvider);
if (foundMethodBinding != null) {
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(foundMethodBinding.getResourceName());
String resourceName = foundMethodBinding.getResourceName();
ResourceBinding resourceBinding;
if (myResourceNameToProvider.containsKey(definition.getName())) {
resourceBinding = myResourceNameToProvider.get(definition.getName());
if (resourceName == null) {
resourceBinding = myNullResourceBinding;
} else {
resourceBinding = new ResourceBinding();
String resourceName = definition.getName();
resourceBinding.setResourceName(resourceName);
myResourceNameToProvider.put(resourceName, resourceBinding);
RuntimeResourceDefinition definition = myFhirContext.getResourceDefinition(resourceName);
if (myResourceNameToProvider.containsKey(definition.getName())) {
resourceBinding = myResourceNameToProvider.get(definition.getName());
} else {
resourceBinding = new ResourceBinding();
resourceBinding.setResourceName(resourceName);
myResourceNameToProvider.put(resourceName, resourceBinding);
}
}
resourceBinding.addMethod(foundMethodBinding);
@ -453,18 +480,24 @@ public class RestfulServer extends HttpServlet {
StringTokenizer tok = new StringTokenizer(requestPath, "/");
if (!tok.hasMoreTokens()) {
throw new MethodNotFoundException("No resource name specified");
throw new ResourceNotFoundException("No resource name specified");
}
resourceName = tok.nextToken();
if (resourceName.startsWith("_")) {
operation = resourceName;
resourceName = null;
}
ResourceBinding resourceBinding = null;
BaseMethodBinding resourceMethod = null;
if ("metadata".equals(resourceName)) {
resourceMethod = myServerConformanceMethod;
} else if (resourceName == null) {
resourceBinding = myNullResourceBinding;
} else {
resourceBinding = myResourceNameToProvider.get(resourceName);
if (resourceBinding == null) {
throw new MethodNotFoundException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToProvider.keySet());
throw new ResourceNotFoundException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToProvider.keySet());
}
}
@ -515,14 +548,14 @@ public class RestfulServer extends HttpServlet {
resourceMethod = resourceBinding.getMethod(r);
}
if (null == resourceMethod) {
throw new MethodNotFoundException("No resource method available for the supplied parameters " + params);
throw new ResourceNotFoundException("No resource method available for the supplied parameters " + params);
}
resourceMethod.invokeServer(this, r, theResponse);
} catch (AuthenticationException e) {
theResponse.setStatus(e.getStatusCode());
addHapiHeader(theResponse);
addHeadersToResponse(theResponse);
theResponse.setContentType("text/plain");
theResponse.setCharacterEncoding("UTF-8");
theResponse.getWriter().write(e.getMessage());
@ -535,7 +568,7 @@ public class RestfulServer extends HttpServlet {
}
theResponse.setStatus(e.getStatusCode());
addHapiHeader(theResponse);
addHeadersToResponse(theResponse);
theResponse.setContentType("text/plain");
theResponse.setCharacterEncoding("UTF-8");
theResponse.getWriter().append(e.getMessage());

View File

@ -1,5 +1,7 @@
package ca.uhn.fhir.rest.server.exceptions;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
/*
* #%L
* HAPI FHIR Library
@ -22,7 +24,8 @@ package ca.uhn.fhir.rest.server.exceptions;
/**
* Created by dsotnikov on 3/10/2014.
* Represents an <b>HTTP 401 Client Unauthorized</b> response, which means that
* the client needs to provide credentials, or has provided invalid credentials.
*/
public class AuthenticationException extends BaseServerResponseException {
@ -36,7 +39,4 @@ public class AuthenticationException extends BaseServerResponseException {
super(401, theMessage);
}
public AuthenticationException(int theStatusCode, String theMessage) {
super(theStatusCode, theMessage);
}
}

View File

@ -20,6 +20,9 @@ package ca.uhn.fhir.rest.server.exceptions;
* #L%
*/
/**
* TODO: javadoc this
*/
public class InternalErrorException extends BaseServerResponseException {
private static final long serialVersionUID = 1L;

View File

@ -21,7 +21,7 @@ package ca.uhn.fhir.rest.server.exceptions;
*/
/**
* Created by dsotnikov on 2/27/2014.
* TODO: javadoc this
*/
public class MethodNotAllowedException extends BaseServerResponseException {
private static final long serialVersionUID = 1L;

View File

@ -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);
}
}

View File

@ -24,6 +24,10 @@ import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.primitive.IdDt;
/**
* Represents an <b>HTTP 404 Resource Not Found</b> response, which means that
* the request is pointing to a resource that does not exist.
*/
public class ResourceNotFoundException extends BaseServerResponseException {
public ResourceNotFoundException(IdDt theId) {

View File

@ -20,10 +20,14 @@ package ca.uhn.fhir.rest.server.exceptions;
* #L%
*/
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.server.Constants;
/**
* Created by dsotnikov on 2/27/2014.
* Represents an <b>HTTP 409 Conflict</b> response. This exception should be
* thrown in methods which accept a version (e.g. {@link Update}, {@link Delete})
* when the operation fails because of a version conflict as specified in the FHIR specification.
*/
public class ResourceVersionConflictException extends BaseServerResponseException {
private static final long serialVersionUID = 1L;

View File

@ -20,10 +20,12 @@ package ca.uhn.fhir.rest.server.exceptions;
* #L%
*/
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.server.Constants;
/**
* Thrown for an Update operation if that operation requires a version to
* Represents an <b>HTTP 412 Precondition Failed</b> response. This exception
* should be thrown for an {@link Update} operation if that operation requires a version to
* be specified in an HTTP header, and none was.
*/
public class ResourceVersionNotSpecifiedException extends BaseServerResponseException {

View File

@ -22,7 +22,9 @@ package ca.uhn.fhir.rest.server.exceptions;
/**
* Exception for use when a response is received or being sent that
* does not correspond to any other exception type
* does not correspond to any other exception type. An HTTP status code
* must be provided, and will be provided to the caller in the case of a
* server implementation.
*/
public class UnclassifiedServerFailureException extends BaseServerResponseException {

View File

@ -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
}

View File

@ -1,7 +1,6 @@
package example;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import javax.servlet.annotation.WebServlet;
@ -21,18 +20,19 @@ public class ExampleRestfulServlet extends RestfulServer {
private static final long serialVersionUID = 1L;
/**
* Restful servers must provide an implementation of this method, which
* returns all resource providers to be used by this server. In the example
* below, we are creating a RESTful server which is able to serve
* Patient and Observation resources.
* Constructor
*/
@Override
public Collection<IResourceProvider> getResourceProviders() {
List<IResourceProvider> retVal = new ArrayList<IResourceProvider>();
retVal.add(new RestfulPatientResourceProvider());
retVal.add(new RestfulObservationResourceProvider());
return retVal;
public ExampleRestfulServlet() {
/*
* The servlet defines any number of resource providers, and
* configures itself to use them by calling
* setResourceProviders()
*/
List<IResourceProvider> resourceProviders = new ArrayList<IResourceProvider>();
resourceProviders.add(new RestfulPatientResourceProvider());
resourceProviders.add(new RestfulObservationResourceProvider());
setResourceProviders(resourceProviders);
}
}
//END SNIPPET: servlet

View File

@ -67,6 +67,22 @@ System.out.println(encoded);
}
public void fluent() throws DataFormatException, IOException {
FhirContext ctx = new FhirContext(Patient.class, Observation.class);
String encoded;
//START SNIPPET: encodeMsgFluent
Patient patient = new Patient();
patient.addIdentifier().setSystem("http://example.com/fictitious-mrns").setValue("MRN001");
patient.addName().setUse(NameUseEnum.OFFICIAL).addFamily("Tester").addGiven("John").addGiven("Q");
encoded = ctx.newJsonParser().setPrettyPrint(true).encodeResourceToString(patient);
System.out.println(encoded);
//END SNIPPET: encodeMsgFluent
}
public static void parseMsg() {
FhirContext ctx = new FhirContext(Patient.class, Observation.class);

View File

@ -28,6 +28,7 @@ import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam;
@ -38,11 +39,13 @@ import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.Validate;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.client.ITestClient;
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.param.CodingListParam;
import ca.uhn.fhir.rest.param.DateRangeParam;
@ -443,22 +446,29 @@ public interface MetadataClient extends IRestfulClient {
}
//END SNIPPET: metadataClient
public interface HistoryClient {
//START SNIPPET: historyClient
// Server level (history of ALL resources)
@History
Bundle getHistoryServer();
public interface HistoryClient extends IBasicClient {
/** Server level (history of ALL resources) */
@History
Bundle getHistoryServer();
// Type level (history of all resources of a given type)
@History(type=Patient.class)
Bundle getHistoryPatientType();
/** Type level (history of all resources of a given type) */
@History(type=Patient.class)
Bundle getHistoryPatientType();
/** Instance level (history of a specific resource instance by type and ID) */
@History(type=Patient.class)
Bundle getHistoryPatientInstance(@IdParam IdDt theId);
/**
* 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);
// Instance level (history of a specific resource instance by type and ID)
@History(type=Patient.class)
Bundle getHistoryPatientInstance(@IdParam IdDt theId);
//END SNIPPET: historyClient
}
//END SNIPPET: historyClient
public void bbbbb() throws DataFormatException, IOException {

View File

@ -109,6 +109,9 @@
<param name="id" value="encodeMsg" />
<param name="file" value="src/site/example/java/example/FhirContextIntro.java" />
</macro>
<!-- ****** The section below on fluent references the snippet above
****** so be careful about any reordering! -->
<p>
This code gives the following output:
@ -129,6 +132,26 @@
</subsection>
<subsection name="Fluent Programming">
<p>
Much of the HAPI FHIR API is designed using a fluent style,
where method calls can be chained in a natural way. This
leads to tighter and easier-to-read code.
</p>
<p>
The following snippet is functionally identical to the
example above:
</p>
<macro name="snippet">
<param name="id" value="encodeMsgFluent" />
<param name="file" value="src/site/example/java/example/FhirContextIntro.java" />
</macro>
</subsection>
<subsection name="JSON Encoding">
<p>

View File

@ -10,6 +10,14 @@
<section name="Implementing Resource Provider Operations">
<p>
Jump To...
</p>
<ul>
<li><a href="#operations">Operations</a></li>
<li><a href="#exceptions">Exceptions</a></li>
</ul>
<p>
RESTful Clients and Servers both share the same
method pattern, with one key difference: A client
@ -26,6 +34,10 @@
implementations, but client methods will follow the same patterns.
</p>
<a name="operations"/>
</section>
<section name="Operations">
<p>
The following table lists the operations supported by
HAPI FHIR RESTful Servers and Clients.
@ -81,7 +93,7 @@
</tr>
<tr>
<td>
<a href="#type_create">Type - Create</a>
<a href="#type_search">Type - Create</a>
</td>
<td>
Create a new resource with a server assigned id
@ -99,14 +111,6 @@
Search the resource type based on some filter criteria
</td>
</tr>
<tr>
<td>
<a href="#type_search">Type - Search</a>
</td>
<td>
Search the resource type based on some filter criteria
</td>
</tr>
<tr>
<td>
<a href="#history">Type - History</a>
@ -892,7 +896,7 @@
<section name="System Level - Transaction">
<p>
Not yet implemented
Not yet implemented - Get in touch if you would like to help!
</p>
<a name="system_search" />
@ -905,7 +909,7 @@
<section name="System Level - Search">
<p>
Not yet implemented
Not yet implemented - Get in touch if you would like to help!
</p>
<a name="history" />
@ -945,18 +949,38 @@
annotated with the
<a href="./apidocs/ca/uhn/fhir/rest/annotation/IdParam.html">@IdParam</a>
annotation, indicating the ID of the resource for which to return history.
<ul><li>
For a server
implementation, the method must either be defined in a
<a href="./doc_rest_server.html#resource_providers">resource provider</a>
or have a <code>type()</code> value in the @History annotation if it is
defined in a
<a href="./doc_rest_server.html#plain_providers">plain provider</a>.
</li></ul>
</li>
<li>
For an
For a
<b>Type History</b>
method, the method must not have any @IdParam parameter.
<ul><li>
For a server
implementation, the method must either be defined in a
<a href="./doc_rest_server.html#resource_providers">resource provider</a>
or have a <code>type()</code> value in the @History annotation if it is
defined in a
<a href="./doc_rest_server.html#plain_providers">plain provider</a>.
</li></ul>
</li>
<li>
For an
For a
<b>Server History</b>
method, the method must not have any @IdParam parameter
and must not be found in a ResourceProvider definition.
<!-- TODO: make ResourceProvider a link to a defintion of these on the RESTFul server page -->
method, the method must not have any @IdParam parameter, and
must not have a <code>type()</code> value specified in
the @History annotation.
<ul><li>
In a server implementation, the method must
be defined in a <a href="./doc_rest_server.html#plain_providers">plain provider</a>.
</li></ul>
</li>
</ul>
<p>
@ -977,6 +1001,32 @@
<param name="file" value="src/site/example/java/example/RestfulPatientResourceProviderMore.java" />
</macro>
<a name="exceptions"/>
</section>
<section name="Exceptions">
<p>
When implementing a server operation, there are a number of failure conditions
specified. For example, an
<a href="#instance_read">Instance Read</a> request might specify an unknown
resource ID, or a <a href="#type_create">Type Create</a> request might contain an
invalid resource which can not be created.
</p>
<p>
In these cases, an appropriate exception should be thrown. The HAPI RESTful
API includes a set of exceptions extending
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/BaseServerResponseException.html">BaseServerResponseException</a>
which represent specific HTTP failure codes.
</p>
<p>
See the
<a href="./apidocs/ca/uhn/fhir/rest/server/exceptions/package-summary.html">Exceptions List</a>
for a complete list of these exceptions. Note that these exceptions are all <b>unchecked</b>
exceptions, so they do not need to ne explicitly declared in the method
signature.
</p>
</section>
</body>

View File

@ -26,6 +26,7 @@
be possible to create a FHIR compliant server quickly and easily.
</p>
<a name="resource_providers"/>
<subsection name="Defining Resource Providers">
<p>
@ -90,8 +91,56 @@
<param name="file" value="src/site/example/java/example/ExampleRestfulServlet.java" />
</macro>
<a name="plain_providers"/>
</subsection>
<!-- NB there is an anchor for this section above -->
<subsection name="Plain Providers (non-resource specific)">
<p>
Defining one provider per resource is a good strategy to keep
code readable and maintainable, but it is also possible to put
methods for multiple resource types in a provider class. Providers
which do not implement the
<a href="./apidocs/ca/uhn/fhir/rest/server/IResourceProvider.html">IResourceProvider</a>
(and therefore are not bound to one specific resource type) are known as
<b>Plain Providers</b>.
</p>
<p>
A plain provider may implement any
<a href="./doc_rest_operations.html">RESTful operation</a>, but will generally
need to explicitly state what type of resource it applies to. If the method directly
returns a resource or a collection of resources (as in an
<a href="./doc_rest_operations.html#instance_read">instance read</a> or
<a href="./doc_rest_operations.html#type_search">type search</a> operation)
the resource type will be inferred automatically. If the method returns a
<a href="./apidocs/ca/uhn/fhir/model/api/Bundle.html">Bundle</a>
resource, it is necessary to explicitly specify the resource type
in the method annotation. The following example shows this:
</p>
<macro name="snippet">
<param name="id" value="plainProvider" />
<param name="file" value="src/site/example/java/example/ExampleProviders.java" />
</macro>
<p>
In addition, some methods are not resource specific. For example, the
<a href="./doc_rest_operations.html#history">system history</a> operation
returns historical versions of <b>all resource types</b> on a server,
so it needs to be defined in a plain provider.
</p>
<p>
Once you have defined your resource providers, they are passed to the
server in a similar way to the resource providers.
</p>
<macro name="snippet">
<param name="id" value="plainProviderServer" />
<param name="file" value="src/site/example/java/example/ExampleProviders.java" />
</macro>
</subsection>
<subsection name="Deploy">
<p>
@ -99,6 +148,12 @@
you can bundle these into a WAR file and you are ready to deploy to
any JEE container (Tomcat, Websphere, Glassfish, etc).
</p>
<p>
Bundling a servlet into a WAR file and deploying it to an application server
is beyond the scope of this page, but there are many good tutorials on how
to do this.
</p>
</subsection>
@ -167,6 +222,22 @@
</section>
-->
<section name="Using the Server">
<p>
Your RESTful server should now support the methods you have declared. Here are a
few helpful tricks for interacting with the server:
</p>
<p>
<b>Pretty Printing:</b> The HAPI RESTful server supports a non-standard parameter called
<code>_pretty</code>, which can be used to request that responses be pretty-printed (indented for
easy reading by humans) by setting the value to <code>true</code>. This can be useful in testing. An example URL for this might be:<br/>
<code>http://example.com/fhir/Patient/_search?name=TESTING&amp;_pretty=true</code>
</p>
</section>
</body>
</document>

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.rest.client;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
import java.io.InputStream;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.util.Arrays;
@ -27,6 +28,8 @@ import org.junit.Before;
import org.junit.Test;
import org.mockito.ArgumentCaptor;
import org.mockito.internal.stubbing.defaultanswers.ReturnsDeepStubs;
import org.mockito.invocation.InvocationOnMock;
import org.mockito.stubbing.Answer;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
@ -41,6 +44,7 @@ import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.QuantityCompararatorEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.api.MethodOutcome;
import ca.uhn.fhir.rest.param.CodingListParam;
@ -249,6 +253,50 @@ public class ClientTest {
}
}
@Test
public void testHistoryWithParams() throws Exception {
//@formatter:off
final String msg = "<feed xmlns=\"http://www.w3.org/2005/Atom\"><title/><id>6c1d93be-027f-468d-9d47-f826cd15cf42</id><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history\"/><link rel=\"fhir-base\" href=\"http://localhost:51698\"/><os:totalResults xmlns:os=\"http://a9.com/-/spec/opensearch/1.1/\">2</os:totalResults><published>2014-04-13T18:24:50-04:00</published><author><name>ca.uhn.fhir.rest.method.HistoryMethodBinding</name></author><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:20.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/1\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"OlderFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry><entry><title>Patient 222</title><id>222</id><updated>1969-12-31T19:00:30.000-05:00</updated><published>1969-12-31T19:00:10.000-05:00</published><link rel=\"self\" href=\"http://localhost:51698/Patient/222/_history/2\"/><content type=\"text/xml\"><Patient xmlns=\"http://hl7.org/fhir\"><identifier><use value=\"official\"/><system value=\"urn:hapitest:mrns\"/><value value=\"00001\"/></identifier><name><family value=\"NewerFamily\"/><given value=\"PatientOne\"/></name><gender><text value=\"M\"/></gender></Patient></content></entry></feed>";
//@formatter:on
ArgumentCaptor<HttpUriRequest> capt = ArgumentCaptor.forClass(HttpUriRequest.class);
when(httpClient.execute(capt.capture())).thenReturn(httpResponse);
when(httpResponse.getStatusLine()).thenReturn(new BasicStatusLine(new ProtocolVersion("HTTP", 1, 1), 200, "OK"));
when(httpResponse.getEntity().getContentType()).thenReturn(new BasicHeader("content-type", Constants.CT_ATOM_XML + "; charset=UTF-8"));
when(httpResponse.getEntity().getContent()).thenAnswer(new Answer<InputStream>() {
@Override
public InputStream answer(InvocationOnMock theInvocation) throws Throwable {
return new ReaderInputStream(new StringReader(msg), Charset.forName("UTF-8"));
}
});
ITestClient client = ctx.newRestfulClient(ITestClient.class, "http://foo");
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02&_count=12", capt.getAllValues().get(0).getURI().toString());
String expectedDateString= new InstantDt(new InstantDt("2012-01-02T00:01:02").getValue()).getValueAsString(); // ensures the local timezone
expectedDateString=expectedDateString.replace(":", "%3A");
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02").getValue(), new IntegerDt(12).getValue());
assertEquals("http://foo/Patient/111/_history?_since="+expectedDateString+"&_count=12", capt.getAllValues().get(1).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), null, new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), null);
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02", capt.getAllValues().get(3).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt(), new IntegerDt(12));
assertEquals("http://foo/Patient/111/_history?_count=12", capt.getAllValues().get(2).getURI().toString());
client.getHistoryPatientInstance(new IdDt("111"), new InstantDt("2012-01-02T00:01:02"), new IntegerDt());
assertEquals("http://foo/Patient/111/_history?_since=2012-01-02T00%3A01%3A02", capt.getAllValues().get(3).getURI().toString());
}
@Test
public void testHistoryResourceType() throws Exception {
@ -408,7 +456,7 @@ public class ClientTest {
DateRangeParam param = new DateRangeParam();
param.setLowerBound(new QualifiedDateParam(QuantityCompararatorEnum.GREATERTHAN_OR_EQUALS, "2011-01-01"));
param.setUpperBound(new QualifiedDateParam(QuantityCompararatorEnum.LESSTHAN_OR_EQUALS, "2021-01-01"));
List<Patient> response = client.getPatientByDateRange(param);
client.getPatientByDateRange(param);
assertEquals("http://foo/Patient?dateRange=%3E%3D2011-01-01&dateRange=%3C%3D2021-01-01", capt.getValue().getURI().toString());
@ -467,7 +515,7 @@ public class ClientTest {
CodingListParam identifiers = new CodingListParam();
identifiers.add(new CodingDt("foo", "bar"));
identifiers.add(new CodingDt("baz", "boz"));
List<Patient> response = client.getPatientMultipleIdentifiers(identifiers);
client.getPatientMultipleIdentifiers(identifiers);
assertEquals("http://foo/Patient?ids=foo%7Cbar%2Cbaz%7Cboz", capt.getValue().getURI().toString());

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.rest.client;
import java.util.Date;
import java.util.List;
import ca.uhn.fhir.model.api.Bundle;
@ -8,7 +9,10 @@ import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.DiagnosticReport;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.Create;
import ca.uhn.fhir.rest.annotation.Delete;
import ca.uhn.fhir.rest.annotation.History;
@ -19,6 +23,7 @@ import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.ResourceParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.annotation.VersionIdParam;
import ca.uhn.fhir.rest.api.MethodOutcome;
@ -34,10 +39,10 @@ public interface ITestClient extends IBasicClient {
@Search()
public List<Patient> getPatientByDateRange(@RequiredParam(name = "dateRange") DateRangeParam theIdentifiers);
@Search()
public List<Patient> getPatientByDob(@RequiredParam(name=Patient.SP_BIRTHDATE) QualifiedDateParam theBirthDate);
@Search()
public List<Patient> getPatientMultipleIdentifiers(@RequiredParam(name = "ids") CodingListParam theIdentifiers);
@ -46,13 +51,13 @@ public interface ITestClient extends IBasicClient {
@Search(queryName="someQueryOneParam")
public Patient getPatientOneParam(@RequiredParam(name="param1") StringDt theParam);
@Search()
public Patient getPatientWithIncludes(@RequiredParam(name = "withIncludes") StringDt theString, @IncludeParam List<PathSpecification> theIncludes);
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @VersionIdParam IdDt theVersion, @ResourceParam Patient thePatient);
@Update
public MethodOutcome updatePatient(@IdParam IdDt theId, @ResourceParam Patient thePatient);
@ -71,6 +76,12 @@ public interface ITestClient extends IBasicClient {
@History(type=Patient.class)
Bundle getHistoryPatientInstance(@IdParam IdDt theId);
@History(type=Patient.class)
Bundle getHistoryPatientInstance(@IdParam IdDt theId, @Since InstantDt theSince, @Count IntegerDt theCount);
@History(type=Patient.class)
Bundle getHistoryPatientInstance(@IdParam IdDt theId, @Since Date theSince, @Count Integer theCount);
@History(type=Patient.class)
Bundle getHistoryPatientType();

View File

@ -2,7 +2,9 @@ package ca.uhn.fhir.rest.server;
import static org.junit.Assert.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;
@ -15,69 +17,83 @@ import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.hamcrest.core.IsEqual;
import org.hamcrest.core.StringStartsWith;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum;
import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Organization;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.InstantDt;
import ca.uhn.fhir.model.primitive.IntegerDt;
import ca.uhn.fhir.model.primitive.UriDt;
import ca.uhn.fhir.rest.annotation.Count;
import ca.uhn.fhir.rest.annotation.History;
import ca.uhn.fhir.rest.annotation.IdParam;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.annotation.RequiredParam;
import ca.uhn.fhir.rest.annotation.Search;
import ca.uhn.fhir.rest.annotation.Since;
import ca.uhn.fhir.testutil.RandomServerPortProvider;
public class NonResourceProviderServerTest {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(NonResourceProviderServerTest.class);
private static int ourPort;
private static Server ourServer;
private static CloseableHttpClient ourClient;
private static FhirContext ourCtx;
private int myPort;
private Server myServer;
private CloseableHttpClient myClient;
private FhirContext myCtx;
private RestfulServer myRestfulServer;
@BeforeClass
public static void beforeClass() throws Exception {
ourPort = RandomServerPortProvider.findFreePort();
ourServer = new Server(ourPort);
ourCtx = new FhirContext(Patient.class);
@Before
public void before() throws Exception {
myPort = RandomServerPortProvider.findFreePort();
myServer = new Server(myPort);
myCtx = new FhirContext(Patient.class);
ServletHandler proxyHandler = new ServletHandler();
ServletHolder servletHolder = new ServletHolder(new DummyRestfulServer());
ServletHolder servletHolder = new ServletHolder();
myRestfulServer = new RestfulServer();
servletHolder.setServlet(myRestfulServer);
proxyHandler.addServletWithMapping(servletHolder, "/fhir/context/*");
ourServer.setHandler(proxyHandler);
ourServer.start();
myServer.setHandler(proxyHandler);
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager(5000, TimeUnit.MILLISECONDS);
HttpClientBuilder builder = HttpClientBuilder.create();
builder.setConnectionManager(connectionManager);
ourClient = builder.build();
myClient = builder.build();
}
@AfterClass
public static void afterClass() throws Exception {
ourServer.stop();
@After
public void after() throws Exception {
myServer.stop();
}
@Test
public void testSearchByParamIdentifier() throws Exception {
myRestfulServer.setProviders(new SearchProvider());
myServer.start();
String baseUri = "http://localhost:" + ourPort + "/fhir/context";
String baseUri = "http://localhost:" + myPort + "/fhir/context";
String uri = baseUri + "/Patient?identifier=urn:hapitest:mrns%7C00001";
HttpGet httpGet = new HttpGet(uri);
HttpResponse status = ourClient.execute(httpGet);
HttpResponse status = myClient.execute(httpGet);
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = ourCtx.newXmlParser().parseBundle(responseContent);
Bundle bundle = myCtx.newXmlParser().parseBundle(responseContent);
assertEquals(1, bundle.getEntries().size());
@ -87,34 +103,60 @@ public class NonResourceProviderServerTest {
assertEquals(uri, bundle.getLinkSelf().getValue());
assertEquals(baseUri, bundle.getLinkBase().getValue());
}
@Test
public void testGlobalHistory() throws Exception {
GlobalHistoryProvider provider = new GlobalHistoryProvider();
myRestfulServer.setProviders(provider);
myServer.start();
public static class DummyRestfulServer extends RestfulServer {
String baseUri = "http://localhost:" + myPort + "/fhir/context";
HttpResponse status = myClient.execute(new HttpGet(baseUri + "/_history?_since=2012-01-02T00%3A01%3A02&_count=12"));
private static final long serialVersionUID = 1L;
String responseContent = IOUtils.toString(status.getEntity().getContent());
ourLog.info("Response was:\n{}", responseContent);
assertEquals(200, status.getStatusLine().getStatusCode());
Bundle bundle = myCtx.newXmlParser().parseBundle(responseContent);
assertEquals(3, bundle.getEntries().size());
assertThat(provider.myLastSince.getValueAsString(), StringStartsWith.startsWith("2012-01-02T00:01:02"));
assertThat(provider.myLastCount.getValueAsString(), IsEqual.equalTo("12"));
public DummyRestfulServer() {
setProviders(new DummyProvider());
}
status = myClient.execute(new HttpGet(baseUri + "/_history?&_count=12"));
responseContent = IOUtils.toString(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
bundle = myCtx.newXmlParser().parseBundle(responseContent);
assertEquals(3, bundle.getEntries().size());
assertNull(provider.myLastSince.getValueAsString());
assertThat(provider.myLastCount.getValueAsString(), IsEqual.equalTo("12"));
status =myClient.execute(new HttpGet(baseUri + "/_history?_since=2012-01-02T00%3A01%3A02"));
responseContent = IOUtils.toString(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
bundle = myCtx.newXmlParser().parseBundle(responseContent);
assertEquals(3, bundle.getEntries().size());
assertThat(provider.myLastSince.getValueAsString(), StringStartsWith.startsWith("2012-01-02T00:01:02"));
assertNull(provider.myLastCount.getValueAsString());
status =myClient.execute(new HttpGet(baseUri + "/_history"));
responseContent = IOUtils.toString(status.getEntity().getContent());
assertEquals(200, status.getStatusLine().getStatusCode());
bundle = myCtx.newXmlParser().parseBundle(responseContent);
assertEquals(3, bundle.getEntries().size());
assertNull(provider.myLastSince.getValueAsString());
assertNull(provider.myLastCount.getValueAsString());
}
/**
* Created by dsotnikov on 2/25/2014.
*/
public static class DummyProvider {
public static class SearchProvider {
public Map<String, Patient> getIdToPatient() {
Map<String, Patient> idToPatient = new HashMap<String, Patient>();
{
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
Patient patient = createPatient();
idToPatient.put("1", patient);
}
{
@ -158,4 +200,64 @@ public class NonResourceProviderServerTest {
}
public static class GlobalHistoryProvider {
private InstantDt myLastSince;
private IntegerDt myLastCount;
@History
public List<IResource> getGlobalHistory(@Since InstantDt theSince, @Count IntegerDt theCount) {
myLastSince = theSince;
myLastCount = theCount;
ArrayList<IResource> retVal = new ArrayList<IResource>();
IResource p = createPatient();
p.setId(new IdDt("1"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("A"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2012-01-01T01:00:01"));
retVal.add(p);
p = createPatient();
p.setId(new IdDt("1"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("B"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2012-01-01T00:00:01"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2012-01-01T01:00:03"));
retVal.add(p);
p = createOrganization();
p.setId(new IdDt("1"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.VERSION_ID, new IdDt("A"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.PUBLISHED, new InstantDt("2013-01-01T00:00:01"));
p.getResourceMetadata().put(ResourceMetadataKeyEnum.UPDATED, new InstantDt("2013-01-01T01:00:01"));
retVal.add(p);
return retVal;
}
}
private static Patient createPatient() {
Patient patient = new Patient();
patient.addIdentifier();
patient.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
patient.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
patient.getIdentifier().get(0).setValue("00001");
patient.addName();
patient.getName().get(0).addFamily("Test");
patient.getName().get(0).addGiven("PatientOne");
patient.getGender().setText("M");
return patient;
}
private static Organization createOrganization() {
Organization retVal = new Organization();
retVal.addIdentifier();
retVal.getIdentifier().get(0).setUse(IdentifierUseEnum.OFFICIAL);
retVal.getIdentifier().get(0).setSystem(new UriDt("urn:hapitest:mrns"));
retVal.getIdentifier().get(0).setValue("00001");
retVal.getName().setValue("Test Org");
return retVal;
}
}

View File

@ -5,11 +5,6 @@
<classpathentry kind="var" path="M2_REPO/javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2.jar" sourcepath="M2_REPO/javax/xml/stream/stax-api/1.0-2/stax-api-1.0-2-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/javax/servlet/javax.servlet-api/3.0.1/javax.servlet-api-3.0.1.jar" sourcepath="M2_REPO/javax/servlet/javax.servlet-api/3.0.1/javax.servlet-api-3.0.1-sources.jar"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="var" path="M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-base/0.3/hapi-fhir-base-0.3.jar" sourcepath="M2_REPO/ca/uhn/hapi/fhir/hapi-fhir-base/0.1/hapi-fhir-base-0.1-sources.jar">
<attributes>
<attribute name="javadoc_location" value="jar:file:/home/t3903uhn/.m2/repository/ca/uhn/hapi/fhir/hapi-fhir-base/0.1/hapi-fhir-base-0.1-javadoc.jar!/"/>
</attributes>
</classpathentry>
<classpathentry kind="var" path="M2_REPO/org/glassfish/javax.json/1.0.4/javax.json-1.0.4.jar" sourcepath="M2_REPO/org/glassfish/javax.json/1.0.4/javax.json-1.0.4-sources.jar"/>
<classpathentry kind="var" path="M2_REPO/org/codehaus/woodstox/woodstox-core-asl/4.2.0/woodstox-core-asl-4.2.0.jar" sourcepath="M2_REPO/org/codehaus/woodstox/woodstox-core-asl/4.2.0/woodstox-core-asl-4.2.0-sources.jar">
<attributes>
@ -42,5 +37,6 @@
<attribute name="javadoc_location" value="jar:file:/home/t3903uhn/.m2/repository/ch/qos/logback/logback-core/1.1.1/logback-core-1.1.1-javadoc.jar!/"/>
</attributes>
</classpathentry>
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-base"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

View File

@ -1,7 +1,6 @@
package ca.uhn.example.rest;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@ -10,7 +9,6 @@ import ca.uhn.fhir.model.dstu.composite.HumanNameDt;
import ca.uhn.fhir.model.dstu.resource.OperationOutcome;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.dstu.valueset.AdministrativeGenderCodesEnum;
import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum;
import ca.uhn.fhir.model.dstu.valueset.IssueSeverityEnum;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.model.primitive.StringDt;