diff --git a/hapi-fhir-base/.classpath b/hapi-fhir-base/.classpath index 8a933cf4164..42f0d694287 100644 --- a/hapi-fhir-base/.classpath +++ b/hapi-fhir-base/.classpath @@ -3,7 +3,7 @@ - + @@ -80,8 +80,7 @@ - + - diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Read.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Read.java index b5ae7f3c5e9..19ace2234b1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Read.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Read.java @@ -5,10 +5,18 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import ca.uhn.fhir.model.api.IResource; + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.METHOD) public @interface Read { + /** + * Returns the resource type that is returned by the method annotated + * with this annotation + */ + Class value(); + @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.PARAMETER) public @interface IdParam { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocation.java new file mode 100644 index 00000000000..b3c257c1757 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocation.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.rest.client; + +public class ClientInvocation { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandler.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandler.java index eed8e841f0b..6793e99d446 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandler.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/ClientInvocationHandler.java @@ -2,21 +2,34 @@ package ca.uhn.fhir.rest.client; import java.lang.reflect.InvocationHandler; import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; import org.apache.http.client.HttpClient; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.common.BaseMethodBinding; + public class ClientInvocationHandler implements InvocationHandler { private HttpClient myClient; + private Map myBindings = new HashMap(); + private FhirContext myContext; - public ClientInvocationHandler(HttpClient theClient) { + public ClientInvocationHandler(HttpClient theClient, FhirContext theContext) { myClient = theClient; + myContext = theContext; } @Override 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; } + public void addBinding(Method theMethod, BaseMethodBinding theBinding) { + myBindings.put(theMethod, theBinding); + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/RestfulClientFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/RestfulClientFactory.java index 68f28df3f5f..d1590313cdf 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/RestfulClientFactory.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/RestfulClientFactory.java @@ -1,6 +1,7 @@ package ca.uhn.fhir.rest.client; import java.lang.reflect.InvocationHandler; +import java.lang.reflect.Method; import java.lang.reflect.Proxy; 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.FhirContext; +import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.rest.client.api.IRestfulClient; +import ca.uhn.fhir.rest.common.BaseMethodBinding; public class RestfulClientFactory { @@ -20,41 +23,50 @@ public class RestfulClientFactory { /** * Constructor * - * @param theContext The context + * @param theContext + * The context */ public RestfulClientFactory(FhirContext theContext) { myContext = theContext; } - - + /** * Instantiates a new client instance * - * @param theClientType 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 + * @param theClientType + * 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 - * @throws ConfigurationException If the interface type is not an interface + * @throws ConfigurationException + * If the interface type is not an interface */ - public T newClient(Class theClientType, String theServerBase) { + public T newClient(Class theClientType, String theServerBase){ if (!theClientType.isInterface()) { throw new ConfigurationException(theClientType.getCanonicalName() + " is not an interface"); } - + PoolingClientConnectionManager connectionManager = new PoolingClientConnectionManager(SchemeRegistryFactory.createDefault(), 5000, TimeUnit.MILLISECONDS); 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); - + return proxy; } + @SuppressWarnings("unchecked") private T instantiateProxy(Class 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; } - + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/common/BaseMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/common/BaseMethodBinding.java new file mode 100644 index 00000000000..86fb07ee9cd --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/common/BaseMethodBinding.java @@ -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 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 invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, InternalErrorException; + + public abstract boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set 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 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 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 retVal = new ArrayList(); + 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 + } +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/ReadMethod.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/common/ReadMethodBinding.java similarity index 73% rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/ReadMethod.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/common/ReadMethodBinding.java index 7ae2dd2e3a9..465c78fc471 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/ReadMethod.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/common/ReadMethodBinding.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.rest.server; +package ca.uhn.fhir.rest.common; import java.lang.reflect.Method; import java.util.List; @@ -10,23 +10,29 @@ import org.apache.commons.lang3.Validate; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.model.api.IResource; 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.InvalidRequestException; -class ReadMethod extends BaseMethod { +public class ReadMethodBinding extends BaseMethodBinding { private Method myMethod; private Integer myIdIndex; private Integer myVersionIdIndex; private int myParameterCount; - ReadMethod(Method theMethod, Integer theIdIndex, Integer theVersionIdIndex) { - Validate.notNull(theMethod, "Method must not be null"); - Validate.notNull(theIdIndex, "ID Index must not be null"); + public ReadMethodBinding(Class theAnnotatedResourceType, Method theMethod) { + super(theAnnotatedResourceType); + Validate.notNull(theMethod, "Method must not be null"); + + Integer idIndex = Util.findReadIdParameterIndex(theMethod); + Integer versionIdIndex = Util.findReadVersionIdParameterIndex(theMethod); + myMethod = theMethod; - myIdIndex = theIdIndex; - myVersionIdIndex = theVersionIdIndex; + myIdIndex = idIndex; + myVersionIdIndex = versionIdIndex; myParameterCount = myMethod.getParameterTypes().length; Class[] parameterTypes = theMethod.getParameterTypes(); @@ -41,7 +47,7 @@ class ReadMethod extends BaseMethod { @Override public boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set theParameterNames) { - if (!theResourceName.equals(getResource().getResourceName())) { + if (!theResourceName.equals(getResourceName())) { return false; } if (theParameterNames.isEmpty() == false) { @@ -62,7 +68,7 @@ class ReadMethod extends BaseMethod { } @Override - public List invoke(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, + public List invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, InternalErrorException { Object[] params = new Object[myParameterCount]; params[myIdIndex] = theId; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/SearchMethod.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/common/SearchMethodBinding.java similarity index 66% rename from hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/SearchMethod.java rename to hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/common/SearchMethodBinding.java index a5843676028..9ccb161e7b0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/SearchMethod.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/common/SearchMethodBinding.java @@ -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.Method; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; 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.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.InvalidRequestException; /** * Created by dsotnikov on 2/25/2014. */ -public class SearchMethod extends BaseMethod { - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethod.class); +public class SearchMethodBinding extends BaseMethodBinding { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SearchMethodBinding.class); private Method method; private List parameters; private RequestType requestType; - private Class resourceType; + private Class myDeclaredResourceType; - public SearchMethod() { - } - - public SearchMethod(Method method, List parameters) { - this.method = method; - this.parameters = parameters; + public SearchMethodBinding(Class theAnnotatedResourceType, Method theMethod) { + super(theAnnotatedResourceType); + this.method = theMethod; + this.parameters = Util.getResourceParameters(theMethod); + + this.myDeclaredResourceType = theMethod.getReturnType(); } public Method getMethod() { @@ -50,8 +50,8 @@ public class SearchMethod extends BaseMethod { return requestType; } - public Class getResourceType() { - return resourceType.getClass(); + public Class getDeclaredResourceType() { + return myDeclaredResourceType.getClass(); } @Override @@ -60,7 +60,7 @@ public class SearchMethod extends BaseMethod { } @Override - public List invoke(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map parameterValues) throws InvalidRequestException, InternalErrorException { + public List invokeServer(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map parameterValues) throws InvalidRequestException, InternalErrorException { assert theId == null; assert theVersionId == null; @@ -94,12 +94,12 @@ public class SearchMethod extends BaseMethod { @Override public boolean matches(String theResourceName, IdDt theId, IdDt theVersion, Set theParameterNames) { - if (!theResourceName.equals(getResource().getResourceName())) { - ourLog.info("Method {} doesn't match because resource name {} != {}", method.getName(), theResourceName, getResource().getResourceName()); + if (!theResourceName.equals(getResourceName())) { + ourLog.trace("Method {} doesn't match because resource name {} != {}", method.getName(), theResourceName, getResourceName()); return false; } 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; } @@ -108,13 +108,13 @@ public class SearchMethod extends BaseMethod { Parameter temp = this.parameters.get(i); methodParamsTemp.add(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; } } boolean retVal = methodParamsTemp.containsAll(theParameterNames); - ourLog.info("Method {} matches: {}", method.getName(), retVal); + ourLog.trace("Method {} matches: {}", method.getName(), retVal); return retVal; } @@ -132,7 +132,7 @@ public class SearchMethod extends BaseMethod { } public void setResourceType(Class resourceType) { - this.resourceType = resourceType; + this.myDeclaredResourceType = resourceType; } public static enum RequestType { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/BaseMethod.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/BaseMethod.java deleted file mode 100644 index b952ad51547..00000000000 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/BaseMethod.java +++ /dev/null @@ -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 theParameterNames); - - public void setResource(Resource theResource) { - this.resource = theResource; - } - - public abstract List invoke(IResourceProvider theResourceProvider, IdDt theId, IdDt theVersionId, Map theParameterValues) throws InvalidRequestException, - InternalErrorException; - - protected static List 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 retVal = new ArrayList(); - for (Object next : ((Collection) response)) { - retVal.add((IResource) next); - } - return retVal; - } else { - throw new InternalErrorException("Unexpected return type: " + response.getClass().getCanonicalName()); - } - } -} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Resource.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Resource.java index 27667590896..d1d741c094b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Resource.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Resource.java @@ -5,6 +5,7 @@ import java.util.List; import java.util.Set; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.common.BaseMethodBinding; /** * 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 String resourceName; - private List methods = new ArrayList(); + private List methods = new ArrayList(); private IResourceProvider resourceProvider; public Resource() { } - public Resource(String resourceName, List methods) { + public Resource(String resourceName, List methods) { this.resourceName = resourceName; this.methods = methods; } - public BaseMethod getMethod(String theResourceName, IdDt theId, IdDt theVersionId, Set theParameters) throws Exception { + public BaseMethodBinding getMethod(String theResourceName, IdDt theId, IdDt theVersionId, Set theParameters) throws Exception { if (null == methods) { ourLog.warn("No methods exist for resource provider: {}", resourceProvider.getClass()); return null; } 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)) { ourLog.info("Handler {} matches", rm); return rm; @@ -51,17 +52,16 @@ public class Resource { this.resourceName = resourceName; } - public List getMethods() { + public List getMethods() { return methods; } - public void setMethods(List methods) { + public void setMethods(List methods) { this.methods = methods; } - public void addMethod(BaseMethod method) { + public void addMethod(BaseMethodBinding method) { this.methods.add(method); - method.setResource(this); } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java index ccd4456d9a8..682a327ad73 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java @@ -1,14 +1,10 @@ package ca.uhn.fhir.rest.server; -import java.io.File; import java.io.IOException; import java.io.PrintWriter; import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.net.URL; -import java.util.ArrayList; import java.util.Collection; -import java.util.Enumeration; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -20,22 +16,20 @@ import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang3.StringUtils; + import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.parser.XmlParser; -import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.common.BaseMethodBinding; +import ca.uhn.fhir.rest.common.SearchMethodBinding; import ca.uhn.fhir.rest.server.exceptions.AbstractResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.MethodNotFoundException; 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 { @@ -50,38 +44,6 @@ public abstract class RestfulServer extends HttpServlet { // map of request handler resources keyed by resource name private Map resources = new HashMap(); - 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") private EncodingUtil determineResponseEncoding(Map theParams) { String[] format = theParams.remove(Constants.PARAM_FORMAT); @@ -91,22 +53,22 @@ public abstract class RestfulServer extends HttpServlet { @Override protected void doDelete(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - handleRequest(SearchMethod.RequestType.DELETE, request, response); + handleRequest(SearchMethodBinding.RequestType.DELETE, request, response); } @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - handleRequest(SearchMethod.RequestType.GET, request, response); + handleRequest(SearchMethodBinding.RequestType.GET, request, response); } @Override protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { - handleRequest(SearchMethod.RequestType.POST, request, response); + handleRequest(SearchMethodBinding.RequestType.POST, request, response); } @Override 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 { @@ -120,16 +82,17 @@ public abstract class RestfulServer extends HttpServlet { resources.put(definition.getName(), r); ourLog.info("Scanning type for RESTful methods: {}", theProvider.getClass()); - + Class clazz = theProvider.getClass(); for (Method m : clazz.getDeclaredMethods()) { if (Modifier.isPublic(m.getModifiers())) { ourLog.info("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); - boolean foundMethod = addResourceMethod(r, m); - if (foundMethod) { + BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m); + if (foundMethodBinding != null) { + r.addMethod(foundMethodBinding); ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); - }else { + } else { ourLog.info(" * Method: {}#{} is not a handler", theProvider.getClass(), m.getName()); } } @@ -138,19 +101,20 @@ public abstract class RestfulServer extends HttpServlet { public abstract Collection 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 { String resourceName = null; Long identity = null; - String requestPath = request.getRequestURI(); - requestPath = requestPath.substring(request.getContextPath().length()); - if (requestPath.charAt(0)=='/') { + String requestPath = StringUtils.defaultString(request.getRequestURI()); + String contextPath = StringUtils.defaultString(request.getContextPath()); + requestPath = requestPath.substring(contextPath.length()); + if (requestPath.charAt(0) == '/') { requestPath = requestPath.substring(1); } - + ourLog.info("Request URI: {}", requestPath); - + Map params = new HashMap(request.getParameterMap()); EncodingUtil responseEncoding = determineResponseEncoding(params); @@ -178,21 +142,23 @@ public abstract class RestfulServer extends HttpServlet { // // if (identity != null && !tok.hasMoreTokens()) { // if (params == null || params.isEmpty()) { - // IResource resource = resourceBinding.getResourceProvider().getResourceById(identity); + // IResource resource = + // resourceBinding.getResourceProvider().getResourceById(identity); // if (resource == null) { // throw new ResourceNotFoundException(identity); // } - // streamResponseAsResource(response, resource, resourceBinding, responseEncoding); + // streamResponseAsResource(response, resource, resourceBinding, + // responseEncoding); // return; // } // } - BaseMethod resourceMethod = resourceBinding.getMethod(resourceName, id, versionId, params.keySet()); + BaseMethodBinding resourceMethod = resourceBinding.getMethod(resourceName, id, versionId, params.keySet()); if (null == resourceMethod) { throw new MethodNotFoundException("No resource method available for the supplied parameters " + params); } - List result = resourceMethod.invoke(resourceBinding.getResourceProvider(), id, versionId, params); + List result = resourceMethod.invokeServer(resourceBinding.getResourceProvider(), id, versionId, params); switch (resourceMethod.getReturnType()) { case BUNDLE: streamResponseAsBundle(response, result, responseEncoding); @@ -209,13 +175,13 @@ public abstract class RestfulServer extends HttpServlet { // resourceMethod.get } catch (AbstractResponseException e) { - + if (e instanceof InternalErrorException) { ourLog.error("Failure during REST processing", e); } else { ourLog.warn("Failure during REST processing: {}", e.toString()); } - + response.setStatus(e.getStatusCode()); response.setContentType("text/plain"); response.setCharacterEncoding("UTF-8"); @@ -293,6 +259,4 @@ public abstract class RestfulServer extends HttpServlet { } - - } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/operations/Search.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/operations/Search.java index d42f6ea04e3..347173f7387 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/operations/Search.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/operations/Search.java @@ -3,8 +3,16 @@ package ca.uhn.fhir.rest.server.operations; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import ca.uhn.fhir.model.api.IResource; + @Retention(RetentionPolicy.RUNTIME) public @interface Search { + /** + * Returns the resource type that is returned by the method annotated + * with this annotation + */ + Class value(); + } \ No newline at end of file diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java new file mode 100644 index 00000000000..8ce8648f715 --- /dev/null +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/client/ITestClient.java @@ -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); +} diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DummyPatientResourceProvider.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DummyPatientResourceProvider.java index 383069acb6d..a046e1dc43b 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DummyPatientResourceProvider.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/DummyPatientResourceProvider.java @@ -51,7 +51,7 @@ public class DummyPatientResourceProvider implements IResourceProvider { } } - @Search + @Search(Patient.class) public Patient getPatient(@Required(name = "identifier") IdentifierDt theIdentifier) { for (Patient next : myIdToPatient.values()) { for (IdentifierDt nextId : next.getIdentifier()) { @@ -75,7 +75,7 @@ public class DummyPatientResourceProvider implements IResourceProvider { * The resource identity * @return The resource */ - @Read + @Read(Patient.class) public Patient getResourceById(@Read.IdParam IdDt theId) { return myIdToPatient.get(theId.getValue()); } diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java index 68de4a36687..5d95afeb78a 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java @@ -10,20 +10,17 @@ import java.util.Set; import org.junit.Before; 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.SearchMethod; public class ResourceMethodTest { - private SearchMethod rm; + private SearchMethodBinding rm; @Before - public void before() { - Resource resource = new Resource(); - resource.setResourceName("ResName"); - - rm = new SearchMethod(); - rm.setResource(resource); + public void before() throws NoSuchMethodException, SecurityException { + rm = new SearchMethodBinding(Patient.class, ResourceMethodTest.class.getMethod("before")); } @Test