From 7d319fa770dee1f4a0637acec89ff2f7bbf9bbfe Mon Sep 17 00:00:00 2001 From: jamesagnew Date: Tue, 8 Apr 2014 18:30:51 -0400 Subject: [PATCH] Start to implement delete --- .../ca/uhn/fhir/rest/annotation/DELETE.java | 10 -- .../ca/uhn/fhir/rest/annotation/Delete.java | 30 +++++ .../ca/uhn/fhir/rest/api/MethodOutcome.java | 14 +++ .../rest/client/DeleteClientInvocation.java | 31 +++++ .../rest/client/RestfulClientFactory.java | 2 +- .../fhir/rest/method/BaseMethodBinding.java | 12 +- .../BaseOutcomeReturningMethodBinding.java | 101 +++++++--------- ...turningMethodBindingWithResourceParam.java | 61 ++++++++++ .../fhir/rest/method/CreateMethodBinding.java | 2 +- .../fhir/rest/method/DeleteMethodBinding.java | 112 ++++++++++++++++++ .../fhir/rest/method/UpdateMethodBinding.java | 2 +- .../ca/uhn/fhir/rest/server/Constants.java | 1 + .../uhn/fhir/rest/server/RestfulServer.java | 2 +- .../rest/server/ResfulServerMethodTest.java | 39 ++++++ 14 files changed, 347 insertions(+), 72 deletions(-) delete mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DELETE.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Delete.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/DeleteClientInvocation.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DELETE.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DELETE.java deleted file mode 100644 index 76af748e194..00000000000 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/DELETE.java +++ /dev/null @@ -1,10 +0,0 @@ -package ca.uhn.fhir.rest.annotation; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; - -@Retention(RetentionPolicy.RUNTIME) - -public @interface DELETE { - -} \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Delete.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Delete.java new file mode 100644 index 00000000000..02bb57a3451 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Delete.java @@ -0,0 +1,30 @@ +package ca.uhn.fhir.rest.annotation; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import ca.uhn.fhir.model.api.IResource; + +/** + * RESTful method annotation to be used for the FHIR + * delete method. + * + *

+ * Delete is used to remove an existing resource, meaning that any attempts to + * do a non-version-specific read of that resource will fail. + *

+ */ +@Retention(RetentionPolicy.RUNTIME) +@Target(value=ElementType.METHOD) +public @interface Delete { + + Class resourceType() default NotSpecified.class; + + + interface NotSpecified extends IResource{ + // nothing + } + +} \ No newline at end of file diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java index c0391d8a390..f7830c7de06 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/api/MethodOutcome.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.rest.api; +import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.primitive.IdDt; public class MethodOutcome { @@ -7,10 +8,15 @@ public class MethodOutcome { private IdDt myId; private IdDt myVersionId; private boolean myCreated; + private OperationOutcome myOperationOutcome; public MethodOutcome() { } + public MethodOutcome(IdDt theId) { + myId=theId; + } + public MethodOutcome(boolean theCreated, IdDt theId, IdDt theVersionId) { super(); myId = theId; @@ -50,4 +56,12 @@ public class MethodOutcome { myCreated=theCreated; } + public void setOperationOutcome(OperationOutcome theOperationOutcome) { + myOperationOutcome=theOperationOutcome; + } + + public OperationOutcome getOperationOutcome() { + return myOperationOutcome; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/DeleteClientInvocation.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/DeleteClientInvocation.java new file mode 100644 index 00000000000..5b3ac63f776 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/client/DeleteClientInvocation.java @@ -0,0 +1,31 @@ +package ca.uhn.fhir.rest.client; + +import java.io.IOException; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.client.methods.HttpDelete; +import org.apache.http.client.methods.HttpRequestBase; + +import ca.uhn.fhir.parser.DataFormatException; + +public class DeleteClientInvocation extends BaseClientInvocation { + + private String myUrlPath; + + public DeleteClientInvocation(String... theUrlFragments) { + super(); + myUrlPath = StringUtils.join(theUrlFragments, '/'); + } + + @Override + public HttpRequestBase asHttpRequest(String theUrlBase) throws DataFormatException, IOException { + StringBuilder b = new StringBuilder(); + b.append(theUrlBase); + b.append(myUrlPath); + + HttpDelete retVal = new HttpDelete(b.toString()); + return retVal; + } + + +} 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 f1d9af2286b..99010e0faf8 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 @@ -81,7 +81,7 @@ public class RestfulClientFactory implements IRestfulClientFactory { } resReturnType = (Class) returnTypeColl; } - BaseMethodBinding binding = BaseMethodBinding.bindMethod(resReturnType, nextMethod, myContext); + BaseMethodBinding binding = BaseMethodBinding.bindMethod(resReturnType, nextMethod, myContext,null); invocationHandler.addBinding(nextMethod, binding); } myInvocationHandlers.put(theClientType, invocationHandler); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java index 271e8913e96..dc203770650 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java @@ -22,6 +22,7 @@ import ca.uhn.fhir.model.dstu.valueset.RestfulOperationSystemEnum; import ca.uhn.fhir.model.dstu.valueset.RestfulOperationTypeEnum; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.Delete; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Search; @@ -30,6 +31,7 @@ import ca.uhn.fhir.rest.client.BaseClientInvocation; import ca.uhn.fhir.rest.client.exceptions.NonFhirResponseException; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingUtil; +import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -75,13 +77,15 @@ public abstract class BaseMethodBinding { return parser; } - public static BaseMethodBinding bindMethod(Class theReturnType, Method theMethod, FhirContext theContext) { + public static BaseMethodBinding bindMethod(Class theReturnType, Method theMethod, FhirContext theContext, IResourceProvider theProvider) { Read read = theMethod.getAnnotation(Read.class); Search search = theMethod.getAnnotation(Search.class); Metadata conformance = theMethod.getAnnotation(Metadata.class); Create create = theMethod.getAnnotation(Create.class); Update update = theMethod.getAnnotation(Update.class); - if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance,create,update)) { + Delete delete = theMethod.getAnnotation(Delete.class); + // ** if you add another annotation above, also add it to the next line: + if (!verifyMethodHasZeroOrOneOperationAnnotation(theMethod, read, search, conformance,create,update,delete)) { return null; } @@ -109,6 +113,8 @@ public abstract class BaseMethodBinding { return new CreateMethodBinding(theMethod, theContext); } else if (update != null) { return new UpdateMethodBinding(theMethod, theContext); + } else if (delete != null) { + return new DeleteMethodBinding(theMethod, theContext, theProvider); } else { throw new ConfigurationException("Did not detect any FHIR annotations on method '" + theMethod.getName() + "' on type: " + theMethod.getDeclaringClass().getCanonicalName()); } @@ -202,7 +208,7 @@ public abstract class BaseMethodBinding { public abstract Object invokeClient(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode, Map> theHeaders) throws IOException, BaseServerResponseException; public static BaseMethodBinding bindSystemMethod(Method theMethod, FhirContext theContext) { - return bindMethod(null, theMethod, theContext); + return bindMethod(null, theMethod, theContext, null); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java index 1bf82f12f55..9eef3da3d0f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBinding.java @@ -18,17 +18,14 @@ import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.annotation.ResourceParam; 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.ResourceParameter; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingUtil; import ca.uhn.fhir.rest.server.RestfulServer; @@ -51,48 +48,36 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin ALLOWED_PARAMS = Collections.unmodifiableSet(set); } - private int myResourceParameterIndex; private List myParameters; - private String myResourceName; + private boolean myReturnVoid; public BaseOutcomeReturningMethodBinding(Method theMethod, FhirContext theContext, Class theMethodAnnotation) { super(theMethod, theContext); myParameters = Util.getResourceParameters(theMethod); - ResourceParameter resourceParameter = null; - - int index = 0; - for (IParameter next : myParameters) { - if (next instanceof ResourceParameter) { - resourceParameter = (ResourceParameter) next; - myResourceName = theContext.getResourceDefinition(resourceParameter.getResourceType()).getName(); - myResourceParameterIndex = index; - } - index++; - } - - if (resourceParameter == null) { - throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a parameter annotated with @" + ResourceParam.class.getSimpleName()); - } if (!theMethod.getReturnType().equals(MethodOutcome.class)) { - throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " is a @" + theMethodAnnotation.getSimpleName() + " method but it does not return " + MethodOutcome.class); + if (!allowVoidReturnType()) { + throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " is a @" + theMethodAnnotation.getSimpleName() + + " method but it does not return " + MethodOutcome.class); + } else if (theMethod.getReturnType() == Void.class) { + myReturnVoid = true; + } } } - @Override - public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { - IResource resource = (IResource) theArgs[myResourceParameterIndex]; - if (resource == null) { - throw new NullPointerException("Resource can not be null"); - } - - RuntimeResourceDefinition def = getContext().getResourceDefinition(resource); - String resourceName = def.getName(); - - return createClientInvocation(theArgs, resource, resourceName); + protected List getParameters() { + return myParameters; } + /** + * Subclasses may override to allow a void method return type, which is allowable for some methods (e.g. delete) + */ + protected boolean allowVoidReturnType() { + return false; + } + + protected abstract BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName); @Override @@ -100,6 +85,10 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin switch (theResponseStatusCode) { case Constants.STATUS_HTTP_200_OK: case Constants.STATUS_HTTP_201_CREATED: + case Constants.STATUS_HTTP_204_NO_CONTENT: + if (myReturnVoid) { + return null; + } List locationHeaders = theHeaders.get("location"); MethodOutcome retVal = new MethodOutcome(); if (locationHeaders != null && locationHeaders.size() > 0) { @@ -128,7 +117,7 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin } protected void parseContentLocation(MethodOutcome theOutcomeToPopulate, String theLocationHeader) { - String resourceNamePart = "/" + myResourceName + "/"; + String resourceNamePart = "/" + getResourceName() + "/"; int resourceIndex = theLocationHeader.lastIndexOf(resourceNamePart); if (resourceIndex > -1) { int idIndexStart = resourceIndex + resourceNamePart.length(); @@ -146,9 +135,7 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin } } - public String getResourceName() { - return myResourceName; - } + public abstract String getResourceName(); @Override public void invokeServer(RestfulServer theServer, Request theRequest, HttpServletResponse theResponse) throws BaseServerResponseException, IOException { @@ -166,7 +153,7 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin } addAdditionalParams(theRequest, params); - + MethodOutcome response; try { response = (MethodOutcome) this.getMethod().invoke(theRequest.getResourceProvider(), params); @@ -179,24 +166,28 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin } if (response == null) { - throw new ConfigurationException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null"); - } - - if (response.isCreated()) { - theResponse.setStatus(Constants.STATUS_HTTP_201_CREATED); - StringBuilder b = new StringBuilder(); - b.append(theRequest.getFhirServerBase()); - b.append('/'); - b.append(myResourceName); - b.append('/'); - b.append(response.getId().getValue()); - if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) { - b.append("/_history/"); - b.append(response.getVersionId().getValue()); + if (myReturnVoid == false) { + throw new ConfigurationException("Method " + getMethod().getName() + " in type " + getMethod().getDeclaringClass().getCanonicalName() + " returned null"); + } + } else if (!myReturnVoid) { + if (response.isCreated()) { + theResponse.setStatus(Constants.STATUS_HTTP_201_CREATED); + StringBuilder b = new StringBuilder(); + b.append(theRequest.getFhirServerBase()); + b.append('/'); + b.append(getResourceName()); + b.append('/'); + b.append(response.getId().getValue()); + if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) { + b.append("/_history/"); + b.append(response.getVersionId().getValue()); + } + theResponse.addHeader("Location", b.toString()); + } else { + theResponse.setStatus(Constants.STATUS_HTTP_200_OK); } - theResponse.addHeader("Location", b.toString()); } else { - theResponse.setStatus(Constants.STATUS_HTTP_200_OK); + theResponse.setStatus(Constants.STATUS_HTTP_204_NO_CONTENT); } theServer.addHapiHeader(theResponse); @@ -221,7 +212,7 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin } protected abstract Set provideAllowableRequestTypes(); - + @Override public boolean matches(Request theRequest) { Set allowableRequestTypes = provideAllowableRequestTypes(); @@ -229,7 +220,7 @@ public abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBindin if (!allowableRequestTypes.contains(requestType)) { return false; } - if (!myResourceName.equals(theRequest.getResourceName())) { + if (!getResourceName().equals(theRequest.getResourceName())) { return false; } if (StringUtils.isNotBlank(theRequest.getOperation())) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java new file mode 100644 index 00000000000..2e94e2a9e2c --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java @@ -0,0 +1,61 @@ +package ca.uhn.fhir.rest.method; + +import java.lang.reflect.Method; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.client.BaseClientInvocation; +import ca.uhn.fhir.rest.param.IParameter; +import ca.uhn.fhir.rest.param.ResourceParameter; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; + +public abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOutcomeReturningMethodBinding { + private int myResourceParameterIndex; + private String myResourceName; + + public BaseOutcomeReturningMethodBindingWithResourceParam(Method theMethod, FhirContext theContext, Class theMethodAnnotation) { + super(theMethod, theContext, theMethodAnnotation); + + ResourceParameter resourceParameter = null; + + int index = 0; + for (IParameter next : getParameters()) { + if (next instanceof ResourceParameter) { + resourceParameter = (ResourceParameter) next; + myResourceName = theContext.getResourceDefinition(resourceParameter.getResourceType()).getName(); + myResourceParameterIndex = index; + } + index++; + } + + if (resourceParameter == null) { + throw new ConfigurationException("Method " + theMethod.getName() + " in type " + theMethod.getDeclaringClass().getCanonicalName() + " does not have a parameter annotated with @" + + ResourceParam.class.getSimpleName()); + } + + } + + + @Override + public String getResourceName() { + return myResourceName; + } + + @Override + public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { + IResource resource = (IResource) theArgs[myResourceParameterIndex]; + if (resource == null) { + throw new NullPointerException("Resource can not be null"); + } + + RuntimeResourceDefinition def = getContext().getResourceDefinition(resource); + String resourceName = def.getName(); + + return createClientInvocation(theArgs, resource, resourceName); + } + + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CreateMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CreateMethodBinding.java index 48b3be31746..ff9b0c17755 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CreateMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CreateMethodBinding.java @@ -13,7 +13,7 @@ import ca.uhn.fhir.rest.client.BaseClientInvocation; import ca.uhn.fhir.rest.client.PostClientInvocation; import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; -public class CreateMethodBinding extends BaseOutcomeReturningMethodBinding { +public class CreateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java new file mode 100644 index 00000000000..5524538a36c --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DeleteMethodBinding.java @@ -0,0 +1,112 @@ +package ca.uhn.fhir.rest.method; + +import java.lang.reflect.Method; +import java.util.Collections; +import java.util.Set; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +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.rest.annotation.Delete; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.VersionIdParam; +import ca.uhn.fhir.rest.client.BaseClientInvocation; +import ca.uhn.fhir.rest.client.DeleteClientInvocation; +import ca.uhn.fhir.rest.client.PostClientInvocation; +import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; + +public class DeleteMethodBinding extends BaseOutcomeReturningMethodBinding { + + + + private String myResourceName; + private Integer myIdParameterIndex; + + public DeleteMethodBinding(Method theMethod, FhirContext theContext, IResourceProvider theProvider) { + super(theMethod, theContext, Delete.class); + + Delete deleteAnnotation = theMethod.getAnnotation(Delete.class); + Class resourceType = deleteAnnotation.resourceType(); + if (resourceType != Delete.NotSpecified.class) { + RuntimeResourceDefinition def = theContext.getResourceDefinition(resourceType); + myResourceName = def.getName(); + } else { + if (theProvider!=null) { + RuntimeResourceDefinition def = theContext.getResourceDefinition(theProvider.getResourceType()); + myResourceName = def.getName(); + }else { + throw new ConfigurationException("Can not determine resource type for method '"+theMethod.getName() + "' on type " + theMethod.getDeclaringClass().getCanonicalName() + " - Did you forget to include the resourceType() value on the @" + Delete.class.getSimpleName() + " method annotation?"); + } + } + + myIdParameterIndex = Util.findIdParameterIndex(theMethod); + if (myIdParameterIndex == null) { + throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has no parameter annotated with the @" + IdParam.class.getSimpleName() + " annotation"); + } + + Integer versionIdParameterIndex = Util.findVersionIdParameterIndex(theMethod); + if (versionIdParameterIndex!=null) { + throw new ConfigurationException("Method '" + theMethod.getName() + "' on type '" + theMethod.getDeclaringClass().getCanonicalName() + "' has a parameter annotated with the @" + VersionIdParam.class.getSimpleName() + " annotation but delete methods may not have this annotation"); + } + + } + + @Override + protected boolean allowVoidReturnType() { + return true; + } + + @Override + public RestfulOperationTypeEnum getResourceOperationType() { + return RestfulOperationTypeEnum.DELETE; + } + + @Override + public RestfulOperationSystemEnum getSystemOperationType() { + return null; + } + + @Override + protected Set provideAllowableRequestTypes() { + return Collections.singleton(RequestType.DELETE); + } + + @Override + protected BaseClientInvocation createClientInvocation(Object[] theArgs, IResource resource, String resourceName) { + StringBuilder urlExtension = new StringBuilder(); + urlExtension.append(resourceName); + + return new PostClientInvocation(getContext(), resource, urlExtension.toString()); + } + + @Override + public String getResourceName() { + return myResourceName; + } + +// @Override +// public boolean matches(Request theRequest) { +// // TODO Auto-generated method stub +// return super.matches(theRequest); +// } + + @Override + public BaseClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException { + IdDt idDt = (IdDt) theArgs[myIdParameterIndex]; + if (idDt == null) { + throw new NullPointerException("ID can not be null"); + } + String id = idDt.getValue(); + + DeleteClientInvocation retVal = new DeleteClientInvocation(getResourceName(), id); + + return retVal; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java index 2ce75910292..20bae496de9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/UpdateMethodBinding.java @@ -23,7 +23,7 @@ import ca.uhn.fhir.rest.method.SearchMethodBinding.RequestType; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -public class UpdateMethodBinding extends BaseOutcomeReturningMethodBinding { +public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { private Integer myIdParameterIndex; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java index 694b110c6cc..864dbc5c749 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Constants.java @@ -33,6 +33,7 @@ public class Constants { public static final int STATUS_HTTP_409_CONFLICT = 409; public static final int STATUS_HTTP_412_PRECONDITION_FAILED = 412; public static final String HEADER_CONTENT_LOCATION = "Content-Location"; + public static final int STATUS_HTTP_204_NO_CONTENT = 204; static { Map valToEncoding = new HashMap(); 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 396965fc744..4af39f4454c 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 @@ -101,7 +101,7 @@ public abstract class RestfulServer extends HttpServlet { if (Modifier.isPublic(m.getModifiers())) { ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); - BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(theProvider.getResourceType(), m, myFhirContext); + BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(theProvider.getResourceType(), m, myFhirContext, theProvider); if (foundMethodBinding != null) { r.addMethod(foundMethodBinding); ourLog.info(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java index cdcaa0021ca..184813ef19c 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/rest/server/ResfulServerMethodTest.java @@ -14,6 +14,7 @@ import java.util.concurrent.TimeUnit; import org.apache.commons.io.IOUtils; import org.apache.http.HttpResponse; import org.apache.http.client.methods.CloseableHttpResponse; +import org.apache.http.client.methods.HttpDelete; import org.apache.http.client.methods.HttpGet; import org.apache.http.client.methods.HttpPost; import org.apache.http.client.methods.HttpPut; @@ -41,6 +42,7 @@ import ca.uhn.fhir.model.dstu.composite.HumanNameDt; import ca.uhn.fhir.model.dstu.composite.IdentifierDt; import ca.uhn.fhir.model.dstu.resource.Conformance; import ca.uhn.fhir.model.dstu.resource.DiagnosticReport; +import ca.uhn.fhir.model.dstu.resource.OperationOutcome; import ca.uhn.fhir.model.dstu.resource.Patient; import ca.uhn.fhir.model.dstu.valueset.IdentifierUseEnum; import ca.uhn.fhir.model.primitive.IdDt; @@ -48,6 +50,7 @@ import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.model.primitive.UriDt; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.Delete; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.IncludeParam; import ca.uhn.fhir.rest.annotation.OptionalParam; @@ -547,6 +550,27 @@ public class ResfulServerMethodTest { } + @Test + public void testDelete() throws Exception { + + // HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + + // "/Patient/1"); + // httpPost.setEntity(new StringEntity("test", + // ContentType.create(Constants.CT_FHIR_XML, "UTF-8"))); + + HttpDelete httpGet = new HttpDelete("http://localhost:" + ourPort + "/Patient/1234"); + HttpResponse status = ourClient.execute(httpGet); + + String responseContent = IOUtils.toString(status.getEntity().getContent()); + ourLog.info("Response was:\n{}", responseContent); + + assertEquals(200, status.getStatusLine().getStatusCode()); + + OperationOutcome patient = (OperationOutcome) ourCtx.newXmlParser().parseBundle(responseContent).getEntries().get(0).getResource(); + assertEquals("1234", patient.getIssueFirstRep().getDetails().getValue()); + + } + @Test public void testPrettyPrint() throws Exception { @@ -718,6 +742,12 @@ public class ResfulServerMethodTest { return new MethodOutcome(true, id, version); } + @SuppressWarnings("unused") + @Delete() + public void deleteDiagnosticReport(@IdParam IdDt theId) { + // do nothing + } + } /** @@ -768,6 +798,15 @@ public class ResfulServerMethodTest { return new MethodOutcome(true, id, version); } + @Delete() + public MethodOutcome deletePatient(@IdParam IdDt theId) { + MethodOutcome retVal = new MethodOutcome(); + retVal.setOperationOutcome(new OperationOutcome()); + retVal.getOperationOutcome().addIssue().setDetails(theId.getValue()); + return retVal; + } + + @Search(queryName="someQueryNoParams") public Patient getPatientNoParams() { Patient next = getIdToPatient().get("1");