More client work

This commit is contained in:
jamesagnew 2014-03-06 08:55:31 -05:00
parent b05ea01b45
commit 82193017d8
15 changed files with 296 additions and 184 deletions

View File

@ -3,7 +3,7 @@
<classpathentry including="**/*.java" kind="src" output="target/test-classes" path="src/test/java"/> <classpathentry including="**/*.java" kind="src" output="target/test-classes" path="src/test/java"/>
<classpathentry excluding="**/*.java" kind="src" output="target/test-classes" path="src/test/resources"/> <classpathentry excluding="**/*.java" kind="src" output="target/test-classes" path="src/test/resources"/>
<classpathentry including="**/*.java" kind="src" path="src/main/java"/> <classpathentry including="**/*.java" kind="src" path="src/main/java"/>
<classpathentry kind="var" path="M2_REPO/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0.jar" sourcepath="M2_REPO/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0-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.1.0/javax.servlet-api-3.1.0-sources.jar">
<attributes> <attributes>
<attribute name="javadoc_location" value="jar:file:/Users/james/.m2/repository/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0-javadoc.jar!/"/> <attribute name="javadoc_location" value="jar:file:/Users/james/.m2/repository/javax/servlet/javax.servlet-api/3.1.0/javax.servlet-api-3.1.0-javadoc.jar!/"/>
</attributes> </attributes>
@ -80,8 +80,7 @@
<attribute name="javadoc_location" value="jar:file:/Users/james/.m2/repository/xmlunit/xmlunit/1.5/xmlunit-1.5-javadoc.jar!/"/> <attribute name="javadoc_location" value="jar:file:/Users/james/.m2/repository/xmlunit/xmlunit/1.5/xmlunit-1.5-javadoc.jar!/"/>
</attributes> </attributes>
</classpathentry> </classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.6"/> <classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.7"/>
<classpathentry kind="var" path="M2_REPO/org/mockito/mockito-all/1.9.5/mockito-all-1.9.5.jar"/> <classpathentry kind="var" path="M2_REPO/org/mockito/mockito-all/1.9.5/mockito-all-1.9.5.jar"/>
<classpathentry combineaccessrules="false" kind="src" path="/hapi-fhir-structures-dstu"/>
<classpathentry kind="output" path="target/classes"/> <classpathentry kind="output" path="target/classes"/>
</classpath> </classpath>

View File

