Merge pull request #367 from SRiviere/jaxrs-sever-evolution

Jaxrs server evolution
This commit is contained in:
James Agnew 2016-05-22 14:57:20 -04:00
commit cd18ee4fde
11 changed files with 896 additions and 447 deletions

View File

@ -0,0 +1,196 @@
package ca.uhn.fhir.jaxrs.server;
/*
* #%L
* HAPI FHIR JAX-RS Server
* %%
* Copyright (C) 2014 - 2016 University Health Network
* %%
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
* #L%
*/
import java.io.IOException;
import java.util.Collections;
import java.util.List;
import javax.interceptor.Interceptors;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor;
import ca.uhn.fhir.jaxrs.server.util.JaxRsMethodBindings;
import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder;
import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.server.BundleInclusionRule;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IBundleProvider;
import ca.uhn.fhir.rest.server.IPagingProvider;
import ca.uhn.fhir.rest.server.IRestfulServer;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
/**
* This server is the abstract superclass for all bundle providers. It exposes
* a large amount of the fhir api functionality using JAXRS
*
* @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
*/
@SuppressWarnings("javadoc")
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
@Consumes({ MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
@Interceptors(JaxRsExceptionInterceptor.class)
public abstract class AbstractJaxRsBundleProvider extends AbstractJaxRsProvider implements IRestfulServer<JaxRsRequest>, IBundleProvider {
/** the method bindings for this class */
private final JaxRsMethodBindings theBindings;
/**
* The default constructor. The method bindings are retrieved from the class
* being constructed.
*/
protected AbstractJaxRsBundleProvider() {
super();
theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass());
}
/**
* Provides the ability to specify the {@link FhirContext}.
* @param ctx the {@link FhirContext} instance.
*/
protected AbstractJaxRsBundleProvider(final FhirContext ctx) {
super(ctx);
theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass());
}
/**
* This constructor takes in an explicit interface class. This subclass
* should be identical to the class being constructed but is given
* explicitly in order to avoid issues with proxy classes in a jee
* environment.
*
* @param theProviderClass the interface of the class
*/
protected AbstractJaxRsBundleProvider(final Class<? extends AbstractJaxRsProvider> theProviderClass) {
theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass);
}
/**
* Create all resources in one transaction
*
* @param resource the body of the post method containing the bundle of the resources being created in a xml/json form
* @return the response
* @see <a href="https://www.hl7.org/fhir/http.html#create">https://www.hl7. org/fhir/http.html#create</a>
*/
@POST
public Response create(final String resource)
throws IOException {
return execute(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.TRANSACTION).resource(resource));
}
/**
* Search the resource type based on some filter criteria
*
* @return the response
* @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a>
*/
@GET
public Response search()
throws IOException {
return execute(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE));
}
/**
* Execute the method described by the requestBuilder and methodKey
*
* @param theRequestBuilder the requestBuilder that contains the information about the request
* @param methodKey the key determining the method to be executed
* @return the response
*/
private Response execute(final Builder theRequestBuilder, final String methodKey)
throws IOException {
final JaxRsRequest theRequest = theRequestBuilder.build();
final BaseMethodBinding<?> method = getBinding(theRequest.getRestOperationType(), methodKey);
try {
return (Response) method.invokeServer(this, theRequest);
}
catch (final Throwable theException) {
return handleException(theRequest, theException);
}
}
/**
* Execute the method described by the requestBuilder
*
* @param theRequestBuilder the requestBuilder that contains the information about the request
* @return the response
*/
private Response execute(final Builder theRequestBuilder)
throws IOException {
return execute(theRequestBuilder, JaxRsMethodBindings.DEFAULT_METHOD_KEY);
}
/**
* Return the method binding for the given rest operation
*
* @param restOperation the rest operation to retrieve
* @param theBindingKey the key determining the method to be executed (needed for e.g. custom operation)
* @return
*/
protected BaseMethodBinding<?> getBinding(final RestOperationTypeEnum restOperation, final String theBindingKey) {
return getBindings().getBinding(restOperation, theBindingKey);
}
/**
* Default: an empty list of interceptors
*
* @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors()
*/
@Override
public List<IServerInterceptor> getInterceptors() {
return Collections.emptyList();
}
/**
* Default: no paging provider
*/
@Override
public IPagingProvider getPagingProvider() {
return null;
}
/**
* Default: BundleInclusionRule.BASED_ON_INCLUDES
*/
@Override
public BundleInclusionRule getBundleInclusionRule() {
return BundleInclusionRule.BASED_ON_INCLUDES;
}
/**
* Return the bindings defined in this resource provider
*
* @return the jax-rs method bindings
*/
public JaxRsMethodBindings getBindings() {
return theBindings;
}
}

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.jaxrs.server; package ca.uhn.fhir.jaxrs.server;
import java.util.Collections; import java.io.IOException;
/* /*
* #%L * #%L
* HAPI FHIR JAX-RS Server * HAPI FHIR JAX-RS Server
@ -12,7 +11,7 @@ import java.util.Collections;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -21,7 +20,7 @@ import java.util.Collections;
* limitations under the License. * limitations under the License.
* #L% * #L%
*/ */
import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -30,11 +29,18 @@ import java.util.Map.Entry;
import javax.ws.rs.core.Context; import javax.ws.rs.core.Context;
import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.HttpHeaders;
import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo; import javax.ws.rs.core.UriInfo;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.hl7.fhir.instance.model.api.IBaseOperationOutcome;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor;
import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException;
import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum;
import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.AddProfileTagEnum; import ca.uhn.fhir.rest.server.AddProfileTagEnum;
@ -43,7 +49,9 @@ import ca.uhn.fhir.rest.server.EncodingEnum;
import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy;
import ca.uhn.fhir.rest.server.IRestfulServerDefaults; import ca.uhn.fhir.rest.server.IRestfulServerDefaults;
import ca.uhn.fhir.rest.server.IServerAddressStrategy; import ca.uhn.fhir.rest.server.IServerAddressStrategy;
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
import ca.uhn.fhir.util.OperationOutcomeUtil;
/** /**
* This is the abstract superclass for all jaxrs providers. It contains some defaults implementing * This is the abstract superclass for all jaxrs providers. It contains some defaults implementing
@ -55,6 +63,11 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
private final FhirContext CTX; private final FhirContext CTX;
/** The default exception interceptor */
private static final JaxRsExceptionInterceptor DEFAULT_EXCEPTION_HANDLER = new JaxRsExceptionInterceptor();
private static final String PROCESSING = "processing";
private static final String ERROR = "error";
/** the uri info */ /** the uri info */
@Context @Context
private UriInfo theUriInfo; private UriInfo theUriInfo;
@ -63,7 +76,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
private HttpHeaders theHeaders; private HttpHeaders theHeaders;
@Override @Override
public FhirContext getFhirContext() { public FhirContext getFhirContext() {
return CTX; return CTX;
} }
@ -78,7 +91,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
* *
* @param ctx the {@link FhirContext} to support. * @param ctx the {@link FhirContext} to support.
*/ */
protected AbstractJaxRsProvider(FhirContext ctx) { protected AbstractJaxRsProvider(final FhirContext ctx) {
CTX = ctx; CTX = ctx;
} }
@ -87,9 +100,9 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
* @return the query parameters * @return the query parameters
*/ */
public Map<String, String[]> getParameters() { public Map<String, String[]> getParameters() {
MultivaluedMap<String, String> queryParameters = getUriInfo().getQueryParameters(); final MultivaluedMap<String, String> queryParameters = getUriInfo().getQueryParameters();
HashMap<String, String[]> params = new HashMap<String, String[]>(); final HashMap<String, String[]> params = new HashMap<String, String[]>();
for (Entry<String, List<String>> paramEntry : queryParameters.entrySet()) { for (final Entry<String, List<String>> paramEntry : queryParameters.entrySet()) {
params.put(paramEntry.getKey(), paramEntry.getValue().toArray(new String[paramEntry.getValue().size()])); params.put(paramEntry.getKey(), paramEntry.getValue().toArray(new String[paramEntry.getValue().size()]));
} }
return params; return params;
@ -101,7 +114,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
* @return * @return
*/ */
public IServerAddressStrategy getServerAddressStrategy() { public IServerAddressStrategy getServerAddressStrategy() {
HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy(); final HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy();
addressStrategy.setValue(getBaseForRequest()); addressStrategy.setValue(getBaseForRequest());
return addressStrategy; return addressStrategy;
} }
@ -124,64 +137,75 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
return getBaseForServer(); return getBaseForServer();
} }
/** /**
* Default: an empty list of interceptors (Interceptors are not yet supported * Default: an empty list of interceptors (Interceptors are not yet supported
* in the JAX-RS server). Please get in touch if you'd like to help! * in the JAX-RS server). Please get in touch if you'd like to help!
* *
* @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors() * @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors()
*/ */
@Override @Override
public List<IServerInterceptor> getInterceptors() { public List<IServerInterceptor> getInterceptors() {
return Collections.emptyList(); return Collections.emptyList();
} }
/** /**
* Get the uriInfo * Get the uriInfo
* @return the uri info * @return the uri info
*/ */
public UriInfo getUriInfo() { public UriInfo getUriInfo() {
return this.theUriInfo; return this.theUriInfo;
} }
/** /**
* Set the Uri Info * Set the Uri Info
* @param uriInfo the uri info * @param uriInfo the uri info
*/ */
public void setUriInfo(UriInfo uriInfo) { public void setUriInfo(final UriInfo uriInfo) {
this.theUriInfo = uriInfo; this.theUriInfo = uriInfo;
} }
/** /**
* Get the headers * Get the headers
* @return the headers * @return the headers
*/ */
public HttpHeaders getHeaders() { public HttpHeaders getHeaders() {
return this.theHeaders; return this.theHeaders;
} }
/** /**
* Set the headers * Set the headers
* @param headers the headers to set * @param headers the headers to set
*/ */
public void setHeaders(HttpHeaders headers) { public void setHeaders(final HttpHeaders headers) {
this.theHeaders = headers; this.theHeaders = headers;
} }
/** /**
* Return the requestbuilder for the server * Return the requestbuilder for the server
* @param requestType the type of the request * @param requestType the type of the request
* @param restOperation the rest operation type * @param restOperation the rest operation type
* @return the requestbuilder * @param theResourceName the resource name
*/ * @return the requestbuilder
public Builder getRequest(RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { */
return new JaxRsRequest.Builder(this, requestType, restOperation, theUriInfo.getRequestUri().toString()); public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation, final String theResourceName) {
} return new JaxRsRequest.Builder(this, requestType, restOperation, theUriInfo.getRequestUri().toString(), theResourceName);
}
/**
* Return the requestbuilder for the server
* @param requestType the type of the request
* @param restOperation the rest operation type
* @return the requestbuilder
*/
public Builder getRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation) {
return getRequest(requestType, restOperation, null);
}
/** /**
* DEFAULT = EncodingEnum.JSON * DEFAULT = EncodingEnum.JSON
*/ */
@Override @Override
public EncodingEnum getDefaultResponseEncoding() { public EncodingEnum getDefaultResponseEncoding() {
return EncodingEnum.JSON; return EncodingEnum.JSON;
} }
@ -189,32 +213,65 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults {
* DEFAULT = true * DEFAULT = true
*/ */
@Override @Override
public boolean isDefaultPrettyPrint() { public boolean isDefaultPrettyPrint() {
return true; return true;
} }
/** /**
* DEFAULT = ETagSupportEnum.DISABLED * DEFAULT = ETagSupportEnum.DISABLED
*/ */
@Override @Override
public ETagSupportEnum getETagSupport() { public ETagSupportEnum getETagSupport() {
return ETagSupportEnum.DISABLED; return ETagSupportEnum.DISABLED;
} }
/** /**
* DEFAULT = AddProfileTagEnum.NEVER * DEFAULT = AddProfileTagEnum.NEVER
*/ */
@Override @Override
public AddProfileTagEnum getAddProfileTag() { public AddProfileTagEnum getAddProfileTag() {
return AddProfileTagEnum.NEVER; return AddProfileTagEnum.NEVER;
} }
/** /**
* DEFAULT = false * DEFAULT = false
*/ */
@Override @Override
public boolean isUseBrowserFriendlyContentTypes() { public boolean isUseBrowserFriendlyContentTypes() {
return true; return true;
} }
/**
* DEFAULT = false
*/
public boolean withStackTrace() {
return false;
}
/**
* Convert an exception to a response
* @param theRequest the incoming request
* @param theException the exception to convert
* @return response
* @throws IOException
*/
public Response handleException(final JaxRsRequest theRequest, final Throwable theException)
throws IOException {
if (theException instanceof JaxRsResponseException) {
return DEFAULT_EXCEPTION_HANDLER.convertExceptionIntoResponse(theRequest, (JaxRsResponseException) theException);
} else if (theException instanceof DataFormatException) {
return DEFAULT_EXCEPTION_HANDLER.convertExceptionIntoResponse(theRequest, new JaxRsResponseException(
new InvalidRequestException(theException.getMessage(), createOutcome((DataFormatException) theException))));
} else {
return DEFAULT_EXCEPTION_HANDLER.convertExceptionIntoResponse(theRequest,
DEFAULT_EXCEPTION_HANDLER.convertException(this, theException));
}
}
private IBaseOperationOutcome createOutcome(final DataFormatException theException) {
final IBaseOperationOutcome oo = OperationOutcomeUtil.newInstance(getFhirContext());
final String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException);
OperationOutcomeUtil.addIssue(getFhirContext(), oo, ERROR, detailsValue, null, PROCESSING);
return oo;
}
} }

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.jaxrs.server;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -39,7 +39,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor;
import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException;
import ca.uhn.fhir.jaxrs.server.util.JaxRsMethodBindings; import ca.uhn.fhir.jaxrs.server.util.JaxRsMethodBindings;
import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder;
@ -58,272 +57,315 @@ import ca.uhn.fhir.rest.server.IRestfulServer;
* @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare
*/ */
@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN }) @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN })
@Consumes({ MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, @Consumes({ MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML })
Constants.CT_FHIR_XML })
@Interceptors(JaxRsExceptionInterceptor.class) @Interceptors(JaxRsExceptionInterceptor.class)
public abstract class AbstractJaxRsResourceProvider<R extends IBaseResource> extends AbstractJaxRsProvider public abstract class AbstractJaxRsResourceProvider<R extends IBaseResource> extends AbstractJaxRsProvider
implements IRestfulServer<JaxRsRequest>, IResourceProvider {
/** the method bindings for this class */ implements IRestfulServer<JaxRsRequest>, IResourceProvider {
private final JaxRsMethodBindings theBindings;
/** /** the method bindings for this class */
* The default constructor. The method bindings are retrieved from the class private final JaxRsMethodBindings theBindings;
* being constructed.
*/
protected AbstractJaxRsResourceProvider() {
super();
theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass());
}
/** /**
* Provides the ability to specify the {@link FhirContext}. * The default constructor. The method bindings are retrieved from the class
* @param ctx the {@link FhirContext} instance. * being constructed.
*/ */
protected AbstractJaxRsResourceProvider(FhirContext ctx) { protected AbstractJaxRsResourceProvider() {
super(ctx); super();
theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass()); theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass());
} }
/** /**
* This constructor takes in an explicit interface class. This subclass * Provides the ability to specify the {@link FhirContext}.
* should be identical to the class being constructed but is given * @param ctx the {@link FhirContext} instance.
* explicitly in order to avoid issues with proxy classes in a jee */
* environment. protected AbstractJaxRsResourceProvider(final FhirContext ctx) {
* super(ctx);
* @param theProviderClass the interface of the class theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass());
*/ }
protected AbstractJaxRsResourceProvider(Class<? extends AbstractJaxRsProvider> theProviderClass) {
super();
theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass);
}
/** /**
* This constructor takes in an explicit interface class. This subclass * This constructor takes in an explicit interface class. This subclass
* should be identical to the class being constructed but is given * should be identical to the class being constructed but is given
* explicitly in order to avoid issues with proxy classes in a jee * explicitly in order to avoid issues with proxy classes in a jee
* environment. * environment.
* *
* @param ctx the {@link FhirContext} instance. * @param theProviderClass the interface of the class
* @param theProviderClass the interface of the class */
*/ protected AbstractJaxRsResourceProvider(final Class<? extends AbstractJaxRsProvider> theProviderClass) {
protected AbstractJaxRsResourceProvider(FhirContext ctx, Class<? extends AbstractJaxRsProvider> theProviderClass) { super();
super(ctx); theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass);
theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass); }
}
/** /**
* The base for request for a resource provider has the following form:</br> * This constructor takes in an explicit interface class. This subclass
* {@link AbstractJaxRsResourceProvider#getBaseForServer() * should be identical to the class being constructed but is given
* getBaseForServer()} + "/" + * explicitly in order to avoid issues with proxy classes in a jee
* {@link AbstractJaxRsResourceProvider#getResourceType() getResourceType()} * environment.
* .{@link java.lang.Class#getSimpleName() getSimpleName()} *
*/ * @param ctx the {@link FhirContext} instance.
@Override * @param theProviderClass the interface of the class
public String getBaseForRequest() { */
try { protected AbstractJaxRsResourceProvider(final FhirContext ctx, final Class<? extends AbstractJaxRsProvider> theProviderClass) {
return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); super(ctx);
} catch (Exception e) { theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass);
// cannot happen }
return null;
}
}
/** /**
* Create a new resource with a server assigned id * The base for request for a resource provider has the following form:</br>
* * {@link AbstractJaxRsResourceProvider#getBaseForServer()
* @param resource the body of the post method containing resource being created in a xml/json form * getBaseForServer()} + "/" +
* @return the response * {@link AbstractJaxRsResourceProvider#getResourceType() getResourceType()}
* @see <a href="https://www.hl7.org/fhir/http.html#create">https://www.hl7. org/fhir/http.html#create</a> * .{@link java.lang.Class#getSimpleName() getSimpleName()}
*/ */
@POST @Override
public Response create(final String resource) throws IOException { public String getBaseForRequest() {
return execute(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.CREATE).resource(resource)); try {
} return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm();
}
catch (final Exception e) {
// cannot happen
return null;
}
}
/** /**
* Search the resource type based on some filter criteria * Create a new resource with a server assigned id
* *
* @return the response * @param resource the body of the post method containing resource being created in a xml/json form
* @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a> * @return the response
*/ * @see <a href="https://www.hl7.org/fhir/http.html#create">https://www.hl7. org/fhir/http.html#create</a>
@POST */
@Path("/_search") @POST
public Response searchWithPost() throws IOException { public Response create(final String resource)
return execute(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE)); throws IOException {
} return execute(getResourceRequest(RequestTypeEnum.POST, RestOperationTypeEnum.CREATE).resource(resource));
}
/** /**
* Search the resource type based on some filter criteria * Search the resource type based on some filter criteria
* *
* @return the response * @return the response
* @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a> * @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a>
*/ */
@GET @POST
public Response search() throws IOException { @Path("/_search")
return execute(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE)); public Response searchWithPost()
} throws IOException {
return execute(getResourceRequest(RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE));
}
/** /**
* Update an existing resource by its id (or create it if it is new) * Search the resource type based on some filter criteria
* *
* @param id the id of the resource * @return the response
* @param resource the body contents for the put method * @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a>
* @return the response */
* @see <a href="https://www.hl7.org/fhir/http.html#update">https://www.hl7.org/fhir/http.html#update</a> @GET
*/ public Response search()
@PUT throws IOException {
@Path("/{id}") return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE));
public Response update(@PathParam("id") final String id, final String resource) throws IOException { }
return execute(getRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).id(id).resource(resource));
}
/** /**
* Delete a resource * Update an existing resource based on the given condition
* * @param resource the body contents for the put method
* @param id the id of the resource to delete * @return the response
* @return the response * @see <a href="https://www.hl7.org/fhir/http.html#update">https://www.hl7.org/fhir/http.html#update</a>
* @see <a href="https://www.hl7.org/fhir/http.html#delete">https://www.hl7.org/fhir/http.html#delete</a> */
*/ @PUT
@DELETE public Response conditionalUpdate(final String resource)
@Path("/{id}") throws IOException {
public Response delete(@PathParam("id") final String id) throws IOException { return execute(getResourceRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).resource(resource));
return execute(getRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE).id(id)); }
}
/** /**
* Read the current state of the resource * Update an existing resource by its id (or create it if it is new)
* *
* @param id the id of the resource to read * @param id the id of the resource
* @return the response * @param resource the body contents for the put method
* @see <a href="https://www.hl7.org/fhir/http.html#read">https://www.hl7.org/fhir/http.html#read</a> * @return the response
*/ * @see <a href="https://www.hl7.org/fhir/http.html#update">https://www.hl7.org/fhir/http.html#update</a>
@GET */
@Path("/{id}") @PUT
public Response find(@PathParam("id") final String id) throws IOException { @Path("/{id}")
return execute(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).id(id)); public Response update(@PathParam("id") final String id, final String resource)
} throws IOException {
return execute(getResourceRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).id(id).resource(resource));
}
/** /**
* Execute a custom operation * Delete a resource based on the given condition
* *
* @param resource the resource to create * @return the response
* @param requestType the type of request * @see <a href="https://www.hl7.org/fhir/http.html#delete">https://www.hl7.org/fhir/http.html#delete</a>
* @param id the id of the resource on which to perform the operation */
* @param operationName the name of the operation to execute @DELETE
* @param operationType the rest operation type public Response delete()
* @return the response throws IOException {
* @see <a href="https://www.hl7.org/fhir/operations.html">https://www.hl7.org/fhir/operations.html</a> return execute(getResourceRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE));
*/ }
protected Response customOperation(final String resource, RequestTypeEnum requestType, String id,
String operationName, RestOperationTypeEnum operationType) throws IOException {
Builder request = getRequest(requestType, operationType).resource(resource).id(id);
return execute(request, operationName);
}
/** /**
* Retrieve the update history for a particular resource * Delete a resource
* *
* @param id the id of the resource * @param id the id of the resource to delete
* @param version the version of the resource * @return the response
* @return the response * @see <a href="https://www.hl7.org/fhir/http.html#delete">https://www.hl7.org/fhir/http.html#delete</a>
* @see <a href="https://www.hl7.org/fhir/http.html#history">https://www.hl7.org/fhir/http.html#history</a> */
*/ @DELETE
@GET @Path("/{id}")
@Path("/{id}/_history/{version}") public Response delete(@PathParam("id") final String id)
public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version) throws IOException {
throws IOException { return execute(getResourceRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE).id(id));
Builder theRequest = getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.VREAD).id(id) }
.version(version);
return execute(theRequest);
}
/** /**
* Compartment Based Access * Read the current state of the resource
* *
* @param id the resource to which the compartment belongs * @param id the id of the resource to read
* @param compartment the compartment * @return the response
* @return the repsonse * @see <a href="https://www.hl7.org/fhir/http.html#read">https://www.hl7.org/fhir/http.html#read</a>
* @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a> */
* @see <a href="https://www.hl7.org/fhir/compartments.html#compartment">https://www.hl7.org/fhir/compartments.html#compartment</a> @GET
*/ @Path("/{id}")
@GET public Response find(@PathParam("id") final String id)
@Path("/{id}/{compartment}") throws IOException {
public Response findCompartment(@PathParam("id") final String id, return execute(getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).id(id));
@PathParam("compartment") final String compartment) throws IOException { }
Builder theRequest = getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE).id(id)
.compartment(compartment);
return execute(theRequest, compartment);
}
/** /**
* Execute the method described by the requestBuilder and methodKey * Execute a custom operation
* *
* @param theRequestBuilder the requestBuilder that contains the information about the request * @param resource the resource to create
* @param methodKey the key determining the method to be executed * @param requestType the type of request
* @return the response * @param id the id of the resource on which to perform the operation
*/ * @param operationName the name of the operation to execute
private Response execute(Builder theRequestBuilder, String methodKey) throws IOException { * @param operationType the rest operation type
JaxRsRequest theRequest = theRequestBuilder.build(); * @return the response
BaseMethodBinding<?> method = getBinding(theRequest.getRestOperationType(), methodKey); * @see <a href="https://www.hl7.org/fhir/operations.html">https://www.hl7.org/fhir/operations.html</a>
try { */
return (Response) method.invokeServer(this, theRequest); protected Response customOperation(final String resource, final RequestTypeEnum requestType, final String id,
} catch (JaxRsResponseException theException) { final String operationName, final RestOperationTypeEnum operationType)
return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(theRequest, theException); throws IOException {
} final Builder request = getResourceRequest(requestType, operationType).resource(resource).id(id);
} return execute(request, operationName);
}
/** /**
* Execute the method described by the requestBuilder * Retrieve the update history for a particular resource
* *
* @param theRequestBuilder the requestBuilder that contains the information about the request * @param id the id of the resource
* @return the response * @param version the version of the resource
*/ * @return the response
private Response execute(Builder theRequestBuilder) throws IOException { * @see <a href="https://www.hl7.org/fhir/http.html#history">https://www.hl7.org/fhir/http.html#history</a>
return execute(theRequestBuilder, JaxRsMethodBindings.DEFAULT_METHOD_KEY); */
} @GET
@Path("/{id}/_history/{version}")
public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version)
throws IOException {
final Builder theRequest = getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.VREAD).id(id).version(version);
return execute(theRequest);
}
/** /**
* Return the method binding for the given rest operation * Compartment Based Access
* *
* @param restOperation the rest operation to retrieve * @param id the resource to which the compartment belongs
* @param theBindingKey the key determining the method to be executed (needed for e.g. custom operation) * @param compartment the compartment
* @return * @return the repsonse
*/ * @see <a href="https://www.hl7.org/fhir/http.html#search">https://www.hl7.org/fhir/http.html#search</a>
protected BaseMethodBinding<?> getBinding(RestOperationTypeEnum restOperation, String theBindingKey) { * @see <a href="https://www.hl7.org/fhir/compartments.html#compartment">https://www.hl7.org/fhir/compartments.html#compartment</a>
return getBindings().getBinding(restOperation, theBindingKey); */
} @GET
@Path("/{id}/{compartment}")
public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment)
throws IOException {
final Builder theRequest = getResourceRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE).id(id).compartment(
compartment);
return execute(theRequest, compartment);
}
/** /**
* Default: no paging provider * Execute the method described by the requestBuilder and methodKey
*/ *
@Override * @param theRequestBuilder the requestBuilder that contains the information about the request
public IPagingProvider getPagingProvider() { * @param methodKey the key determining the method to be executed
return null; * @return the response
} */
private Response execute(final Builder theRequestBuilder, final String methodKey)
throws IOException {
final JaxRsRequest theRequest = theRequestBuilder.build();
final BaseMethodBinding<?> method = getBinding(theRequest.getRestOperationType(), methodKey);
try {
return (Response) method.invokeServer(this, theRequest);
}
catch (final Throwable theException) {
return handleException(theRequest, theException);
}
}
/** /**
* Default: BundleInclusionRule.BASED_ON_INCLUDES * Execute the method described by the requestBuilder
*/ *
@Override * @param theRequestBuilder the requestBuilder that contains the information about the request
public BundleInclusionRule getBundleInclusionRule() { * @return the response
return BundleInclusionRule.BASED_ON_INCLUDES; */
} private Response execute(final Builder theRequestBuilder)
throws IOException {
return execute(theRequestBuilder, JaxRsMethodBindings.DEFAULT_METHOD_KEY);
}
/** /**
* The resource type should return conform to the generic resource included * Return the method binding for the given rest operation
* in the topic *
*/ * @param restOperation the rest operation to retrieve
@Override * @param theBindingKey the key determining the method to be executed (needed for e.g. custom operation)
public abstract Class<R> getResourceType(); * @return
*/
protected BaseMethodBinding<?> getBinding(final RestOperationTypeEnum restOperation, final String theBindingKey) {
return getBindings().getBinding(restOperation, theBindingKey);
}
/** /**
* Return the bindings defined in this resource provider * Default: no paging provider
* */
* @return the jax-rs method bindings @Override
*/ public IPagingProvider getPagingProvider() {
public JaxRsMethodBindings getBindings() { return null;
return theBindings; }
}
/**
* Default: BundleInclusionRule.BASED_ON_INCLUDES
*/
@Override
public BundleInclusionRule getBundleInclusionRule() {
return BundleInclusionRule.BASED_ON_INCLUDES;
}
/**
* The resource type should return conform to the generic resource included
* in the topic
*/
@Override
public abstract Class<R> getResourceType();
/**
* Return the bindings defined in this resource provider
*
* @return the jax-rs method bindings
*/
public JaxRsMethodBindings getBindings() {
return theBindings;
}
/**
* Return the request builder based on the resource name for the server
* @param requestType the type of the request
* @param restOperation the rest operation type
* @return the requestbuilder
*/
private Builder getResourceRequest(final RequestTypeEnum requestType, final RestOperationTypeEnum restOperation) {
return getRequest(requestType, restOperation, getResourceType().getSimpleName());
}
} }

View File

@ -10,7 +10,7 @@ package ca.uhn.fhir.jaxrs.server.interceptor;
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
@ -41,71 +41,89 @@ import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor;
public class JaxRsExceptionInterceptor { public class JaxRsExceptionInterceptor {
/** the existing exception handler which is able to convert exception into responses*/ /** the existing exception handler which is able to convert exception into responses*/
private ExceptionHandlingInterceptor exceptionHandler; private final ExceptionHandlingInterceptor exceptionHandler;
/** /**
* The default constructor * The default constructor
*/ */
public JaxRsExceptionInterceptor() { public JaxRsExceptionInterceptor() {
this.exceptionHandler = new ExceptionHandlingInterceptor(); this.exceptionHandler = new ExceptionHandlingInterceptor();
} }
/** /**
* A utility constructor for unit testing * A utility constructor for unit testing
* @param exceptionHandler the handler for the exception conversion * @param exceptionHandler the handler for the exception conversion
*/ */
JaxRsExceptionInterceptor(ExceptionHandlingInterceptor exceptionHandler) { JaxRsExceptionInterceptor(final ExceptionHandlingInterceptor exceptionHandler) {
this.exceptionHandler = exceptionHandler; this.exceptionHandler = exceptionHandler;
} }
/** /**
* This interceptor will catch all exception and convert them using the exceptionhandler * This interceptor will catch all exception and convert them using the exceptionhandler
* @param ctx the invocation context * @param ctx the invocation context
* @return the result * @return the result
* @throws JaxRsResponseException an exception that can be handled by a jee container * @throws JaxRsResponseException an exception that can be handled by a jee container
*/ */
@AroundInvoke @AroundInvoke
public Object intercept(final InvocationContext ctx) throws JaxRsResponseException { public Object intercept(final InvocationContext ctx)
throws JaxRsResponseException {
try { try {
return ctx.proceed(); return ctx.proceed();
} catch(final Exception theException) { }
AbstractJaxRsProvider theServer = (AbstractJaxRsProvider) ctx.getTarget(); catch (final Exception theException) {
throw convertException(theServer, theException); final AbstractJaxRsProvider theServer = (AbstractJaxRsProvider) ctx.getTarget();
throw convertException(theServer, theException);
} }
} }
private JaxRsResponseException convertException(final AbstractJaxRsProvider theServer, final Exception theException) { /**
JaxRsRequest requestDetails = theServer.getRequest(null, null).build(); * This method convert an exception to a JaxRsResponseException
BaseServerResponseException convertedException = preprocessException(theException, requestDetails); * @param theServer the provider
return new JaxRsResponseException(convertedException); * @param theException the exception to convert
} * @return JaxRsResponseException
*/
public JaxRsResponseException convertException(final AbstractJaxRsProvider theServer, final Throwable theException) {
if (theServer.withStackTrace()) {
exceptionHandler.setReturnStackTracesForExceptionTypes(Throwable.class);
}
final JaxRsRequest requestDetails = theServer.getRequest(null, null).build();
final BaseServerResponseException convertedException = preprocessException(theException, requestDetails);
return new JaxRsResponseException(convertedException);
}
/** /**
* This method converts an exception into a response * This method converts an exception into a response
* @param theRequest the request * @param theRequest the request
* @param theException the thrown exception * @param theException the thrown exception
* @return the response describing the error * @return the response describing the error
* @throws IOException * @throws IOException
*/ */
public Response convertExceptionIntoResponse(JaxRsRequest theRequest, JaxRsResponseException theException) public Response convertExceptionIntoResponse(final JaxRsRequest theRequest, final JaxRsResponseException theException)
throws IOException { throws IOException {
return handleExceptionWithoutServletError(theRequest, theException); return handleExceptionWithoutServletError(theRequest, theException);
} }
private BaseServerResponseException preprocessException(final Exception theException, JaxRsRequest requestDetails) { private BaseServerResponseException preprocessException(final Throwable theException, final JaxRsRequest requestDetails) {
try { try {
return exceptionHandler.preProcessOutgoingException(requestDetails, theException, null); Throwable theExceptionToConvert = theException;
} catch(ServletException e) { if (!(theException instanceof BaseServerResponseException) && (theException.getCause() instanceof BaseServerResponseException)) {
return new InternalErrorException(e); theExceptionToConvert = theException.getCause();
} }
} return exceptionHandler.preProcessOutgoingException(requestDetails, theExceptionToConvert, null);
}
catch (final ServletException e) {
return new InternalErrorException(e);
}
}
private Response handleExceptionWithoutServletError(JaxRsRequest theRequest, BaseServerResponseException theException) throws IOException { private Response handleExceptionWithoutServletError(final JaxRsRequest theRequest, final BaseServerResponseException theException)
try { throws IOException {
return (Response) exceptionHandler.handleException(theRequest, theException); try {
} catch (ServletException e) { return (Response) exceptionHandler.handleException(theRequest, theException);
BaseServerResponseException newException = preprocessException(new InternalErrorException(e), theRequest); }
return handleExceptionWithoutServletError(theRequest, newException); catch (final ServletException e) {
} final BaseServerResponseException newException = preprocessException(new InternalErrorException(e), theRequest);
} return handleExceptionWithoutServletError(theRequest, newException);
}
}
} }

View File

@ -64,6 +64,7 @@ public class JaxRsRequest extends RequestDetails {
private String myVersion; private String myVersion;
private String myCompartment; private String myCompartment;
private String myRequestUrl; private String myRequestUrl;
private final String myResourceName;
/** /**
* Utility Constructor * Utility Constructor
@ -73,11 +74,12 @@ public class JaxRsRequest extends RequestDetails {
* @param theRequestUrl * @param theRequestUrl
*/ */
public Builder(AbstractJaxRsProvider theServer, RequestTypeEnum theRequestType, public Builder(AbstractJaxRsProvider theServer, RequestTypeEnum theRequestType,
RestOperationTypeEnum theRestOperation, String theRequestUrl) { RestOperationTypeEnum theRestOperation, String theRequestUrl, String theResourceName) {
this.myServer = theServer; this.myServer = theServer;
this.myRequestType = theRequestType; this.myRequestType = theRequestType;
this.myRestOperation = theRestOperation; this.myRestOperation = theRestOperation;
this.myRequestUrl = theRequestUrl; this.myRequestUrl = theRequestUrl;
this.myResourceName = theResourceName;
} }
/** /**
@ -125,44 +127,45 @@ public class JaxRsRequest extends RequestDetails {
* @return the jax-rs request * @return the jax-rs request
*/ */
public JaxRsRequest build() { public JaxRsRequest build() {
JaxRsRequest result = new JaxRsRequest(myServer, myResource, myRequestType, myRestOperation); JaxRsRequest result = new JaxRsRequest(myServer, myResource, myRequestType, myRestOperation);
if ((StringUtils.isNotBlank(myVersion) || StringUtils.isNotBlank(myCompartment)) if ((StringUtils.isNotBlank(myVersion) || StringUtils.isNotBlank(myCompartment))
&& StringUtils.isBlank(myId)) { && StringUtils.isBlank(myId)) {
throw new InvalidRequestException("Don't know how to handle request path: " throw new InvalidRequestException("Don't know how to handle request path: "
+ myServer.getUriInfo().getRequestUri().toASCIIString()); + myServer.getUriInfo().getRequestUri().toASCIIString());
} }
FhirVersionEnum fhirContextVersion = myServer.getFhirContext().getVersion().getVersion(); FhirVersionEnum fhirContextVersion = myServer.getFhirContext().getVersion().getVersion();
if (StringUtils.isNotBlank(myVersion)) { if (StringUtils.isNotBlank(myVersion)) {
if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) { if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) {
result.setId( result.setId(
new IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); new IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion)));
} else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) { } else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) {
result.setId( result.setId(
new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion))); new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId), UrlUtil.unescape(myVersion)));
} }
} else if (StringUtils.isNotBlank(myId)) { } else if (StringUtils.isNotBlank(myId)) {
if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) { if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) {
result.setId(new IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId))); result.setId(new IdType(myServer.getBaseForRequest(), UrlUtil.unescape(myId)));
} else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) { } else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) {
result.setId(new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId))); result.setId(new IdDt(myServer.getBaseForRequest(), UrlUtil.unescape(myId)));
} }
} }
if (myRestOperation == RestOperationTypeEnum.UPDATE) { if (myRestOperation == RestOperationTypeEnum.UPDATE) {
String contentLocation = result.getHeader(Constants.HEADER_CONTENT_LOCATION); String contentLocation = result.getHeader(Constants.HEADER_CONTENT_LOCATION);
if (contentLocation != null) { if (contentLocation != null) {
if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) { if (FhirVersionEnum.DSTU3.equals(fhirContextVersion)) {
result.setId(new IdType(contentLocation)); result.setId(new IdType(contentLocation));
} else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) { } else if (FhirVersionEnum.DSTU2.equals(fhirContextVersion)) {
result.setId(new IdDt(contentLocation)); result.setId(new IdDt(contentLocation));
} }
} }
} }
result.setCompartmentName(myCompartment); result.setCompartmentName(myCompartment);
result.setCompleteUrl(myRequestUrl); result.setCompleteUrl(myRequestUrl);
result.setResourceName(myResourceName);
return result; return result;
} }