@ -5,10 +5,18 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; import java.lang.annotation.Target;
import ca.uhn.fhir.model.api.IResource;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD) @Target(ElementType.METHOD)
public @interface Read { public @interface Read {
/**
* Returns the resource type that is returned by the method annotated
* with this annotation
*/
Class<? extends IResource> value();
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.PARAMETER) @Target(ElementType.PARAMETER)
public @interface IdParam { public @interface IdParam {

View File

@ -0,0 +1,5 @@
package ca.uhn.fhir.rest.client;
public class ClientInvocation {
}

View File

@ -2,21 +2,34 @@ package ca.uhn.fhir.rest.client;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.rest.common.BaseMethodBinding;
public class ClientInvocationHandler implements InvocationHandler { public class ClientInvocationHandler implements InvocationHandler {
private HttpClient myClient; private HttpClient myClient;
private Map<Method, BaseMethodBinding> myBindings = new HashMap<Method, BaseMethodBinding>();
private FhirContext myContext;
public ClientInvocationHandler(HttpClient theClient) { public ClientInvocationHandler(HttpClient theClient, FhirContext theContext) {
myClient = theClient; myClient = theClient;
myContext = theContext;
} }
@Override @Override
public Object invoke(Object theProxy, Method theMethod, Object[] theArgs) throws Throwable { public Object invoke(Object theProxy, Method theMethod, Object[] theArgs) throws Throwable {
// TODO Auto-generated method stub BaseMethodBinding binding = myBindings.get(theMethod);
ClientInvocation clientInvocation = binding.invokeClient(theArgs);
return null; return null;
} }
public void addBinding(Method theMethod, BaseMethodBinding theBinding) {
myBindings.put(theMethod, theBinding);
}
} }

View File

@ -1,6 +1,7 @@
package ca.uhn.fhir.rest.client; package ca.uhn.fhir.rest.client;
import java.lang.reflect.InvocationHandler; import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy; import java.lang.reflect.Proxy;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -11,7 +12,9 @@ import org.apache.http.impl.conn.SchemeRegistryFactory;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.common.BaseMethodBinding;
public class RestfulClientFactory { public class RestfulClientFactory {
@ -20,41 +23,50 @@ public class RestfulClientFactory {
/** /**
* Constructor * Constructor
* *
* @param theContext The context * @param theContext
* The context
*/ */
public RestfulClientFactory(FhirContext theContext) { public RestfulClientFactory(FhirContext theContext) {
myContext = theContext; myContext = theContext;
} }
/** /**
* Instantiates a new client instance * Instantiates a new client instance
* *
* @param theClientType The client type, which is an interface type to be instantiated * @param theClientType
* @param theServerBase The URL of the base for the restful FHIR server to connect to * The client type, which is an interface type to be instantiated
* @param theServerBase
* The URL of the base for the restful FHIR server to connect to
* @return A newly created client * @return A newly created client
* @throws ConfigurationException If the interface type is not an interface * @throws ConfigurationException
* If the interface type is not an interface
*/ */
public <T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase) { public <T extends IRestfulClient> T newClient(Class<T> theClientType, String theServerBase){
if (!theClientType.isInterface()) { if (!theClientType.isInterface()) {
throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface"); throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface");
} }
PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(SchemeRegistryFactory.createDefault(), 5000, TimeUnit.MILLISECONDS); PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(SchemeRegistryFactory.createDefault(), 5000, TimeUnit.MILLISECONDS);
HttpClient client = new DefaultHttpClient(connectionManager); HttpClient client = new DefaultHttpClient(connectionManager);
ClientInvocationHandler theInvocationHandler = new ClientInvocationHandler(client); ClientInvocationHandler theInvocationHandler = new ClientInvocationHandler(client, myContext);
for (Method nextMethod : theClientType.getMethods()) {
BaseMethodBinding binding = BaseMethodBinding.bindMethod(nextMethod);
theInvocationHandler.addBinding(nextMethod, binding);
}
T proxy = instantiateProxy(theClientType, theInvocationHandler); T proxy = instantiateProxy(theClientType, theInvocationHandler);
return proxy; return proxy;
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private <T extends IRestfulClient> T instantiateProxy(Class<T> theClientType, InvocationHandler theInvocationHandler) { private <T extends IRestfulClient> T instantiateProxy(Class<T> theClientType, InvocationHandler theInvocationHandler) {
T proxy = (T) Proxy.newProxyInstance(RestfulClientFactory.class.getClassLoader(), new Class[] {theClientType}, theInvocationHandler); T proxy = (T) Proxy.newProxyInstance(RestfulClientFactory.class.getClassLoader(), new Class[] { theClientType }, theInvocationHandler);
return proxy; return proxy;
} }
} }

View File

@ -0,0 +1,131 @@
package ca.uhn.fhir.rest.common;
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;
import java.util.Set;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.api.annotation.ResourceDef;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.client.ClientInvocation;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.Resource;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.operations.Search;
public abstract class BaseMethodBinding {
private String myResourceName;
public BaseMethodBinding(Class<? extends IResource> theAnnotatedResourceType) {
ResourceDef resourceDefAnnotation = theAnnotatedResourceType.getAnnotation(ResourceDef.class);
if (resourceDefAnnotation == null) {
throw new ConfigurationException(theAnnotatedResourceType.getCanonicalName() + " has no @" + ResourceDef.class.getSimpleName()+ " annotation");
}
myResourceName = resourceDefAnnotation.name();
}
public abstract ReturnTypeEnum getReturnType();
public ClientInvocation invokeClient(Object[] theArgs) {
// TODO Auto-generated method stub
return null;
}
public abstract List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, InternalErrorException;
public abstract boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set<String> theParameterNames);
public String getResourceName() {
return myResourceName;
}
public static BaseMethodBinding bindMethod(Method theMethod) {
Read read = theMethod.getAnnotation(Read.class);
Search search = theMethod.getAnnotation(Search.class);
verifyExactlyOneValued(theMethod, read, search);
Class<? extends IResource> annotatedResourceType;
if (read != null) {
annotatedResourceType = read.value();
} else {
annotatedResourceType = search.value();
}
Class<?> methodReturnType = theMethod.getReturnType();
if (read != null) {
return new ReadMethodBinding(annotatedResourceType, theMethod);
} else if (search != null) {
return new SearchMethodBinding(annotatedResourceType, theMethod);
} else {
throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName());
}
// // each operation name must have a request type annotation and be
// unique
// if (null != read) {
// return rm;
// }
//
// SearchMethodBinding sm = new SearchMethodBinding();
// if (null != search) {
// sm.setRequestType(SearchMethodBinding.RequestType.GET);
// } else if (null != theMethod.getAnnotation(PUT.class)) {
// sm.setRequestType(SearchMethodBinding.RequestType.PUT);
// } else if (null != theMethod.getAnnotation(POST.class)) {
// sm.setRequestType(SearchMethodBinding.RequestType.POST);
// } else if (null != theMethod.getAnnotation(DELETE.class)) {
// sm.setRequestType(SearchMethodBinding.RequestType.DELETE);
// } else {
// return null;
// }
//
// return sm;
}
public static void verifyExactlyOneValued(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) {
throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has no FHIR method annotations.");
}
}
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 enum ReturnTypeEnum {
BUNDLE, RESOURCE
}
}

View File

@ -1,4 +1,4 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.common;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.List; import java.util.List;
@ -10,23 +10,29 @@ import org.apache.commons.lang3.Validate;
import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.Util;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
class ReadMethod extends BaseMethod { public class ReadMethodBinding extends BaseMethodBinding {
private Method myMethod; private Method myMethod;
private Integer myIdIndex; private Integer myIdIndex;
private Integer myVersionIdIndex; private Integer myVersionIdIndex;
private int myParameterCount; private int myParameterCount;
ReadMethod(Method theMethod, Integer theIdIndex, Integer theVersionIdIndex) { public ReadMethodBinding(Class<? extends IResource> theAnnotatedResourceType, Method theMethod) {
Validate.notNull(theMethod, "Method must not be null"); super(theAnnotatedResourceType);
Validate.notNull(theIdIndex, "ID Index must not be null");
Validate.notNull(theMethod, "Method must not be null");
Integer idIndex = Util.findReadIdParameterIndex(theMethod);
Integer versionIdIndex = Util.findReadVersionIdParameterIndex(theMethod);
myMethod = theMethod; myMethod = theMethod;
myIdIndex = theIdIndex; myIdIndex = idIndex;
myVersionIdIndex = theVersionIdIndex; myVersionIdIndex = versionIdIndex;
myParameterCount = myMethod.getParameterTypes().length; myParameterCount = myMethod.getParameterTypes().length;
Class<?>[] parameterTypes = theMethod.getParameterTypes(); Class<?>[] parameterTypes = theMethod.getParameterTypes();
@ -41,7 +47,7 @@ class ReadMethod extends BaseMethod {
@Override @Override
public boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set<String> theParameterNames) { public boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set<String> theParameterNames) {
if (!theResourceName.equals(getResource().getResourceName())) { if (!theResourceName.equals(getResourceName())) {
return false; return false;
} }
if (theParameterNames.isEmpty() == false) { if (theParameterNames.isEmpty() == false) {
@ -62,7 +68,7 @@ class ReadMethod extends BaseMethod {
} }
@Override @Override
public List<IResource> invoke(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException, public List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException,
InternalErrorException { InternalErrorException {
Object[] params = new Object[myParameterCount]; Object[] params = new Object[myParameterCount];
params[myIdIndex] = theId; params[myIdIndex] = theId;

View File

@ -1,41 +1,41 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.common;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.junit.internal.MethodSorter;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.IResourceProvider;
import ca.uhn.fhir.rest.server.Parameter;
import ca.uhn.fhir.rest.server.Util;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
/** /**
* Created by dsotnikov on 2/25/2014. * Created by dsotnikov on 2/25/2014.
*/ */
public class SearchMethod extends BaseMethod { public class SearchMethodBinding extends BaseMethodBinding {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethod.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class);
private Method method; private Method method;
private List<Parameter> parameters; private List<Parameter> parameters;
private RequestType requestType; private RequestType requestType;
private Class<?> resourceType; private Class<?> myDeclaredResourceType;
public SearchMethod() { public SearchMethodBinding(Class<? extends IResource> theAnnotatedResourceType, Method theMethod) {
} super(theAnnotatedResourceType);
this.method = theMethod;
public SearchMethod(Method method, List<Parameter> parameters) { this.parameters = Util.getResourceParameters(theMethod);
this.method = method;
this.parameters = parameters; this.myDeclaredResourceType = theMethod.getReturnType();
} }
public Method getMethod() { public Method getMethod() {
@ -50,8 +50,8 @@ public class SearchMethod extends BaseMethod {
return requestType; return requestType;
} }
public Class getResourceType() { public Class getDeclaredResourceType() {
return resourceType.getClass(); return myDeclaredResourceType.getClass();
} }
@Override @Override
@ -60,7 +60,7 @@ public class SearchMethod extends BaseMethod {
} }
@Override @Override
public List<IResource> invoke(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> parameterValues) throws InvalidRequestException, InternalErrorException { public List<IResource> invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> parameterValues) throws InvalidRequestException, InternalErrorException {
assert theId == null; assert theId == null;
assert theVersionId == null; assert theVersionId == null;
@ -94,12 +94,12 @@ public class SearchMethod extends BaseMethod {
@Override @Override
public boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set<String> theParameterNames) { public boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set<String> theParameterNames) {
if (!theResourceName.equals(getResource().getResourceName())) { if (!theResourceName.equals(getResourceName())) {
ourLog.info("Method {} doesn't match because resource name {} != {}", method.getName(), theResourceName, getResource().getResourceName()); ourLog.trace("Method {} doesn't match because resource name {} != {}", method.getName(), theResourceName, getResourceName());
return false; return false;
} }
if (theId != null || theVersion != null) { if (theId != null || theVersion != null) {
ourLog.info("Method {} doesn't match because ID or Version are not null: {} - {}", theId, theVersion); ourLog.trace("Method {} doesn't match because ID or Version are not null: {} - {}", theId, theVersion);
return false; return false;
} }
@ -108,13 +108,13 @@ public class SearchMethod extends BaseMethod {
Parameter temp = this.parameters.get(i); Parameter temp = this.parameters.get(i);
methodParamsTemp.add(temp.getName()); methodParamsTemp.add(temp.getName());
if (temp.isRequired() && !theParameterNames.contains(temp.getName())) { if (temp.isRequired() && !theParameterNames.contains(temp.getName())) {
ourLog.info("Method {} doesn't match param '{}' is not present", temp.getName()); ourLog.trace("Method {} doesn't match param '{}' is not present", method.getName(), temp.getName());
return false; return false;
} }
} }
boolean retVal = methodParamsTemp.containsAll(theParameterNames); boolean retVal = methodParamsTemp.containsAll(theParameterNames);
ourLog.info("Method {} matches: {}", method.getName(), retVal); ourLog.trace("Method {} matches: {}", method.getName(), retVal);
return retVal; return retVal;
} }
@ -132,7 +132,7 @@ public class SearchMethod extends BaseMethod {
} }
public void setResourceType(Class<?> resourceType) { public void setResourceType(Class<?> resourceType) {
this.resourceType = resourceType; this.myDeclaredResourceType = resourceType;
} }
public static enum RequestType { public static enum RequestType {

View File

@ -1,53 +0,0 @@
package ca.uhn.fhir.rest.server;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
abstract class BaseMethod {
public enum ReturnTypeEnum {
RESOURCE, BUNDLE
}
private Resource resource;
public Resource getResource() {
return resource;
}
public abstract ReturnTypeEnum getReturnType();
public abstract boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set<String> theParameterNames);
public void setResource(Resource theResource) {
this.resource = theResource;
}
public abstract List<IResource> invoke(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map<String, String[]> theParameterValues) throws InvalidRequestException,
InternalErrorException;
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

@ -5,6 +5,7 @@ import java.util.List;
import java.util.Set; import java.util.Set;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.common.BaseMethodBinding;
/** /**
* Created by dsotnikov on 2/25/2014. * Created by dsotnikov on 2/25/2014.
@ -14,25 +15,25 @@ public class Resource {
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Resource.class); private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(Resource.class);
private String resourceName; private String resourceName;
private List<BaseMethod> methods = new ArrayList<BaseMethod>(); private List<BaseMethodBinding> methods = new ArrayList<BaseMethodBinding>();
private IResourceProvider resourceProvider; private IResourceProvider resourceProvider;
public Resource() { public Resource() {
} }
public Resource(String resourceName, List<BaseMethod> methods) { public Resource(String resourceName, List<BaseMethodBinding> methods) {
this.resourceName = resourceName; this.resourceName = resourceName;
this.methods = methods; this.methods = methods;
} }
public BaseMethod getMethod(String theResourceName, IdDt theId, IdDt theVersionId, Set<String> theParameters) throws Exception { public BaseMethodBinding getMethod(String theResourceName, IdDt theId, IdDt theVersionId, Set<String> theParameters) throws Exception {
if (null == methods) { if (null == methods) {
ourLog.warn("No methods exist for resource provider: {}", resourceProvider.getClass()); ourLog.warn("No methods exist for resource provider: {}", resourceProvider.getClass());
return null; return null;
} }
ourLog.info("Looking for a handler for {} / {} / {} / {}", new Object[] {theResourceName,theId, theVersionId, theParameters}); ourLog.info("Looking for a handler for {} / {} / {} / {}", new Object[] {theResourceName,theId, theVersionId, theParameters});
for (BaseMethod rm : methods) { for (BaseMethodBinding rm : methods) {
if (rm.matches(theResourceName, theId, theVersionId, theParameters)) { if (rm.matches(theResourceName, theId, theVersionId, theParameters)) {
ourLog.info("Handler {} matches", rm); ourLog.info("Handler {} matches", rm);
return rm; return rm;
@ -51,17 +52,16 @@ public class Resource {
this.resourceName = resourceName; this.resourceName = resourceName;
} }
public List<BaseMethod> getMethods() { public List<BaseMethodBinding> getMethods() {
return methods; return methods;
} }
public void setMethods(List<BaseMethod> methods) { public void setMethods(List<BaseMethodBinding> methods) {
this.methods = methods; this.methods = methods;
} }
public void addMethod(BaseMethod method) { public void addMethod(BaseMethodBinding method) {
this.methods.add(method); this.methods.add(method);
method.setResource(this);
} }
@Override @Override

View File

@ -1,14 +1,10 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.PrintWriter; import java.io.PrintWriter;
import java.lang.reflect.Method; import java.lang.reflect.Method;
import java.lang.reflect.Modifier; import java.lang.reflect.Modifier;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -20,22 +16,20 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponse;
import org.apache.commons.lang3.StringUtils;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.BundleEntry;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.XmlParser; import ca.uhn.fhir.rest.common.BaseMethodBinding;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.common.SearchMethodBinding;
import ca.uhn.fhir.rest.server.exceptions.AbstractResponseException; import ca.uhn.fhir.rest.server.exceptions.AbstractResponseException;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
import ca.uhn.fhir.rest.server.exceptions.MethodNotFoundException; import ca.uhn.fhir.rest.server.exceptions.MethodNotFoundException;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
import ca.uhn.fhir.rest.server.operations.DELETE;
import ca.uhn.fhir.rest.server.operations.Search;
import ca.uhn.fhir.rest.server.operations.POST;
import ca.uhn.fhir.rest.server.operations.PUT;
public abstract class RestfulServer extends HttpServlet { public abstract class RestfulServer extends HttpServlet {
@ -50,38 +44,6 @@ public abstract class RestfulServer extends HttpServlet {
// map of request handler resources keyed by resource name // map of request handler resources keyed by resource name
private Map<String, Resource> resources = new HashMap<String, Resource>(); private Map<String, Resource> resources = new HashMap<String, Resource>();
private boolean addResourceMethod(Resource resource, Method method) throws Exception {
// each operation name must have a request type annotation and be unique
if (null != method.getAnnotation(Read.class)) {
Integer idIndex = Util.findReadIdParameterIndex(method);
Integer versionIdIndex = Util.findReadVersionIdParameterIndex(method);
ReadMethod rm = new ReadMethod(method, idIndex, versionIdIndex);
resource.addMethod(rm);
return true;
}
SearchMethod sm = new SearchMethod();
if (null != method.getAnnotation(Search.class)) {
sm.setRequestType(SearchMethod.RequestType.GET);
} else if (null != method.getAnnotation(PUT.class)) {
sm.setRequestType(SearchMethod.RequestType.PUT);
} else if (null != method.getAnnotation(POST.class)) {
sm.setRequestType(SearchMethod.RequestType.POST);
} else if (null != method.getAnnotation(DELETE.class)) {
sm.setRequestType(SearchMethod.RequestType.DELETE);
} else {
return false;
}
sm.setMethod(method);
sm.setResourceType(method.getReturnType());
sm.setParameters(Util.getResourceParameters(method));
resource.addMethod(sm);
return true;
}
@SuppressWarnings("unused") @SuppressWarnings("unused")
private EncodingUtil determineResponseEncoding(Map<String, String[]> theParams) { private EncodingUtil determineResponseEncoding(Map<String, String[]> theParams) {
String[] format = theParams.remove(Constants.PARAM_FORMAT); String[] format = theParams.remove(Constants.PARAM_FORMAT);
@ -91,22 +53,22 @@ public abstract class RestfulServer extends HttpServlet {
@Override @Override
protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethod.RequestType.DELETE, request, response); handleRequest(SearchMethodBinding.RequestType.DELETE, request, response);
} }
@Override @Override
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethod.RequestType.GET, request, response); handleRequest(SearchMethodBinding.RequestType.GET, request, response);
} }
@Override @Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethod.RequestType.POST, request, response); handleRequest(SearchMethodBinding.RequestType.POST, request, response);
} }
@Override @Override
protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void doPut(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
handleRequest(SearchMethod.RequestType.PUT, request, response); handleRequest(SearchMethodBinding.RequestType.PUT, request, response);
} }
private void findResourceMethods(IResourceProvider theProvider) throws Exception { private void findResourceMethods(IResourceProvider theProvider) throws Exception {
@ -120,16 +82,17 @@ public abstract class RestfulServer extends HttpServlet {
resources.put(definition.getName(), r); resources.put(definition.getName(), r);
ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass()); ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass());
Class<?> clazz = theProvider.getClass(); Class<?> clazz = theProvider.getClass();
for (Method m : clazz.getDeclaredMethods()) { for (Method m : clazz.getDeclaredMethods()) {
if (Modifier.isPublic(m.getModifiers())) { if (Modifier.isPublic(m.getModifiers())) {
ourLog.info("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); ourLog.info("Scanning public method: {}#{}", theProvider.getClass(), m.getName());
boolean foundMethod = addResourceMethod(r, m); BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m);
if (foundMethod) { if (foundMethodBinding != null) {
r.addMethod(foundMethodBinding);
ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName());
}else { } else {
ourLog.info(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName()); ourLog.info(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName());
} }
} }
@ -138,19 +101,20 @@ public abstract class RestfulServer extends HttpServlet {
public abstract Collection<IResourceProvider> getResourceProviders(); public abstract Collection<IResourceProvider> getResourceProviders();
protected void handleRequest(SearchMethod.RequestType requestType, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { protected void handleRequest(SearchMethodBinding.RequestType requestType, HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
try { try {
String resourceName = null; String resourceName = null;
Long identity = null; Long identity = null;
String requestPath = request.getRequestURI(); String requestPath = StringUtils.defaultString(request.getRequestURI());
requestPath = requestPath.substring(request.getContextPath().length()); String contextPath = StringUtils.defaultString(request.getContextPath());
if (requestPath.charAt(0)=='/') { requestPath = requestPath.substring(contextPath.length());
if (requestPath.charAt(0) == '/') {
requestPath = requestPath.substring(1); requestPath = requestPath.substring(1);
} }
ourLog.info("Request URI: {}", requestPath); ourLog.info("Request URI: {}", requestPath);
Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap()); Map<String, String[]> params = new HashMap<String, String[]>(request.getParameterMap());
EncodingUtil responseEncoding = determineResponseEncoding(params); EncodingUtil responseEncoding = determineResponseEncoding(params);
@ -178,21 +142,23 @@ public abstract class RestfulServer extends HttpServlet {
// //
// if (identity != null && !tok.hasMoreTokens()) { // if (identity != null && !tok.hasMoreTokens()) {
// if (params == null || params.isEmpty()) { // if (params == null || params.isEmpty()) {
// IResource resource = resourceBinding.getResourceProvider().getResourceById(identity); // IResource resource =
// resourceBinding.getResourceProvider().getResourceById(identity);
// if (resource == null) { // if (resource == null) {
// throw new ResourceNotFoundException(identity); // throw new ResourceNotFoundException(identity);
// } // }
// streamResponseAsResource(response, resource, resourceBinding, responseEncoding); // streamResponseAsResource(response, resource, resourceBinding,
// responseEncoding);
// return; // return;
// } // }
// } // }
BaseMethod resourceMethod = resourceBinding.getMethod(resourceName, id, versionId, params.keySet()); BaseMethodBinding resourceMethod = resourceBinding.getMethod(resourceName, id, versionId, params.keySet());
if (null == resourceMethod) { if (null == resourceMethod) {
throw new MethodNotFoundException("No resource method available for the supplied parameters " + params); throw new MethodNotFoundException("No resource method available for the supplied parameters " + params);
} }
List<IResource> result = resourceMethod.invoke(resourceBinding.getResourceProvider(), id, versionId, params); List<IResource> result = resourceMethod.invokeServer(resourceBinding.getResourceProvider(), id, versionId, params);
switch (resourceMethod.getReturnType()) { switch (resourceMethod.getReturnType()) {
case BUNDLE: case BUNDLE:
streamResponseAsBundle(response, result, responseEncoding); streamResponseAsBundle(response, result, responseEncoding);
@ -209,13 +175,13 @@ public abstract class RestfulServer extends HttpServlet {
// resourceMethod.get // resourceMethod.get
} catch (AbstractResponseException e) { } catch (AbstractResponseException e) {
if (e instanceof InternalErrorException) { if (e instanceof InternalErrorException) {
ourLog.error("Failure during REST processing", e); ourLog.error("Failure during REST processing", e);
} else { } else {
ourLog.warn("Failure during REST processing: {}", e.toString()); ourLog.warn("Failure during REST processing: {}", e.toString());
} }
response.setStatus(e.getStatusCode()); response.setStatus(e.getStatusCode());
response.setContentType("text/plain"); response.setContentType("text/plain");
response.setCharacterEncoding("UTF-8"); response.setCharacterEncoding("UTF-8");
@ -293,6 +259,4 @@ public abstract class RestfulServer extends HttpServlet {
} }
} }

View File

@ -3,8 +3,16 @@ package ca.uhn.fhir.rest.server.operations;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import ca.uhn.fhir.model.api.IResource;
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)
public @interface Search { public @interface Search {
/**
* Returns the resource type that is returned by the method annotated
* with this annotation
*/
Class<? extends IResource> value();
} }

View File

@ -0,0 +1,22 @@
package ca.uhn.fhir.rest.client;
import ca.uhn.fhir.model.api.Bundle;
import ca.uhn.fhir.model.dstu.composite.IdentifierDt;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.server.operations.Search;
import ca.uhn.fhir.rest.server.parameters.Required;
public interface ITestClient extends IRestfulClient {
@Read(value=Patient.class)
Patient getPatientById(@Read.IdParam IdDt theId);
@Search(value=Patient.class)
Patient findPatientByMrn(@Required(name = Patient.SP_IDENTIFIER) IdentifierDt theId);
@Search(value=Patient.class)
Bundle findPatientByLastName(@Required(name = Patient.SP_FAMILY) IdentifierDt theId);
}

View File

@ -51,7 +51,7 @@ public class DummyPatientResourceProvider implements IResourceProvider {
} }
} }
@Search @Search(Patient.class)
public Patient getPatient(@Required(name = "identifier") IdentifierDt theIdentifier) { public Patient getPatient(@Required(name = "identifier") IdentifierDt theIdentifier) {
for (Patient next : myIdToPatient.values()) { for (Patient next : myIdToPatient.values()) {
for (IdentifierDt nextId : next.getIdentifier()) { for (IdentifierDt nextId : next.getIdentifier()) {
@ -75,7 +75,7 @@ public class DummyPatientResourceProvider implements IResourceProvider {
* The resource identity * The resource identity
* @return The resource * @return The resource
*/ */
@Read @Read(Patient.class)
public Patient getResourceById(@Read.IdParam IdDt theId) { public Patient getResourceById(@Read.IdParam IdDt theId) {
return myIdToPatient.get(theId.getValue()); return myIdToPatient.get(theId.getValue());
} }

View File

@ -10,20 +10,17 @@ import java.util.Set;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import ca.uhn.fhir.model.dstu.resource.Patient;
import ca.uhn.fhir.rest.common.SearchMethodBinding;
import ca.uhn.fhir.rest.server.Parameter; import ca.uhn.fhir.rest.server.Parameter;
import ca.uhn.fhir.rest.server.SearchMethod;
public class ResourceMethodTest { public class ResourceMethodTest {
private SearchMethod rm; private SearchMethodBinding rm;
@Before @Before
public void before() { public void before() throws NoSuchMethodException, SecurityException {
Resource resource = new Resource(); rm = new SearchMethodBinding(Patient.class, ResourceMethodTest.class.getMethod("before"));
resource.setResourceName("ResName");
rm = new SearchMethod();
rm.setResource(resource);
} }
@Test @Test