View File

@ -0,0 +1,6 @@
package ca.uhn.fhir.jaxrs.server;
@SuppressWarnings("javadoc")
public class AbstractJaxRsProviderMock extends AbstractJaxRsProvider {
}

View File

@ -0,0 +1,86 @@
package ca.uhn.fhir.jaxrs.server;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException;
import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest;
import ca.uhn.fhir.jaxrs.server.util.JaxRsResponse;
import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.parser.DataFormatException;
import ca.uhn.fhir.rest.server.Constants;
import ca.uhn.fhir.rest.server.IRestfulResponse;
import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
@SuppressWarnings("javadoc")
@RunWith(MockitoJUnitRunner.class)
public class AbstractJaxRsProviderTest {
private AbstractJaxRsProviderMock provider;
@Mock
private JaxRsRequest theRequest;
@Before
public void setUp() {
provider = new AbstractJaxRsProviderMock();
final IRestfulResponse response = new JaxRsResponse(theRequest);
doReturn(provider).when(theRequest).getServer();
doReturn(response).when(theRequest).getResponse();
}
@Test
public void testWithStackTrace() {
assertFalse(provider.withStackTrace());
}
@Test
public void testHandleExceptionJaxRsResponseException() throws IOException {
final ResourceNotFoundException base = new ResourceNotFoundException(new IdDt(1L));
final JaxRsResponseException theException = new JaxRsResponseException(base);
final Response result = provider.handleException(theRequest, theException);
assertNotNull(result);
assertEquals(base.getStatusCode(), result.getStatus());
}
@Test
public void testHandleExceptionDataFormatException() throws IOException {
final DataFormatException theException = new DataFormatException();
final Response result = provider.handleException(theRequest, theException);
assertNotNull(result);
assertEquals(Constants.STATUS_HTTP_400_BAD_REQUEST, result.getStatus());
}
@Test
public void testHandleExceptionRuntimeException() throws IOException, URISyntaxException {
final RuntimeException theException = new RuntimeException();
final UriInfo mockUriInfo = mock(UriInfo.class);
final MultivaluedMap<String, String> mockMap = mock(MultivaluedMap.class);
when(mockUriInfo.getBaseUri()).thenReturn(new URI("http://www.test.com"));
when(mockUriInfo.getRequestUri()).thenReturn(new URI("http://www.test.com/test"));
when(mockUriInfo.getQueryParameters()).thenReturn(mockMap);
provider.setUriInfo(mockUriInfo);
final Response result = provider.handleException(theRequest, theException);
assertNotNull(result);
assertEquals(Constants.STATUS_HTTP_500_INTERNAL_ERROR, result.getStatus());
}
}

View File

@ -4,10 +4,12 @@ import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue; import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Matchers.any; import static org.mockito.Matchers.any;
import static org.mockito.Matchers.argThat; import static org.mockito.Matchers.argThat;
import static org.mockito.Matchers.eq; import static org.mockito.Matchers.eq;
import static org.mockito.Matchers.isNull; import static org.mockito.Matchers.isNull;
import static org.mockito.Mockito.reset; import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ -78,6 +80,7 @@ public class AbstractJaxRsResourceProviderDstu3Test {
private static Server jettyServer; private static Server jettyServer;
private TestJaxRsMockPatientRestProviderDstu3 mock; private TestJaxRsMockPatientRestProviderDstu3 mock;
private ArgumentCaptor<IdType> idCaptor; private ArgumentCaptor<IdType> idCaptor;
private ArgumentCaptor<String> conditionalCaptor;
private ArgumentCaptor<Patient> patientCaptor; private ArgumentCaptor<Patient> patientCaptor;
private void compareResultId(int id, IBaseResource resource) { private void compareResultId(int id, IBaseResource resource) {
@ -132,6 +135,7 @@ public class AbstractJaxRsResourceProviderDstu3Test {
this.mock = TestJaxRsMockPatientRestProviderDstu3.mock; this.mock = TestJaxRsMockPatientRestProviderDstu3.mock;
idCaptor = ArgumentCaptor.forClass(IdType.class); idCaptor = ArgumentCaptor.forClass(IdType.class);
patientCaptor = ArgumentCaptor.forClass(Patient.class); patientCaptor = ArgumentCaptor.forClass(Patient.class);
conditionalCaptor = ArgumentCaptor.forClass(String.class);
reset(mock); reset(mock);
} }
@ -179,11 +183,18 @@ public class AbstractJaxRsResourceProviderDstu3Test {
@Test @Test
public void testDeletePatient() { public void testDeletePatient() {
when(mock.delete(idCaptor.capture())).thenReturn(new MethodOutcome()); when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
final BaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute(); final BaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute();
assertEquals("1", idCaptor.getValue().getIdPart()); assertEquals("1", idCaptor.getValue().getIdPart());
} }
@Test
public void testConditionalDelete() throws Exception {
when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
client.delete().resourceConditionalByType("Patient").where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
}
/** Extended Operations */ /** Extended Operations */
@Test @Test
public void testExtendedOperations() { public void testExtendedOperations() {
@ -357,17 +368,27 @@ public class AbstractJaxRsResourceProviderDstu3Test {
@Test @Test
public void testUpdateById() throws Exception { public void testUpdateById() throws Exception {
when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenReturn(new MethodOutcome()); when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
client.update("1", createPatient(1)); client.update("1", createPatient(1));
assertEquals("1", idCaptor.getValue().getIdPart()); assertEquals("1", idCaptor.getValue().getIdPart());
compareResultId(1, patientCaptor.getValue()); compareResultId(1, patientCaptor.getValue());
} }
@Test
public void testConditionalUpdate() throws Exception {
when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
compareResultId(1, patientCaptor.getValue());
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
compareResultId(1, patientCaptor.getValue());
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Ignore @Ignore
@Test @Test
public void testResourceNotFound() throws Exception { public void testResourceNotFound() throws Exception {
when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenThrow(ResourceNotFoundException.class); when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenThrow(ResourceNotFoundException.class);
try { try {
client.update("1", createPatient(2)); client.update("1", createPatient(2));
fail(); fail();

View File

@ -80,6 +80,7 @@ public class AbstractJaxRsResourceProviderTest {
private static Server jettyServer; private static Server jettyServer;
private TestJaxRsMockPatientRestProvider mock; private TestJaxRsMockPatientRestProvider mock;
private ArgumentCaptor<IdDt> idCaptor; private ArgumentCaptor<IdDt> idCaptor;
private ArgumentCaptor<String> conditionalCaptor;
private ArgumentCaptor<Patient> patientCaptor; private ArgumentCaptor<Patient> patientCaptor;
private void compareResultId(int id, IResource resource) { private void compareResultId(int id, IResource resource) {
@ -133,6 +134,7 @@ public class AbstractJaxRsResourceProviderTest {
this.mock = TestJaxRsMockPatientRestProvider.mock; this.mock = TestJaxRsMockPatientRestProvider.mock;
idCaptor = ArgumentCaptor.forClass(IdDt.class); idCaptor = ArgumentCaptor.forClass(IdDt.class);
patientCaptor = ArgumentCaptor.forClass(Patient.class); patientCaptor = ArgumentCaptor.forClass(Patient.class);
conditionalCaptor = ArgumentCaptor.forClass(String.class);
reset(mock); reset(mock);
} }
@ -180,11 +182,18 @@ public class AbstractJaxRsResourceProviderTest {
@Test @Test
public void testDeletePatient() { public void testDeletePatient() {
when(mock.delete(idCaptor.capture())).thenReturn(new MethodOutcome()); when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
final BaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute(); final BaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute();
assertEquals("1", idCaptor.getValue().getIdPart()); assertEquals("1", idCaptor.getValue().getIdPart());
} }
@Test
public void testConditionalDelete() throws Exception {
when(mock.delete(idCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
client.delete().resourceConditionalByType("Patient").where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
}
/** Extended Operations */ /** Extended Operations */
@Test @Test
public void testExtendedOperations() { public void testExtendedOperations() {
@ -344,17 +353,28 @@ public class AbstractJaxRsResourceProviderTest {
@Test @Test
public void testUpdateById() throws Exception { public void testUpdateById() throws Exception {
when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenReturn(new MethodOutcome()); when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
client.update("1", createPatient(1)); client.update("1", createPatient(1));
assertEquals("1", idCaptor.getValue().getIdPart()); assertEquals("1", idCaptor.getValue().getIdPart());
compareResultId(1, patientCaptor.getValue()); compareResultId(1, patientCaptor.getValue());
} }
@Test
public void testConditionalUpdate() throws Exception {
when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenReturn(new MethodOutcome());
client.update().resource(createPatient(1)).conditional().where(Patient.IDENTIFIER.exactly().identifier("2")).execute();
assertEquals("1", patientCaptor.getValue().getId().getIdPart());
assertEquals("Patient?identifier=2&_format=json", conditionalCaptor.getValue());
compareResultId(1, patientCaptor.getValue());
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
@Ignore @Ignore
@Test @Test
public void testResourceNotFound() throws Exception { public void testResourceNotFound() throws Exception {
when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenThrow(ResourceNotFoundException.class); when(mock.update(idCaptor.capture(), patientCaptor.capture(), conditionalCaptor.capture())).thenThrow(ResourceNotFoundException.class);
try { try {
client.update("1", createPatient(2)); client.update("1", createPatient(2));
fail(); fail();

View File

@ -76,8 +76,8 @@ public class TestJaxRsMockPatientRestProvider extends AbstractJaxRsResourceProvi
} }
@Update @Update
public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) throws Exception { public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient,@ConditionalUrlParam final String theConditional) throws Exception {
return mock.update(theId, patient); return mock.update(theId, patient, theConditional);
} }
@Read @Read
@ -97,8 +97,8 @@ public class TestJaxRsMockPatientRestProvider extends AbstractJaxRsResourceProvi
} }
@Delete @Delete
public MethodOutcome delete(@IdParam final IdDt theId) { public MethodOutcome delete(@IdParam final IdDt theId, @ConditionalUrlParam final String theConditional) {
return mock.delete(theId); return mock.delete(theId, theConditional);
} }
@Search(compartmentName = "Condition") @Search(compartmentName = "Condition")

View File

@ -78,8 +78,8 @@ public class TestJaxRsMockPatientRestProviderDstu3 extends AbstractJaxRsResource
} }
@Update @Update
public MethodOutcome update(@IdParam final IdType theId, @ResourceParam final Patient patient) throws Exception { public MethodOutcome update(@IdParam final IdType theId, @ResourceParam final Patient patient,@ConditionalUrlParam final String theConditional) throws Exception {
return mock.update(theId, patient); return mock.update(theId, patient, theConditional);
} }
@Read @Read
@ -99,8 +99,8 @@ public class TestJaxRsMockPatientRestProviderDstu3 extends AbstractJaxRsResource
} }
@Delete @Delete
public MethodOutcome delete(@IdParam final IdType theId) { public MethodOutcome delete(@IdParam final IdType theId, @ConditionalUrlParam final String theConditional) {
return mock.delete(theId); return mock.delete(theId, theConditional);
} }
@Search(compartmentName = "Condition") @Search(compartmentName = "Condition")