From b4df6f9612cbbd6b25fe524ce8d99d20d24ce6fb Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Fri, 2 Oct 2015 11:27:21 +0200 Subject: [PATCH 01/13] add server using jaxrs --- .../uhn/fhir/rest/method/IRestfulHeader.java | 5 + .../uhn/fhir/rest/server/IRestfulServer.java | 37 +++ .../rest/server/IRestfulServerDefaults.java | 16 + .../uhn/fhir/rest/server/RestfulServer.java | 4 +- .../fhir/rest/server/RestfulServerUtils.java | 2 +- .../server/RestulfulServerConfiguration.java | 215 +++++++++++++ hapi-fhir-jaxrsserver-base/pom.xml | 274 +++++++++++++++++ .../server/AbstractConformanceRestServer.java | 152 ++++++++++ .../jaxrs/server/AbstractJaxRsRestServer.java | 128 ++++++++ .../server/AbstractResourceRestServer.java | 230 ++++++++++++++ .../jaxrs/server/IConformanceRestServer.java | 7 + .../fhir/jaxrs/server/StaticJaxRsServer.java | 46 +++ .../interceptor/ExceptionInterceptor.java | 169 +++++++++++ .../uhn/fhir/jaxrs/server/util/GZipUtil.java | 39 +++ .../jaxrs/server/util/MethodBindings.java | 79 +++++ .../server/util/RestfulServerDefaults.java | 38 +++ .../src/test/java/DemoTest.java | 287 ++++++++++++++++++ hapi-fhir-jaxrsserver-example/pom.xml | 93 ++++++ .../server/example/ConformanceRestServer.java | 41 +++ .../example/FhirPatientDemoApplication.java | 13 + .../server/example/FhirPatientRestServer.java | 222 ++++++++++++++ .../example/IFhirPatientRestServer.java | 39 +++ .../src/main/webapp/WEB-INF/web.xml | 52 ++++ .../provider/ServerConformanceProvider.java | 24 +- .../dstu2/ServerConformanceProvider.java | 61 ++-- 25 files changed, 2228 insertions(+), 45 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IRestfulHeader.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java create mode 100644 hapi-fhir-jaxrsserver-base/pom.xml create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/GZipUtil.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/RestfulServerDefaults.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/DemoTest.java create mode 100644 hapi-fhir-jaxrsserver-example/pom.xml create mode 100644 hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java create mode 100644 hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java create mode 100644 hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java create mode 100644 hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java create mode 100644 hapi-fhir-jaxrsserver-example/src/main/webapp/WEB-INF/web.xml diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IRestfulHeader.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IRestfulHeader.java new file mode 100644 index 00000000000..2abce5282a8 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IRestfulHeader.java @@ -0,0 +1,5 @@ +package ca.uhn.fhir.rest.method; + +public interface IRestfulHeader { + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java new file mode 100644 index 00000000000..31156e72c7a --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java @@ -0,0 +1,37 @@ +package ca.uhn.fhir.rest.server; + +import java.util.List; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; + +/* + * #%L + * HAPI FHIR - Core Library + * %% + * Copyright (C) 2014 - 2015 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% + */ +public interface IRestfulServer { + + /** + * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to + * creating their own. + */ + public FhirContext getFhirContext(); + + public List getInterceptors(); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java new file mode 100644 index 00000000000..7b5e65d033c --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java @@ -0,0 +1,16 @@ +package ca.uhn.fhir.rest.server; + +public interface IRestfulServerDefaults { + + /** + * Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an Accept header in the request, or a _pretty + * parameter in the request URL. + *

+ * The default is false + *

+ * + * @return Returns the default pretty print setting + */ + public boolean isDefaultPrettyPrint(); + +} 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 a18a8dd5544..6a42349da5e 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 @@ -41,7 +41,6 @@ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import javax.servlet.ServletException; -import javax.servlet.UnavailableException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; @@ -76,7 +75,7 @@ import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.VersionUtil; -public class RestfulServer extends HttpServlet { +public class RestfulServer extends HttpServlet implements IRestfulServerDefaults, IRestfulServer { private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor(); private static final long serialVersionUID = 1L; @@ -1086,6 +1085,7 @@ public class RestfulServer extends HttpServlet { * * @return Returns the default pretty print setting */ + @Override public boolean isDefaultPrettyPrint() { return myDefaultPrettyPrint; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index f0cf84d7250..f612bbb640c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -510,7 +510,7 @@ public class RestfulServerUtils { return null; } - public static boolean prettyPrintResponse(RestfulServer theServer, RequestDetails theRequest) { + public static boolean prettyPrintResponse(IRestfulServerDefaults theServer, RequestDetails theRequest) { Map requestParams = theRequest.getParameters(); String[] pretty = requestParams.get(Constants.PARAM_PRETTY); boolean prettyPrint; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java new file mode 100644 index 00000000000..432f57cf06a --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java @@ -0,0 +1,215 @@ +package ca.uhn.fhir.rest.server; + +import java.io.IOException; +import java.io.InputStream; +import java.util.LinkedList; +import java.util.List; +import java.util.jar.Manifest; + +import javax.servlet.ServletContext; +import javax.servlet.http.HttpServletRequest; + +import org.apache.commons.io.IOUtils; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.method.BaseMethodBinding; + +public class RestulfulServerConfiguration { + + private List resourceBindings; + private List> serverBindings; + private String implementationDescription; + private String serverVersion; + private String serverName; + private FhirContext fhirContext; + private ServletContext servletContext; + private IServerAddressStrategy serverAddressStrategy; + private String conformanceDate; + + public RestulfulServerConfiguration() { + } + + public RestulfulServerConfiguration(RestfulServer theRestfulServer) { + this.resourceBindings = new LinkedList(theRestfulServer.getResourceBindings()); + this.serverBindings = theRestfulServer.getServerBindings(); + this.implementationDescription = theRestfulServer.getImplementationDescription(); + this.serverVersion = theRestfulServer.getServerVersion(); + this.serverName = theRestfulServer.getServerName(); + this.fhirContext = theRestfulServer.getFhirContext(); + this.serverAddressStrategy= theRestfulServer.getServerAddressStrategy(); + this.servletContext = theRestfulServer.getServletContext(); + if (servletContext != null) { + InputStream inputStream = null; + try { + inputStream = getServletContext().getResourceAsStream("/META-INF/MANIFEST.MF"); + if (inputStream != null) { + Manifest manifest = new Manifest(inputStream); + this.conformanceDate = manifest.getMainAttributes().getValue("Build-Time"); + } + } catch (IOException e) { + // fall through + } + finally { + if (inputStream != null) { + IOUtils.closeQuietly(inputStream); + } + } + } + } + + /** + * Get the resourceBindings + * @return the resourceBindings + */ + public List getResourceBindings() { + return resourceBindings; + } + + /** + * Set the resourceBindings + * @param resourceBindings the resourceBindings to set + */ + public RestulfulServerConfiguration setResourceBindings(List resourceBindings) { + this.resourceBindings = resourceBindings; + return this; + } + + /** + * Get the serverBindings + * @return the serverBindings + */ + public List> getServerBindings() { + return serverBindings; + } + + /** + * Set the serverBindings + * @param serverBindings the serverBindings to set + */ + public RestulfulServerConfiguration setServerBindings(List> serverBindings) { + this.serverBindings = serverBindings; + return this; + } + + /** + * Get the implementationDescription + * @return the implementationDescription + */ + public String getImplementationDescription() { + return implementationDescription; + } + + /** + * Set the implementationDescription + * @param implementationDescription the implementationDescription to set + */ + public RestulfulServerConfiguration setImplementationDescription(String implementationDescription) { + this.implementationDescription = implementationDescription; + return this; + } + + /** + * Get the serverVersion + * @return the serverVersion + */ + public String getServerVersion() { + return serverVersion; + } + + /** + * Set the serverVersion + * @param serverVersion the serverVersion to set + */ + public RestulfulServerConfiguration setServerVersion(String serverVersion) { + this.serverVersion = serverVersion; + return this; + } + + /** + * Get the serverName + * @return the serverName + */ + public String getServerName() { + return serverName; + } + + /** + * Set the serverName + * @param serverName the serverName to set + */ + public RestulfulServerConfiguration setServerName(String serverName) { + this.serverName = serverName; + return this; + } + + /** + * Get the servletContext + * @return the servletContext + */ + public ServletContext getServletContext() { + return servletContext; + } + + /** + * Set the servletContext + * @param servletContext the servletContext to set + */ + public RestulfulServerConfiguration setServletContext(ServletContext servletContext) { + this.servletContext = servletContext; + return this; + } + + /** + * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to + * creating their own. + */ + public FhirContext getFhirContext() { + return this.fhirContext; + } + + /** + * Set the fhirContext + * @param fhirContext the fhirContext to set + */ + public RestulfulServerConfiguration setFhirContext(FhirContext fhirContext) { + this.fhirContext = fhirContext; + return this; + } + + /** + * Get the serverAddressStrategy + * @return the serverAddressStrategy + */ + public IServerAddressStrategy getServerAddressStrategy() { + return serverAddressStrategy; + } + + /** + * Set the serverAddressStrategy + * @param serverAddressStrategy the serverAddressStrategy to set + */ + public void setServerAddressStrategy(IServerAddressStrategy serverAddressStrategy) { + this.serverAddressStrategy = serverAddressStrategy; + } + + + /** + * Get the conformanceDate + * @return the conformanceDate + */ + public String getConformanceDate() { + return conformanceDate; + } + + /** + * Set the conformanceDate + * @param conformanceDate the conformanceDate to set + */ + public void setConformanceDate(String conformanceDate) { + this.conformanceDate = conformanceDate; + } + + public String getServerBaseForRequest(HttpServletRequest theRequest) { + return getServerAddressStrategy().determineServerBase(getServletContext(), theRequest); + } +} diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml new file mode 100644 index 00000000000..604d408c0c2 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -0,0 +1,274 @@ + + 4.0.0 + + + ca.uhn.hapi.fhir + hapi-deployable-pom + 1.3-SNAPSHOT + ../hapi-deployable-pom/pom.xml + + + + + maven.java.net + + true + + + false + + https://maven.java.net/service/local/repositories/snapshots/content/ + + + + hapi-fhir-jaxrsserver-base + jar + + HAPI FHIR JAX-RS Server + + + + + ca.uhn.hapi.fhir + hapi-fhir-base + 1.3-SNAPSHOT + + + commons-logging + commons-logging + + + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu + 1.3-SNAPSHOT + + + ca.uhn.hapi.fhir + hapi-fhir-structures-dstu2 + 1.3-SNAPSHOT + + + ca.uhn.hapi.fhir + hapi-fhir-structures-hl7org-dstu2 + 1.3-SNAPSHOT + + + ca.uhn.hapi.fhir + hapi-fhir-validation-resources-dstu2 + 1.3-SNAPSHOT + + + + + org.slf4j + jcl-over-slf4j + + + + ch.qos.logback + logback-classic + test + + + org.thymeleaf + thymeleaf + test + + + + com.phloc + phloc-schematron + test + + + com.phloc + phloc-commons + test + + + + + + + javax.ws.rs + jsr311-api + 1.1.1 + + + javax.ejb + ejb-api + 3.0 + + + + + + + + org.eclipse.jetty + jetty-server + test + + + org.eclipse.jetty + jetty-util + test + + + org.eclipse.jetty + jetty-webapp + test + + + + + + + + + + + + + + org.apache.maven.plugins + maven-jxr-plugin + + + + + diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java new file mode 100644 index 00000000000..ae17a41f5af --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java @@ -0,0 +1,152 @@ +package ca.uhn.fhir.jaxrs.server; + +import java.lang.annotation.Annotation; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.GET; +import javax.ws.rs.OPTIONS; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.LoggerFactory; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.model.dstu2.resource.Conformance; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; +import ca.uhn.fhir.rest.server.ResourceBinding; +import ca.uhn.fhir.rest.server.RestulfulServerConfiguration; +import ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider; +import ca.uhn.fhir.util.ReflectionUtil; + +/** + * Conformance Rest Service + * @author Peter Van Houte + */ +@Produces(MediaType.APPLICATION_JSON) +public abstract class AbstractConformanceRestServer extends AbstractJaxRsRestServer implements IConformanceRestServer { + + public static final String PATH = "/"; + private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractConformanceRestServer.class); + + private ResourceBinding myServerBinding = new ResourceBinding(); + private ConcurrentHashMap myResourceNameToBinding = new ConcurrentHashMap(); + private RestulfulServerConfiguration serverConfiguration = new RestulfulServerConfiguration(); + + private Conformance myConformance; + + protected AbstractConformanceRestServer(String implementationDescription, String serverName, String serverVersion) { + serverConfiguration.setFhirContext(getFhirContext()); + serverConfiguration.setImplementationDescription(implementationDescription); + serverConfiguration.setServerName(serverName); + serverConfiguration.setServerVersion(serverVersion); + } + + protected void setUpPostConstruct() + throws Exception { + List> serverBindings = new ArrayList>(); + for (ResourceBinding baseMethodBinding : myResourceNameToBinding.values()) { + serverBindings.addAll(baseMethodBinding.getMethodBindings()); + } + serverConfiguration.setServerBindings(serverBindings); + serverConfiguration.setResourceBindings(new LinkedList(myResourceNameToBinding.values())); + HardcodedServerAddressStrategy hardcodedServerAddressStrategy = new HardcodedServerAddressStrategy(); + hardcodedServerAddressStrategy.setValue(getBaseUri()); + serverConfiguration.setServerAddressStrategy(hardcodedServerAddressStrategy); + ServerConformanceProvider serverConformanceProvider = new ServerConformanceProvider(serverConfiguration); + serverConformanceProvider.initializeOperations(); + myConformance = serverConformanceProvider.getServerConformance(null); + } + + @GET + @OPTIONS + @Path("/metadata") + @Produces(MediaType.APPLICATION_JSON) + public Response conformance(String string) { + String conformanceString = getParser().encodeResourceToString(myConformance); + ResponseBuilder entity = Response.status(Constants.STATUS_HTTP_200_OK).entity(conformanceString); + entity.header("Access-Control-Allow-Origin", "*"); + return entity.build(); + } + + protected int findResourceMethods(Object theProvider, Class clazz) throws ConfigurationException { + int count = 0; + + for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) { + BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider); + if (foundMethodBinding == null) { + continue; + } + + count++; + +// if (foundMethodBinding instanceof ConformanceMethodBinding) { +// myServerConformanceMethod = foundMethodBinding; +// continue; +// } + + if (!Modifier.isPublic(m.getModifiers())) { + throw new ConfigurationException("Method '" + m.getName() + "' is not public, FHIR RESTful methods must be public"); + } else { + if (Modifier.isStatic(m.getModifiers())) { + throw new ConfigurationException("Method '" + m.getName() + "' is static, FHIR RESTful methods must not be static"); + } else { + ourLog.debug("Scanning public method: {}#{}", theProvider.getClass(), m.getName()); + + String resourceName = foundMethodBinding.getResourceName(); + ResourceBinding resourceBinding; + if (resourceName == null) { + resourceBinding = myServerBinding; + } else { + RuntimeResourceDefinition definition = getFhirContext().getResourceDefinition(resourceName); + if (myResourceNameToBinding.containsKey(definition.getName())) { + resourceBinding = myResourceNameToBinding.get(definition.getName()); + } else { + resourceBinding = new ResourceBinding(); + resourceBinding.setResourceName(resourceName); + myResourceNameToBinding.put(resourceName, resourceBinding); + } + } + + List> allowableParams = foundMethodBinding.getAllowableParamAnnotations(); + if (allowableParams != null) { + for (Annotation[] nextParamAnnotations : m.getParameterAnnotations()) { + for (Annotation annotation : nextParamAnnotations) { + Package pack = annotation.annotationType().getPackage(); + if (pack.equals(IdParam.class.getPackage())) { + if (!allowableParams.contains(annotation.annotationType())) { + throw new ConfigurationException("Method[" + m.toString() + "] is not allowed to have a parameter annotated with " + annotation); + } + } + } + } + } + + resourceBinding.addMethod(foundMethodBinding); + ourLog.debug(" * Method: {}#{} is a handler", theProvider.getClass(), m.getName()); + } + } + } + + return count; + } + + + @Override + public Class getResourceType() { + return Conformance.class; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java new file mode 100644 index 00000000000..c2f663d42c6 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java @@ -0,0 +1,128 @@ +package ca.uhn.fhir.jaxrs.server; + +import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; + +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; + +/** + * Abstract Jax Rs Rest Server + * @author axmpm + * + */ +@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) +public abstract class AbstractJaxRsRestServer { + + private static Logger ourLog = LoggerFactory.getLogger(AbstractJaxRsRestServer.class); + public static FhirContext CTX = FhirContext.forDstu2(); + + @Context + protected UriInfo info; + @Context + HttpHeaders headers; + + private IParser jsonParser = getFhirContext().newJsonParser(); + private IParser xmlParser = getFhirContext().newXmlParser(); + private String baseUri; + + public static FhirContext getFhirContext() { + return CTX; + } + + /** + * param and query methods + */ + protected HashMap getQueryMap() { + MultivaluedMap queryParameters = info.getQueryParameters(); + HashMap params = new HashMap(); + for (String key : queryParameters.keySet()) { + params.put(key, queryParameters.get(key).toArray(new String[] {})); + } + return params; + } + + private String getParam(String string) { + for (Entry> entry : info.getQueryParameters().entrySet()) { + if (string.equalsIgnoreCase(entry.getKey())) { + return entry.getValue().iterator().next(); + } + } + return null; + } + + protected Integer getIntParam(String string) { + String param = getParam(string); + return param == null ? 0 : Integer.valueOf(param); + } + + protected String getBaseUri() { + if(this.baseUri == null) { + this.baseUri = info.getBaseUri().toASCIIString(); + } + ourLog.debug("BaseUri is equal to %s", baseUri); + return this.baseUri; + } + + /** + * PARSING METHODS + */ + public IParser getParser() { + IParser parser = MediaType.APPLICATION_XML.equals(getParserType()) ? xmlParser : jsonParser; + return parser.setPrettyPrint(getPrettyPrint()); + } + + private boolean getPrettyPrint() { + String printPretty = getParam("_pretty"); + return printPretty == null || printPretty.trim().length() == 0 ? true : Boolean.valueOf(printPretty); + } + + protected String getParserType() { + if ((headers != null && headers.getMediaType() != null && headers.getMediaType().getSubtype() != null + && headers.getMediaType().getSubtype().contains("xml")) || getDefaultResponseEncoding() == EncodingEnum.XML + || "xml".equals(getParam("_format"))) { + return MediaType.APPLICATION_XML; + } else { + return MediaType.APPLICATION_JSON; + } + } + + Response createResponse(IBaseResource resource) { + Bundle resultingBundle = new Bundle(); + resultingBundle.addEntry().setResource((IResource) resource); + return ok(encodeResponse(resultingBundle)); + } + + protected Response ok(String entity) { + return Response.status(Constants.STATUS_HTTP_200_OK).header("Content-Type", getParserType()).entity(entity).build(); + } + + private String encodeResponse(Bundle resource) { + return resource == null ? "null" : getParser().encodeBundleToString(resource); + } + + /** + * DEFAULT VALUES + */ + public EncodingEnum getDefaultResponseEncoding() { + return EncodingEnum.JSON; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java new file mode 100644 index 00000000000..913694a92e3 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java @@ -0,0 +1,230 @@ +package ca.uhn.fhir.jaxrs.server; + +import java.lang.reflect.InvocationTargetException; +import java.util.List; +import java.util.Map; + +import javax.interceptor.Interceptors; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import ca.uhn.fhir.jaxrs.server.interceptor.ExceptionInterceptor; +import ca.uhn.fhir.jaxrs.server.util.MethodBindings; +import ca.uhn.fhir.jaxrs.server.util.RestfulServerDefaults; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.BundleEntry; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.method.CreateMethodBinding; +import ca.uhn.fhir.rest.method.OperationMethodBinding; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.method.SearchMethodBinding; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; +import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; +import ca.uhn.fhir.rest.server.ResourceBinding; + +/** + * Fhir Physician Rest Service + * @author axmpm + * + */ +@SuppressWarnings({ "unused"}) +@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) +public abstract class AbstractResourceRestServer extends AbstractJaxRsRestServer implements IResourceProvider { + + private static final Logger ourLog = LoggerFactory.getLogger(AbstractResourceRestServer.class); + + private ResourceBinding myServerBinding = new ResourceBinding(); + private IRestfulServerDefaults serverDefaults = new RestfulServerDefaults(); + private MethodBindings bindings = new MethodBindings(); + + public AbstractResourceRestServer(Class subclass) { + bindings.findMethods(this, subclass, getFhirContext()); + } + + @GET + @Interceptors(ExceptionInterceptor.class) + Response search() throws Exception { + return execute(); + } + + protected Response customOperation(final IBaseResource resource, RequestTypeEnum requestType) + throws Exception, IllegalAccessException, InvocationTargetException { + OperationMethodBinding method = bindings.getBinding(OperationMethodBinding.class); + final RequestDetails theRequest = createRequestDetails(resource, requestType); + final Object[] paramsServer = bindings.createParams(resource, method, theRequest); + Parameters result = (Parameters) method.getMethod().invoke(this, paramsServer); + return ok(getParser().encodeResourceToString(result)); + } + + Response create(final Map params, R resource) throws Exception { + CreateMethodBinding method = bindings.getBinding(CreateMethodBinding.class); + final RequestDetails theRequest = createRequestDetails(resource, null); + final Object[] paramsServer = bindings.createParams(resource, method, theRequest); + MethodOutcome result = (MethodOutcome) method.getMethod().invoke(this, paramsServer); + return createResponse(result.getResource()); + } + + @POST + @Interceptors(ExceptionInterceptor.class) + public Response create(final String resourceString) + throws Exception { + return create(getQueryMap(), parseResource(resourceString)); + } + + @POST + @Interceptors(ExceptionInterceptor.class) + @Path("/_search") + public Response searchWithPost() throws Exception { + return search(); + } + + @GET + @Path("/{id}") + @Interceptors(ExceptionInterceptor.class) + public Response find(@PathParam("id") final String id) { + final R resource = find(new IdDt(id)); + return createSingleResponse(resource); + } + + @PUT + @Path("/{id}") + @Interceptors(ExceptionInterceptor.class) + public Response update(@PathParam("id") final String id, final String resourceString) + throws Exception { + final R resource = parseResource(resourceString); +// final MethodOutcome update = update(new IdDt(resource.getId()), practitioner); +// return createResponse(update.getResource()); + return createResponse(resource); + } + + @DELETE + @Path("/{id}") + @Interceptors(ExceptionInterceptor.class) + public Response delete(@PathParam("id") final String id) + throws Exception { +// final MethodOutcome delete = delete(new IdDt(id)); +// return createResponse(delete.getResource()); + return null; + } + + @GET + @Path("/{id}/_history/{version}") + @Interceptors(ExceptionInterceptor.class) + public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version) { + final IdDt dt = new IdDt(getBaseUri(), getResourceType().getSimpleName(), id, version); + final R resource = findHistory(dt); + return createSingleResponse(resource); + } + + @GET + @Path("/{id}/{compartment}") + @Interceptors(ExceptionInterceptor.class) + public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) { + final IdDt dt = new IdDt(getBaseUri(), getResourceType().getSimpleName(), id); + final R resource = find(new IdDt(id)); + return createResponse(resource); + } + + /** + * PARSING METHODS + */ + private Response createSingleResponse(final R resource) { + return ok(getParser().encodeResourceToString(resource)); + } + + Response createResponse(final IBaseResource resource) { + final Bundle resultingBundle = new Bundle(); + resultingBundle.addEntry().setResource((IResource) resource); + return ok(encodeToString(resultingBundle)); + } + + Response createResponse(final List resources) { + final Bundle resultingBundle = new Bundle(); + for (final R resource : resources) { + addBundleEntry(resultingBundle, resource); + } + return ok(encodeToString(resultingBundle)); + } + + protected Response ok(String entity) { + return Response.status(Constants.STATUS_HTTP_200_OK).header("Content-Type", getParserType()).entity(entity).build(); + } + + protected String encodeToString(final Bundle resource) { + return resource != null ? getParser().encodeBundleToString(resource) : "null"; + } + + private R parseResource(final String resource) { + return getParser().parseResource(getResourceType(), resource); + } + + @Deprecated + private void addBundleEntry(final Bundle resultingBundle, final R resource) { + final BundleEntry entry = resultingBundle.addEntry(); + entry.setResource(resource); + if (resource != null && resource.getId() != null) { + entry.setId(resource.getId()); + } + } + + + private RequestDetails createRequestDetails(final IBaseResource resource, RequestTypeEnum requestType) { + final RequestDetails theRequest = new RequestDetails() { +// @Override +// public String getHeader(String headerIfNoneExist) { +// List requestHeader = headers.getRequestHeader(headerIfNoneExist); +// return (requestHeader == null || requestHeader.size() == 0) ? null : requestHeader.get(0); +// } + }; + theRequest.setFhirServerBase(getBaseUri()); +// theRequest.setServer(this); + theRequest.setParameters(getQueryMap()); +// theRequest.setRequestContent(resource); + theRequest.setRequestType(requestType); + return theRequest; + } + + public Response execute() { + SearchMethodBinding method = bindings.getBinding(SearchMethodBinding.class); + final RequestDetails theRequest = createRequestDetails(null, null); + final Object[] paramsServer = bindings.createParams(null, method, theRequest); + Object result = null; //method.invokeServer(null, paramsServer); + final IBundleProvider bundle = (IBundleProvider) result; + IVersionSpecificBundleFactory bundleFactory = getFhirContext().newBundleFactory(); +// bundleFactory.initializeBundleFromBundleProvider(this, bundle, EncodingEnum.JSON, info.getAbsolutePath().toASCIIString(), +// info.getAbsolutePath().toASCIIString(), getPrettyPrint(), getIntParam("_getpagesoffset"), getIntParam("_count"), null, +// BundleTypeEnum.SEARCHSET, Collections.emptySet()); + IBaseResource resource = bundleFactory.getResourceBundle(); + return ok(getParser().encodeResourceToString(resource)); + } + + public R find(final IdDt theId) { + throw new UnsupportedOperationException(); + } + + public R findHistory(final IdDt theId) { + throw new UnsupportedOperationException(); + } + + @Override + public abstract Class getResourceType(); + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java new file mode 100644 index 00000000000..db1b2af5ee6 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java @@ -0,0 +1,7 @@ +package ca.uhn.fhir.jaxrs.server; + +import ca.uhn.fhir.rest.server.IResourceProvider; + +public interface IConformanceRestServer extends IResourceProvider { + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java new file mode 100644 index 00000000000..7f257611829 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java @@ -0,0 +1,46 @@ +package ca.uhn.fhir.jaxrs.server; + +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.interceptor.Interceptors; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.slf4j.LoggerFactory; + +import ca.uhn.fhir.jaxrs.server.interceptor.ExceptionInterceptor; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.rest.annotation.Transaction; +import ca.uhn.fhir.rest.annotation.TransactionParam; + +/** + * Conformance Rest Service + * @author Peter Van Houte + */ +@Local +@Path(StaticJaxRsServer.PATH) +@Stateless +@Produces(MediaType.APPLICATION_JSON) +public class StaticJaxRsServer { + + private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(StaticJaxRsServer.class); + static final String PATH = "/"; + + @POST + @Path("/") + @Interceptors(ExceptionInterceptor.class) + public Response transaction(final String resource) { + ourLog.debug("calling transaction method"); + return null; + } + + @Transaction + public Bundle transaction(@TransactionParam Bundle theResources) { + ourLog.debug("transaction implemented"); + return theResources; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java new file mode 100644 index 00000000000..d8fead97eda --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java @@ -0,0 +1,169 @@ +package ca.uhn.fhir.jaxrs.server.interceptor; + +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import java.util.Arrays; +import java.util.Map; +import java.util.Map.Entry; + +import javax.interceptor.AroundInvoke; +import javax.interceptor.InvocationContext; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang3.exception.ExceptionUtils; +import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; +import org.slf4j.LoggerFactory; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsRestServer; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.util.OperationOutcomeUtil; + +public class ExceptionInterceptor { + + private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(ExceptionInterceptor.class); + private Class[] myReturnStackTracesForExceptionTypes; + + @Context + private UriInfo info; + @Context + private HttpHeaders headers; + + FhirContext fhirContext = AbstractJaxRsRestServer.getFhirContext(); + + @AroundInvoke + public Object intercept(final InvocationContext ctx) throws Exception { + try { + if(!ourLog.isDebugEnabled() || ctx.getMethod().getName().contains("getResourceType")) { + return ctx.proceed(); + } else { + ourLog.debug("METHOD_CALL : " + ctx.getMethod().getName() + " [ " + Arrays.asList(ctx.getParameters()) + "] "); + Object proceed = ctx.proceed(); + ourLog.debug("RESULT : " + proceed.toString()); + return proceed; + } + } catch(final Exception theException) { + return handleException(theException); + } + } + + public Response handleException(final Throwable theException) + { + IBaseOperationOutcome oo = null; + int statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR; + + if (theException instanceof BaseServerResponseException) { + oo = ((BaseServerResponseException) theException).getOperationOutcome(); + statusCode = ((BaseServerResponseException) theException).getStatusCode(); + } + + /* + * Generate an OperationOutcome to return, unless the exception throw by the resource provider had one + */ + if (oo == null) { + try { + final RuntimeResourceDefinition ooDef = fhirContext.getResourceDefinition("OperationOutcome"); + oo = (IBaseOperationOutcome) ooDef.getImplementingClass().newInstance(); + + if (theException instanceof InternalErrorException) { + ourLog.error("Failure during REST processing", theException); + populateDetails(fhirContext, theException, oo); + } else if (theException instanceof BaseServerResponseException) { + ourLog.warn("Failure during REST processing: {}", theException); + final BaseServerResponseException baseServerResponseException = (BaseServerResponseException) theException; + statusCode = baseServerResponseException.getStatusCode(); + populateDetails(fhirContext, theException, oo); + if (baseServerResponseException.getAdditionalMessages() != null) { + for (final String next : baseServerResponseException.getAdditionalMessages()) { + OperationOutcomeUtil.addIssue(fhirContext, oo, "error", next); + } + } + } else { + ourLog.error("Failure during REST processing: " + theException.toString(), theException); + populateDetails(fhirContext, theException, oo); + statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR; + } + } catch (final Exception e1) { + ourLog.error("Failed to instantiate OperationOutcome resource instance", e1); + final ResponseBuilder result = Response.status(Constants.STATUS_HTTP_500_INTERNAL_ERROR); + result.header(Constants.HEADER_CONTENT_TYPE, Constants.CT_TEXT_WITH_UTF8); + result.header(Constants.HEADER_CONTENT_ENCODING, Constants.CHARSET_NAME_UTF8); + result.entity(theException.getMessage()); + return result.build(); + } + } else { + ourLog.error("Unknown error during processing", theException); + } + + // Add headers associated with the specific error code + if (theException instanceof BaseServerResponseException) { + final Map additional = ((BaseServerResponseException) theException).getAssociatedHeaders(); + if (additional != null) { + for (final Entry next : additional.entrySet()) { + if (isNotBlank(next.getKey()) && next.getValue() != null) { + final String nextKey = next.getKey(); + for (final String nextValue : next.getValue()) { + addHeader(nextKey, nextValue); + } + } + } + } + } + + final boolean requestIsBrowser = false; // RestfulServer.requestIsBrowser(theRequest); + final String fhirServerBase = ""; // theRequestDetails.getFhirServerBase(); + + // theResponse.setStatus(statusCode); + // theRequestDetails.getServer().addHeadersToResponse(theResponse); + // theResponse.setContentType("text/plain"); + // theResponse.setCharacterEncoding("UTF-8"); + // theResponse.getWriter().append(theException.getMessage()); + // theResponse.getWriter().close(); + + final ResponseBuilder result = Response.status(statusCode); + //final String resName = ctx.getResourceDefinition(oo).getName(); + result.header(Constants.HEADER_CONTENT_TYPE, Constants.CT_TEXT_WITH_UTF8); + result.entity(theException.getMessage()); + return result.build(); + } + + private void addHeader(final String nextKey, final String nextValue) { + throw new UnsupportedOperationException(); + } + + private void populateDetails(final FhirContext theCtx, final Throwable theException, final IBaseOperationOutcome theOo) { + if (myReturnStackTracesForExceptionTypes != null) { + for (final Class next : myReturnStackTracesForExceptionTypes) { + if (next.isAssignableFrom(theException.getClass())) { + final String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException); + OperationOutcomeUtil.addIssue(theCtx, theOo, "error", detailsValue); + return; + } + } + } + + OperationOutcomeUtil.addIssue(theCtx, theOo, "error", theException.getMessage()); + } + + /** + * If any server methods throw an exception which extends any of the given exception types, the exception stack trace + * will be returned to the user. This can be useful for helping to diagnose issues, but may not be desirable for + * production situations. + * + * @param theExceptionTypes + * The exception types for which to return the stack trace to the user. + * @return Returns an instance of this interceptor, to allow for easy method chaining. + */ + public ExceptionInterceptor setReturnStackTracesForExceptionTypes(final Class... theExceptionTypes) { + myReturnStackTracesForExceptionTypes = theExceptionTypes; + return this; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/GZipUtil.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/GZipUtil.java new file mode 100644 index 00000000000..6b1dac7b7fd --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/GZipUtil.java @@ -0,0 +1,39 @@ +package ca.uhn.fhir.jaxrs.server.util; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.util.zip.GZIPInputStream; +import java.util.zip.GZIPOutputStream; + +import org.apache.commons.io.IOUtils; + +import ca.uhn.fhir.parser.DataFormatException; + +public class GZipUtil { + + public static String decompress(byte[] theResource) { + GZIPInputStream is; + try { + is = new GZIPInputStream(new ByteArrayInputStream(theResource)); + return IOUtils.toString(is, "UTF-8"); + } catch (IOException e) { + throw new DataFormatException("Failed to decompress contents", e); + } + } + + public static byte[] compress(String theEncoded) { + try { + ByteArrayOutputStream os = new ByteArrayOutputStream(); + GZIPOutputStream gos = new GZIPOutputStream(os); + IOUtils.write(theEncoded, gos, "UTF-8"); + gos.close(); + os.close(); + byte[] retVal = os.toByteArray(); + return retVal; + } catch (IOException e) { + throw new DataFormatException("Compress contents", e); + } + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java new file mode 100644 index 00000000000..a536459b2fc --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java @@ -0,0 +1,79 @@ +package ca.uhn.fhir.jaxrs.server.util; + +import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; + +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.AbstractResourceRestServer; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.method.CreateMethodBinding; +import ca.uhn.fhir.rest.method.DeleteMethodBinding; +import ca.uhn.fhir.rest.method.IParameter; +import ca.uhn.fhir.rest.method.OperationMethodBinding; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.method.SearchMethodBinding; +import ca.uhn.fhir.rest.method.UpdateMethodBinding; +import ca.uhn.fhir.rest.param.ResourceParameter; +import ca.uhn.fhir.util.ReflectionUtil; + +@SuppressWarnings({"unchecked", "rawtypes"}) +public class MethodBindings { + + /** BaseOutcomeReturningMethodBinding */ + private ConcurrentHashMap createMethods = new ConcurrentHashMap(); + private ConcurrentHashMap updateMethods = new ConcurrentHashMap(); + private ConcurrentHashMap delete = new ConcurrentHashMap(); + + /** BaseResourceReturingMethodBinding */ + private ConcurrentHashMap searchMethods = new ConcurrentHashMap(); + private ConcurrentHashMap operationMethods = new ConcurrentHashMap(); + + public > void findMethods(T theProvider, Class subclass, FhirContext fhirContext) { + for (final Method m : ReflectionUtil.getDeclaredMethods(subclass)) { + final BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, fhirContext, theProvider); + if(foundMethodBinding == null) { + continue; + } + ConcurrentHashMap map = getMap(foundMethodBinding.getClass()); + if (map.contains(theProvider.getResourceType().getName())) { + throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + foundMethodBinding.getMethod() + " -- " + + foundMethodBinding.getMethod()); + } else { + map.put(theProvider.getResourceType().getName(), foundMethodBinding); + } + } + } + + private ConcurrentHashMap getMap(Class class1) { + if(class1.isAssignableFrom(CreateMethodBinding.class)) return (ConcurrentHashMap) createMethods; + if(class1.isAssignableFrom(UpdateMethodBinding.class)) return (ConcurrentHashMap) updateMethods; + if(class1.isAssignableFrom(DeleteMethodBinding.class)) return (ConcurrentHashMap) delete; + if(class1.isAssignableFrom(SearchMethodBinding.class)) return (ConcurrentHashMap) searchMethods; + if(class1.isAssignableFrom(OperationMethodBinding.class)) return (ConcurrentHashMap) operationMethods; + return new ConcurrentHashMap(); + } + + public Object[] createParams(IBaseResource resource, final BaseMethodBinding method, final RequestDetails theRequest) { + final Object[] paramsServer = new Object[method.getParameters().size()]; + for (int i = 0; i < method.getParameters().size(); i++) { + final IParameter param = method.getParameters().get(i); + if(param instanceof ResourceParameter) { + paramsServer[i] = resource; + } else { + paramsServer[i] = param.translateQueryParametersIntoServerArgument(theRequest, null, method); + } + } + return paramsServer; + } + + public T getBinding(Class clazz) { + ConcurrentHashMap map = getMap((Class) clazz); + if(map.values().size() == 0) { + throw new UnsupportedOperationException(); + } + return (T) map.values().iterator().next(); + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/RestfulServerDefaults.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/RestfulServerDefaults.java new file mode 100644 index 00000000000..f2d10e3f2b1 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/RestfulServerDefaults.java @@ -0,0 +1,38 @@ +package ca.uhn.fhir.jaxrs.server.util; + +import ca.uhn.fhir.rest.server.AddProfileTagEnum; +import ca.uhn.fhir.rest.server.BundleInclusionRule; +import ca.uhn.fhir.rest.server.ETagSupportEnum; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.IPagingProvider; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; + +public class RestfulServerDefaults implements IRestfulServerDefaults { + + public boolean isUseBrowserFriendlyContentTypes() { + return true; + } + + public ETagSupportEnum getETagSupport() { + return ETagSupportEnum.DISABLED; + } + + public AddProfileTagEnum getAddProfileTag() { + return AddProfileTagEnum.NEVER; + } + + public boolean isDefaultPrettyPrint() { + return true; + } + + public IPagingProvider getPagingProvider() { + //Integer count = getIntParam("_count"); + Integer count = 0; + return count == 0 ? null : new FifoMemoryPagingProvider(count); + } + + public BundleInclusionRule getBundleInclusionRule() { + return BundleInclusionRule.BASED_ON_INCLUDES; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/DemoTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/DemoTest.java new file mode 100644 index 00000000000..c6f1be9d549 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/DemoTest.java @@ -0,0 +1,287 @@ + + +import static junit.framework.Assert.assertEquals; +import static junit.framework.Assert.assertNotNull; +import static junit.framework.Assert.assertTrue; + +import java.util.List; + +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.model.api.BundleEntry; +import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Conformance; +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.BoundCodeDt; +import ca.uhn.fhir.model.primitive.DateDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.rest.method.SearchStyleEnum; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; + +@Ignore +public class DemoTest { + + private static final String serverBase = "http://localhost:8580/hapi-fhir-jaxrsserver-example/jaxrs-demo/"; + private static IGenericClient client; + + //START SNIPPET: client + @BeforeClass + public static void setUpOnce() { + final FhirContext ctx = FhirContext.forDstu2(); + client = ctx.newRestfulGenericClient(serverBase); + client.setEncoding(EncodingEnum.JSON); + } + + //END SNIPPET: client + + /** Search/Query - Type */ + @Test + public void findUsingGenericClientBySearch() { + // Perform a search + final ca.uhn.fhir.model.api.Bundle results = client.search().forResource(Patient.class) + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("SHORTNAME", "TOYS")).execute(); + System.out.println(results.getEntries().get(0)); + assertEquals(results.getEntries().size(), 1); + } + + /** Search - Multi-valued Parameters (ANY/OR) */ + @Test + public void findUsingGenericClientBySearchWithMultiValues() { + final ca.uhn.fhir.model.api.Bundle response = client.search().forResource(Patient.class) + .where(Patient.ADDRESS.matches().values("Toronto")).and(Patient.ADDRESS.matches().values("Ontario")) + .and(Patient.ADDRESS.matches().values("Canada")) + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("SHORTNAME", "TOYS")).execute(); + System.out.println(response.getEntries().get(0)); + } + + /** Search - Paging */ + @Test + public void findWithPaging() { + // Perform a search + for(int i = 0 ; i < 10 ; i++) { + testCreatePatient(); + } + final Bundle results = client.search().forResource(Patient.class).limitTo(8).returnBundle(Bundle.class).execute(); + System.out.println(results.getEntry().size()); + + if (results.getLink(Bundle.LINK_NEXT) != null) { + + // load next page + final Bundle nextPage = client.loadPage().next(results).execute(); + System.out.println(nextPage.getEntry().size()); + } + } + + /** Search using other query options */ + public void testOther() { + //missing + } + + /** */ + @Test + public void testSearchPost() { + Bundle response = client.search() + .forResource("Patient") + .usingStyle(SearchStyleEnum.POST) + .returnBundle(Bundle.class) + .execute(); + assertTrue(response.getEntry().size() > 0); + } + + /** Search - Compartments */ + @Test + public void testSearchCompartements() { + Bundle response = client.search() + .forResource(Patient.class) + .withIdAndCompartment("1", "condition") + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); + assertTrue(response.getEntry().size() > 0); + } + + /** Search - Subsetting (_summary and _elements) */ + @Test + @Ignore + public void testSummary() { + Object response = client.search() + .forResource(Patient.class) + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); + } + + @Test + public void testCreatePatient() { + final Patient existing = new Patient(); + existing.setId((IdDt) null); + existing.getNameFirstRep().addFamily("Created Patient 54"); + client.setEncoding(EncodingEnum.XML); + final MethodOutcome results = client.create().resource(existing).execute(); + System.out.println(results.getId()); + final Bundle bundle = (Bundle) results.getResource(); + final Patient patient = (Patient) bundle.getEntryFirstRep().getResource(); + System.out.println(patient); + assertNotNull(client.read(patient.getId())); + client.setEncoding(EncodingEnum.JSON); + } + + + /** Conditional Creates */ + @Test + public void testConditionalCreate() { + final Patient existing = new Patient(); + existing.setId((IdDt) null); + existing.getNameFirstRep().addFamily("Created Patient 54"); + client.setEncoding(EncodingEnum.XML); + final MethodOutcome results = client.create().resource(existing).execute(); + System.out.println(results.getId()); + final Bundle bundle = (Bundle) results.getResource(); + final Patient patient = (Patient) bundle.getEntryFirstRep().getResource(); + + client.create() + .resource(patient) + .conditional() + .where(Patient.IDENTIFIER.exactly().identifier(patient.getIdentifierFirstRep())) + .execute(); + } + + + /** Find By Id */ + @Test + public void findUsingGenericClientById() { + final Patient results = client.read(Patient.class, "1"); + assertTrue(results.getIdentifier().toString().contains("THOR")); + } + + @Test + public void testUpdateById() { + final Patient existing = client.read(Patient.class, "1"); + final List name = existing.getName(); + name.get(0).addSuffix("The Second"); + existing.setName(name); + client.setEncoding(EncodingEnum.XML); + final MethodOutcome results = client.update("1", existing); + } + + @Test + public void testDeletePatient() { + final Patient existing = new Patient(); + existing.getNameFirstRep().addFamily("Created Patient XYZ"); + final MethodOutcome results = client.create().resource(existing).execute(); + System.out.println(results.getId()); + final Bundle bundle = (Bundle) results.getResource(); + final Patient patient = (Patient) bundle.getEntryFirstRep().getResource(); + client.delete(Patient.class, patient.getId()); + try { + assertNotNull(client.read(patient.getId())); + } + catch (final ResourceNotFoundException e) { + assertEquals(e.getStatusCode(), Constants.STATUS_HTTP_404_NOT_FOUND); + } + } + + /** Transaction - Server */ + @Test + public void testTransaction() { + ca.uhn.fhir.model.api.Bundle bundle = new ca.uhn.fhir.model.api.Bundle(); + BundleEntry entry = bundle.addEntry(); + final Patient existing = new Patient(); + existing.getNameFirstRep().addFamily("Created with bundle"); + entry.setResource(existing); + + BoundCodeDt theTransactionOperation = + new BoundCodeDt( + BundleEntryTransactionMethodEnum.VALUESET_BINDER, + BundleEntryTransactionMethodEnum.POST); + entry.setTransactionMethod(theTransactionOperation); + ca.uhn.fhir.model.api.Bundle response = client.transaction().withBundle(bundle).execute(); + } + + /** Conformance - Server */ + @Test + public void testConformance() { + final Conformance conf = client.fetchConformance().ofType(Conformance.class).execute(); + System.out.println(conf.getRest().get(0).getResource().get(0).getType()); + System.out.println(conf.getRest().get(0).getResource().get(1).getType()); + } + + /** Extended Operations */ + // Create a client to talk to the HeathIntersections server + @Test + public void testExtendedOperations() { + client.registerInterceptor(new LoggingInterceptor(true)); + + // Create the input parameters to pass to the server + Parameters inParams = new Parameters(); + inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); + inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); + inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue")); + + // Invoke $everything on "Patient/1" + Parameters outParams = client + .operation() + .onInstance(new IdDt("Patient", "1")) + .named("$last") + .withParameters(inParams) + //.useHttpGet() // Use HTTP GET instead of POST + .execute(); + String resultValue = outParams.getParameter().get(0).getValue().toString(); + System.out.println(resultValue); + assertEquals("expected but found : "+ resultValue, resultValue.contains("myAwesomeDummyValue"), true); + } + + @Test + public void testExtendedOperationsUsingGet() { + client.registerInterceptor(new LoggingInterceptor(true)); + + // Create the input parameters to pass to the server + Parameters inParams = new Parameters(); + inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); + inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); + inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue")); + + // Invoke $everything on "Patient/1" + Parameters outParams = client + .operation() + .onInstance(new IdDt("Patient", "1")) + .named("$last") + .withParameters(inParams) + .useHttpGet() // Use HTTP GET instead of POST + .execute(); + String resultValue = outParams.getParameter().get(0).getValue().toString(); + System.out.println(resultValue); + assertEquals("expected but found : "+ resultValue, resultValue.contains("myAwesomeDummyValue"), true); + } + + + + + @Test + public void testFindUnknownPatient() { + try { + final Patient existing = client.read(Patient.class, "999955541264"); + } + catch (final ResourceNotFoundException e) { + e.printStackTrace(); + assertEquals(e.getStatusCode(), 404); + } + } + + @Test + public void testVRead() { + final Patient patient = client.vread(Patient.class, "1", "1"); + System.out.println(patient); + } + +} diff --git a/hapi-fhir-jaxrsserver-example/pom.xml b/hapi-fhir-jaxrsserver-example/pom.xml new file mode 100644 index 00000000000..d7cdf79a677 --- /dev/null +++ b/hapi-fhir-jaxrsserver-example/pom.xml @@ -0,0 +1,93 @@ + + 4.0.0 + + + + ca.uhn.hapi.fhir + hapi-fhir + 1.3-SNAPSHOT + ../pom.xml + + + hapi-fhir-jaxrsserver-example + war + + HAPI FHIR JPA Server - Example + + + + oss-snapshots + + true + + https://oss.sonatype.org/content/repositories/snapshots/ + + + + + + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + 1.3-SNAPSHOT + + + + + + + + hapi-fhir-jaxrsserver-example + + + + org.apache.maven.plugins + maven-failsafe-plugin + + true + + + + + integration-test + verify + + + + + + + maven-dependency-plugin + + + unpack + package + + unpack + + + + + ${project.groupId} + ${project.artifactId} + ${project.version} + war + *.war + + + C:\Agfa\ORBIS-AS\server\orbis-as-08.05.07.00.0009200-UK\standalone\deployments + true + + + + + + + + + + + + diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java new file mode 100644 index 00000000000..6a185ca92fd --- /dev/null +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java @@ -0,0 +1,41 @@ +package ca.uhn.fhir.jaxrs.server.example; + +import javax.annotation.PostConstruct; +import javax.ejb.EJB; +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +/** + * Conformance Rest Service + * @author Peter Van Houte + */ +@Local +@Path(ConformanceRestServer.PATH) +@Stateless +@Produces(MediaType.APPLICATION_JSON) +public class ConformanceRestServer extends ca.uhn.fhir.jaxrs.server.AbstractConformanceRestServer { + + private static final String SERVER_VERSION = "1.0.0"; + private static final String SERVER_DESCRIPTION = "Jax-Rs Test Example Description"; + private static final String SERVER_NAME = "Jax-Rs Test Example"; + + @EJB + private ConformanceRestServer conformanceRestServer; + @EJB + private IFhirPatientRestServer patientRestServer; + + public ConformanceRestServer() { + super(SERVER_DESCRIPTION, SERVER_NAME, SERVER_VERSION); + } + + @PostConstruct + public void createMethod() + throws Exception { + findResourceMethods(conformanceRestServer, ConformanceRestServer.class); + findResourceMethods(patientRestServer, FhirPatientRestServer.class); + super.setUpPostConstruct(); + } +} diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java new file mode 100644 index 00000000000..6be1bdb3754 --- /dev/null +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java @@ -0,0 +1,13 @@ +package ca.uhn.fhir.jaxrs.server.example; + +import javax.ws.rs.ApplicationPath; +import javax.ws.rs.core.Application; + +/** + * Fhir Patient Demo Application + * @author Peter Van Houte + */ +@ApplicationPath(value=FhirPatientDemoApplication.PATH) +public class FhirPatientDemoApplication extends Application { + public final static String PATH = "/jaxrs-demo"; +} diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java new file mode 100644 index 00000000000..43486a088ad --- /dev/null +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java @@ -0,0 +1,222 @@ +package ca.uhn.fhir.jaxrs.server.example; + +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.interceptor.Interceptors; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import ca.uhn.fhir.jaxrs.server.AbstractResourceRestServer; +import ca.uhn.fhir.jaxrs.server.interceptor.ExceptionInterceptor; +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +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.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; + +/** + * Fhir Physician Rest Service + * @author axmpm + * + */ +@Local(IFhirPatientRestServer.class) +@Path(FhirPatientRestServer.PATH) +@Stateless +@Produces(MediaType.APPLICATION_JSON) +public class FhirPatientRestServer extends AbstractResourceRestServer implements IFhirPatientRestServer { + + static final String PATH = "/Patient"; + + private static Long counter = 1L; + private static final ConcurrentHashMap> patients = new ConcurrentHashMap>(); + + protected FhirPatientRestServer() throws Exception { + super(FhirPatientRestServer.class); + } + + static { + patients.put(""+counter, createPatient("Agfa")); + patients.put(""+(counter), createPatient("Healthcare")); + for(int i = 0 ; i<20 ; i++) { + patients.put(""+(counter), createPatient("Random Patient " + counter)); + } + } + + private static List createPatient(final String name) { + final Patient patient = new Patient(); + patient.getNameFirstRep().addFamily(name); + return createPatient(patient); + } + + private static List createPatient(final Patient patient) { + patient.setId(createId(counter, 1L)); + final LinkedList list = new LinkedList(); + list.add(patient); + counter++; + return list ; + } + + private static IdDt createId(final Long id, final Long theVersionId) { + return new IdDt("Patient", "" + id, "" + theVersionId); + } + + @Search + @Override + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { + final List result = new LinkedList(); + for (final List patientIterator : patients.values()) { + Patient single = null; + for (Patient patient : patientIterator) { + if (name == null || patient.getNameFirstRep().getFamilyFirstRep().getValueNotNull().equals(name.getValueNotNull())) { + single = patient; + } + } + if (single != null) { + result.add(single); + } + } + return result; + } + + @Update + @Override + public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) + throws Exception { + final String idPart = theId.getIdPart(); + if(patients.containsKey(idPart)) { + final List patientList = patients.get(idPart); + final Patient lastPatient = getLast(patientList); + patient.setId(createId(theId.getIdPartAsLong(), lastPatient.getId().getVersionIdPartAsLong()+1)); + patientList.add(patient); + final MethodOutcome result = new MethodOutcome().setCreated(false); + result.setResource(patient); + result.setId(patient.getId()); + return result; + } else { + throw new ResourceNotFoundException(theId); + } + } + + @Override + @Read + public Patient find(@IdParam final IdDt theId) { + if(patients.containsKey(theId.getIdPart())) { + return getLast(patients.get(theId.getIdPart())); + } else { + throw new ResourceNotFoundException(theId); + } + } + + + private Patient getLast(final List list) { + return list.get(list.size()-1); + } + + @Override + @Read(version = false) + public Patient findHistory(@IdParam final IdDt theId) { + if (patients.containsKey(theId.getIdPart())) { + final List list = patients.get(theId.getIdPart()); + for (final Patient patient : list) { + if (patient.getId().getVersionIdPartAsLong().equals(theId.getVersionIdPartAsLong())) { + return patient; + } + } + } + throw new ResourceNotFoundException(theId); + } + + @Create + @Override + public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) + throws Exception { + patients.put(""+counter, createPatient(patient)); + final MethodOutcome result = new MethodOutcome().setCreated(true); + result.setResource(patient); + result.setId(patient.getId()); + return result; + } + + @Delete + @Override + public MethodOutcome delete(@IdParam final IdDt theId) { + final Patient deletedPatient = find(theId); + patients.remove(deletedPatient.getId().getIdPart()); + final MethodOutcome result = new MethodOutcome().setCreated(true); + result.setResource(deletedPatient); + return result; + } + + + @GET + @Path("/{id}/$last") + @Interceptors(ExceptionInterceptor.class) + @Override + public Response operationLastGet(final String resource) + throws Exception { + return customOperation(null, RequestTypeEnum.GET); + } + + @POST + @Path("/{id}/$last") + @Interceptors(ExceptionInterceptor.class) + @Override + public Response operationLast(final String resource) + throws Exception { + return customOperation(getParser().parseResource(resource), RequestTypeEnum.POST); + } + +// @ca.uhn.fhir.rest.annotation.Validate +// public MethodOutcome validate( +// @ResourceParam T theResource, +// @ResourceParam String theRawResource, +// @ResourceParam EncodingEnum theEncoding, +// @ca.uhn.fhir.rest.annotation.Validate.Mode ValidationModeEnum theMode, +// @ca.uhn.fhir.rest.annotation.Validate.Profile String theProfile) { +// return validate(theResource, null, theRawResource, theEncoding, theMode, theProfile); +// } + + @Operation(name="last", idempotent=true, returnParameters= { + @OperationParam(name="return", type=StringDt.class) + }) + @Override + public Parameters last(@OperationParam(name = "dummy") StringDt dummyInput) { + System.out.println("inputparameter"); + Parameters parameters = new Parameters(); + Patient patient = find(new IdDt(counter.intValue()-1)); + parameters + .addParameter() + .setName("return") + .setResource(patient) + .setValue(new StringDt((counter-1)+"" + "inputVariable [ " + dummyInput.getValue()+ "]")); + return parameters; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + +} diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java new file mode 100644 index 00000000000..269d7af1391 --- /dev/null +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java @@ -0,0 +1,39 @@ +package ca.uhn.fhir.jaxrs.server.example; + +import java.util.List; + +import javax.ws.rs.core.Response; + +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.IResourceProvider; + +public interface IFhirPatientRestServer extends IResourceProvider { + + List search(StringParam name); + + MethodOutcome update(IdDt theId, Patient patient) + throws Exception; + + Patient find(IdDt theId); + + Patient findHistory(IdDt theId); + + MethodOutcome create(Patient patient, String theConditional) + throws Exception; + + MethodOutcome delete(IdDt theId); + + Response operationLastGet(String resource) + throws Exception; + + Response operationLast(String resource) + throws Exception; + + Parameters last(StringDt dummyInput); + +} diff --git a/hapi-fhir-jaxrsserver-example/src/main/webapp/WEB-INF/web.xml b/hapi-fhir-jaxrsserver-example/src/main/webapp/WEB-INF/web.xml new file mode 100644 index 00000000000..0bd809a85b1 --- /dev/null +++ b/hapi-fhir-jaxrsserver-example/src/main/webapp/WEB-INF/web.xml @@ -0,0 +1,52 @@ + + + + + + diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java index 84e059a7d9e..1e265b19554 100644 --- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java +++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java @@ -22,13 +22,17 @@ package ca.uhn.fhir.rest.server.provider; import java.io.IOException; import java.io.InputStream; -import java.text.DateFormat; -import java.text.ParseException; -import java.text.SimpleDateFormat; -import java.util.*; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.TreeSet; import java.util.jar.Manifest; -import ca.uhn.fhir.parser.DataFormatException; +import javax.servlet.http.HttpServletRequest; + import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.context.RuntimeResourceDefinition; @@ -49,6 +53,7 @@ import ca.uhn.fhir.model.primitive.CodeDt; import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.parser.DataFormatException; import ca.uhn.fhir.rest.annotation.Metadata; import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding; @@ -59,10 +64,9 @@ import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IServerConformanceProvider; import ca.uhn.fhir.rest.server.ResourceBinding; import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.RestulfulServerConfiguration; import ca.uhn.fhir.util.ExtensionConstants; -import javax.servlet.http.HttpServletRequest; - /** * Server FHIR Provider which serves the conformance statement for a RESTful server implementation * @@ -78,10 +82,10 @@ public class ServerConformanceProvider implements IServerConformanceProvider myOperationBindingToName; private HashMap> myOperationNameToBindings; private String myPublisher = "Not provided"; - private RestfulServer myRestfulServer; + private RestulfulServerConfiguration myServerConfiguration; public ServerConformanceProvider(RestfulServer theRestfulServer) { - myRestfulServer = theRestfulServer; + this.myServerConfiguration = new RestulfulServerConfiguration(theRestfulServer); + } + + public ServerConformanceProvider(RestulfulServerConfiguration theServerConfiguration) { + this.myServerConfiguration = theServerConfiguration; } /* @@ -103,7 +115,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider systemOps, BaseMethodBinding nextMethodBinding) { @@ -124,7 +136,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider>> collectMethodBindings() { Map>> resourceToMethods = new TreeMap>>(); - for (ResourceBinding next : myRestfulServer.getResourceBindings()) { + for (ResourceBinding next : myServerConfiguration.getResourceBindings()) { String resourceName = next.getResourceName(); for (BaseMethodBinding nextMethodBinding : next.getMethodBindings()) { if (resourceToMethods.containsKey(resourceName) == false) { @@ -133,7 +145,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider nextMethodBinding : myRestfulServer.getServerBindings()) { + for (BaseMethodBinding nextMethodBinding : myServerConfiguration.getServerBindings()) { String resourceName = ""; if (resourceToMethods.containsKey(resourceName) == false) { resourceToMethods.put(resourceName, new ArrayList>()); @@ -170,10 +182,10 @@ public class ServerConformanceProvider implements IServerConformanceProvider resourceOps = new HashSet(); RestResource resource = rest.addResource(); String resourceName = nextEntry.getKey(); - RuntimeResourceDefinition def = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); + RuntimeResourceDefinition def = myServerConfiguration.getFhirContext().getResourceDefinition(resourceName); resource.getTypeElement().setValue(def.getName()); - resource.getProfile().setReference(new IdDt(def.getResourceProfile(myRestfulServer.getServerBaseForRequest(theRequest)))); + resource.getProfile().setReference(new IdDt(def.getResourceProfile(myServerConfiguration.getServerBaseForRequest(theRequest)))); TreeSet includes = new TreeSet(); @@ -297,7 +309,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes, DynamicSearchMethodBinding searchMethodBinding) { includes.addAll(searchMethodBinding.getIncludes()); @@ -427,7 +424,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider nextTarget : nextParameter.getDeclaredTypes()) { - RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget); + RuntimeResourceDefinition targetDef = myServerConfiguration.getFhirContext().getResourceDefinition(nextTarget); if (targetDef != null) { ResourceTypeEnum code = ResourceTypeEnum.VALUESET_BINDER.fromCodeString(targetDef.getName()); if (code != null) { From 7f910974a232ed9854698559b28351015865846c Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Wed, 7 Oct 2015 17:11:16 +0200 Subject: [PATCH 02/13] create seperate response and server interfaces and classes for servlets and jaxrs --- .../BaseAddOrDeleteTagsMethodBinding.java | 23 +- .../fhir/rest/method/BaseMethodBinding.java | 59 +--- .../BaseOutcomeReturningMethodBinding.java | 99 +++---- ...turningMethodBindingWithResourceParam.java | 1 - .../BaseResourceReturningMethodBinding.java | 56 ++-- .../rest/method/ConditionalParamBinder.java | 4 +- .../rest/method/ConformanceMethodBinding.java | 3 +- .../uhn/fhir/rest/method/CountParameter.java | 2 +- .../method/DynamicSearchMethodBinding.java | 4 +- .../rest/method/DynamicSearchParameter.java | 2 +- .../fhir/rest/method/ElementsParameter.java | 2 +- .../rest/method/GetTagsMethodBinding.java | 33 +-- .../rest/method/HistoryMethodBinding.java | 5 +- .../ca/uhn/fhir/rest/method/IParameter.java | 2 +- .../uhn/fhir/rest/method/NullParameter.java | 2 +- .../rest/method/OperationMethodBinding.java | 4 +- .../fhir/rest/method/OperationParameter.java | 3 +- .../ca/uhn/fhir/rest/method/ParseAction.java | 44 +++ .../fhir/rest/method/ReadMethodBinding.java | 6 +- .../uhn/fhir/rest/method/RequestDetails.java | 117 ++++---- .../fhir/rest/method/SearchMethodBinding.java | 4 +- .../rest/method/ServerBaseParamBinder.java | 2 +- .../rest/method/ServletRequestParameter.java | 5 +- .../rest/method/ServletResponseParameter.java | 5 +- .../uhn/fhir/rest/method/SinceParameter.java | 2 +- .../uhn/fhir/rest/method/SortParameter.java | 2 +- .../rest/method/SummaryEnumParameter.java | 2 +- .../rest/method/TransactionMethodBinding.java | 3 +- .../fhir/rest/method/UpdateMethodBinding.java | 6 +- .../fhir/rest/param/BaseQueryParameter.java | 2 +- .../fhir/rest/param/ResourceParameter.java | 78 +++--- .../fhir/rest/param/TransactionParameter.java | 53 ++-- .../fhir/rest/server/Dstu1BundleFactory.java | 2 +- .../fhir/rest/server/IRestfulResponse.java | 31 +++ .../uhn/fhir/rest/server/IRestfulServer.java | 16 +- .../rest/server/IRestfulServerDefaults.java | 50 +++- .../fhir/rest/server/IRestfulServerUtil.java | 24 ++ .../server/IVersionSpecificBundleFactory.java | 2 +- .../uhn/fhir/rest/server/RestfulResponse.java | 66 +++++ .../uhn/fhir/rest/server/RestfulServer.java | 121 ++++++-- .../fhir/rest/server/RestfulServerUtil.java | 262 ++++++++++++++++++ .../fhir/rest/server/RestfulServerUtils.java | 151 +++++----- .../server/RestulfulServerConfiguration.java | 68 +---- .../ExceptionHandlingInterceptor.java | 3 +- .../interceptor/IServerInterceptor.java | 70 ++++- .../interceptor/InterceptorAdapter.java | 25 ++ .../interceptor/LoggingInterceptor.java | 2 +- .../ResponseHighlighterInterceptor.java | 4 +- .../server/servlet/ServletRequestDetails.java | 144 ++++++++++ .../servlet/ServletRestfulResponse.java | 77 +++++ .../server/AbstractConformanceRestServer.java | 9 +- .../jaxrs/server/AbstractJaxRsRestServer.java | 141 +++++----- .../server/AbstractResourceRestServer.java | 258 +++++++---------- .../jaxrs/server/IConformanceRestServer.java | 3 +- .../jaxrs/server/IResourceRestServer.java | 36 +++ .../jaxrs/server/JaxRsRestfulResponse.java | 78 ++++++ .../interceptor/ExceptionInterceptor.java | 2 +- .../server/util/JaxRsRequestDetails.java | 68 +++++ .../jaxrs/server/util/MethodBindings.java | 89 +++--- hapi-fhir-jaxrsserver-example/pom.xml | 2 +- .../server/example/ConformanceRestServer.java | 4 +- .../server/example/FhirPatientRestServer.java | 79 +++++- .../example/IFhirPatientRestServer.java | 10 +- .../provider/ServerConformanceProvider.java | 71 ++--- .../uhn/fhir/rest/server/InterceptorTest.java | 8 +- .../fhir/rest/server/ResourceMethodTest.java | 11 +- .../provider/dstu2/Dstu2BundleFactory.java | 7 +- .../dstu2/ServerConformanceProvider.java | 9 +- .../ResponseHighlightingInterceptorTest.java | 13 +- .../dstu2hl7org/Dstu2Hl7OrgBundleFactory.java | 3 +- 70 files changed, 1746 insertions(+), 908 deletions(-) create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulResponse.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerUtil.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulResponse.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java create mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java index e79b2edb99f..679438e35db 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseAddOrDeleteTagsMethodBinding.java @@ -45,6 +45,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -154,8 +155,8 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding } @Override - public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { - Object[] params = createParametersForServerRequest(theRequest, null); + public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { + Object[] params = createParametersForServerRequest(theRequest); params[myIdParamIndex] = theRequest.getId(); @@ -164,7 +165,7 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding } IParser parser = createAppropriateParserForParsingServerRequest(theRequest); - Reader reader = theRequest.getServletRequest().getReader(); + Reader reader = theRequest.getReader(); try { TagList tagList = parser.parseTagList(reader); params[myTagListParamIndex] = tagList; @@ -175,21 +176,13 @@ abstract class BaseAddOrDeleteTagsMethodBinding extends BaseMethodBinding for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest); if (!continueProcessing) { - return; + return null; } } - - HttpServletResponse response = theRequest.getServletResponse(); - response.setContentType(Constants.CT_TEXT); - response.setStatus(Constants.STATUS_HTTP_200_OK); - response.setCharacterEncoding(Constants.CHARSET_NAME_UTF8); - - theServer.addHeadersToResponse(response); - - PrintWriter writer = response.getWriter(); - writer.close(); + + return theRequest.getResponse().returnResponse(null, Constants.STATUS_HTTP_200_OK, false, null, null); } @Override 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 1a81d325cc5..6d5fd53815a 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 @@ -69,7 +69,7 @@ import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -86,10 +86,6 @@ import ca.uhn.fhir.util.ReflectionUtil; public abstract class BaseMethodBinding implements IClientResponseHandler { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class); - /** - * @see BaseMethodBinding#loadRequestContents(RequestDetails) - */ - private static volatile IRequestReader ourRequestReader; private FhirContext myContext; private Method myMethod; private List myParameters; @@ -131,7 +127,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler } protected IParser createAppropriateParserForParsingServerRequest(RequestDetails theRequest) { - String contentTypeHeader = theRequest.getServletRequest().getHeader("content-type"); + String contentTypeHeader = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); EncodingEnum encoding; if (isBlank(contentTypeHeader)) { encoding = EncodingEnum.XML; @@ -151,14 +147,14 @@ public abstract class BaseMethodBinding implements IClientResponseHandler return parser; } - protected Object[] createParametersForServerRequest(RequestDetails theRequest, byte[] theRequestContents) { + protected Object[] createParametersForServerRequest(RequestDetails theRequest) { Object[] params = new Object[getParameters().size()]; for (int i = 0; i < getParameters().size(); i++) { IParameter param = getParameters().get(i); if (param == null) { continue; } - params[i] = param.translateQueryParametersIntoServerArgument(theRequest, theRequestContents, this); + params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this); } return params; } @@ -251,9 +247,9 @@ public abstract class BaseMethodBinding implements IClientResponseHandler public abstract BaseHttpClientInvocation invokeClient(Object[] theArgs) throws InternalErrorException; - public abstract void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException; + public abstract Object invokeServer(IRestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException; - protected final Object invokeServerMethod(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) { + protected final Object invokeServerMethod(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) { // Handle server action interceptors RestOperationTypeEnum operationType = getRestOperationType(theRequest); if (operationType != null) { @@ -293,41 +289,6 @@ public abstract class BaseMethodBinding implements IClientResponseHandler return mySupportsConditionalMultiple; } - protected byte[] loadRequestContents(RequestDetails theRequest) throws IOException { - /* - * This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on servlet-api in clients since there is no point. So we dynamically load a class - * that does the servlet processing in servers. Down the road it may make sense to just split the method binding classes into server and client versions, but this isn't actually a huge deal I - * don't think. - */ - IRequestReader reader = ourRequestReader; - if (reader == null) { - try { - Class.forName("javax.servlet.ServletInputStream"); - String className = BaseMethodBinding.class.getName() + "$" + "ActiveRequestReader"; - try { - reader = (IRequestReader) Class.forName(className).newInstance(); - } catch (Exception e1) { - throw new ConfigurationException("Failed to instantiate class " + className, e1); - } - } catch (ClassNotFoundException e) { - String className = BaseMethodBinding.class.getName() + "$" + "InactiveRequestReader"; - try { - reader = (IRequestReader) Class.forName(className).newInstance(); - } catch (Exception e1) { - throw new ConfigurationException("Failed to instantiate class " + className, e1); - } - } - ourRequestReader = reader; - } - - InputStream inputStream = reader.getInputStream(theRequest); - byte[] requestContents = IOUtils.toByteArray(inputStream); - - theRequest.setRawRequest(requestContents); - - return requestContents; - } - /** * Subclasses may override this method (but should also call super.{@link #populateActionRequestDetailsForInterceptor(RequestDetails, ActionRequestDetails, Object[])} to provide method specifics to the * interceptors. @@ -643,17 +604,17 @@ public abstract class BaseMethodBinding implements IClientResponseHandler /** * @see BaseMethodBinding#loadRequestContents(RequestDetails) */ - static class ActiveRequestReader implements IRequestReader { + public static class ActiveRequestReader implements IRequestReader { @Override public InputStream getInputStream(RequestDetails theRequestDetails) throws IOException { - return theRequestDetails.getServletRequest().getInputStream(); + return theRequestDetails.getInputStream(); } } /** * @see BaseMethodBinding#loadRequestContents(RequestDetails) */ - static class InactiveRequestReader implements IRequestReader { + public static class InactiveRequestReader implements IRequestReader { @Override public InputStream getInputStream(RequestDetails theRequestDetails) { throw new IllegalStateException("The servlet-api JAR is not found on the classpath. Please check that this library is available."); @@ -663,7 +624,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler /** * @see BaseMethodBinding#loadRequestContents(RequestDetails) */ - private static interface IRequestReader { + public static interface IRequestReader { InputStream getInputStream(RequestDetails theRequestDetails) throws IOException; } 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 7d9f021ed63..37316520fa4 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 @@ -24,6 +24,7 @@ import java.io.IOException; import java.io.Reader; import java.io.Writer; import java.lang.reflect.Method; +import java.util.EnumSet; import java.util.List; import java.util.Map; import java.util.Set; @@ -41,9 +42,11 @@ import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.PreferReturnEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @@ -68,24 +71,6 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { // if (requestContainsResource()) { // requestContents = parseIncomingServerResource(theRequest); // } else { // requestContents = null; // } - Object[] params = createParametersForServerRequest(theRequest, requestContents); + Object[] params = createParametersForServerRequest(theRequest); addParametersForServerRequest(theRequest, params); - HttpServletResponse servletResponse = theRequest.getServletResponse(); /* * No need to catch nd handle exceptions here, we already handle them one level up @@ -182,13 +164,16 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, outcome, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, outcome); if (!continueProcessing) { - return; + return null; } } - boolean allowPrefer = false; + return returnResponse(theRequest, response, outcome, resource); + } + + private int getOperationStatus(MethodOutcome response) { switch (getRestOperationType()) { case CREATE: if (response == null) { @@ -196,23 +181,17 @@ abstract class BaseOutcomeReturningMethodBinding extends BaseMethodBinding theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException; @Override - public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { + public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { // Pretty print boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(theServer, theRequest); @@ -245,38 +243,36 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding summaryMode = RestfulServerUtils.determineSummaryMode(theRequest); // Determine response encoding - EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest(), theServer.getDefaultResponseEncoding()); + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest, theServer.getDefaultResponseEncoding()); // Is this request coming from a browser - String uaHeader = theRequest.getServletRequest().getHeader("user-agent"); + String uaHeader = theRequest.getHeader("user-agent"); boolean requestIsBrowser = false; if (uaHeader != null && uaHeader.contains("Mozilla")) { requestIsBrowser = true; } - byte[] requestContents = loadRequestContents(theRequest); - // Method params Object[] params = new Object[getParameters().size()]; for (int i = 0; i < getParameters().size(); i++) { IParameter param = getParameters().get(i); if (param != null) { - params[i] = param.translateQueryParametersIntoServerArgument(theRequest, requestContents, this); + params[i] = param.translateQueryParametersIntoServerArgument(theRequest, this); } } Object resultObj = invokeServer(theServer, theRequest, params); - Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest()); + Integer count = RestfulServerUtils.extractCountParameter(theRequest); boolean respondGzip = theRequest.isRespondGzip(); - HttpServletResponse response = theRequest.getServletResponse(); + switch (getReturnType()) { case BUNDLE: { /* * Figure out the self-link for this request */ - String serverBase = theServer.getServerBaseForRequest(theRequest.getServletRequest()); + String serverBase = theRequest.getServerBaseForRequest(); String linkSelf; StringBuilder b = new StringBuilder(); b.append(serverBase); @@ -325,16 +321,15 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, resource, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, resource); if (!continueProcessing) { ourLog.debug("Interceptor {} returned false, not continuing processing"); - return; + return null; } } - RestfulServerUtils.streamResponseAsResource(theServer, response, resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip, - isAddContentLocationHeader(), theRequest); - break; + return theRequest.getResponse().streamResponseAsResource(resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip, + isAddContentLocationHeader()); } else { Set includes = getRequestIncludesFromParams(params); @@ -350,28 +345,26 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, bundle, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, bundle); if (!continueProcessing) { ourLog.debug("Interceptor {} returned false, not continuing processing"); - return; + return null; } } - RestfulServerUtils.streamResponseAsBundle(theServer, response, bundle, theRequest.getFhirServerBase(), summaryMode, respondGzip, requestIsBrowser, theRequest); + return theRequest.getResponse().streamResponseAsBundle(bundle, summaryMode, respondGzip, requestIsBrowser); } else { IBaseResource resBundle = bundleFactory.getResourceBundle(); for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, resBundle, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, resBundle); if (!continueProcessing) { ourLog.debug("Interceptor {} returned false, not continuing processing"); - return; + return null; } } - RestfulServerUtils.streamResponseAsResource(theServer, response, resBundle, prettyPrint, summaryMode, - Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), isAddContentLocationHeader(), theRequest); + return theRequest.getResponse().streamResponseAsResource(resBundle, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, + theRequest.isRespondGzip(), isAddContentLocationHeader()); } - - break; } } case RESOURCE: { @@ -386,17 +379,16 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, resource, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, resource); if (!continueProcessing) { - return; + return null; } } - RestfulServerUtils.streamResponseAsResource(theServer, response, resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip, - isAddContentLocationHeader(), theRequest); - break; + return theRequest.getResponse().streamResponseAsResource(resource, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, respondGzip, isAddContentLocationHeader()); } } + return null; } /** diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java index b97b05436ac..bcb77d9f9ca 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConditionalParamBinder.java @@ -61,10 +61,10 @@ class ConditionalParamBinder implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { if (myOperationType == RestOperationTypeEnum.CREATE) { - String retVal = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_NONE_EXIST); + String retVal = theRequest.getHeader(Constants.HEADER_IF_NONE_EXIST); if (isBlank(retVal)) { return null; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java index 3e751b41c6a..c6ecbcbcf1f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ConformanceMethodBinding.java @@ -31,6 +31,7 @@ import ca.uhn.fhir.model.valueset.BundleTypeEnum; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @@ -72,7 +73,7 @@ public class ConformanceMethodBinding extends BaseResourceReturningMethodBinding } @Override - public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException { + public IBundleProvider invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException { IBaseResource conf = (IBaseResource) invokeServerMethod(theServer, theRequest, theMethodParams); return new SimpleBundleProvider(conf); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CountParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CountParameter.java index 87003347a4e..6ce2ea39276 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CountParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/CountParameter.java @@ -54,7 +54,7 @@ public class CountParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { String[] sinceParams = theRequest.getParameters().remove(Constants.PARAM_COUNT); if (sinceParams != null) { if (sinceParams.length > 0) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java index 00acc18a617..8b4296fbf41 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchMethodBinding.java @@ -37,7 +37,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IDynamicSearchResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -86,7 +86,7 @@ public class DynamicSearchMethodBinding extends BaseResourceReturningMethodBindi } @Override - public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { + public IBundleProvider invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { if (myIdParamIndex != null) { theMethodParams[myIdParamIndex] = theRequest.getId(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchParameter.java index bafd18ff861..ad89e934285 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/DynamicSearchParameter.java @@ -67,7 +67,7 @@ public class DynamicSearchParameter implements IParameter { @SuppressWarnings("unchecked") @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { SearchParameterMap retVal = new SearchParameterMap(); for (String next : theRequest.getParameters().keySet()) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ElementsParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ElementsParameter.java index 62768163752..d08f12630cc 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ElementsParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ElementsParameter.java @@ -71,7 +71,7 @@ public class ElementsParameter implements IParameter { @Override @SuppressWarnings({ "rawtypes", "unchecked" }) - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { Set value = getElementsValueOrNull(theRequest); if (value == null || value.isEmpty()) { return null; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java index 30e52674fb4..47d44af4794 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/GetTagsMethodBinding.java @@ -21,15 +21,12 @@ package ca.uhn.fhir.rest.method; */ import java.io.IOException; -import java.io.PrintWriter; import java.io.Reader; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; import java.util.Map; -import javax.servlet.http.HttpServletResponse; - import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.ConfigurationException; @@ -44,10 +41,8 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; -import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.rest.server.RestfulServerUtils; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @@ -168,8 +163,8 @@ public class GetTagsMethodBinding extends BaseMethodBinding { } @Override - public void invokeServer(RestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { - Object[] params = createParametersForServerRequest(theRequest, null); + public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest) throws BaseServerResponseException, IOException { + Object[] params = createParametersForServerRequest(theRequest); if (myIdParamIndex != null) { params[myIdParamIndex] = theRequest.getId(); @@ -182,29 +177,13 @@ public class GetTagsMethodBinding extends BaseMethodBinding { for (int i = theServer.getInterceptors().size() - 1; i >= 0; i--) { IServerInterceptor next = theServer.getInterceptors().get(i); - boolean continueProcessing = next.outgoingResponse(theRequest, resp, theRequest.getServletRequest(), theRequest.getServletResponse()); + boolean continueProcessing = next.outgoingResponse(theRequest, resp); if (!continueProcessing) { - return; + return null; } } - EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequest.getServletRequest()); - - HttpServletResponse response = theRequest.getServletResponse(); - response.setContentType(responseEncoding.getResourceContentType()); - response.setStatus(Constants.STATUS_HTTP_200_OK); - response.setCharacterEncoding(Constants.CHARSET_NAME_UTF8); - - theServer.addHeadersToResponse(response); - - IParser parser = responseEncoding.newParser(getContext()); - parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(theServer, theRequest)); - PrintWriter writer = response.getWriter(); - try { - parser.encodeTagListToWriter(resp, writer); - } finally { - writer.close(); - } + return theRequest.getResponse().returnResponse(ParseAction.create(resp), Constants.STATUS_HTTP_200_OK, false, null, null); } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java index f5a2a0330b9..fb83132e0d3 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/HistoryMethodBinding.java @@ -26,7 +26,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.List; -import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -43,7 +42,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -155,7 +154,7 @@ public class HistoryMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { + public IBundleProvider invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { if (myIdParamIndex != null) { theMethodParams[myIdParamIndex] = theRequest.getId(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParameter.java index 51bd0febf5d..c98baad5171 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/IParameter.java @@ -48,7 +48,7 @@ public interface IParameter { * @param theMethodBinding TODO * @return Returns the argument object as it will be passed to the {@link IResourceProvider} method. */ - Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException; + Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException; void initializeTypes(Method theMethod, Class> theOuterCollectionType, Class> theInnerCollectionType, Class theParameterType); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NullParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NullParameter.java index 428bbd27f62..b115c43546d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NullParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/NullParameter.java @@ -39,7 +39,7 @@ class NullParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { // nothing return null; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java index 153c1cead64..5c827dc8234 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationMethodBinding.java @@ -49,7 +49,7 @@ import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.MethodNotAllowedException; @@ -223,7 +223,7 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException { + public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws BaseServerResponseException { if (theRequest.getRequestType() == RequestTypeEnum.POST) { // always ok } else if (theRequest.getRequestType() == RequestTypeEnum.GET) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationParameter.java index b89132dc997..901c7e6110c 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/OperationParameter.java @@ -24,7 +24,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.Collection; -import java.util.Date; import java.util.List; import java.util.Map; @@ -156,7 +155,7 @@ public class OperationParameter implements IParameter { @SuppressWarnings("unchecked") @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { List matchingParamValues = new ArrayList(); if (theRequest.getRequestType() == RequestTypeEnum.GET) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java new file mode 100644 index 00000000000..89d59b848bc --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ParseAction.java @@ -0,0 +1,44 @@ +package ca.uhn.fhir.rest.method; + +import java.io.IOException; +import java.io.Writer; + +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.parser.IParser; + +/** + * @author Peter Van Houte + * + * @param A functional class that parses an outcome + */ +public abstract class ParseAction { + + protected T theOutcome; + + protected ParseAction(T outcome) { + this.theOutcome = outcome; + } + + public abstract void execute(IParser parser, Writer writer) throws IOException; + + public static ParseAction create(TagList outcome) { + return outcome == null ? null : new ParseAction(outcome) { + @Override + public void execute(IParser theParser, Writer theWriter) throws IOException { + theParser.encodeTagListToWriter(this.theOutcome, theWriter); + } + }; + } + + public static ParseAction create(IBaseResource outcome) { + return outcome == null ? null : new ParseAction(outcome) { + @Override + public void execute(IParser theParser, Writer theWriter) throws IOException { + theParser.encodeResourceToWriter(this.theOutcome, theWriter); + } + }; + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java index 4805c990855..e490a305b2d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ReadMethodBinding.java @@ -49,7 +49,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.SimpleBundleProvider; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -204,7 +204,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem } @Override - public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { + public IBundleProvider invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { theMethodParams[myIdIndex] = MethodUtil.convertIdToType(theRequest.getId(), myIdParameterType); if (myVersionIdIndex != null) { theMethodParams[myVersionIdIndex] = new IdDt(theRequest.getId().getVersionIdPart()); @@ -214,7 +214,7 @@ public class ReadMethodBinding extends BaseResourceReturningMethodBinding implem IBundleProvider retVal = toResourceList(response); if (theRequest.getServer().getETagSupport() == ETagSupportEnum.ENABLED) { - String ifNoneMatch = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_NONE_MATCH_LC); + String ifNoneMatch = theRequest.getHeader(Constants.HEADER_IF_NONE_MATCH_LC); if (retVal.size() == 1 && StringUtils.isNotBlank(ifNoneMatch)) { List responseResources = retVal.getResources(0, 1); IBaseResource responseResource = responseResources.get(0); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java index c26ede12486..79835e79967 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java @@ -1,5 +1,9 @@ package ca.uhn.fhir.rest.method; +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; + /* * #%L * HAPI FHIR - Core Library @@ -25,36 +29,31 @@ import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Set; - -import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; -import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.IRestfulResponse; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; -public class RequestDetails { +public abstract class RequestDetails { + private byte[] myRequestContents; private String myCompartmentName; private String myCompleteUrl; private String myFhirServerBase; private IdDt myId; private String myOperation; private Map myParameters; - private byte[] myRawRequest; private String myRequestPath; private RequestTypeEnum myRequestType; private String myResourceName; private boolean myRespondGzip; private RestOperationTypeEnum myRestOperationType; private String mySecondaryOperation; - private RestfulServer myServer; - private HttpServletRequest myServletRequest; - private HttpServletResponse myServletResponse; private Map> myUnqualifiedToQualifiedNames; - + private IRestfulResponse myResponse; + public String getCompartmentName() { return myCompartmentName; } @@ -79,10 +78,6 @@ public class RequestDetails { return myParameters; } - public byte[] getRawRequest() { - return myRawRequest; - } - /** * The part of the request URL that comes after the server base. *

@@ -109,17 +104,7 @@ public class RequestDetails { return mySecondaryOperation; } - public RestfulServer getServer() { - return myServer; - } - - public HttpServletRequest getServletRequest() { - return myServletRequest; - } - - public HttpServletResponse getServletResponse() { - return myServletResponse; - } + public abstract IRestfulServerDefaults getServer(); public Map> getUnqualifiedToQualifiedNames() { return myUnqualifiedToQualifiedNames; @@ -177,10 +162,6 @@ public class RequestDetails { } - public void setRawRequest(byte[] theRawRequest) { - myRawRequest = theRawRequest; - } - public void setRequestPath(String theRequestPath) { assert theRequestPath.length() == 0 || theRequestPath.charAt(0) != '/'; myRequestPath = theRequestPath; @@ -206,28 +187,66 @@ public class RequestDetails { mySecondaryOperation = theSecondaryOperation; } - public void setServer(RestfulServer theServer) { - myServer = theServer; - } + public IRestfulResponse getResponse() { + return myResponse; + } - public void setServletRequest(HttpServletRequest theRequest) { - myServletRequest = theRequest; - } + public void setResponse(IRestfulResponse theResponse) { + this.myResponse = theResponse; + } - public void setServletResponse(HttpServletResponse theServletResponse) { - myServletResponse = theServletResponse; - } - - public static RequestDetails withResourceAndParams(String theResourceName, RequestTypeEnum theRequestType, Set theParamNames) { - RequestDetails retVal = new RequestDetails(); - retVal.setResourceName(theResourceName); - retVal.setRequestType(theRequestType); - Map paramNames = new HashMap(); - for (String next : theParamNames) { - paramNames.put(next, new String[0]); + public abstract String getHeader(String name); + + public final byte[] loadRequestContents(RequestDetails theRequest) { + if (myRequestContents == null) { + myRequestContents = getByteStreamRequestContents(); } - retVal.setParameters(paramNames); - return retVal; + return myRequestContents; } + protected abstract byte[] getByteStreamRequestContents(); + + public abstract List getHeaders(String name); + + /** + * Retrieves the body of the request as character data using + * a BufferedReader. The reader translates the character + * data according to the character encoding used on the body. + * Either this method or {@link #getInputStream} may be called to read the + * body, not both. + * + * @return a Reader containing the body of the request + * + * @exception UnsupportedEncodingException if the character set encoding + * used is not supported and the text cannot be decoded + * + * @exception IllegalStateException if {@link #getInputStream} method + * has been called on this request + * + * @exception IOException if an input or output exception occurred + * + * @see javax.servlet.http.HttpServletRequest#getInputStream + */ + public abstract Reader getReader() throws IOException; + + /** + * Retrieves the body of the request as binary data. + * Either this method or {@link #getReader} may be called to + * read the body, not both. + * + * @return a {@link InputStream} object containing + * the body of the request + * + * @exception IllegalStateException if the {@link #getReader} method + * has already been called for this request + * + * @exception IOException if an input or output exception occurred + */ + public abstract InputStream getInputStream() throws IOException; + + /** + * Returns the server base URL (with no trailing '/') for a given request + */ + public abstract String getServerBaseForRequest(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java index a4ce070aa0c..20c4f6b7159 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SearchMethodBinding.java @@ -48,7 +48,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.param.BaseQueryParameter; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -283,7 +283,7 @@ public class SearchMethodBinding extends BaseResourceReturningMethodBinding { } @Override - public IBundleProvider invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { + public IBundleProvider invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { if (myIdParamIndex != null) { theMethodParams[myIdParamIndex] = theRequest.getId(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServerBaseParamBinder.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServerBaseParamBinder.java index 8e7f44af757..3b3de0328d1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServerBaseParamBinder.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServerBaseParamBinder.java @@ -43,7 +43,7 @@ class ServerBaseParamBinder implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { return theRequest.getFhirServerBase(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java index 31b132a9340..cff46c8bb1a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletRequestParameter.java @@ -30,6 +30,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; class ServletRequestParameter implements IParameter { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestParameter.class); @@ -43,8 +44,8 @@ class ServletRequestParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { - return theRequest.getServletRequest(); + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + return ((ServletRequestDetails) theRequest).getServletRequest(); } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletResponseParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletResponseParameter.java index 89e571e8b1b..8137b5009c4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletResponseParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/ServletResponseParameter.java @@ -30,6 +30,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; class ServletResponseParameter implements IParameter { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletResponseParameter.class); @@ -43,8 +44,8 @@ class ServletResponseParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { - return theRequest.getServletResponse(); + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + return ((ServletRequestDetails) theRequest).getServletResponse(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SinceParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SinceParameter.java index 5e3a3f90a0d..d1cbd1e0b07 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SinceParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SinceParameter.java @@ -54,7 +54,7 @@ class SinceParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { String[] sinceParams = theRequest.getParameters().remove(Constants.PARAM_SINCE); if (sinceParams != null) { if (sinceParams.length > 0) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SortParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SortParameter.java index 27b21ee28a5..38e8bc51bd0 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SortParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SortParameter.java @@ -65,7 +65,7 @@ public class SortParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT)) { if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT_ASC)) { if (!theRequest.getParameters().containsKey(Constants.PARAM_SORT_DESC)) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SummaryEnumParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SummaryEnumParameter.java index b656e9ae106..d76a9362b12 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SummaryEnumParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/SummaryEnumParameter.java @@ -67,7 +67,7 @@ public class SummaryEnumParameter implements IParameter { @Override @SuppressWarnings({ "rawtypes", "unchecked" }) - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { Set value = getSummaryValueOrNull(theRequest); if (value == null || value.isEmpty()) { return null; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java index bab344c93b9..4439e895cbe 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/TransactionMethodBinding.java @@ -44,6 +44,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.param.TransactionParameter; import ca.uhn.fhir.rest.param.TransactionParameter.ParamStyle; import ca.uhn.fhir.rest.server.IBundleProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; @@ -119,7 +120,7 @@ public class TransactionMethodBinding extends BaseResourceReturningMethodBinding @SuppressWarnings("unchecked") @Override - public Object invokeServer(RestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { + public Object invokeServer(IRestfulServer theServer, RequestDetails theRequest, Object[] theMethodParams) throws InvalidRequestException, InternalErrorException { /* * The design of HAPI's transaction method for DSTU1 support assumed that a transaction was just an update on a 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 0a4a5d9eac5..d463e9c7dc6 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 @@ -37,7 +37,7 @@ import ca.uhn.fhir.rest.client.BaseHttpClientInvocation; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { +public class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceParam { private Integer myIdParameterIndex; @@ -58,7 +58,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP * We are being a bit lenient here, since technically the client is supposed to include the version in the * Content-Location header, but we allow it in the PUT URL as well.. */ - String locationHeader = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_LOCATION); + String locationHeader = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION); IdDt id = theRequest.getId(); if (isNotBlank(locationHeader)) { id = new IdDt(locationHeader); @@ -69,7 +69,7 @@ class UpdateMethodBinding extends BaseOutcomeReturningMethodBindingWithResourceP } } - String ifMatchValue = theRequest.getServletRequest().getHeader(Constants.HEADER_IF_MATCH); + String ifMatchValue = theRequest.getHeader(Constants.HEADER_IF_MATCH); if (isNotBlank(ifMatchValue)) { ifMatchValue = MethodUtil.parseETagValue(ifMatchValue); if (id != null && id.hasVersionIdPart() == false) { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java index c27715a72c2..f38412016d4 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/BaseQueryParameter.java @@ -133,7 +133,7 @@ public abstract class BaseQueryParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { List paramList = new ArrayList(); String name = getName(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java index c4e941218e6..f626ae783c2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/ResourceParameter.java @@ -30,7 +30,6 @@ import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.nio.charset.Charset; import java.util.Collection; -import java.util.Enumeration; import java.util.List; import java.util.Map; @@ -102,38 +101,37 @@ public class ResourceParameter implements IParameter { } @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { - switch (myMode) { - case BODY: - try { - return IOUtils.toString(createRequestReader(theRequest, theRequestContents)); - } catch (IOException e) { - // Shouldn't happen since we're reading from a byte array - throw new InternalErrorException("Failed to load request"); + switch (myMode) { + case BODY: + try { + return IOUtils.toString(createRequestReader(theRequest)); + } + catch (IOException e) { + // Shouldn't happen since we're reading from a byte array + throw new InternalErrorException("Failed to load request"); + } + case ENCODING: + return RestfulServerUtils.determineRequestEncoding(theRequest); + case RESOURCE: + default: + return parseResourceFromRequest(theRequest, theMethodBinding, myResourceType); } - case ENCODING: - return RestfulServerUtils.determineRequestEncoding(theRequest); - case RESOURCE: - break; - } - - IBaseResource retVal = parseResourceFromRequest(theRequest, theMethodBinding, myResourceType); - - return retVal; +// } } - - static Reader createRequestReader(byte[] theRequestContents, Charset charset) { - Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequestContents), charset); + + public static Reader createRequestReader(RequestDetails theRequest, Charset charset) { + Reader requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents(theRequest)), charset); return requestReader; } - static Reader createRequestReader(RequestDetails theRequest, byte[] theRequestContents) { - return createRequestReader(theRequestContents, determineRequestCharset(theRequest)); - } + public static Reader createRequestReader(RequestDetails theRequest) throws IOException { + return createRequestReader(theRequest, determineRequestCharset(theRequest)); + } - static Charset determineRequestCharset(RequestDetails theRequest) { - String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); + public static Charset determineRequestCharset(RequestDetails theRequest) { + String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); Charset charset = null; if (isNotBlank(ct)) { @@ -144,20 +142,21 @@ public class ResourceParameter implements IParameter { charset = Charset.forName("UTF-8"); } return charset; - } - + } + @SuppressWarnings("unchecked") - public static T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType) { + public static T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, + Class theResourceType) { FhirContext ctx = theRequest.getServer().getFhirContext(); final Charset charset = determineRequestCharset(theRequest); - Reader requestReader = createRequestReader(theRequest.getRawRequest(), charset); + Reader requestReader = createRequestReader(theRequest, charset); RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null; EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest); if (encoding == null) { - String ctValue = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); + String ctValue = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); if (ctValue != null) { if (ctValue.startsWith("application/x-www-form-urlencoded")) { String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType()); @@ -171,7 +170,7 @@ public class ResourceParameter implements IParameter { String body; try { body = IOUtils.toString(requestReader); - } catch (IOException e) { + } catch (IOException e) { // This shouldn't happen since we're reading from a byte array.. throw new InternalErrorException(e); } @@ -180,7 +179,7 @@ public class ResourceParameter implements IParameter { String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType); throw new InvalidRequestException(msg); } else { - requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.getRawRequest()), charset); + requestReader = new InputStreamReader(new ByteArrayInputStream(theRequest.loadRequestContents(theRequest)), charset); } } else { String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType); @@ -203,8 +202,7 @@ public class ResourceParameter implements IParameter { if (theRequest.getServer().getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { TagList tagList = new TagList(); - for (Enumeration enumeration = theRequest.getServletRequest().getHeaders(Constants.HEADER_CATEGORY); enumeration.hasMoreElements();) { - String nextTagComplete = enumeration.nextElement(); + for (String nextTagComplete : theRequest.getHeaders(Constants.HEADER_CATEGORY)) { MethodUtil.parseTagValue(tagList, nextTagComplete); } if (tagList.isEmpty() == false) { @@ -212,23 +210,23 @@ public class ResourceParameter implements IParameter { } } return retVal; - } - + } + public static IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType) { IBaseResource retVal; if (IBaseBinary.class.isAssignableFrom(theResourceType)) { FhirContext ctx = theRequest.getServer().getFhirContext(); - String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); + String ct = theRequest.getHeader(Constants.HEADER_CONTENT_TYPE); IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance(); binary.setContentType(ct); - binary.setContent(theRequest.getRawRequest()); + binary.setContent(theRequest.loadRequestContents(theRequest)); retVal = binary; } else { retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType); } return retVal; - } + } public enum Mode { BODY, ENCODING, RESOURCE diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java index 5cc912672d7..2c56a61110f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/param/TransactionParameter.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.rest.param; +import java.io.IOException; + /* * #%L * HAPI FHIR - Core Library @@ -48,6 +50,7 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; public class TransactionParameter implements IParameter { + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(TransactionParameter.class); private FhirContext myContext; private ParamStyle myParamStyle; private Class myResourceBundleType; @@ -96,35 +99,41 @@ public class TransactionParameter implements IParameter { // nothing } - + @Override - public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, byte[] theRequestContents, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { - + public Object translateQueryParametersIntoServerArgument(RequestDetails theRequest, BaseMethodBinding theMethodBinding) throws InternalErrorException, InvalidRequestException { // TODO: don't use a default encoding, just fail! EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest); - IParser parser = encoding.newParser(myContext); + IParser parser = encoding.newParser(theRequest.getServer().getFhirContext()); + + Reader reader; + try { + reader = ResourceParameter.createRequestReader(theRequest); + } + catch (IOException e) { + ourLog.error("Could not load request resource", e); + throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage())); + } - Reader reader = ResourceParameter.createRequestReader(theRequest, theRequestContents); - switch (myParamStyle) { - case DSTU1_BUNDLE: { - Bundle bundle; - bundle = parser.parseBundle(reader); - return bundle; - } - case RESOURCE_LIST: { - Bundle bundle = parser.parseBundle(reader); - ArrayList resourceList = new ArrayList(); - for (BundleEntry next : bundle.getEntries()) { - if (next.getResource() != null) { - resourceList.add(next.getResource()); - } + case DSTU1_BUNDLE: { + Bundle bundle; + bundle = parser.parseBundle(reader); + return bundle; } - return resourceList; - } - case RESOURCE_BUNDLE: - return parser.parseResource(myResourceBundleType, reader); + case RESOURCE_LIST: { + Bundle bundle = parser.parseBundle(reader); + ArrayList resourceList = new ArrayList(); + for (BundleEntry next : bundle.getEntries()) { + if (next.getResource() != null) { + resourceList.add(next.getResource()); + } + } + return resourceList; + } + case RESOURCE_BUNDLE: + return parser.parseResource(myResourceBundleType, reader); } throw new IllegalStateException("Unknown type: " + myParamStyle); // should not happen diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java index 15c16ae9514..e81147c2e8d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/Dstu1BundleFactory.java @@ -197,7 +197,7 @@ public class Dstu1BundleFactory implements IVersionSpecificBundleFactory { } @Override - public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes) { + public void initializeBundleFromBundleProvider(IRestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes) { int numToReturn; String searchId = null; List resourceList; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulResponse.java new file mode 100644 index 00000000000..afbb3000fd3 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulResponse.java @@ -0,0 +1,31 @@ +package ca.uhn.fhir.rest.server; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.util.Set; + +import org.hl7.fhir.instance.model.api.IBaseBinary; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.method.ParseAction; + +public interface IRestfulResponse { + + Object streamResponseAsResource(IBaseResource resource, boolean prettyPrint, Set summaryMode, int operationStatus, boolean respondGzip, boolean addContentLocationHeader) + throws IOException; + Object streamResponseAsBundle(Bundle bundle, Set summaryMode, boolean respondGzip, boolean requestIsBrowser) + throws IOException; + + Object returnResponse(ParseAction outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, String resourceName) throws IOException; + + Writer getResponseWriter(int statusCode, String contentType, String charset, boolean respondGzip) throws UnsupportedEncodingException, IOException; + Object sendWriterResponse(int status, String contentType, String charset, Writer writer) throws IOException; + + void addHeader(String headerKey, String headerValue); + + Object sendAttachmentResponse(IBaseBinary bin, int stausCode, String contentType) throws IOException; +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java index 31156e72c7a..da77bd1c9c2 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServer.java @@ -2,7 +2,7 @@ package ca.uhn.fhir.rest.server; import java.util.List; -import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; /* @@ -24,14 +24,12 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; * limitations under the License. * #L% */ -public interface IRestfulServer { +public interface IRestfulServer extends IRestfulServerDefaults { - /** - * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to - * creating their own. - */ - public FhirContext getFhirContext(); - - public List getInterceptors(); + List getInterceptors(); + IPagingProvider getPagingProvider(); + + BundleInclusionRule getBundleInclusionRule(); + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java index 7b5e65d033c..73a0479b72d 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerDefaults.java @@ -1,16 +1,46 @@ package ca.uhn.fhir.rest.server; +import ca.uhn.fhir.context.FhirContext; + public interface IRestfulServerDefaults { - /** - * Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an Accept header in the request, or a _pretty - * parameter in the request URL. - *

- * The default is false - *

- * - * @return Returns the default pretty print setting - */ - public boolean isDefaultPrettyPrint(); + /** + * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to + * creating their own. + */ + FhirContext getFhirContext(); + + /** + * Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an Accept header in the request, or a _pretty + * parameter in the request URL. + *

+ * The default is false + *

+ * + * @return Returns the default pretty print setting + */ + boolean isDefaultPrettyPrint(); + + /** + * @return Returns the server support for ETags (will not be null). Default is {@link #DEFAULT_ETAG_SUPPORT} + */ + ETagSupportEnum getETagSupport(); + + /** + * @return Returns the setting for automatically adding profile tags + */ + AddProfileTagEnum getAddProfileTag(); + + /** + * @return Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the _format URL parameter, or with an Accept header + * in the request. The default is {@link EncodingEnum#XML}. Will not return null. + */ + EncodingEnum getDefaultResponseEncoding(); + + /** + * @return If true the server will use browser friendly content-types (instead of standard FHIR ones) when it detects that the request is coming from a browser + * instead of a FHIR + */ + boolean isUseBrowserFriendlyContentTypes(); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerUtil.java new file mode 100644 index 00000000000..b55330d0836 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IRestfulServerUtil.java @@ -0,0 +1,24 @@ +package ca.uhn.fhir.rest.server; + +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.param.ResourceParameter.Mode; +import ca.uhn.fhir.rest.param.TransactionParameter.ParamStyle; + +public interface IRestfulServerUtil { + + Object getResourceParameter( + RequestDetails requestDetails, + Mode myMode, + BaseMethodBinding theMethodBinding, + Class myResourceType); + + Object getRequestResource(RequestDetails theRequest, ParamStyle myParamStyle, Class myResourceBundleType); + + T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType); + + IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType); + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IVersionSpecificBundleFactory.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IVersionSpecificBundleFactory.java index 77394f88f84..d2298ee5394 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IVersionSpecificBundleFactory.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/IVersionSpecificBundleFactory.java @@ -40,7 +40,7 @@ public interface IVersionSpecificBundleFactory { void addRootPropertiesToBundle(String theAuthor, String theServerBase, String theCompleteUrl, Integer theTotalResults, BundleTypeEnum theBundleType, IPrimitiveType theLastUpdated); - void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, + void initializeBundleFromBundleProvider(IRestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theCount, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes); Bundle getDstu1Bundle(); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulResponse.java new file mode 100644 index 00000000000..7d30b9f7959 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulResponse.java @@ -0,0 +1,66 @@ +package ca.uhn.fhir.rest.server; + +import java.io.IOException; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; + +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.method.RequestDetails; + +public abstract class RestfulResponse implements IRestfulResponse { + + private T theRequestDetails; + private ConcurrentHashMap theHeaders = new ConcurrentHashMap(); + + public RestfulResponse(T requestDetails) { + this.theRequestDetails = requestDetails; + } + + @Override + public final Object streamResponseAsResource(IBaseResource resource, boolean prettyPrint, Set summaryMode, + int statusCode, boolean respondGzip, boolean addContentLocationHeader) + throws IOException { + return RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), resource, prettyPrint, summaryMode, statusCode, respondGzip, addContentLocationHeader, + respondGzip, getRequestDetails()); + + } + + @Override + public Object streamResponseAsBundle(Bundle bundle, Set summaryMode, boolean respondGzip, boolean requestIsBrowser) + throws IOException { + return RestfulServerUtils.streamResponseAsBundle(theRequestDetails.getServer(), bundle, summaryMode, requestIsBrowser, respondGzip, getRequestDetails()); + } + + @Override + public void addHeader(String headerKey, String headerValue) { + this.getHeaders().put(headerKey, headerValue); + } + + /** + * Get the http headers + * @return the headers + */ + public ConcurrentHashMap getHeaders() { + return theHeaders; + } + + /** + * Get the requestDetails + * @return the requestDetails + */ + public T getRequestDetails() { + return theRequestDetails; + } + + /** + * Set the requestDetails + * @param requestDetails the requestDetails to set + */ + public void setRequestDetails(T requestDetails) { + this.theRequestDetails = requestDetails; + } + +} 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 6a42349da5e..69e88dbce41 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 @@ -23,6 +23,8 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; +import java.io.InputStream; +import java.io.Writer; import java.lang.annotation.Annotation; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; @@ -39,12 +41,14 @@ import java.util.Set; import java.util.StringTokenizer; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; +import java.util.jar.Manifest; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -56,14 +60,17 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.Include; import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.annotation.Destroy; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.annotation.Initialize; +import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.ConformanceMethodBinding; +import ca.uhn.fhir.rest.method.ParseAction; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @@ -71,11 +78,12 @@ import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.exceptions.NotModifiedException; import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.ReflectionUtil; import ca.uhn.fhir.util.UrlUtil; import ca.uhn.fhir.util.VersionUtil; -public class RestfulServer extends HttpServlet implements IRestfulServerDefaults, IRestfulServer { +public class RestfulServer extends HttpServlet implements IRestfulServer { private static final ExceptionHandlingInterceptor DEFAULT_EXCEPTION_HANDLER = new ExceptionHandlingInterceptor(); private static final long serialVersionUID = 1L; @@ -316,15 +324,12 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return count; } - /** - * Returns the setting for automatically adding profile tags - * - * @see #setAddProfileTag(AddProfileTagEnum) - */ + @Override public AddProfileTagEnum getAddProfileTag() { return myAddProfileTag; } + @Override public BundleInclusionRule getBundleInclusionRule() { return myBundleInclusionRule; } @@ -337,9 +342,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return myDefaultResponseEncoding; } - /** - * Returns the server support for ETags (will not be null). Default is {@link #DEFAULT_ETAG_SUPPORT} - */ + @Override public ETagSupportEnum getETagSupport() { return myETagSupport; } @@ -411,7 +414,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults public IServerAddressStrategy getServerAddressStrategy() { return myServerAddressStrategy; } - + /** * Returns the server base URL (with no trailing '/') for a given request */ @@ -463,7 +466,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return myServerVersion; } - private void handlePagingRequest(RequestDetails theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException { + private void handlePagingRequest(ServletRequestDetails theRequest, HttpServletResponse theResponse, String thePagingAction) throws IOException { IBundleProvider resultList = getPagingProvider().retrieveResultList(thePagingAction); if (resultList == null) { ourLog.info("Client requested unknown paging ID[{}]", thePagingAction); @@ -476,21 +479,21 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return; } - Integer count = RestfulServerUtils.extractCountParameter(theRequest.getServletRequest()); + Integer count = RestfulServerUtils.extractCountParameter(theRequest); if (count == null) { count = getPagingProvider().getDefaultPageSize(); } else if (count > getPagingProvider().getMaximumPageSize()) { count = getPagingProvider().getMaximumPageSize(); } - Integer offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest.getServletRequest(), Constants.PARAM_PAGINGOFFSET); + Integer offsetI = RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_PAGINGOFFSET); if (offsetI == null || offsetI < 0) { offsetI = 0; } int start = Math.min(offsetI, resultList.size() - 1); - EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest.getServletRequest(), getDefaultResponseEncoding()); + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequest, getDefaultResponseEncoding()); boolean prettyPrint = RestfulServerUtils.prettyPrintResponse(this, theRequest); boolean requestIsBrowser = requestIsBrowser(theRequest.getServletRequest()); Set summaryMode = RestfulServerUtils.determineSummaryMode(theRequest); @@ -506,7 +509,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults } } - String linkSelfBase = getServerAddressStrategy().determineServerBase(getServletContext(), theRequest.getServletRequest()); + String linkSelfBase = myServerAddressStrategy.determineServerBase(getServletContext(), theRequest.getServletRequest()); String linkSelf = linkSelfBase + theRequest.getCompleteUrl().substring(theRequest.getCompleteUrl().indexOf('?')); bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), linkSelf, prettyPrint, start, count, thePagingAction, null, includes); @@ -521,7 +524,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return; } } - RestfulServerUtils.streamResponseAsBundle(this, theResponse, bundle, theRequest.getFhirServerBase(), summaryMode, respondGzip, requestIsBrowser, theRequest); + theRequest.getResponse().streamResponseAsBundle(bundle, summaryMode, respondGzip, requestIsBrowser); } else { IBaseResource resBundle = bundleFactory.getResourceBundle(); for (int i = getInterceptors().size() - 1; i >= 0; i--) { @@ -532,14 +535,14 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return; } } - RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), false, theRequest); + theRequest.getResponse().streamResponseAsResource(resBundle, prettyPrint, summaryMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), false); } } protected void handleRequest(RequestTypeEnum theRequestType, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException { String fhirServerBase = null; boolean requestIsBrowser = requestIsBrowser(theRequest); - RequestDetails requestDetails = new RequestDetails(); + ServletRequestDetails requestDetails = new ServletRequestDetails(); requestDetails.setServer(this); requestDetails.setRequestType(theRequestType); requestDetails.setServletRequest(theRequest); @@ -1338,4 +1341,86 @@ public class RestfulServer extends HttpServlet implements IRestfulServerDefaults return userAgent != null && userAgent.contains("Mozilla"); } + public Object returnResponse(ServletRequestDetails theRequest, ParseAction outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, + String resourceName) throws IOException { + HttpServletResponse servletResponse = theRequest.getServletResponse(); + servletResponse.setStatus(operationStatus); + servletResponse.setCharacterEncoding(Constants.CHARSET_NAME_UTF8); + addHeadersToResponse(servletResponse); + if(allowPrefer) { + addContentLocationHeaders(theRequest, servletResponse, response, resourceName); + } + if (outcome != null) { + EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequest); + servletResponse.setContentType(encoding.getResourceContentType()); + Writer writer = servletResponse.getWriter(); + IParser parser = encoding.newParser(getFhirContext()); + parser.setPrettyPrint(RestfulServerUtils.prettyPrintResponse(this, theRequest)); + try { + outcome.execute(parser, writer); + } finally { + writer.close(); + } + } else { + servletResponse.setContentType(Constants.CT_TEXT_WITH_UTF8); + Writer writer = servletResponse.getWriter(); + writer.close(); + } + // getMethod().in + return null; + } + + private void addContentLocationHeaders(RequestDetails theRequest, HttpServletResponse servletResponse, MethodOutcome response, String resourceName) { + if (response != null && response.getId() != null) { + addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_LOCATION, resourceName); + addLocationHeader(theRequest, servletResponse, response, Constants.HEADER_CONTENT_LOCATION, resourceName); + } + } + + private void addLocationHeader(RequestDetails theRequest, HttpServletResponse theResponse, MethodOutcome response, String headerLocation, String resourceName) { + StringBuilder b = new StringBuilder(); + b.append(theRequest.getFhirServerBase()); + b.append('/'); + b.append(resourceName); + b.append('/'); + b.append(response.getId().getIdPart()); + if (response.getId().hasVersionIdPart()) { + b.append("/" + Constants.PARAM_HISTORY + "/"); + b.append(response.getId().getVersionIdPart()); + } else if (response.getVersionId() != null && response.getVersionId().isEmpty() == false) { + b.append("/" + Constants.PARAM_HISTORY + "/"); + b.append(response.getVersionId().getValue()); + } + theResponse.addHeader(headerLocation, b.toString()); + + } + + public RestulfulServerConfiguration createConfiguration() { + RestulfulServerConfiguration result = new RestulfulServerConfiguration(); + result.setResourceBindings(getResourceBindings()); + result.setServerBindings(getServerBindings()); + result.setImplementationDescription(getImplementationDescription()); + result.setServerVersion(getServerVersion()); + result.setServerName(getServerName()); + result.setFhirContext(getFhirContext()); + result.setServerAddressStrategy(myServerAddressStrategy); + InputStream inputStream = null; + try { + inputStream = getClass().getResourceAsStream("/META-INF/MANIFEST.MF"); + if (inputStream != null) { + Manifest manifest = new Manifest(inputStream); + result.setConformanceDate(manifest.getMainAttributes().getValue("Build-Time")); + } + } + catch (IOException e) { + // fall through + } + finally { + if (inputStream != null) { + IOUtils.closeQuietly(inputStream); + } + } + return result; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java new file mode 100644 index 00000000000..d86c5ab8a3f --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java @@ -0,0 +1,262 @@ +package ca.uhn.fhir.rest.server; + +import static org.apache.commons.lang3.StringUtils.isBlank; +import static org.apache.commons.lang3.StringUtils.isNotBlank; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.Reader; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.Enumeration; + +import org.apache.commons.io.IOUtils; +import org.apache.http.entity.ContentType; +import org.hl7.fhir.instance.model.api.IBaseBinary; +import org.hl7.fhir.instance.model.api.IBaseResource; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.context.FhirVersionEnum; +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.api.BundleEntry; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; +import ca.uhn.fhir.model.api.TagList; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.method.MethodUtil; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.method.BaseMethodBinding.IRequestReader; +import ca.uhn.fhir.rest.param.ResourceParameter; +import ca.uhn.fhir.rest.param.ResourceParameter.Mode; +import ca.uhn.fhir.rest.param.TransactionParameter.ParamStyle; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class RestfulServerUtil implements IRestfulServerUtil { + + /** + * @see BaseMethodBinding#loadRequestContents(RequestDetails) + */ + private static volatile IRequestReader ourRequestReader; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtil.class); + private byte[] requestContents; + + @Override + public Object getResourceParameter(RequestDetails theRequest, Mode myMode, BaseMethodBinding theMethodBinding, + Class myResourceType) { + switch (myMode) { + case BODY: + try { + return IOUtils.toString(createRequestReader(theRequest)); + } catch (IOException e) { + // Shouldn't happen since we're reading from a byte array + throw new InternalErrorException("Failed to load request"); + } + case ENCODING: + return RestfulServerUtils.determineRequestEncoding(theRequest); + case RESOURCE: + default: + return parseResourceFromRequest(theRequest, theMethodBinding, myResourceType); + } + } + + + protected byte[] loadRequestContents(RequestDetails theRequest) { + if(requestContents != null) { + return requestContents; + } + /* + * This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on servlet-api in clients since there is no point. So we dynamically load a class + * that does the servlet processing in servers. Down the road it may make sense to just split the method binding classes into server and client versions, but this isn't actually a huge deal I + * don't think. + */ + IRequestReader reader = ourRequestReader; + if (reader == null) { + try { + Class.forName("javax.servlet.ServletInputStream"); + String className = BaseMethodBinding.class.getName() + "$" + "ActiveRequestReader"; + try { + reader = (IRequestReader) Class.forName(className).newInstance(); + } catch (Exception e1) { + throw new ConfigurationException("Failed to instantiate class " + className, e1); + } + } catch (ClassNotFoundException e) { + String className = BaseMethodBinding.class.getName() + "$" + "InactiveRequestReader"; + try { + reader = (IRequestReader) Class.forName(className).newInstance(); + } catch (Exception e1) { + throw new ConfigurationException("Failed to instantiate class " + className, e1); + } + } + ourRequestReader = reader; + } + + try { + InputStream inputStream = reader.getInputStream(theRequest); + requestContents = IOUtils.toByteArray(inputStream); + return requestContents; + } + catch (IOException e) { + ourLog.error("Could not load request resource", e); + throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage())); + } + + + } + + + public Reader createRequestReader(RequestDetails theRequest, Charset charset) { + Reader requestReader = new InputStreamReader(new ByteArrayInputStream(loadRequestContents(theRequest)), charset); + return requestReader; + } + + public Reader createRequestReader(RequestDetails theRequest) throws IOException { + return createRequestReader(theRequest, determineRequestCharset(theRequest)); + } + + public static Charset determineRequestCharset(RequestDetails theRequest) { + String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); + + Charset charset = null; + if (isNotBlank(ct)) { + ContentType parsedCt = ContentType.parse(ct); + charset = parsedCt.getCharset(); + } + if (charset == null) { + charset = Charset.forName("UTF-8"); + } + return charset; + } + + @SuppressWarnings("unchecked") + @Override + public T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType) { + FhirContext ctx = theRequest.getServer().getFhirContext(); + + final Charset charset = determineRequestCharset(theRequest); + Reader requestReader = createRequestReader(theRequest, charset); + + RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null; + + EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest); + if (encoding == null) { + String ctValue = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); + if (ctValue != null) { + if (ctValue.startsWith("application/x-www-form-urlencoded")) { + String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType()); + throw new InvalidRequestException(msg); + } + } + if (isBlank(ctValue)) { + /* + * If the client didn't send a content type, try to guess + */ + String body; + try { + body = IOUtils.toString(requestReader); + } catch (IOException e) { + // This shouldn't happen since we're reading from a byte array.. + throw new InternalErrorException(e); + } + encoding = MethodUtil.detectEncodingNoDefault(body); + if (encoding == null) { + String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType); + throw new InvalidRequestException(msg); + } else { + requestReader = new InputStreamReader(new ByteArrayInputStream(loadRequestContents(theRequest)), charset); + } + } else { + String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType); + throw new InvalidRequestException(msg); + } + } + + IParser parser = encoding.newParser(ctx); + + T retVal; + if (theResourceType != null) { + retVal = parser.parseResource(theResourceType, requestReader); + } else { + retVal = (T) parser.parseResource(requestReader); + } + + if (theRequest.getId() != null && theRequest.getId().hasIdPart()) { + retVal.setId(theRequest.getId()); + } + + if (theRequest.getServer().getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { + TagList tagList = new TagList(); + for (Enumeration enumeration = theRequest.getServletRequest().getHeaders(Constants.HEADER_CATEGORY); enumeration.hasMoreElements();) { + String nextTagComplete = enumeration.nextElement(); + MethodUtil.parseTagValue(tagList, nextTagComplete); + } + if (tagList.isEmpty() == false) { + ((IResource) retVal).getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList); + } + } + return retVal; + } + + @Override + public IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType) { + IBaseResource retVal; + if (IBaseBinary.class.isAssignableFrom(theResourceType)) { + FhirContext ctx = theRequest.getServer().getFhirContext(); + String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); + IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance(); + binary.setContentType(ct); + binary.setContent(loadRequestContents(theRequest)); + + retVal = binary; + } else { + retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType); + } + return retVal; + } + + + @Override + public Object getRequestResource(RequestDetails theRequest, ParamStyle myParamStyle, Class myResourceBundleType) { + // TODO: don't use a default encoding, just fail! + EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest); + + IParser parser = encoding.newParser(theRequest.getServer().getFhirContext()); + + Reader reader; + try { + reader = createRequestReader(theRequest); + } + catch (IOException e) { + ourLog.error("Could not load request resource", e); + throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage())); + } + + switch (myParamStyle) { + case DSTU1_BUNDLE: { + Bundle bundle; + bundle = parser.parseBundle(reader); + return bundle; + } + case RESOURCE_LIST: { + Bundle bundle = parser.parseBundle(reader); + ArrayList resourceList = new ArrayList(); + for (BundleEntry next : bundle.getEntries()) { + if (next.getResource() != null) { + resourceList.add(next.getResource()); + } + } + return resourceList; + } + case RESOURCE_BUNDLE: + return parser.parseResource(myResourceBundleType, reader); + } + + throw new IllegalStateException("Unknown type: " + myParamStyle); // should not happen + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index f612bbb640c..92694476b96 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -23,7 +23,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank; import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.io.IOException; -import java.io.OutputStreamWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.net.URLEncoder; @@ -32,17 +31,15 @@ import java.util.Collections; import java.util.Date; import java.util.Enumeration; import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.StringTokenizer; import java.util.regex.Matcher; import java.util.regex.Pattern; -import java.util.zip.GZIPOutputStream; -import javax.servlet.ServletOutputStream; import javax.servlet.http.HttpServletRequest; -import javax.servlet.http.HttpServletResponse; import org.apache.commons.lang3.StringUtils; import org.apache.http.client.utils.DateUtils; @@ -189,10 +186,10 @@ public class RestfulServerUtils { public static EncodingEnum determineRequestEncodingNoDefault(RequestDetails theReq) { EncodingEnum retVal = null; - Enumeration acceptValues = theReq.getServletRequest().getHeaders(Constants.HEADER_CONTENT_TYPE); + Iterator acceptValues = theReq.getHeaders(Constants.HEADER_CONTENT_TYPE).iterator(); if (acceptValues != null) { - while (acceptValues.hasMoreElements() && retVal == null) { - String nextAcceptHeaderValue = acceptValues.nextElement(); + while (acceptValues.hasNext() && retVal == null) { + String nextAcceptHeaderValue = acceptValues.next(); if (nextAcceptHeaderValue != null && isNotBlank(nextAcceptHeaderValue)) { for (String nextPart : nextAcceptHeaderValue.split(",")) { int scIdx = nextPart.indexOf(';'); @@ -218,8 +215,8 @@ public class RestfulServerUtils { * Returns null if the request doesn't express that it wants FHIR. If it expresses that it wants * XML and JSON equally, returns thePrefer. */ - public static EncodingEnum determineResponseEncodingNoDefault(HttpServletRequest theReq, EncodingEnum thePrefer) { - String[] format = theReq.getParameterValues(Constants.PARAM_FORMAT); + public static EncodingEnum determineResponseEncodingNoDefault(RequestDetails theReq, EncodingEnum thePrefer) { + String[] format = theReq.getParameters().get(Constants.PARAM_FORMAT); if (format != null) { for (String nextFormat : format) { EncodingEnum retVal = Constants.FORMAT_VAL_TO_ENCODING.get(nextFormat); @@ -234,12 +231,11 @@ public class RestfulServerUtils { */ // text/xml, application/xml, application/xhtml+xml, text/html;q=0.9, text/plain;q=0.8, image/png, */*;q=0.5 - Enumeration acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT); - if (acceptValues != null) { - float bestQ = -1f; - EncodingEnum retVal = null; - while (acceptValues.hasMoreElements()) { - String nextAcceptHeaderValue = acceptValues.nextElement(); + List acceptValues = theReq.getHeaders(Constants.HEADER_ACCEPT); + float bestQ = -1f; + EncodingEnum retVal = null; + if (acceptValues != null) { + for (String nextAcceptHeaderValue : acceptValues) { StringTokenizer tok = new StringTokenizer(nextAcceptHeaderValue, ","); while (tok.hasMoreTokens()) { String nextToken = tok.nextToken(); @@ -281,7 +277,7 @@ public class RestfulServerUtils { int equalsIndex = nextQualifier.indexOf('='); if (equalsIndex != -1) { String nextQualifierKey = nextQualifier.substring(0, equalsIndex).trim(); - String nextQualifierValue = nextQualifier.substring(equalsIndex+1, nextQualifier.length()).trim(); + String nextQualifierValue = nextQualifier.substring(equalsIndex + 1, nextQualifier.length()).trim(); if (nextQualifierKey.equals("q")) { try { q = Float.parseFloat(nextQualifierValue); @@ -350,10 +346,10 @@ public class RestfulServerUtils { /** * Determine whether a response should be given in JSON or XML format based on the incoming HttpServletRequest's "_format" parameter and "Accept:" HTTP header. */ - public static EncodingEnum determineResponseEncodingWithDefault(RestfulServer theServer, HttpServletRequest theReq) { - EncodingEnum retVal = determineResponseEncodingNoDefault(theReq, theServer.getDefaultResponseEncoding()); + public static EncodingEnum determineResponseEncodingWithDefault(RequestDetails theReq) { + EncodingEnum retVal = determineResponseEncodingNoDefault(theReq, theReq.getServer().getDefaultResponseEncoding()); if (retVal == null) { - retVal = theServer.getDefaultResponseEncoding(); + retVal = theReq.getServer().getDefaultResponseEncoding(); } return retVal; } @@ -394,14 +390,14 @@ public class RestfulServerUtils { return retVal; } - public static Integer extractCountParameter(HttpServletRequest theRequest) { + public static Integer extractCountParameter(RequestDetails theRequest) { return RestfulServerUtils.tryToExtractNamedParameter(theRequest, Constants.PARAM_COUNT); } public static IParser getNewParser(FhirContext theContext, RequestDetails theRequestDetails) { // Determine response encoding - EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails.getServer(), theRequestDetails.getServletRequest()); + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails); IParser parser; switch (responseEncoding) { case JSON: @@ -418,17 +414,6 @@ public class RestfulServerUtils { return parser; } - static Writer getWriter(HttpServletResponse theHttpResponse, boolean theRespondGzip) throws UnsupportedEncodingException, IOException { - Writer writer; - if (theRespondGzip) { - theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP); - writer = new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), "UTF-8"); - } else { - writer = theHttpResponse.getWriter(); - } - return writer; - } - public static Set parseAcceptHeaderAndReturnHighestRankedOptions(HttpServletRequest theRequest) { Set retVal = new HashSet(); @@ -450,7 +435,8 @@ public class RestfulServerUtils { try { q = Float.parseFloat(value); q = Math.max(q, 0.0f); - } catch (NumberFormatException e) { + } + catch (NumberFormatException e) { ourLog.debug("Invalid Accept header q value: {}", value); } } @@ -522,10 +508,9 @@ public class RestfulServerUtils { } } else { prettyPrint = theServer.isDefaultPrettyPrint(); - Enumeration acceptValues = theRequest.getServletRequest().getHeaders(Constants.HEADER_ACCEPT); + List acceptValues = theRequest.getHeaders(Constants.HEADER_ACCEPT); if (acceptValues != null) { - while (acceptValues.hasMoreElements()) { - String nextAcceptHeaderValue = acceptValues.nextElement(); + for (String nextAcceptHeaderValue : acceptValues) { if (nextAcceptHeaderValue.contains("pretty=true")) { prettyPrint = true; } @@ -535,54 +520,59 @@ public class RestfulServerUtils { return prettyPrint; } - public static void streamResponseAsBundle(RestfulServer theServer, HttpServletResponse theHttpResponse, Bundle bundle, String theServerBase, Set theSummaryMode, boolean theRespondGzip, - boolean theRequestIsBrowser, RequestDetails theRequestDetails) throws IOException { - assert!theServerBase.endsWith("/"); + public static Object streamResponseAsBundle(IRestfulServerDefaults theServer, Bundle bundle, Set theSummaryMode, + boolean theRequestIsBrowser, boolean respondGzip, RequestDetails theRequestDetails) + throws IOException { - theHttpResponse.setStatus(200); + int status = 200; // Determine response encoding - EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theServer, theRequestDetails.getServletRequest()); + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingWithDefault(theRequestDetails); + String contentType; if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) { - theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType()); + contentType = responseEncoding.getBrowserFriendlyBundleContentType(); } else { - theHttpResponse.setContentType(responseEncoding.getBundleContentType()); + contentType = responseEncoding.getBundleContentType(); } - theHttpResponse.setCharacterEncoding(Constants.CHARSET_NAME_UTF8); + String charset = Constants.CHARSET_NAME_UTF8; + Writer writer = theRequestDetails.getResponse().getResponseWriter(status, contentType, charset, respondGzip); - theServer.addHeadersToResponse(theHttpResponse); - - Writer writer = RestfulServerUtils.getWriter(theHttpResponse, theRespondGzip); try { IParser parser = RestfulServerUtils.getNewParser(theServer.getFhirContext(), theRequestDetails); if (theSummaryMode.contains(SummaryEnum.TEXT)) { parser.setEncodeElements(TEXT_ENCODE_ELEMENTS); } parser.encodeBundleToWriter(bundle, writer); - } finally { - writer.close(); } + catch (Exception e) { + //always send a response, even if the parsing went wrong + } + return theRequestDetails.getResponse().sendWriterResponse(status, contentType, charset, writer); } - public static void streamResponseAsResource(RestfulServer theServer, HttpServletResponse theHttpResponse, IBaseResource theResource, boolean theRequestIsBrowser, Set theSummaryMode, - int stausCode, boolean theRespondGzip, boolean theAddContentLocationHeader, RequestDetails theRequestDetails) throws IOException { - theHttpResponse.setStatus(stausCode); + public static Object streamResponseAsResource(IRestfulServerDefaults theServer, IBaseResource theResource, boolean theRequestIsBrowser, Set theSummaryMode, + int stausCode, boolean theRespondGzip, boolean theAddContentLocationHeader, boolean respondGzip, + RequestDetails theRequestDetails) + throws IOException { + IRestfulResponse restUtil = theRequestDetails.getResponse(); // Determine response encoding - EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails.getServletRequest(), theServer.getDefaultResponseEncoding()); + EncodingEnum responseEncoding = RestfulServerUtils.determineResponseEncodingNoDefault(theRequestDetails, + theServer.getDefaultResponseEncoding()); String serverBase = theRequestDetails.getFhirServerBase(); - if (theAddContentLocationHeader && theResource.getIdElement() != null && theResource.getIdElement().hasIdPart() && isNotBlank(serverBase)) { + if (theAddContentLocationHeader && theResource.getIdElement() != null && theResource.getIdElement().hasIdPart() + && isNotBlank(serverBase)) { String resName = theServer.getFhirContext().getResourceDefinition(theResource).getName(); IIdType fullId = theResource.getIdElement().withServerBase(serverBase, resName); - theHttpResponse.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue()); + restUtil.addHeader(Constants.HEADER_CONTENT_LOCATION, fullId.getValue()); } if (theServer.getETagSupport() == ETagSupportEnum.ENABLED) { if (theResource.getIdElement().hasVersionIdPart()) { - theHttpResponse.addHeader(Constants.HEADER_ETAG, "W/\"" + theResource.getIdElement().getVersionIdPart() + '"'); + restUtil.addHeader(Constants.HEADER_ETAG, "W/\"" + theResource.getIdElement().getVersionIdPart() + '"'); } } @@ -593,26 +583,19 @@ public class RestfulServerUtils { } } + String contentType; if (theResource instanceof IBaseBinary && responseEncoding == null) { IBaseBinary bin = (IBaseBinary) theResource; if (isNotBlank(bin.getContentType())) { - theHttpResponse.setContentType(bin.getContentType()); + contentType = bin.getContentType(); } else { - theHttpResponse.setContentType(Constants.CT_OCTET_STREAM); + contentType = Constants.CT_OCTET_STREAM; } - if (bin.getContent() == null || bin.getContent().length == 0) { - return; - } - // Force binary resources to download - This is a security measure to prevent // malicious images or HTML blocks being served up as content. - theHttpResponse.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;"); + restUtil.addHeader(Constants.HEADER_CONTENT_DISPOSITION, "Attachment;"); - theHttpResponse.setContentLength(bin.getContent().length); - ServletOutputStream oos = theHttpResponse.getOutputStream(); - oos.write(bin.getContent()); - oos.close(); - return; + return restUtil.sendAttachmentResponse(bin, stausCode, contentType); } // Ok, we're not serving a binary resource, so apply default encoding @@ -630,38 +613,36 @@ public class RestfulServerUtils { } if (theRequestIsBrowser && theServer.isUseBrowserFriendlyContentTypes()) { - theHttpResponse.setContentType(responseEncoding.getBrowserFriendlyBundleContentType()); + contentType = responseEncoding.getBrowserFriendlyBundleContentType(); } else if (encodingDomainResourceAsText) { - theHttpResponse.setContentType(Constants.CT_HTML); + contentType = Constants.CT_HTML; } else { - theHttpResponse.setContentType(responseEncoding.getResourceContentType()); + contentType = responseEncoding.getResourceContentType(); } - theHttpResponse.setCharacterEncoding(Constants.CHARSET_NAME_UTF8); - - theServer.addHeadersToResponse(theHttpResponse); + String charset = Constants.CHARSET_NAME_UTF8; if (theResource instanceof IResource) { InstantDt lastUpdated = ResourceMetadataKeyEnum.UPDATED.get((IResource) theResource); if (lastUpdated != null && lastUpdated.isEmpty() == false) { - theHttpResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue())); + restUtil.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated.getValue())); } TagList list = (TagList) ((IResource) theResource).getResourceMetadata().get(ResourceMetadataKeyEnum.TAG_LIST); if (list != null) { for (Tag tag : list) { if (StringUtils.isNotBlank(tag.getTerm())) { - theHttpResponse.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); + restUtil.addHeader(Constants.HEADER_CATEGORY, tag.toHeaderValue()); } } } } else { Date lastUpdated = ((IAnyResource) theResource).getMeta().getLastUpdated(); if (lastUpdated != null) { - theHttpResponse.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated)); + restUtil.addHeader(Constants.HEADER_LAST_MODIFIED, DateUtils.formatDate(lastUpdated)); } } - Writer writer = getWriter(theHttpResponse, theRespondGzip); + Writer writer = restUtil.getResponseWriter(stausCode, contentType, charset, respondGzip); try { if (encodingDomainResourceAsText && theResource instanceof IResource) { writer.append(((IResource) theResource).getText().getDiv().getValueAsString()); @@ -669,17 +650,19 @@ public class RestfulServerUtils { IParser parser = getNewParser(theServer.getFhirContext(), theRequestDetails); parser.encodeResourceToWriter(theResource, writer); } - } finally { - writer.close(); + } catch (Exception e) { + //always send a response, even if the parsing went wrong } + return restUtil.sendWriterResponse(stausCode, contentType, charset, writer); + } - static Integer tryToExtractNamedParameter(HttpServletRequest theRequest, String name) { - String countString = theRequest.getParameter(name); + static Integer tryToExtractNamedParameter(RequestDetails theRequest, String name) { + String[] countString = theRequest.getParameters().get(name); Integer count = null; - if (isNotBlank(countString)) { + if (countString != null && countString.length > 0 && isNotBlank(countString[0])) { try { - count = Integer.parseInt(countString); + count = Integer.parseInt(countString[0]); } catch (NumberFormatException e) { ourLog.debug("Failed to parse _count value '{}': {}", countString, e); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java index 432f57cf06a..195a77b62fe 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestulfulServerConfiguration.java @@ -1,67 +1,27 @@ package ca.uhn.fhir.rest.server; -import java.io.IOException; -import java.io.InputStream; -import java.util.LinkedList; +import java.util.Collection; import java.util.List; -import java.util.jar.Manifest; - -import javax.servlet.ServletContext; -import javax.servlet.http.HttpServletRequest; - -import org.apache.commons.io.IOUtils; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.method.BaseMethodBinding; public class RestulfulServerConfiguration { - private List resourceBindings; + private Collection resourceBindings; private List> serverBindings; private String implementationDescription; private String serverVersion; private String serverName; private FhirContext fhirContext; - private ServletContext servletContext; private IServerAddressStrategy serverAddressStrategy; private String conformanceDate; - public RestulfulServerConfiguration() { - } - - public RestulfulServerConfiguration(RestfulServer theRestfulServer) { - this.resourceBindings = new LinkedList(theRestfulServer.getResourceBindings()); - this.serverBindings = theRestfulServer.getServerBindings(); - this.implementationDescription = theRestfulServer.getImplementationDescription(); - this.serverVersion = theRestfulServer.getServerVersion(); - this.serverName = theRestfulServer.getServerName(); - this.fhirContext = theRestfulServer.getFhirContext(); - this.serverAddressStrategy= theRestfulServer.getServerAddressStrategy(); - this.servletContext = theRestfulServer.getServletContext(); - if (servletContext != null) { - InputStream inputStream = null; - try { - inputStream = getServletContext().getResourceAsStream("/META-INF/MANIFEST.MF"); - if (inputStream != null) { - Manifest manifest = new Manifest(inputStream); - this.conformanceDate = manifest.getMainAttributes().getValue("Build-Time"); - } - } catch (IOException e) { - // fall through - } - finally { - if (inputStream != null) { - IOUtils.closeQuietly(inputStream); - } - } - } - } - /** * Get the resourceBindings * @return the resourceBindings */ - public List getResourceBindings() { + public Collection getResourceBindings() { return resourceBindings; } @@ -69,7 +29,7 @@ public class RestulfulServerConfiguration { * Set the resourceBindings * @param resourceBindings the resourceBindings to set */ - public RestulfulServerConfiguration setResourceBindings(List resourceBindings) { + public RestulfulServerConfiguration setResourceBindings(Collection resourceBindings) { this.resourceBindings = resourceBindings; return this; } @@ -142,23 +102,6 @@ public class RestulfulServerConfiguration { return this; } - /** - * Get the servletContext - * @return the servletContext - */ - public ServletContext getServletContext() { - return servletContext; - } - - /** - * Set the servletContext - * @param servletContext the servletContext to set - */ - public RestulfulServerConfiguration setServletContext(ServletContext servletContext) { - this.servletContext = servletContext; - return this; - } - /** * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain providers should generally use this context if one is needed, as opposed to * creating their own. @@ -209,7 +152,4 @@ public class RestulfulServerConfiguration { this.conformanceDate = conformanceDate; } - public String getServerBaseForRequest(HttpServletRequest theRequest) { - return getServerAddressStrategy().determineServerBase(getServletContext(), theRequest); - } } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java index 5b469f01259..2fed137b4ea 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java @@ -36,7 +36,6 @@ import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.method.RequestDetails; -import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.OperationOutcomeUtil; @@ -71,7 +70,7 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter { } } - RestfulServerUtils.streamResponseAsResource(theRequestDetails.getServer(), theResponse, oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, false, false, theRequestDetails); + theRequestDetails.getResponse().streamResponseAsResource(oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, false, false); // theResponse.setStatus(statusCode); // theRequestDetails.getServer().addHeadersToResponse(theResponse); diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java index 5e0e495b556..67ce92734e6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/IServerInterceptor.java @@ -31,10 +31,6 @@ import org.hl7.fhir.instance.model.api.IIdType; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.model.api.TagList; -import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; @@ -148,6 +144,26 @@ public interface IServerInterceptor { * client. */ boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException; + + /** + * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client + * + * @param theRequestDetails + * A bean containing details about the request that is about to be processed, including + * @param theResponseObject + * The actual object which is being streamed to the client as a response + * @param theServletRequest + * The incoming request + * @param theServletResponse + * The response. Note that interceptors may choose to provide a response (i.e. by calling {@link HttpServletResponse#getWriter()}) but in that case it is important to return + * false to indicate that the server itself should not also provide a response. + * @return Return true if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the + * response normally, you must return false. In this case, no further processing will occur and no further interceptors will be called. + * @throws AuthenticationException + * This exception may be thrown to indicate that the interceptor has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401 will be returned to the + * client. + */ + boolean outgoingResponse(RequestDetails theRequest, Bundle bundle); /** * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client @@ -167,6 +183,20 @@ public interface IServerInterceptor { * client. */ boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException; + + /** + * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client + * + * @param theRequestDetails + * A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the + * request which have been pulled out of the {@link HttpServletRequest servlet request}. + * @return Return true if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the + * response normally, you must return false. In this case, no further processing will occur and no further interceptors will be called. + * @throws AuthenticationException + * This exception may be thrown to indicate that the interceptor has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401 will be returned to the + * client. + */ + boolean outgoingResponse(RequestDetails theRequestDetails); /** * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client @@ -189,6 +219,22 @@ public interface IServerInterceptor { */ boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException; + + /** + * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client + * + * @param theRequestDetails + * A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the + * request which have been pulled out of the {@link HttpServletRequest servlet request}. + * @param theResponseObject + * The actual object which is being streamed to the client as a response + * @return Return true if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the + * response normally, you must return false. In this case, no further processing will occur and no further interceptors will be called. + * @throws AuthenticationException + * This exception may be thrown to indicate that the interceptor has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401 will be returned to the + * client. + */ + boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject); /** * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client @@ -211,6 +257,22 @@ public interface IServerInterceptor { */ boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException; + /** + * This method is called after the server implementation method has been called, but before any attempt to stream the response back to the client + * + * @param theRequestDetails + * A bean containing details about the request that is about to be processed, including details such as the resource type and logical ID (if any) and other FHIR-specific aspects of the + * request which have been pulled out of the {@link HttpServletRequest servlet request}. + * @param theResponseObject + * The actual object which is being streamed to the client as a response + * @return Return true if processing should continue normally. This is generally the right thing to do. If your interceptor is providing a response rather than letting HAPI handle the + * response normally, you must return false. In this case, no further processing will occur and no further interceptors will be called. + * @throws AuthenticationException + * This exception may be thrown to indicate that the interceptor has detected an unauthorized access attempt. If thrown, processing will stop and an HTTP 401 will be returned to the + * client. + */ + boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject); + /** * This method is called upon any exception being thrown within the server's request processing code. This includes any exceptions thrown within resource provider methods (e.g. {@link Search} and * {@link Read} methods) as well as any runtime exceptions thrown by the server itself. This method is invoked for each interceptor (until one of them returns a non-null response or diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java index b3a21db983c..dbc9e4278b6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/InterceptorAdapter.java @@ -34,6 +34,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; /** * Base class for {@link IServerInterceptor} implementations. Provides a No-op implementation @@ -66,21 +67,45 @@ public class InterceptorAdapter implements IServerInterceptor { public boolean outgoingResponse(RequestDetails theRequestDetails, Bundle theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException { return true; } + + @Override + public boolean outgoingResponse(RequestDetails theRequestDetails, Bundle bundle) { + ServletRequestDetails details = (ServletRequestDetails) theRequestDetails; + return outgoingResponse(details, bundle, details.getServletRequest(), details.getServletResponse()); + } @Override public boolean outgoingResponse(RequestDetails theRequestDetails, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException { return true; } + + @Override + public boolean outgoingResponse(RequestDetails theRequestDetails) { + ServletRequestDetails details = (ServletRequestDetails) theRequestDetails; + return outgoingResponse(theRequestDetails, details.getServletRequest(), details.getServletResponse()); + } @Override public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException { return true; } + @Override + public boolean outgoingResponse(RequestDetails theRequestDetails, IBaseResource theResponseObject) { + ServletRequestDetails details = (ServletRequestDetails) theRequestDetails; + return outgoingResponse(details, theResponseObject, details.getServletRequest(), details.getServletResponse()); + } + @Override public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject, HttpServletRequest theServletRequest, HttpServletResponse theServletResponse) throws AuthenticationException { return true; } + + @Override + public boolean outgoingResponse(RequestDetails theRequestDetails, TagList theResponseObject) { + ServletRequestDetails details = (ServletRequestDetails) theRequestDetails; + return outgoingResponse(details, theResponseObject, details.getServletRequest(), details.getServletResponse()); + } @Override public BaseServerResponseException preProcessOutgoingException(RequestDetails theRequestDetails, Throwable theException, HttpServletRequest theServletRequest) throws ServletException { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java index 1f8de109994..7c5e135282f 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/LoggingInterceptor.java @@ -198,7 +198,7 @@ public class LoggingInterceptor extends InterceptorAdapter { } else if (theKey.startsWith("remoteAddr")) { return StringUtils.defaultString(myRequest.getRemoteAddr()); } else if (theKey.equals("responseEncodingNoDefault")) { - EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingNoDefault(myRequest, myRequestDetails.getServer().getDefaultResponseEncoding()); + EncodingEnum encoding = RestfulServerUtils.determineResponseEncodingNoDefault(myRequestDetails, myRequestDetails.getServer().getDefaultResponseEncoding()); if (encoding != null) { return encoding.name(); } else { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java index 390687d8ad9..864aa0c4d13 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlighterInterceptor.java @@ -171,7 +171,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { /* * It's not a browser... */ - Set highestRankedAcceptValues = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theRequestDetails.getServletRequest()); + Set highestRankedAcceptValues = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theServletRequest); if (highestRankedAcceptValues.contains(Constants.CT_HTML) == false) { return super.outgoingResponse(theRequestDetails, theResponseObject, theServletRequest, theServletResponse); } @@ -284,7 +284,7 @@ public class ResponseHighlighterInterceptor extends InterceptorAdapter { /* * It's not a browser... */ - Set accept = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theRequestDetails.getServletRequest()); + Set accept = RestfulServerUtils.parseAcceptHeaderAndReturnHighestRankedOptions(theServletRequest); if (!accept.contains(Constants.CT_HTML)) { return super.handleException(theRequestDetails, theException, theServletRequest, theServletResponse); } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java new file mode 100644 index 00000000000..f3723d9d21d --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRequestDetails.java @@ -0,0 +1,144 @@ +package ca.uhn.fhir.rest.server.servlet; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.Collections; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.io.IOUtils; + +import ca.uhn.fhir.context.ConfigurationException; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.method.BaseMethodBinding.IRequestReader; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.server.RestfulServer; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; + +public class ServletRequestDetails extends RequestDetails { + + private HttpServletRequest myServletRequest; + private HttpServletResponse myServletResponse; + /** + * @see BaseMethodBinding#loadRequestContents(RequestDetails) + */ + private static volatile IRequestReader ourRequestReader; + private byte[] requestContents; + private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServletRequestDetails.class); + private RestfulServer myServer; + + public ServletRequestDetails() { + super(); + setResponse(new ServletRestfulResponse(this)); + } + + public RestfulServer getServer() { + return myServer; + } + + public void setServer(RestfulServer theServer) { + this.myServer = theServer; + } + + + public static RequestDetails withResourceAndParams(String theResourceName, RequestTypeEnum theRequestType, Set theParamNames) { + RequestDetails retVal = new ServletRequestDetails(); + retVal.setResourceName(theResourceName); + retVal.setRequestType(theRequestType); + Map paramNames = new HashMap(); + for (String next : theParamNames) { + paramNames.put(next, new String[0]); + } + retVal.setParameters(paramNames); + return retVal; + } + + @Override + public String getHeader(String name) { + return getServletRequest().getHeader(name); + } + + @Override + public List getHeaders(String name) { + Enumeration headers = getServletRequest().getHeaders(name); + return headers == null ? Collections.emptyList() : Collections.list(getServletRequest().getHeaders(name)); + } + + public HttpServletRequest getServletRequest() { + return myServletRequest; + } + + public void setServletRequest(HttpServletRequest myServletRequest) { + this.myServletRequest = myServletRequest; + } + + public HttpServletResponse getServletResponse() { + return myServletResponse; + } + + public void setServletResponse(HttpServletResponse myServletResponse) { + this.myServletResponse = myServletResponse; + } + + @Override + public Reader getReader() throws IOException { + return getServletRequest().getReader(); + } + + @Override + public InputStream getInputStream() throws IOException { + return getServletRequest().getInputStream(); + } + + @Override + public String getServerBaseForRequest() { + return getServer().getServerBaseForRequest(getServletRequest()); + } + + protected byte[] getByteStreamRequestContents() { + /* + * This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on servlet-api in clients since there is no point. So we dynamically load a class + * that does the servlet processing in servers. Down the road it may make sense to just split the method binding classes into server and client versions, but this isn't actually a huge deal I + * don't think. + */ + IRequestReader reader = ourRequestReader; + if (reader == null) { + try { + Class.forName("javax.servlet.ServletInputStream"); + String className = BaseMethodBinding.class.getName() + "$" + "ActiveRequestReader"; + try { + reader = (IRequestReader) Class.forName(className).newInstance(); + } catch (Exception e1) { + throw new ConfigurationException("Failed to instantiate class " + className, e1); + } + } catch (ClassNotFoundException e) { + String className = BaseMethodBinding.class.getName() + "$" + "InactiveRequestReader"; + try { + reader = (IRequestReader) Class.forName(className).newInstance(); + } catch (Exception e1) { + throw new ConfigurationException("Failed to instantiate class " + className, e1); + } + } + ourRequestReader = reader; + } + + try { + InputStream inputStream = reader.getInputStream(this); + requestContents = IOUtils.toByteArray(inputStream); + return requestContents; + } + catch (IOException e) { + ourLog.error("Could not load request resource", e); + throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage())); + } + } + +} diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java new file mode 100644 index 00000000000..d19375d3142 --- /dev/null +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java @@ -0,0 +1,77 @@ +package ca.uhn.fhir.rest.server.servlet; + +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.util.Map.Entry; +import java.util.zip.GZIPOutputStream; + +import javax.servlet.ServletOutputStream; +import javax.servlet.http.HttpServletResponse; + +import org.hl7.fhir.instance.model.api.IBaseBinary; + +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.method.ParseAction; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.RestfulResponse; + +public class ServletRestfulResponse extends RestfulResponse { + + public ServletRestfulResponse(ServletRequestDetails servletRequestDetails) { + super(servletRequestDetails); + } + + @Override + public Object sendAttachmentResponse(IBaseBinary bin, int stausCode, String contentType) throws IOException { + addHeaders(); + HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse(); + theHttpResponse.setStatus(stausCode); + theHttpResponse.setContentType(contentType); + if (bin.getContent() == null || bin.getContent().length == 0) { + return null; + } else { + theHttpResponse.setContentLength(bin.getContent().length); + ServletOutputStream oos = theHttpResponse.getOutputStream(); + oos.write(bin.getContent()); + oos.close(); + return null; + } + } + + @Override + public Writer getResponseWriter(int statusCode, String contentType, String charset, boolean theRespondGzip) throws UnsupportedEncodingException, IOException { + addHeaders(); + HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse(); + theHttpResponse.setCharacterEncoding(charset); + theHttpResponse.setStatus(statusCode); + theHttpResponse.setContentType(contentType); + if (theRespondGzip) { + theHttpResponse.addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP); + return new OutputStreamWriter(new GZIPOutputStream(theHttpResponse.getOutputStream()), Constants.CHARSET_NAME_UTF8); + } else { + return theHttpResponse.getWriter(); + } + } + + + private void addHeaders() { + HttpServletResponse theHttpResponse = getRequestDetails().getServletResponse(); + getRequestDetails().getServer().addHeadersToResponse(theHttpResponse); + for (Entry header : getHeaders().entrySet()) { + theHttpResponse.setHeader(header.getKey(), header.getValue()); + } + } + + public final Object sendWriterResponse(int status, String contentType, String charset, Writer writer) throws IOException { + writer.close(); + return null; + } + + @Override + public Object returnResponse(ParseAction outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, + String resourceName) throws IOException { + return getRequestDetails().getServer().returnResponse(getRequestDetails(), outcome, operationStatus, allowPrefer, response, resourceName); + } +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java index ae17a41f5af..5c7e64b632e 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java @@ -23,6 +23,8 @@ import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.model.dstu2.resource.Conformance; import ca.uhn.fhir.rest.annotation.IdParam; +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.Constants; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; @@ -35,10 +37,10 @@ import ca.uhn.fhir.util.ReflectionUtil; * Conformance Rest Service * @author Peter Van Houte */ -@Produces(MediaType.APPLICATION_JSON) +@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public abstract class AbstractConformanceRestServer extends AbstractJaxRsRestServer implements IConformanceRestServer { - public static final String PATH = "/"; + public static final String PATH = "/"; private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractConformanceRestServer.class); private ResourceBinding myServerBinding = new ResourceBinding(); @@ -73,9 +75,8 @@ public abstract class AbstractConformanceRestServer extends AbstractJaxRsRestSer @GET @OPTIONS @Path("/metadata") - @Produces(MediaType.APPLICATION_JSON) public Response conformance(String string) { - String conformanceString = getParser().encodeResourceToString(myConformance); + String conformanceString = getParser(createRequestDetails(null, RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA)).encodeResourceToString(myConformance); ResponseBuilder entity = Response.status(Constants.STATUS_HTTP_200_OK).entity(conformanceString); entity.header("Access-Control-Allow-Origin", "*"); return entity.build(); diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java index c2f663d42c6..1bda6dbdc42 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java @@ -1,49 +1,40 @@ package ca.uhn.fhir.jaxrs.server; import java.util.HashMap; -import java.util.List; -import java.util.Map.Entry; -import javax.ws.rs.Produces; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; -import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.Response; import javax.ws.rs.core.UriInfo; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.server.AddProfileTagEnum; +import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; +import ca.uhn.fhir.rest.server.IServerAddressStrategy; +import ca.uhn.fhir.rest.server.RestfulServerUtils; /** * Abstract Jax Rs Rest Server - * @author axmpm + * @author Peter Van Houte * */ -@Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) -public abstract class AbstractJaxRsRestServer { +public abstract class AbstractJaxRsRestServer implements IRestfulServerDefaults { - private static Logger ourLog = LoggerFactory.getLogger(AbstractJaxRsRestServer.class); public static FhirContext CTX = FhirContext.forDstu2(); @Context - protected UriInfo info; + private UriInfo info; @Context - HttpHeaders headers; + private HttpHeaders headers; - private IParser jsonParser = getFhirContext().newJsonParser(); - private IParser xmlParser = getFhirContext().newXmlParser(); - private String baseUri; - - public static FhirContext getFhirContext() { + public FhirContext getFhirContext() { return CTX; } @@ -51,72 +42,57 @@ public abstract class AbstractJaxRsRestServer { * param and query methods */ protected HashMap getQueryMap() { - MultivaluedMap queryParameters = info.getQueryParameters(); + MultivaluedMap queryParameters = getInfo().getQueryParameters(); HashMap params = new HashMap(); for (String key : queryParameters.keySet()) { params.put(key, queryParameters.get(key).toArray(new String[] {})); } return params; } - - private String getParam(String string) { - for (Entry> entry : info.getQueryParameters().entrySet()) { - if (string.equalsIgnoreCase(entry.getKey())) { - return entry.getValue().iterator().next(); - } - } - return null; - } - - protected Integer getIntParam(String string) { - String param = getParam(string); - return param == null ? 0 : Integer.valueOf(param); - } - protected String getBaseUri() { - if(this.baseUri == null) { - this.baseUri = info.getBaseUri().toASCIIString(); - } - ourLog.debug("BaseUri is equal to %s", baseUri); - return this.baseUri; + public IServerAddressStrategy getServerAddressStrategy() { + HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy(); + addressStrategy.setValue(getBaseUri()); + return addressStrategy; } - /** + protected String getBaseUri() { + return getInfo().getBaseUri().toASCIIString(); + } + + /** * PARSING METHODS */ - public IParser getParser() { - IParser parser = MediaType.APPLICATION_XML.equals(getParserType()) ? xmlParser : jsonParser; - return parser.setPrettyPrint(getPrettyPrint()); + public IParser getParser(JaxRsRequestDetails theRequestDetails) { + return RestfulServerUtils.getNewParser(getFhirContext(), theRequestDetails); } - private boolean getPrettyPrint() { - String printPretty = getParam("_pretty"); - return printPretty == null || printPretty.trim().length() == 0 ? true : Boolean.valueOf(printPretty); + protected JaxRsRequestDetails createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { + JaxRsRequestDetails theRequest = new JaxRsRequestDetails(headers, resourceString); + theRequest.setFhirServerBase(getBaseUri()); + theRequest.setRestOperationType(restOperation); + theRequest.setServer(this); + theRequest.setParameters(getQueryMap()); + theRequest.setRequestType(requestType); + return theRequest; } + - protected String getParserType() { - if ((headers != null && headers.getMediaType() != null && headers.getMediaType().getSubtype() != null - && headers.getMediaType().getSubtype().contains("xml")) || getDefaultResponseEncoding() == EncodingEnum.XML - || "xml".equals(getParam("_format"))) { - return MediaType.APPLICATION_XML; - } else { - return MediaType.APPLICATION_JSON; - } - } + /** + * Get the info + * @return the info + */ + public UriInfo getInfo() { + return info; + } - Response createResponse(IBaseResource resource) { - Bundle resultingBundle = new Bundle(); - resultingBundle.addEntry().setResource((IResource) resource); - return ok(encodeResponse(resultingBundle)); - } - - protected Response ok(String entity) { - return Response.status(Constants.STATUS_HTTP_200_OK).header("Content-Type", getParserType()).entity(entity).build(); - } - - private String encodeResponse(Bundle resource) { - return resource == null ? "null" : getParser().encodeBundleToString(resource); - } + /** + * Set the info + * @param info the info to set + */ + public void setInfo(UriInfo info) { + this.info = info; + } /** * DEFAULT VALUES @@ -124,5 +100,24 @@ public abstract class AbstractJaxRsRestServer { public EncodingEnum getDefaultResponseEncoding() { return EncodingEnum.JSON; } + + @Override + public boolean isDefaultPrettyPrint() { + return true; + } + @Override + public ETagSupportEnum getETagSupport() { + return ETagSupportEnum.DISABLED; + } + + @Override + public AddProfileTagEnum getAddProfileTag() { + return AddProfileTagEnum.NEVER; + } + + @Override + public boolean isUseBrowserFriendlyContentTypes() { + return true; + } } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java index 913694a92e3..79f9decf717 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java @@ -1,10 +1,10 @@ package ca.uhn.fhir.jaxrs.server; -import java.lang.reflect.InvocationTargetException; -import java.util.List; -import java.util.Map; +import java.io.IOException; +import java.net.URL; import javax.interceptor.Interceptors; +import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; @@ -15,216 +15,166 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.hl7.fhir.instance.model.api.IBaseResource; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.jaxrs.server.interceptor.ExceptionInterceptor; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; import ca.uhn.fhir.jaxrs.server.util.MethodBindings; -import ca.uhn.fhir.jaxrs.server.util.RestfulServerDefaults; -import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RequestTypeEnum; -import ca.uhn.fhir.rest.method.CreateMethodBinding; -import ca.uhn.fhir.rest.method.OperationMethodBinding; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.RequestDetails; -import ca.uhn.fhir.rest.method.SearchMethodBinding; import ca.uhn.fhir.rest.server.Constants; -import ca.uhn.fhir.rest.server.IBundleProvider; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.IRestfulServerDefaults; -import ca.uhn.fhir.rest.server.IVersionSpecificBundleFactory; -import ca.uhn.fhir.rest.server.ResourceBinding; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.UrlUtil; /** * Fhir Physician Rest Service * @author axmpm * */ -@SuppressWarnings({ "unused"}) -@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML}) -public abstract class AbstractResourceRestServer extends AbstractJaxRsRestServer implements IResourceProvider { +@Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN}) +@Consumes({MediaType.APPLICATION_FORM_URLENCODED,MediaType.APPLICATION_JSON, "application/json+fhir", "application/xml+fhir"}) +public abstract class AbstractResourceRestServer extends AbstractJaxRsRestServer implements IResourceRestServer { + + private static MethodBindings bindings; - private static final Logger ourLog = LoggerFactory.getLogger(AbstractResourceRestServer.class); - - private ResourceBinding myServerBinding = new ResourceBinding(); - private IRestfulServerDefaults serverDefaults = new RestfulServerDefaults(); - private MethodBindings bindings = new MethodBindings(); - public AbstractResourceRestServer(Class subclass) { - bindings.findMethods(this, subclass, getFhirContext()); + initBindings(subclass); } - @GET - @Interceptors(ExceptionInterceptor.class) - Response search() throws Exception { - return execute(); - } + private void initBindings(Class subclass) { + if(bindings == null) { + MethodBindings methodBindings = new MethodBindings(); + methodBindings.findMethods(this, subclass, getFhirContext()); + bindings = methodBindings; + } + } - protected Response customOperation(final IBaseResource resource, RequestTypeEnum requestType) - throws Exception, IllegalAccessException, InvocationTargetException { - OperationMethodBinding method = bindings.getBinding(OperationMethodBinding.class); - final RequestDetails theRequest = createRequestDetails(resource, requestType); - final Object[] paramsServer = bindings.createParams(resource, method, theRequest); - Parameters result = (Parameters) method.getMethod().invoke(this, paramsServer); - return ok(getParser().encodeResourceToString(result)); - } - - Response create(final Map params, R resource) throws Exception { - CreateMethodBinding method = bindings.getBinding(CreateMethodBinding.class); - final RequestDetails theRequest = createRequestDetails(resource, null); - final Object[] paramsServer = bindings.createParams(resource, method, theRequest); - MethodOutcome result = (MethodOutcome) method.getMethod().invoke(this, paramsServer); - return createResponse(result.getResource()); + @Override + protected String getBaseUri() { + try { + return new URL(getInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); + } catch(Exception e) { + // cannot happen + return null; + } } @POST + @Override @Interceptors(ExceptionInterceptor.class) public Response create(final String resourceString) throws Exception { - return create(getQueryMap(), parseResource(resourceString)); + return executeMethod(resourceString, RequestTypeEnum.POST, RestOperationTypeEnum.CREATE, null); } @POST @Interceptors(ExceptionInterceptor.class) @Path("/_search") + @Override public Response searchWithPost() throws Exception { - return search(); + return executeMethod(null, RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE, null); } - + @GET - @Path("/{id}") - @Interceptors(ExceptionInterceptor.class) - public Response find(@PathParam("id") final String id) { - final R resource = find(new IdDt(id)); - return createSingleResponse(resource); - } - + @Override + @Interceptors(ExceptionInterceptor.class) + public Response search() throws Exception { + return executeMethod(null, RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE, null); + } + @PUT + @Override @Path("/{id}") @Interceptors(ExceptionInterceptor.class) public Response update(@PathParam("id") final String id, final String resourceString) throws Exception { - final R resource = parseResource(resourceString); -// final MethodOutcome update = update(new IdDt(resource.getId()), practitioner); -// return createResponse(update.getResource()); - return createResponse(resource); + return executeMethod(resourceString, RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE, id); } - + @DELETE + @Override @Path("/{id}") @Interceptors(ExceptionInterceptor.class) - public Response delete(@PathParam("id") final String id) + public Response delete(@PathParam("id") final String id) throws Exception { + return executeMethod(null, RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE, id); + } + + + @GET + @Override + @Path("/{id}") + @Interceptors(ExceptionInterceptor.class) + public Response find(@PathParam("id") final String id) throws Exception { + return executeMethod(null, RequestTypeEnum.GET, RestOperationTypeEnum.READ, id); + } + + protected Response customOperation(final String resource, RequestTypeEnum requestType, String id, String operationName, RestOperationTypeEnum operationType) throws Exception { -// final MethodOutcome delete = delete(new IdDt(id)); -// return createResponse(delete.getResource()); - return null; + return executeMethod(resource, requestType, operationType, id, bindings.getBinding(operationType, operationName)); } @GET + @Override @Path("/{id}/_history/{version}") @Interceptors(ExceptionInterceptor.class) - public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version) { - final IdDt dt = new IdDt(getBaseUri(), getResourceType().getSimpleName(), id, version); - final R resource = findHistory(dt); - return createSingleResponse(resource); + public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String versionString) + throws BaseServerResponseException, IOException { + BaseMethodBinding method = bindings.getBinding(RestOperationTypeEnum.VREAD); + final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); + if (id == null) { + throw new InvalidRequestException("Don't know how to handle request path: " + getInfo().getRequestUri().toASCIIString()); + } + theRequest.setId(new IdDt(getBaseUri(), id, UrlUtil.unescape(versionString))); + return (Response) method.invokeServer(this, theRequest); } - + @GET + @Override @Path("/{id}/{compartment}") @Interceptors(ExceptionInterceptor.class) - public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) { - final IdDt dt = new IdDt(getBaseUri(), getResourceType().getSimpleName(), id); - final R resource = find(new IdDt(id)); - return createResponse(resource); - } - - /** - * PARSING METHODS - */ - private Response createSingleResponse(final R resource) { - return ok(getParser().encodeResourceToString(resource)); - } - - Response createResponse(final IBaseResource resource) { - final Bundle resultingBundle = new Bundle(); - resultingBundle.addEntry().setResource((IResource) resource); - return ok(encodeToString(resultingBundle)); - } - - Response createResponse(final List resources) { - final Bundle resultingBundle = new Bundle(); - for (final R resource : resources) { - addBundleEntry(resultingBundle, resource); - } - return ok(encodeToString(resultingBundle)); - } - - protected Response ok(String entity) { - return Response.status(Constants.STATUS_HTTP_200_OK).header("Content-Type", getParserType()).entity(entity).build(); - } - - protected String encodeToString(final Bundle resource) { - return resource != null ? getParser().encodeBundleToString(resource) : "null"; - } - - private R parseResource(final String resource) { - return getParser().parseResource(getResourceType(), resource); - } - - @Deprecated - private void addBundleEntry(final Bundle resultingBundle, final R resource) { - final BundleEntry entry = resultingBundle.addEntry(); - entry.setResource(resource); - if (resource != null && resource.getId() != null) { - entry.setId(resource.getId()); + public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) throws BaseServerResponseException, IOException { + BaseMethodBinding method = bindings.getBinding(RestOperationTypeEnum.SEARCH_TYPE, compartment); + final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); + if (id == null) { + throw new InvalidRequestException("Don't know how to handle request path: " + getInfo().getRequestUri().toASCIIString()); } + theRequest.setCompartmentName(compartment); + theRequest.setId(new IdDt(getBaseUri(), id)); + return (Response) method.invokeServer(this, theRequest); } + private > Response executeMethod(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id) + throws BaseServerResponseException, IOException { + BaseMethodBinding method = bindings.getBinding(restOperation); + return executeMethod(resourceString, requestType, restOperation, id, method); + } + + private Response executeMethod(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id, + BaseMethodBinding method) + throws IOException { + final RequestDetails theRequest = createRequestDetails(resourceString, requestType, restOperation, id); + return (Response) method.invokeServer(this, theRequest); + } - private RequestDetails createRequestDetails(final IBaseResource resource, RequestTypeEnum requestType) { - final RequestDetails theRequest = new RequestDetails() { -// @Override -// public String getHeader(String headerIfNoneExist) { -// List requestHeader = headers.getRequestHeader(headerIfNoneExist); -// return (requestHeader == null || requestHeader.size() == 0) ? null : requestHeader.get(0); -// } - }; - theRequest.setFhirServerBase(getBaseUri()); -// theRequest.setServer(this); - theRequest.setParameters(getQueryMap()); -// theRequest.setRequestContent(resource); - theRequest.setRequestType(requestType); + + protected JaxRsRequestDetails createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id) { + JaxRsRequestDetails theRequest = super.createRequestDetails(resourceString, requestType, restOperation); + theRequest.setId(StringUtils.isBlank(id) ? null : new IdDt(getResourceType().getName(), UrlUtil.unescape(id))); + if(restOperation == RestOperationTypeEnum.UPDATE) { + String contentLocation = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION); + if (contentLocation != null) { + theRequest.setId(new IdDt(contentLocation)); + } + } return theRequest; } - public Response execute() { - SearchMethodBinding method = bindings.getBinding(SearchMethodBinding.class); - final RequestDetails theRequest = createRequestDetails(null, null); - final Object[] paramsServer = bindings.createParams(null, method, theRequest); - Object result = null; //method.invokeServer(null, paramsServer); - final IBundleProvider bundle = (IBundleProvider) result; - IVersionSpecificBundleFactory bundleFactory = getFhirContext().newBundleFactory(); -// bundleFactory.initializeBundleFromBundleProvider(this, bundle, EncodingEnum.JSON, info.getAbsolutePath().toASCIIString(), -// info.getAbsolutePath().toASCIIString(), getPrettyPrint(), getIntParam("_getpagesoffset"), getIntParam("_count"), null, -// BundleTypeEnum.SEARCHSET, Collections.emptySet()); - IBaseResource resource = bundleFactory.getResourceBundle(); - return ok(getParser().encodeResourceToString(resource)); - } - - public R find(final IdDt theId) { - throw new UnsupportedOperationException(); - } - - public R findHistory(final IdDt theId) { - throw new UnsupportedOperationException(); - } - - @Override + @Override public abstract Class getResourceType(); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java index db1b2af5ee6..61d08098113 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java @@ -1,7 +1,8 @@ package ca.uhn.fhir.jaxrs.server; import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.IRestfulServerDefaults; -public interface IConformanceRestServer extends IResourceProvider { +public interface IConformanceRestServer extends IResourceProvider, IRestfulServerDefaults { } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java new file mode 100644 index 00000000000..0b8001c0395 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.jaxrs.server; + +import java.io.IOException; + +import javax.ws.rs.core.Response; + +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; + +public interface IResourceRestServer extends IRestfulServer, IResourceProvider { + + Response search() + throws Exception; + + Response create(String resourceString) + throws Exception; + + Response searchWithPost() + throws Exception; + + Response find(String id) throws Exception; + + Response update(String id, String resourceString) + throws Exception; + + Response delete(String id) + throws Exception; + + Response findHistory(String id, String version) throws BaseServerResponseException, IOException; + + Response findCompartment(String id, String compartment) throws BaseServerResponseException, IOException; + + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java new file mode 100644 index 00000000000..19bad6d12cc --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java @@ -0,0 +1,78 @@ +package ca.uhn.fhir.jaxrs.server; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.OutputStreamWriter; +import java.io.StringWriter; +import java.io.UnsupportedEncodingException; +import java.io.Writer; +import java.util.Map.Entry; +import java.util.zip.GZIPOutputStream; + +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.Response.ResponseBuilder; + +import org.hl7.fhir.instance.model.api.IBaseBinary; + +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.method.ParseAction; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.RestfulResponse; +import ca.uhn.fhir.rest.server.RestfulServerUtils; + +public class JaxRsRestfulResponse extends RestfulResponse { + + public JaxRsRestfulResponse(String resourceString, JaxRsRequestDetails jaxRsRequestDetails) { + super(jaxRsRequestDetails); + } + + @Override + public Writer getResponseWriter(int statusCode, String contentType, String charset, boolean respondGzip) + throws UnsupportedEncodingException, IOException { + if (respondGzip) { + addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP); + return new OutputStreamWriter(new GZIPOutputStream(new ByteArrayOutputStream()), Constants.CHARSET_NAME_UTF8); + } else { + return new StringWriter(); + } + } + + @Override + public Response sendWriterResponse(int status, String contentType, String charset, Writer writer) { + return Response.status(status)/*.header(HttpHeaders.CONTENT_TYPE, charset)*/.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(writer.toString()).build(); + } + + @Override + public Object sendAttachmentResponse(IBaseBinary bin, int statusCode, String contentType) throws IOException { + ResponseBuilder response = Response.status(statusCode); + for (Entry header : getHeaders().entrySet()) { + response.header(header.getKey(), header.getValue()); + } + if (bin.getContent() != null && bin.getContent().length > 0) { + response.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(bin.getContent()); + } + return response.build(); + } + + @Override + public Object returnResponse(ParseAction outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, + String resourceName) + throws IOException { + Writer writer = new StringWriter(); + IParser parser = RestfulServerUtils.getNewParser(getRequestDetails().getServer().getFhirContext(), getRequestDetails()); + if(outcome != null) { + outcome.execute(parser, writer); + } + return Response.status(operationStatus).header(Constants.HEADER_CONTENT_TYPE, getParserType()).entity(writer.toString()).build(); + } + + protected String getParserType() { + EncodingEnum encodingEnum = RestfulServerUtils.determineResponseEncodingWithDefault(getRequestDetails()); + return encodingEnum == EncodingEnum.JSON ? MediaType.APPLICATION_JSON : MediaType.APPLICATION_XML; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java index d8fead97eda..73428c0dcee 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java @@ -36,7 +36,7 @@ public class ExceptionInterceptor { @Context private HttpHeaders headers; - FhirContext fhirContext = AbstractJaxRsRestServer.getFhirContext(); + FhirContext fhirContext = AbstractJaxRsRestServer.CTX; @AroundInvoke public Object intercept(final InvocationContext ctx) throws Exception { diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java new file mode 100644 index 00000000000..9aa9998791e --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java @@ -0,0 +1,68 @@ +package ca.uhn.fhir.jaxrs.server.util; + +import java.io.IOException; +import java.io.InputStream; +import java.io.Reader; +import java.util.Collections; +import java.util.List; + +import javax.ws.rs.core.HttpHeaders; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsRestServer; +import ca.uhn.fhir.jaxrs.server.JaxRsRestfulResponse; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.param.ResourceParameter; + +public class JaxRsRequestDetails extends RequestDetails { + + private String theResourceString; + private HttpHeaders headers; + private AbstractJaxRsRestServer myServer; + + public AbstractJaxRsRestServer getServer() { + return myServer; + } + + public void setServer(AbstractJaxRsRestServer theServer) { + this.myServer = theServer; + } + + public JaxRsRequestDetails(HttpHeaders headers, String resourceString) { + this.headers = headers; + this.theResourceString = resourceString; + setResponse(new JaxRsRestfulResponse(resourceString, this)); + } + + @Override + public String getHeader(String headerIfNoneExist) { + List requestHeader = headers.getRequestHeader(headerIfNoneExist); + return (requestHeader == null || requestHeader.size() == 0) ? null : requestHeader.get(0); + } + + @Override + public List getHeaders(String name) { + List requestHeader = headers.getRequestHeader(name); + return requestHeader == null ? Collections. emptyList() : requestHeader; + } + + @Override + public String getServerBaseForRequest() { + return getServer().getServerAddressStrategy().determineServerBase(null, null); + } + + @Override + protected byte[] getByteStreamRequestContents() { + return theResourceString.getBytes(ResourceParameter.determineRequestCharset(this)); + } + + @Override + public Reader getReader() + throws IOException { + throw new UnsupportedOperationException(); + } + + @Override + public InputStream getInputStream() { + throw new UnsupportedOperationException(); + } +} \ No newline at end of file diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java index a536459b2fc..7716919130b 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java @@ -3,77 +3,66 @@ package ca.uhn.fhir.jaxrs.server.util; import java.lang.reflect.Method; import java.util.concurrent.ConcurrentHashMap; -import org.hl7.fhir.instance.model.api.IBaseResource; +import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jaxrs.server.AbstractResourceRestServer; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.BaseMethodBinding; -import ca.uhn.fhir.rest.method.CreateMethodBinding; -import ca.uhn.fhir.rest.method.DeleteMethodBinding; -import ca.uhn.fhir.rest.method.IParameter; import ca.uhn.fhir.rest.method.OperationMethodBinding; -import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.SearchMethodBinding; -import ca.uhn.fhir.rest.method.UpdateMethodBinding; -import ca.uhn.fhir.rest.param.ResourceParameter; import ca.uhn.fhir.util.ReflectionUtil; -@SuppressWarnings({"unchecked", "rawtypes"}) public class MethodBindings { - - /** BaseOutcomeReturningMethodBinding */ - private ConcurrentHashMap createMethods = new ConcurrentHashMap(); - private ConcurrentHashMap updateMethods = new ConcurrentHashMap(); - private ConcurrentHashMap delete = new ConcurrentHashMap(); - /** BaseResourceReturingMethodBinding */ - private ConcurrentHashMap searchMethods = new ConcurrentHashMap(); - private ConcurrentHashMap operationMethods = new ConcurrentHashMap(); + /** ALL METHOD BINDINGS */ + ConcurrentHashMap>> allBindings = new ConcurrentHashMap>>(); public > void findMethods(T theProvider, Class subclass, FhirContext fhirContext) { for (final Method m : ReflectionUtil.getDeclaredMethods(subclass)) { final BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, fhirContext, theProvider); - if(foundMethodBinding == null) { + if (foundMethodBinding == null) { continue; } - ConcurrentHashMap map = getMap(foundMethodBinding.getClass()); - if (map.contains(theProvider.getResourceType().getName())) { - throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + foundMethodBinding.getMethod() + " -- " - + foundMethodBinding.getMethod()); + ConcurrentHashMap> map = getAllBindingsMap(foundMethodBinding.getRestOperationType()); + if (foundMethodBinding instanceof OperationMethodBinding) { + OperationMethodBinding binding = (OperationMethodBinding) foundMethodBinding; + putIfAbsent(map, binding.getName(), binding); + } else if (foundMethodBinding instanceof SearchMethodBinding) { + Search search = m.getAnnotation(Search.class); + String compartmentName = StringUtils.defaultIfBlank(search.compartmentName(), ""); + putIfAbsent(map, compartmentName, foundMethodBinding); } else { - map.put(theProvider.getResourceType().getName(), foundMethodBinding); + putIfAbsent(map, "", foundMethodBinding); } } } + + private void putIfAbsent(ConcurrentHashMap> map, String key, BaseMethodBinding binding) { + if (map.containsKey(key)) { + throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + map.get(key) + " -- " + binding.getMethod()); + } + map.put(key, binding); + } + + private ConcurrentHashMap> getAllBindingsMap(final RestOperationTypeEnum restOperationTypeEnum) { + allBindings.putIfAbsent(restOperationTypeEnum, new ConcurrentHashMap>()); + return allBindings.get(restOperationTypeEnum); + } + + public BaseMethodBinding getBinding(RestOperationTypeEnum operationType, String qualifier) { + String nonEmptyQualifier = StringUtils.defaultIfBlank(qualifier, ""); + ConcurrentHashMap> map = getAllBindingsMap(operationType); + if(!map.containsKey(nonEmptyQualifier)) { + throw new UnsupportedOperationException(); + } else { + return map.get(nonEmptyQualifier); + } + } - private ConcurrentHashMap getMap(Class class1) { - if(class1.isAssignableFrom(CreateMethodBinding.class)) return (ConcurrentHashMap) createMethods; - if(class1.isAssignableFrom(UpdateMethodBinding.class)) return (ConcurrentHashMap) updateMethods; - if(class1.isAssignableFrom(DeleteMethodBinding.class)) return (ConcurrentHashMap) delete; - if(class1.isAssignableFrom(SearchMethodBinding.class)) return (ConcurrentHashMap) searchMethods; - if(class1.isAssignableFrom(OperationMethodBinding.class)) return (ConcurrentHashMap) operationMethods; - return new ConcurrentHashMap(); - } - - public Object[] createParams(IBaseResource resource, final BaseMethodBinding method, final RequestDetails theRequest) { - final Object[] paramsServer = new Object[method.getParameters().size()]; - for (int i = 0; i < method.getParameters().size(); i++) { - final IParameter param = method.getParameters().get(i); - if(param instanceof ResourceParameter) { - paramsServer[i] = resource; - } else { - paramsServer[i] = param.translateQueryParametersIntoServerArgument(theRequest, null, method); - } - } - return paramsServer; - } - - public T getBinding(Class clazz) { - ConcurrentHashMap map = getMap((Class) clazz); - if(map.values().size() == 0) { - throw new UnsupportedOperationException(); - } - return (T) map.values().iterator().next(); + public BaseMethodBinding getBinding(RestOperationTypeEnum operationType) { + return getBinding(operationType, ""); } } diff --git a/hapi-fhir-jaxrsserver-example/pom.xml b/hapi-fhir-jaxrsserver-example/pom.xml index d7cdf79a677..1a98bb79cb0 100644 --- a/hapi-fhir-jaxrsserver-example/pom.xml +++ b/hapi-fhir-jaxrsserver-example/pom.xml @@ -13,7 +13,7 @@ hapi-fhir-jaxrsserver-example war - HAPI FHIR JPA Server - Example + HAPI FHIR JAX-RS Server - Example diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java index 6a185ca92fd..44e1fac3d90 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java @@ -8,6 +8,8 @@ import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import ca.uhn.fhir.rest.server.Constants; + /** * Conformance Rest Service * @author Peter Van Houte @@ -15,7 +17,7 @@ import javax.ws.rs.core.MediaType; @Local @Path(ConformanceRestServer.PATH) @Stateless -@Produces(MediaType.APPLICATION_JSON) +@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) public class ConformanceRestServer extends ca.uhn.fhir.jaxrs.server.AbstractConformanceRestServer { private static final String SERVER_VERSION = "1.0.0"; diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java index 43486a088ad..8c88816b2a4 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientRestServer.java @@ -1,5 +1,7 @@ package ca.uhn.fhir.jaxrs.server.example; +import java.util.ArrayList; +import java.util.Collections; import java.util.LinkedList; import java.util.List; import java.util.concurrent.ConcurrentHashMap; @@ -10,12 +12,15 @@ import javax.interceptor.Interceptors; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; +import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import ca.uhn.fhir.jaxrs.server.AbstractResourceRestServer; import ca.uhn.fhir.jaxrs.server.interceptor.ExceptionInterceptor; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu2.resource.Condition; import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; @@ -33,8 +38,16 @@ import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.AddProfileTagEnum; +import ca.uhn.fhir.rest.server.BundleInclusionRule; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.ETagSupportEnum; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.IPagingProvider; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; /** * Fhir Physician Rest Service @@ -44,7 +57,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; @Local(IFhirPatientRestServer.class) @Path(FhirPatientRestServer.PATH) @Stateless -@Produces(MediaType.APPLICATION_JSON) +@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) public class FhirPatientRestServer extends AbstractResourceRestServer implements IFhirPatientRestServer { static final String PATH = "/Patient"; @@ -57,8 +70,8 @@ public class FhirPatientRestServer extends AbstractResourceRestServer i } static { - patients.put(""+counter, createPatient("Agfa")); - patients.put(""+(counter), createPatient("Healthcare")); + patients.put(""+counter, createPatient("Van Houte")); + patients.put(""+(counter), createPatient("Agnew")); for(int i = 0 ; i<20 ; i++) { patients.put(""+(counter), createPatient("Random Patient " + counter)); } @@ -135,7 +148,7 @@ public class FhirPatientRestServer extends AbstractResourceRestServer i } @Override - @Read(version = false) + @Read(version = true) public Patient findHistory(@IdParam final IdDt theId) { if (patients.containsKey(theId.getIdPart())) { final List list = patients.get(theId.getIdPart()); @@ -174,18 +187,28 @@ public class FhirPatientRestServer extends AbstractResourceRestServer i @Path("/{id}/$last") @Interceptors(ExceptionInterceptor.class) @Override - public Response operationLastGet(final String resource) + public Response operationLastGet(@PathParam("id") String id) throws Exception { - return customOperation(null, RequestTypeEnum.GET); + return customOperation(null, RequestTypeEnum.GET, id, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); } + @Search(compartmentName="Condition") + @Override + public List searchCompartment(@IdParam IdDt thePatientId) { + List retVal=new ArrayList(); + Condition condition = new Condition(); + condition.setId(new IdDt("665577")); + retVal.add(condition); + return retVal; + } + @POST @Path("/{id}/$last") @Interceptors(ExceptionInterceptor.class) @Override public Response operationLast(final String resource) throws Exception { - return customOperation(getParser().parseResource(resource), RequestTypeEnum.POST); + return customOperation(resource, RequestTypeEnum.POST, null, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); } // @ca.uhn.fhir.rest.annotation.Validate @@ -213,10 +236,46 @@ public class FhirPatientRestServer extends AbstractResourceRestServer i .setValue(new StringDt((counter-1)+"" + "inputVariable [ " + dummyInput.getValue()+ "]")); return parameters; } - + + @Override + public Class getResourceType() { + return Patient.class; + } + + /** THE DEFAULTS */ @Override - public Class getResourceType() { - return Patient.class; + public List getInterceptors() { + return Collections.emptyList(); + } + + @Override + public ETagSupportEnum getETagSupport() { + return ETagSupportEnum.DISABLED; + } + + @Override + public boolean isDefaultPrettyPrint() { + return true; + } + + @Override + public AddProfileTagEnum getAddProfileTag() { + return AddProfileTagEnum.NEVER; + } + + @Override + public boolean isUseBrowserFriendlyContentTypes() { + return true; + } + + @Override + public IPagingProvider getPagingProvider() { + return new FifoMemoryPagingProvider(10); + } + + @Override + public BundleInclusionRule getBundleInclusionRule() { + return BundleInclusionRule.BASED_ON_INCLUDES; } } diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java index 269d7af1391..0603fb4016c 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IFhirPatientRestServer.java @@ -4,15 +4,16 @@ import java.util.List; import javax.ws.rs.core.Response; +import ca.uhn.fhir.jaxrs.server.IResourceRestServer; +import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.StringDt; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.server.IResourceProvider; -public interface IFhirPatientRestServer extends IResourceProvider { +public interface IFhirPatientRestServer extends IResourceRestServer { List search(StringParam name); @@ -29,11 +30,14 @@ public interface IFhirPatientRestServer extends IResourceProvider { MethodOutcome delete(IdDt theId); Response operationLastGet(String resource) - throws Exception; + throws Exception; Response operationLast(String resource) throws Exception; Parameters last(StringDt dummyInput); + + List searchCompartment(IdDt thePatientId); + } diff --git a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java index 1e265b19554..60d60eebf36 100644 --- a/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java +++ b/hapi-fhir-structures-dstu/src/main/java/ca/uhn/fhir/rest/server/provider/ServerConformanceProvider.java @@ -1,27 +1,5 @@ package ca.uhn.fhir.rest.server.provider; -/* - * #%L - * HAPI FHIR Structures - DSTU1 (FHIR v0.80) - * %% - * Copyright (C) 2014 - 2015 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.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -29,8 +7,8 @@ import java.util.HashSet; import java.util.List; import java.util.Set; import java.util.TreeSet; -import java.util.jar.Manifest; +import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; @@ -82,12 +60,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider systemOps = new HashSet(); - List bindings = new ArrayList(myRestfulServer.getResourceBindings()); + List bindings = new ArrayList(myServerConfiguration.getResourceBindings()); Collections.sort(bindings, new Comparator() { @Override public int compare(ResourceBinding theArg0, ResourceBinding theArg1) { @@ -155,9 +133,11 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes = new TreeSet(); @@ -219,7 +199,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes, DynamicSearchMethodBinding searchMethodBinding) { includes.addAll(searchMethodBinding.getIncludes()); @@ -352,7 +317,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider nextTarget : nextParameter.getDeclaredTypes()) { - RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget); + RuntimeResourceDefinition targetDef = myServerConfiguration.getFhirContext().getResourceDefinition(nextTarget); if (targetDef != null) { ResourceTypeEnum code = ResourceTypeEnum.VALUESET_BINDER.fromCodeString(targetDef.getName()); if (code != null) { diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java index 9aa92193469..c6914cc2e51 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/InterceptorTest.java @@ -62,10 +62,10 @@ public class InterceptorTest { public void testInterceptorFires() throws Exception { when(myInterceptor1.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); when(myInterceptor1.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor1.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true); when(myInterceptor2.incomingRequestPreProcessed(any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); when(myInterceptor2.incomingRequestPostProcessed(any(RequestDetails.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); - when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class))).thenReturn(true); + when(myInterceptor2.outgoingResponse(any(RequestDetails.class), any(IResource.class))).thenReturn(true); HttpGet httpGet = new HttpGet("http://localhost:" + ourPort + "/Patient/1"); HttpResponse status = ourClient.execute(httpGet); @@ -79,8 +79,8 @@ public class InterceptorTest { order.verify(myInterceptor1, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class)); order.verify(myInterceptor2, times(1)).incomingRequestPreHandled(any(RestOperationTypeEnum.class), any(ActionRequestDetails.class)); - order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); - order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class), any(HttpServletRequest.class), any(HttpServletResponse.class)); + order.verify(myInterceptor2, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class)); + order.verify(myInterceptor1, times(1)).outgoingResponse(any(RequestDetails.class), any(IResource.class)); verifyNoMoreInteractions(myInterceptor1); verifyNoMoreInteractions(myInterceptor2); } diff --git a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java index 36b51ec23f3..021e923ef4b 100644 --- a/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java +++ b/hapi-fhir-structures-dstu/src/test/java/ca/uhn/fhir/rest/server/ResourceMethodTest.java @@ -21,6 +21,7 @@ import ca.uhn.fhir.rest.method.IParameter; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.method.SearchMethodBinding; import ca.uhn.fhir.rest.method.SearchParameter; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; public class ResourceMethodTest { @@ -51,7 +52,7 @@ public class ResourceMethodTest { inputParams.add("lastName"); inputParams.add("mrn"); - RequestDetails params = RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams); + RequestDetails params = ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams); boolean actual = rm.incomingServerRequestMatchesMethod(params); assertTrue( actual); // True } @@ -72,7 +73,7 @@ public class ResourceMethodTest { inputParams.add("mrn"); inputParams.add("foo"); - assertEquals(false, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False + assertEquals(false, rm.incomingServerRequestMatchesMethod(ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False } @Test @@ -89,7 +90,7 @@ public class ResourceMethodTest { inputParams.add("firstName"); inputParams.add("mrn"); - assertEquals(true, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True + assertEquals(true, rm.incomingServerRequestMatchesMethod(ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True } @Test @@ -106,7 +107,7 @@ public class ResourceMethodTest { inputParams.add("firstName"); inputParams.add("lastName"); - assertEquals(false, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False + assertEquals(false, rm.incomingServerRequestMatchesMethod(ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // False } @Test @@ -121,7 +122,7 @@ public class ResourceMethodTest { Set inputParams = new HashSet(); inputParams.add("mrn"); - assertEquals(true, rm.incomingServerRequestMatchesMethod(RequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True + assertEquals(true, rm.incomingServerRequestMatchesMethod(ServletRequestDetails.withResourceAndParams("Patient", RequestTypeEnum.GET, inputParams))); // True } @Test(expected=IllegalStateException.class) diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java index c59329cd265..686cead9059 100644 --- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java +++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java @@ -19,8 +19,7 @@ package ca.uhn.fhir.rest.server.provider.dstu2; * limitations under the License. * #L% */ - -import static org.apache.commons.lang3.StringUtils.*; +import static org.apache.commons.lang3.StringUtils.isNotBlank; import java.util.ArrayList; import java.util.Collections; @@ -59,8 +58,8 @@ import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; 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.IVersionSpecificBundleFactory; -import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServerUtils; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.ResourceReferenceInfo; @@ -324,7 +323,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { } @Override - public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, + public void initializeBundleFromBundleProvider(IRestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes) { myBase = theServerBase; diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java index 18c2a0c17eb..d9f625a4bcf 100644 --- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java +++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java @@ -34,6 +34,7 @@ import java.util.Set; import java.util.TreeMap; import java.util.TreeSet; +import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; import org.apache.commons.lang3.StringUtils; @@ -97,7 +98,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider systemOps, BaseMethodBinding nextMethodBinding) { @@ -204,7 +205,9 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes = new TreeSet(); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java index e7c144394e8..ce8168b1513 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/interceptor/ResponseHighlightingInterceptorTest.java @@ -61,6 +61,7 @@ import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails; import ca.uhn.fhir.util.PortUtil; public class ResponseHighlightingInterceptorTest { @@ -122,7 +123,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); reqDetails.setParameters(new HashMap()); reqDetails.setServer(new RestfulServer()); @@ -157,7 +158,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); HashMap params = new HashMap(); params.put(Constants.PARAM_PRETTY, new String[] { Constants.PARAM_PRETTY_VALUE_TRUE }); @@ -192,7 +193,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); HashMap params = new HashMap(); params.put(Constants.PARAM_PRETTY, new String[] { Constants.PARAM_PRETTY_VALUE_TRUE }); @@ -226,7 +227,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); reqDetails.setParameters(new HashMap()); reqDetails.setServer(new RestfulServer()); @@ -264,7 +265,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); reqDetails.setParameters(new HashMap()); RestfulServer server = new RestfulServer(); @@ -299,7 +300,7 @@ public class ResponseHighlightingInterceptorTest { Patient resource = new Patient(); resource.addName().addFamily("FAMILY"); - RequestDetails reqDetails = new RequestDetails(); + ServletRequestDetails reqDetails = new ServletRequestDetails(); reqDetails.setRequestType(RequestTypeEnum.GET); reqDetails.setParameters(new HashMap()); RestfulServer server = new RestfulServer(); diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java index ca942ad5f64..595b715249c 100644 --- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java +++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2hl7org/Dstu2Hl7OrgBundleFactory.java @@ -57,6 +57,7 @@ import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.EncodingEnum; 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.IVersionSpecificBundleFactory; import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServerUtils; @@ -297,7 +298,7 @@ public class Dstu2Hl7OrgBundleFactory implements IVersionSpecificBundleFactory { } @Override - public void initializeBundleFromBundleProvider(RestfulServer theServer, IBundleProvider theResult, + public void initializeBundleFromBundleProvider(IRestfulServer theServer, IBundleProvider theResult, EncodingEnum theResponseEncoding, String theServerBase, String theCompleteUrl, boolean thePrettyPrint, int theOffset, Integer theLimit, String theSearchId, BundleTypeEnum theBundleType, Set theIncludes) { myBase = theServerBase; From 1afaea81d6f2efe7d01d7839feaa5b3d39d93ef3 Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Thu, 8 Oct 2015 09:08:22 +0200 Subject: [PATCH 03/13] rename jax-rs classes + add unit tests to example project --- .../fhir/rest/server/RestfulServerUtil.java | 262 ------------------ hapi-fhir-jaxrsserver-base/pom.xml | 4 +- ... => AbstractJaxRsConformanceProvider.java} | 6 +- ...Server.java => AbstractJaxRsProvider.java} | 2 +- ...ava => AbstractJaxRsResourceProvider.java} | 22 +- ...er.java => IJaxRsConformanceProvider.java} | 2 +- ...erver.java => IJaxRsResourceProvider.java} | 2 +- ...estfulResponse.java => JaxRsResponse.java} | 4 +- .../fhir/jaxrs/server/StaticJaxRsServer.java | 6 +- ...or.java => JaxRsExceptionInterceptor.java} | 10 +- .../uhn/fhir/jaxrs/server/util/GZipUtil.java | 39 --- .../server/util/JaxRsRequestDetails.java | 12 +- .../jaxrs/server/util/MethodBindings.java | 4 +- .../server/util/RestfulServerDefaults.java | 38 --- hapi-fhir-jaxrsserver-example/pom.xml | 43 ++- .../server/example/ConformanceRestServer.java | 43 --- ...Server.java => IJaxRsPatientProvider.java} | 6 +- .../example/JaxRsConformanceProvider.java | 43 +++ ...ver.java => JaxRsPatientRestProvider.java} | 20 +- .../example/JaxRsPatientProviderTest.java | 119 +++++--- .../example/RandomServerPortProvider.java | 36 +++ pom.xml | 2 + 22 files changed, 245 insertions(+), 480 deletions(-) delete mode 100644 hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java rename hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/{AbstractConformanceRestServer.java => AbstractJaxRsConformanceProvider.java} (95%) rename hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/{AbstractJaxRsRestServer.java => AbstractJaxRsProvider.java} (97%) rename hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/{AbstractResourceRestServer.java => AbstractJaxRsResourceProvider.java} (90%) rename hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/{IConformanceRestServer.java => IJaxRsConformanceProvider.java} (60%) rename hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/{IResourceRestServer.java => IJaxRsResourceProvider.java} (90%) rename hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/{JaxRsRestfulResponse.java => JaxRsResponse.java} (94%) rename hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/{ExceptionInterceptor.java => JaxRsExceptionInterceptor.java} (96%) delete mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/GZipUtil.java delete mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/RestfulServerDefaults.java delete mode 100644 hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/ConformanceRestServer.java rename hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/{IFhirPatientRestServer.java => IJaxRsPatientProvider.java} (83%) create mode 100644 hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java rename hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/{FhirPatientRestServer.java => JaxRsPatientRestProvider.java} (93%) rename hapi-fhir-jaxrsserver-base/src/test/java/DemoTest.java => hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java (74%) create mode 100644 hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java deleted file mode 100644 index d86c5ab8a3f..00000000000 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtil.java +++ /dev/null @@ -1,262 +0,0 @@ -package ca.uhn.fhir.rest.server; - -import static org.apache.commons.lang3.StringUtils.isBlank; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.io.ByteArrayInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.nio.charset.Charset; -import java.util.ArrayList; -import java.util.Enumeration; - -import org.apache.commons.io.IOUtils; -import org.apache.http.entity.ContentType; -import org.hl7.fhir.instance.model.api.IBaseBinary; -import org.hl7.fhir.instance.model.api.IBaseResource; - -import ca.uhn.fhir.context.ConfigurationException; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.FhirVersionEnum; -import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.model.api.BundleEntry; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.api.ResourceMetadataKeyEnum; -import ca.uhn.fhir.model.api.TagList; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.rest.api.RestOperationTypeEnum; -import ca.uhn.fhir.rest.method.BaseMethodBinding; -import ca.uhn.fhir.rest.method.MethodUtil; -import ca.uhn.fhir.rest.method.RequestDetails; -import ca.uhn.fhir.rest.method.BaseMethodBinding.IRequestReader; -import ca.uhn.fhir.rest.param.ResourceParameter; -import ca.uhn.fhir.rest.param.ResourceParameter.Mode; -import ca.uhn.fhir.rest.param.TransactionParameter.ParamStyle; -import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; - -public class RestfulServerUtil implements IRestfulServerUtil { - - /** - * @see BaseMethodBinding#loadRequestContents(RequestDetails) - */ - private static volatile IRequestReader ourRequestReader; - private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestfulServerUtil.class); - private byte[] requestContents; - - @Override - public Object getResourceParameter(RequestDetails theRequest, Mode myMode, BaseMethodBinding theMethodBinding, - Class myResourceType) { - switch (myMode) { - case BODY: - try { - return IOUtils.toString(createRequestReader(theRequest)); - } catch (IOException e) { - // Shouldn't happen since we're reading from a byte array - throw new InternalErrorException("Failed to load request"); - } - case ENCODING: - return RestfulServerUtils.determineRequestEncoding(theRequest); - case RESOURCE: - default: - return parseResourceFromRequest(theRequest, theMethodBinding, myResourceType); - } - } - - - protected byte[] loadRequestContents(RequestDetails theRequest) { - if(requestContents != null) { - return requestContents; - } - /* - * This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on servlet-api in clients since there is no point. So we dynamically load a class - * that does the servlet processing in servers. Down the road it may make sense to just split the method binding classes into server and client versions, but this isn't actually a huge deal I - * don't think. - */ - IRequestReader reader = ourRequestReader; - if (reader == null) { - try { - Class.forName("javax.servlet.ServletInputStream"); - String className = BaseMethodBinding.class.getName() + "$" + "ActiveRequestReader"; - try { - reader = (IRequestReader) Class.forName(className).newInstance(); - } catch (Exception e1) { - throw new ConfigurationException("Failed to instantiate class " + className, e1); - } - } catch (ClassNotFoundException e) { - String className = BaseMethodBinding.class.getName() + "$" + "InactiveRequestReader"; - try { - reader = (IRequestReader) Class.forName(className).newInstance(); - } catch (Exception e1) { - throw new ConfigurationException("Failed to instantiate class " + className, e1); - } - } - ourRequestReader = reader; - } - - try { - InputStream inputStream = reader.getInputStream(theRequest); - requestContents = IOUtils.toByteArray(inputStream); - return requestContents; - } - catch (IOException e) { - ourLog.error("Could not load request resource", e); - throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage())); - } - - - } - - - public Reader createRequestReader(RequestDetails theRequest, Charset charset) { - Reader requestReader = new InputStreamReader(new ByteArrayInputStream(loadRequestContents(theRequest)), charset); - return requestReader; - } - - public Reader createRequestReader(RequestDetails theRequest) throws IOException { - return createRequestReader(theRequest, determineRequestCharset(theRequest)); - } - - public static Charset determineRequestCharset(RequestDetails theRequest) { - String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); - - Charset charset = null; - if (isNotBlank(ct)) { - ContentType parsedCt = ContentType.parse(ct); - charset = parsedCt.getCharset(); - } - if (charset == null) { - charset = Charset.forName("UTF-8"); - } - return charset; - } - - @SuppressWarnings("unchecked") - @Override - public T loadResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType) { - FhirContext ctx = theRequest.getServer().getFhirContext(); - - final Charset charset = determineRequestCharset(theRequest); - Reader requestReader = createRequestReader(theRequest, charset); - - RestOperationTypeEnum restOperationType = theMethodBinding != null ? theMethodBinding.getRestOperationType() : null; - - EncodingEnum encoding = RestfulServerUtils.determineRequestEncodingNoDefault(theRequest); - if (encoding == null) { - String ctValue = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); - if (ctValue != null) { - if (ctValue.startsWith("application/x-www-form-urlencoded")) { - String msg = theRequest.getServer().getFhirContext().getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, theMethodBinding.getRestOperationType()); - throw new InvalidRequestException(msg); - } - } - if (isBlank(ctValue)) { - /* - * If the client didn't send a content type, try to guess - */ - String body; - try { - body = IOUtils.toString(requestReader); - } catch (IOException e) { - // This shouldn't happen since we're reading from a byte array.. - throw new InternalErrorException(e); - } - encoding = MethodUtil.detectEncodingNoDefault(body); - if (encoding == null) { - String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "noContentTypeInRequest", restOperationType); - throw new InvalidRequestException(msg); - } else { - requestReader = new InputStreamReader(new ByteArrayInputStream(loadRequestContents(theRequest)), charset); - } - } else { - String msg = ctx.getLocalizer().getMessage(ResourceParameter.class, "invalidContentTypeInRequest", ctValue, restOperationType); - throw new InvalidRequestException(msg); - } - } - - IParser parser = encoding.newParser(ctx); - - T retVal; - if (theResourceType != null) { - retVal = parser.parseResource(theResourceType, requestReader); - } else { - retVal = (T) parser.parseResource(requestReader); - } - - if (theRequest.getId() != null && theRequest.getId().hasIdPart()) { - retVal.setId(theRequest.getId()); - } - - if (theRequest.getServer().getFhirContext().getVersion().getVersion().equals(FhirVersionEnum.DSTU1)) { - TagList tagList = new TagList(); - for (Enumeration enumeration = theRequest.getServletRequest().getHeaders(Constants.HEADER_CATEGORY); enumeration.hasMoreElements();) { - String nextTagComplete = enumeration.nextElement(); - MethodUtil.parseTagValue(tagList, nextTagComplete); - } - if (tagList.isEmpty() == false) { - ((IResource) retVal).getResourceMetadata().put(ResourceMetadataKeyEnum.TAG_LIST, tagList); - } - } - return retVal; - } - - @Override - public IBaseResource parseResourceFromRequest(RequestDetails theRequest, BaseMethodBinding theMethodBinding, Class theResourceType) { - IBaseResource retVal; - if (IBaseBinary.class.isAssignableFrom(theResourceType)) { - FhirContext ctx = theRequest.getServer().getFhirContext(); - String ct = theRequest.getServletRequest().getHeader(Constants.HEADER_CONTENT_TYPE); - IBaseBinary binary = (IBaseBinary) ctx.getResourceDefinition("Binary").newInstance(); - binary.setContentType(ct); - binary.setContent(loadRequestContents(theRequest)); - - retVal = binary; - } else { - retVal = loadResourceFromRequest(theRequest, theMethodBinding, theResourceType); - } - return retVal; - } - - - @Override - public Object getRequestResource(RequestDetails theRequest, ParamStyle myParamStyle, Class myResourceBundleType) { - // TODO: don't use a default encoding, just fail! - EncodingEnum encoding = RestfulServerUtils.determineRequestEncoding(theRequest); - - IParser parser = encoding.newParser(theRequest.getServer().getFhirContext()); - - Reader reader; - try { - reader = createRequestReader(theRequest); - } - catch (IOException e) { - ourLog.error("Could not load request resource", e); - throw new InvalidRequestException(String.format("Could not load request resource: %s", e.getMessage())); - } - - switch (myParamStyle) { - case DSTU1_BUNDLE: { - Bundle bundle; - bundle = parser.parseBundle(reader); - return bundle; - } - case RESOURCE_LIST: { - Bundle bundle = parser.parseBundle(reader); - ArrayList resourceList = new ArrayList(); - for (BundleEntry next : bundle.getEntries()) { - if (next.getResource() != null) { - resourceList.add(next.getResource()); - } - } - return resourceList; - } - case RESOURCE_BUNDLE: - return parser.parseResource(myResourceBundleType, reader); - } - - throw new IllegalStateException("Unknown type: " + myParamStyle); // should not happen - } - -} diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 604d408c0c2..77ce6caa3d9 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -129,8 +129,8 @@ javax.ws.rs - jsr311-api - 1.1.1 + javax.ws.rs-api + 2.0 javax.ejb diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java similarity index 95% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java index 5c7e64b632e..e4d0a313dba 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractConformanceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java @@ -38,10 +38,10 @@ import ca.uhn.fhir.util.ReflectionUtil; * @author Peter Van Houte */ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) -public abstract class AbstractConformanceRestServer extends AbstractJaxRsRestServer implements IConformanceRestServer { +public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProvider implements IJaxRsConformanceProvider { public static final String PATH = "/"; - private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractConformanceRestServer.class); + private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractJaxRsConformanceProvider.class); private ResourceBinding myServerBinding = new ResourceBinding(); private ConcurrentHashMap myResourceNameToBinding = new ConcurrentHashMap(); @@ -49,7 +49,7 @@ public abstract class AbstractConformanceRestServer extends AbstractJaxRsRestSer private Conformance myConformance; - protected AbstractConformanceRestServer(String implementationDescription, String serverName, String serverVersion) { + protected AbstractJaxRsConformanceProvider(String implementationDescription, String serverName, String serverVersion) { serverConfiguration.setFhirContext(getFhirContext()); serverConfiguration.setImplementationDescription(implementationDescription); serverConfiguration.setServerName(serverName); diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java similarity index 97% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index 1bda6dbdc42..dc8aa0736c4 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -25,7 +25,7 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils; * @author Peter Van Houte * */ -public abstract class AbstractJaxRsRestServer implements IRestfulServerDefaults { +public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { public static FhirContext CTX = FhirContext.forDstu2(); diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java similarity index 90% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java index 79f9decf717..ddf5811e31a 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractResourceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java @@ -17,7 +17,7 @@ import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; -import ca.uhn.fhir.jaxrs.server.interceptor.ExceptionInterceptor; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; import ca.uhn.fhir.jaxrs.server.util.MethodBindings; import ca.uhn.fhir.model.api.IResource; @@ -38,11 +38,11 @@ import ca.uhn.fhir.util.UrlUtil; */ @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN}) @Consumes({MediaType.APPLICATION_FORM_URLENCODED,MediaType.APPLICATION_JSON, "application/json+fhir", "application/xml+fhir"}) -public abstract class AbstractResourceRestServer extends AbstractJaxRsRestServer implements IResourceRestServer { +public abstract class AbstractJaxRsResourceProvider extends AbstractJaxRsProvider implements IJaxRsResourceProvider { private static MethodBindings bindings; - public AbstractResourceRestServer(Class subclass) { + public AbstractJaxRsResourceProvider(Class subclass) { initBindings(subclass); } @@ -66,14 +66,14 @@ public abstract class AbstractResourceRestServer extends Ab @POST @Override - @Interceptors(ExceptionInterceptor.class) + @Interceptors(JaxRsExceptionInterceptor.class) public Response create(final String resourceString) throws Exception { return executeMethod(resourceString, RequestTypeEnum.POST, RestOperationTypeEnum.CREATE, null); } @POST - @Interceptors(ExceptionInterceptor.class) + @Interceptors(JaxRsExceptionInterceptor.class) @Path("/_search") @Override public Response searchWithPost() throws Exception { @@ -82,7 +82,7 @@ public abstract class AbstractResourceRestServer extends Ab @GET @Override - @Interceptors(ExceptionInterceptor.class) + @Interceptors(JaxRsExceptionInterceptor.class) public Response search() throws Exception { return executeMethod(null, RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE, null); } @@ -90,7 +90,7 @@ public abstract class AbstractResourceRestServer extends Ab @PUT @Override @Path("/{id}") - @Interceptors(ExceptionInterceptor.class) + @Interceptors(JaxRsExceptionInterceptor.class) public Response update(@PathParam("id") final String id, final String resourceString) throws Exception { return executeMethod(resourceString, RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE, id); @@ -99,7 +99,7 @@ public abstract class AbstractResourceRestServer extends Ab @DELETE @Override @Path("/{id}") - @Interceptors(ExceptionInterceptor.class) + @Interceptors(JaxRsExceptionInterceptor.class) public Response delete(@PathParam("id") final String id) throws Exception { return executeMethod(null, RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE, id); } @@ -108,7 +108,7 @@ public abstract class AbstractResourceRestServer extends Ab @GET @Override @Path("/{id}") - @Interceptors(ExceptionInterceptor.class) + @Interceptors(JaxRsExceptionInterceptor.class) public Response find(@PathParam("id") final String id) throws Exception { return executeMethod(null, RequestTypeEnum.GET, RestOperationTypeEnum.READ, id); } @@ -121,7 +121,7 @@ public abstract class AbstractResourceRestServer extends Ab @GET @Override @Path("/{id}/_history/{version}") - @Interceptors(ExceptionInterceptor.class) + @Interceptors(JaxRsExceptionInterceptor.class) public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String versionString) throws BaseServerResponseException, IOException { BaseMethodBinding method = bindings.getBinding(RestOperationTypeEnum.VREAD); @@ -136,7 +136,7 @@ public abstract class AbstractResourceRestServer extends Ab @GET @Override @Path("/{id}/{compartment}") - @Interceptors(ExceptionInterceptor.class) + @Interceptors(JaxRsExceptionInterceptor.class) public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) throws BaseServerResponseException, IOException { BaseMethodBinding method = bindings.getBinding(RestOperationTypeEnum.SEARCH_TYPE, compartment); final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsConformanceProvider.java similarity index 60% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsConformanceProvider.java index 61d08098113..54bf5f8fa2f 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IConformanceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsConformanceProvider.java @@ -3,6 +3,6 @@ package ca.uhn.fhir.jaxrs.server; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IRestfulServerDefaults; -public interface IConformanceRestServer extends IResourceProvider, IRestfulServerDefaults { +public interface IJaxRsConformanceProvider extends IResourceProvider, IRestfulServerDefaults { } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java similarity index 90% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java index 0b8001c0395..1b9ce87eb49 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IResourceRestServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java @@ -9,7 +9,7 @@ import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; -public interface IResourceRestServer extends IRestfulServer, IResourceProvider { +public interface IJaxRsResourceProvider extends IRestfulServer, IResourceProvider { Response search() throws Exception; diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsResponse.java similarity index 94% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsResponse.java index 19bad6d12cc..e1437408d21 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsRestfulResponse.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsResponse.java @@ -24,9 +24,9 @@ import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.RestfulResponse; import ca.uhn.fhir.rest.server.RestfulServerUtils; -public class JaxRsRestfulResponse extends RestfulResponse { +public class JaxRsResponse extends RestfulResponse { - public JaxRsRestfulResponse(String resourceString, JaxRsRequestDetails jaxRsRequestDetails) { + public JaxRsResponse(String resourceString, JaxRsRequestDetails jaxRsRequestDetails) { super(jaxRsRequestDetails); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java index 7f257611829..14c95b52bc0 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java @@ -11,13 +11,13 @@ import javax.ws.rs.core.Response; import org.slf4j.LoggerFactory; -import ca.uhn.fhir.jaxrs.server.interceptor.ExceptionInterceptor; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.model.api.Bundle; import ca.uhn.fhir.rest.annotation.Transaction; import ca.uhn.fhir.rest.annotation.TransactionParam; /** - * Conformance Rest Service + * Rest Service for static requests such as * @author Peter Van Houte */ @Local @@ -31,7 +31,7 @@ public class StaticJaxRsServer { @POST @Path("/") - @Interceptors(ExceptionInterceptor.class) + @Interceptors(JaxRsExceptionInterceptor.class) public Response transaction(final String resource) { ourLog.debug("calling transaction method"); return null; diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java similarity index 96% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java index 73428c0dcee..55e70a4ca95 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/ExceptionInterceptor.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java @@ -20,15 +20,15 @@ import org.slf4j.LoggerFactory; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jaxrs.server.AbstractJaxRsRestServer; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.OperationOutcomeUtil; -public class ExceptionInterceptor { +public class JaxRsExceptionInterceptor { - private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(ExceptionInterceptor.class); + private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(JaxRsExceptionInterceptor.class); private Class[] myReturnStackTracesForExceptionTypes; @Context @@ -36,7 +36,7 @@ public class ExceptionInterceptor { @Context private HttpHeaders headers; - FhirContext fhirContext = AbstractJaxRsRestServer.CTX; + FhirContext fhirContext = AbstractJaxRsProvider.CTX; @AroundInvoke public Object intercept(final InvocationContext ctx) throws Exception { @@ -161,7 +161,7 @@ public class ExceptionInterceptor { * The exception types for which to return the stack trace to the user. * @return Returns an instance of this interceptor, to allow for easy method chaining. */ - public ExceptionInterceptor setReturnStackTracesForExceptionTypes(final Class... theExceptionTypes) { + public JaxRsExceptionInterceptor setReturnStackTracesForExceptionTypes(final Class... theExceptionTypes) { myReturnStackTracesForExceptionTypes = theExceptionTypes; return this; } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/GZipUtil.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/GZipUtil.java deleted file mode 100644 index 6b1dac7b7fd..00000000000 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/GZipUtil.java +++ /dev/null @@ -1,39 +0,0 @@ -package ca.uhn.fhir.jaxrs.server.util; - -import java.io.ByteArrayInputStream; -import java.io.ByteArrayOutputStream; -import java.io.IOException; -import java.util.zip.GZIPInputStream; -import java.util.zip.GZIPOutputStream; - -import org.apache.commons.io.IOUtils; - -import ca.uhn.fhir.parser.DataFormatException; - -public class GZipUtil { - - public static String decompress(byte[] theResource) { - GZIPInputStream is; - try { - is = new GZIPInputStream(new ByteArrayInputStream(theResource)); - return IOUtils.toString(is, "UTF-8"); - } catch (IOException e) { - throw new DataFormatException("Failed to decompress contents", e); - } - } - - public static byte[] compress(String theEncoded) { - try { - ByteArrayOutputStream os = new ByteArrayOutputStream(); - GZIPOutputStream gos = new GZIPOutputStream(os); - IOUtils.write(theEncoded, gos, "UTF-8"); - gos.close(); - os.close(); - byte[] retVal = os.toByteArray(); - return retVal; - } catch (IOException e) { - throw new DataFormatException("Compress contents", e); - } - } - -} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java index 9aa9998791e..e32bc31f897 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java @@ -8,8 +8,8 @@ import java.util.List; import javax.ws.rs.core.HttpHeaders; -import ca.uhn.fhir.jaxrs.server.AbstractJaxRsRestServer; -import ca.uhn.fhir.jaxrs.server.JaxRsRestfulResponse; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; +import ca.uhn.fhir.jaxrs.server.JaxRsResponse; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.param.ResourceParameter; @@ -17,20 +17,20 @@ public class JaxRsRequestDetails extends RequestDetails { private String theResourceString; private HttpHeaders headers; - private AbstractJaxRsRestServer myServer; + private AbstractJaxRsProvider myServer; - public AbstractJaxRsRestServer getServer() { + public AbstractJaxRsProvider getServer() { return myServer; } - public void setServer(AbstractJaxRsRestServer theServer) { + public void setServer(AbstractJaxRsProvider theServer) { this.myServer = theServer; } public JaxRsRequestDetails(HttpHeaders headers, String resourceString) { this.headers = headers; this.theResourceString = resourceString; - setResponse(new JaxRsRestfulResponse(resourceString, this)); + setResponse(new JaxRsResponse(resourceString, this)); } @Override diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java index 7716919130b..ae1bd873eba 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java @@ -6,7 +6,7 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jaxrs.server.AbstractResourceRestServer; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.BaseMethodBinding; @@ -19,7 +19,7 @@ public class MethodBindings { /** ALL METHOD BINDINGS */ ConcurrentHashMap>> allBindings = new ConcurrentHashMap>>(); - public > void findMethods(T theProvider, Class subclass, FhirContext fhirContext) { + public > void findMethods(T theProvider, Class subclass, FhirContext fhirContext) { for (final Method m : ReflectionUtil.getDeclaredMethods(subclass)) { final BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, fhirContext, theProvider); if (foundMethodBinding == null) { diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/RestfulServerDefaults.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/RestfulServerDefaults.java deleted file mode 100644 index f2d10e3f2b1..00000000000 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/RestfulServerDefaults.java +++ /dev/null @@ -1,38 +0,0 @@ -package ca.uhn.fhir.jaxrs.server.util; - -import ca.uhn.fhir.rest.server.AddProfileTagEnum; -import ca.uhn.fhir.rest.server.BundleInclusionRule; -import ca.uhn.fhir.rest.server.ETagSupportEnum; -import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; -import ca.uhn.fhir.rest.server.IPagingProvider; -import ca.uhn.fhir.rest.server.IRestfulServerDefaults; - -public class RestfulServerDefaults implements IRestfulServerDefaults { - - public boolean isUseBrowserFriendlyContentTypes() { - return true; - } - - public ETagSupportEnum getETagSupport() { - return ETagSupportEnum.DISABLED; - } - - public AddProfileTagEnum getAddProfileTag() { - return AddProfileTagEnum.NEVER; - } - - public boolean isDefaultPrettyPrint() { - return true; - } - - public IPagingProvider getPagingProvider() { - //Integer count = getIntParam("_count"); - Integer count = 0; - return count == 0 ? null : new FifoMemoryPagingProvider(count); - } - - public BundleInclusionRule getBundleInclusionRule() { - return BundleInclusionRule.BASED_ON_INCLUDES; - } - -} diff --git a/hapi-fhir-jaxrsserver-example/pom.xml b/hapi-fhir-jaxrsserver-example/pom.xml index 1a98bb79cb0..c269234f32b 100644 --- a/hapi-fhir-jaxrsserver-example/pom.xml +++ b/hapi-fhir-jaxrsserver-example/pom.xml @@ -1,4 +1,4 @@ - +4.0.0 ca.uhn.hapi.fhir hapi-fhir-structures-dstu2 1.3-SNAPSHOT - - ca.uhn.hapi.fhir - hapi-fhir-structures-hl7org-dstu2 - 1.3-SNAPSHOT - - - ca.uhn.hapi.fhir - hapi-fhir-validation-resources-dstu2 - 1.3-SNAPSHOT - - - - org.slf4j - jcl-over-slf4j - - - - ch.qos.logback - logback-classic - test - - - org.thymeleaf - thymeleaf - test - - - - com.phloc - phloc-schematron - test - - - com.phloc - phloc-commons - test - - - - - javax.ws.rs javax.ws.rs-api 2.0 + provided javax.ejb ejb-api 3.0 + provided - - - - org.eclipse.jetty jetty-server + ${jetty_version} test org.eclipse.jetty - jetty-util + jetty-servlet + ${jetty_version} test - org.eclipse.jetty - jetty-webapp - test - - - - - - - - diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index dc8aa0736c4..8974f4f60bc 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -8,7 +8,7 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriInfo; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; @@ -30,18 +30,19 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { public static FhirContext CTX = FhirContext.forDstu2(); @Context - private UriInfo info; + private UriInfo theUriInfo; @Context - private HttpHeaders headers; + private HttpHeaders theHeaders; - public FhirContext getFhirContext() { + @Override + public FhirContext getFhirContext() { return CTX; } /** * param and query methods */ - protected HashMap getQueryMap() { + public HashMap getQueryMap() { MultivaluedMap queryParameters = getInfo().getQueryParameters(); HashMap params = new HashMap(); for (String key : queryParameters.keySet()) { @@ -56,48 +57,42 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { return addressStrategy; } - protected String getBaseUri() { + public String getBaseUri() { return getInfo().getBaseUri().toASCIIString(); } /** * PARSING METHODS */ - public IParser getParser(JaxRsRequestDetails theRequestDetails) { + public IParser getParser(JaxRsRequest theRequestDetails) { return RestfulServerUtils.getNewParser(getFhirContext(), theRequestDetails); } - protected JaxRsRequestDetails createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { - JaxRsRequestDetails theRequest = new JaxRsRequestDetails(headers, resourceString); - theRequest.setFhirServerBase(getBaseUri()); - theRequest.setRestOperationType(restOperation); - theRequest.setServer(this); - theRequest.setParameters(getQueryMap()); - theRequest.setRequestType(requestType); - return theRequest; + protected JaxRsRequest createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { + return new JaxRsRequest(this, resourceString, requestType, restOperation); } - /** * Get the info * @return the info */ public UriInfo getInfo() { - return info; + return theUriInfo; } - + /** - * Set the info - * @param info the info to set + * Get the headers + * @return the headers */ - public void setInfo(UriInfo info) { - this.info = info; - } + public HttpHeaders getHeaders() { + return theHeaders; + } /** * DEFAULT VALUES */ - public EncodingEnum getDefaultResponseEncoding() { + @Override + public EncodingEnum getDefaultResponseEncoding() { return EncodingEnum.JSON; } @@ -119,5 +114,6 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { @Override public boolean isUseBrowserFriendlyContentTypes() { return true; - } + } + } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java index ddf5811e31a..93543dafee6 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java @@ -2,6 +2,8 @@ package ca.uhn.fhir.jaxrs.server; import java.io.IOException; import java.net.URL; +import java.util.Collections; +import java.util.List; import javax.interceptor.Interceptors; import javax.ws.rs.Consumes; @@ -18,7 +20,7 @@ import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; -import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.jaxrs.server.util.MethodBindings; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.primitive.IdDt; @@ -26,9 +28,12 @@ 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.method.RequestDetails; +import ca.uhn.fhir.rest.server.BundleInclusionRule; import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IPagingProvider; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.util.UrlUtil; /** @@ -40,22 +45,18 @@ import ca.uhn.fhir.util.UrlUtil; @Consumes({MediaType.APPLICATION_FORM_URLENCODED,MediaType.APPLICATION_JSON, "application/json+fhir", "application/xml+fhir"}) public abstract class AbstractJaxRsResourceProvider extends AbstractJaxRsProvider implements IJaxRsResourceProvider { - private static MethodBindings bindings; + private final MethodBindings bindings; + + protected AbstractJaxRsResourceProvider() { + bindings = MethodBindings.getMethodBindings(this, getClass()); + } - public AbstractJaxRsResourceProvider(Class subclass) { - initBindings(subclass); + protected AbstractJaxRsResourceProvider(Class subclass) { + bindings = MethodBindings.getMethodBindings(this, subclass); } - private void initBindings(Class subclass) { - if(bindings == null) { - MethodBindings methodBindings = new MethodBindings(); - methodBindings.findMethods(this, subclass, getFhirContext()); - bindings = methodBindings; - } - } - @Override - protected String getBaseUri() { + public String getBaseUri() { try { return new URL(getInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); } catch(Exception e) { @@ -115,7 +116,7 @@ public abstract class AbstractJaxRsResourceProvider extends protected Response customOperation(final String resource, RequestTypeEnum requestType, String id, String operationName, RestOperationTypeEnum operationType) throws Exception { - return executeMethod(resource, requestType, operationType, id, bindings.getBinding(operationType, operationName)); + return executeMethod(resource, requestType, operationType, id, getBindings().getBinding(operationType, operationName)); } @GET @@ -124,7 +125,7 @@ public abstract class AbstractJaxRsResourceProvider extends @Interceptors(JaxRsExceptionInterceptor.class) public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String versionString) throws BaseServerResponseException, IOException { - BaseMethodBinding method = bindings.getBinding(RestOperationTypeEnum.VREAD); + BaseMethodBinding method = getBindings().getBinding(RestOperationTypeEnum.VREAD); final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); if (id == null) { throw new InvalidRequestException("Don't know how to handle request path: " + getInfo().getRequestUri().toASCIIString()); @@ -138,7 +139,7 @@ public abstract class AbstractJaxRsResourceProvider extends @Path("/{id}/{compartment}") @Interceptors(JaxRsExceptionInterceptor.class) public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) throws BaseServerResponseException, IOException { - BaseMethodBinding method = bindings.getBinding(RestOperationTypeEnum.SEARCH_TYPE, compartment); + BaseMethodBinding method = getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE, compartment); final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); if (id == null) { throw new InvalidRequestException("Don't know how to handle request path: " + getInfo().getRequestUri().toASCIIString()); @@ -150,7 +151,7 @@ public abstract class AbstractJaxRsResourceProvider extends private > Response executeMethod(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id) throws BaseServerResponseException, IOException { - BaseMethodBinding method = bindings.getBinding(restOperation); + BaseMethodBinding method = getBindings().getBinding(restOperation); return executeMethod(resourceString, requestType, restOperation, id, method); } @@ -162,8 +163,8 @@ public abstract class AbstractJaxRsResourceProvider extends } - protected JaxRsRequestDetails createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id) { - JaxRsRequestDetails theRequest = super.createRequestDetails(resourceString, requestType, restOperation); + protected JaxRsRequest createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id) { + JaxRsRequest theRequest = super.createRequestDetails(resourceString, requestType, restOperation); theRequest.setId(StringUtils.isBlank(id) ? null : new IdDt(getResourceType().getName(), UrlUtil.unescape(id))); if(restOperation == RestOperationTypeEnum.UPDATE) { String contentLocation = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION); @@ -173,8 +174,27 @@ public abstract class AbstractJaxRsResourceProvider extends } return theRequest; } + + @Override + public List getInterceptors() { + return Collections.emptyList(); + } + + @Override + public IPagingProvider getPagingProvider() { + return null; + } + + @Override + public BundleInclusionRule getBundleInclusionRule() { + return BundleInclusionRule.BASED_ON_INCLUDES; + } @Override public abstract Class getResourceType(); - + + public MethodBindings getBindings() { + return bindings; + } + } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java index 1b9ce87eb49..21ac39a784f 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java @@ -4,12 +4,12 @@ import java.io.IOException; import javax.ws.rs.core.Response; -import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; -public interface IJaxRsResourceProvider extends IRestfulServer, IResourceProvider { +public interface IJaxRsResourceProvider extends IRestfulServer, IResourceProvider { Response search() throws Exception; diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java similarity index 57% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java index e32bc31f897..3bbf2b1833a 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestDetails.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java @@ -9,17 +9,34 @@ import java.util.List; import javax.ws.rs.core.HttpHeaders; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; -import ca.uhn.fhir.jaxrs.server.JaxRsResponse; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.param.ResourceParameter; +import ca.uhn.fhir.rest.server.IRestfulResponse; -public class JaxRsRequestDetails extends RequestDetails { +public class JaxRsRequest extends RequestDetails { private String theResourceString; private HttpHeaders headers; private AbstractJaxRsProvider myServer; - public AbstractJaxRsProvider getServer() { + public JaxRsRequest() { + } + + public JaxRsRequest(AbstractJaxRsProvider server, String resourceString, + RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { + this.headers = server.getHeaders(); + this.theResourceString = resourceString; + this.setRestOperationType(restOperation); + setServer(server); + setFhirServerBase(server.getBaseUri()); + setParameters(server.getQueryMap()); + setRequestType(requestType); + } + + @Override + public AbstractJaxRsProvider getServer() { return myServer; } @@ -27,16 +44,10 @@ public class JaxRsRequestDetails extends RequestDetails { this.myServer = theServer; } - public JaxRsRequestDetails(HttpHeaders headers, String resourceString) { - this.headers = headers; - this.theResourceString = resourceString; - setResponse(new JaxRsResponse(resourceString, this)); - } - @Override - public String getHeader(String headerIfNoneExist) { - List requestHeader = headers.getRequestHeader(headerIfNoneExist); - return (requestHeader == null || requestHeader.size() == 0) ? null : requestHeader.get(0); + public String getHeader(String headerKey) { + List requestHeader = getHeaders(headerKey); + return requestHeader.isEmpty() ? null : requestHeader.get(0); } @Override @@ -55,14 +66,24 @@ public class JaxRsRequestDetails extends RequestDetails { return theResourceString.getBytes(ResourceParameter.determineRequestCharset(this)); } + @Override + public IRestfulResponse getResponse() { + if(super.getResponse() == null) { + setResponse(new JaxRsResponse(this)); + } + return super.getResponse(); + } + @Override public Reader getReader() throws IOException { + // not yet implemented throw new UnsupportedOperationException(); } @Override public InputStream getInputStream() { + // not yet implemented throw new UnsupportedOperationException(); } } \ No newline at end of file diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java similarity index 66% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsResponse.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java index e1437408d21..df53b52927f 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/JaxRsResponse.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jaxrs.server; +package ca.uhn.fhir.jaxrs.server.util; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -15,7 +15,6 @@ import javax.ws.rs.core.Response.ResponseBuilder; import org.hl7.fhir.instance.model.api.IBaseBinary; -import ca.uhn.fhir.jaxrs.server.util.JaxRsRequestDetails; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.method.ParseAction; @@ -24,9 +23,9 @@ import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.RestfulResponse; import ca.uhn.fhir.rest.server.RestfulServerUtils; -public class JaxRsResponse extends RestfulResponse { +public class JaxRsResponse extends RestfulResponse { - public JaxRsResponse(String resourceString, JaxRsRequestDetails jaxRsRequestDetails) { + public JaxRsResponse(JaxRsRequest jaxRsRequestDetails) { super(jaxRsRequestDetails); } @@ -43,15 +42,13 @@ public class JaxRsResponse extends RestfulResponse { @Override public Response sendWriterResponse(int status, String contentType, String charset, Writer writer) { - return Response.status(status)/*.header(HttpHeaders.CONTENT_TYPE, charset)*/.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(writer.toString()).build(); + Object entity = writer instanceof StringWriter ? writer.toString() : writer; + return buildResponse(status)/*.header(HttpHeaders.CONTENT_TYPE, charset)*/.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(entity).build(); } @Override - public Object sendAttachmentResponse(IBaseBinary bin, int statusCode, String contentType) throws IOException { - ResponseBuilder response = Response.status(statusCode); - for (Entry header : getHeaders().entrySet()) { - response.header(header.getKey(), header.getValue()); - } + public Response sendAttachmentResponse(IBaseBinary bin, int statusCode, String contentType) throws IOException { + ResponseBuilder response = buildResponse(statusCode); if (bin.getContent() != null && bin.getContent().length > 0) { response.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(bin.getContent()); } @@ -59,20 +56,28 @@ public class JaxRsResponse extends RestfulResponse { } @Override - public Object returnResponse(ParseAction outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, + public Response returnResponse(ParseAction outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, String resourceName) throws IOException { - Writer writer = new StringWriter(); - IParser parser = RestfulServerUtils.getNewParser(getRequestDetails().getServer().getFhirContext(), getRequestDetails()); + StringWriter writer = new StringWriter(); if(outcome != null) { + IParser parser = RestfulServerUtils.getNewParser(getRequestDetails().getServer().getFhirContext(), getRequestDetails()); outcome.execute(parser, writer); } - return Response.status(operationStatus).header(Constants.HEADER_CONTENT_TYPE, getParserType()).entity(writer.toString()).build(); + return sendWriterResponse(operationStatus, getParserType(), null, writer); } protected String getParserType() { EncodingEnum encodingEnum = RestfulServerUtils.determineResponseEncodingWithDefault(getRequestDetails()); return encodingEnum == EncodingEnum.JSON ? MediaType.APPLICATION_JSON : MediaType.APPLICATION_XML; } + + private ResponseBuilder buildResponse(int statusCode) { + ResponseBuilder response = Response.status(statusCode); + for (Entry header : getHeaders().entrySet()) { + response.header(header.getKey(), header.getValue()); + } + return response; + } } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java index ae1bd873eba..0a8623da6e9 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java @@ -5,7 +5,6 @@ import java.util.concurrent.ConcurrentHashMap; import org.apache.commons.lang3.StringUtils; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; import ca.uhn.fhir.rest.annotation.Search; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; @@ -14,55 +13,73 @@ import ca.uhn.fhir.rest.method.OperationMethodBinding; import ca.uhn.fhir.rest.method.SearchMethodBinding; import ca.uhn.fhir.util.ReflectionUtil; +/** + * @author Peter Van Houte + * Class that contains the method bindings defined by a ResourceProvider + */ public class MethodBindings { - /** ALL METHOD BINDINGS */ - ConcurrentHashMap>> allBindings = new ConcurrentHashMap>>(); + /** Static collection of bindings mapped to a class*/ + private static final ConcurrentHashMap, MethodBindings> classBindings = new ConcurrentHashMap, MethodBindings>(); + /** Static collection of operationBindings mapped to a class */ + private ConcurrentHashMap>> operationBindings = new ConcurrentHashMap>>(); - public > void findMethods(T theProvider, Class subclass, FhirContext fhirContext) { - for (final Method m : ReflectionUtil.getDeclaredMethods(subclass)) { - final BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, fhirContext, theProvider); + public > MethodBindings(T theProvider, Class clazz) { + for (final Method m : ReflectionUtil.getDeclaredMethods(clazz)) { + final BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, theProvider.getFhirContext(), theProvider); if (foundMethodBinding == null) { continue; } - ConcurrentHashMap> map = getAllBindingsMap(foundMethodBinding.getRestOperationType()); - if (foundMethodBinding instanceof OperationMethodBinding) { - OperationMethodBinding binding = (OperationMethodBinding) foundMethodBinding; - putIfAbsent(map, binding.getName(), binding); - } else if (foundMethodBinding instanceof SearchMethodBinding) { - Search search = m.getAnnotation(Search.class); - String compartmentName = StringUtils.defaultIfBlank(search.compartmentName(), ""); - putIfAbsent(map, compartmentName, foundMethodBinding); - } else { - putIfAbsent(map, "", foundMethodBinding); - } + String bindingKey = getBindingKey(foundMethodBinding); + putIfAbsent(bindingKey, foundMethodBinding); } } - private void putIfAbsent(ConcurrentHashMap> map, String key, BaseMethodBinding binding) { + private String getBindingKey(final BaseMethodBinding foundMethodBinding) { + if (foundMethodBinding instanceof OperationMethodBinding) { + return ((OperationMethodBinding) foundMethodBinding).getName(); + } else if (foundMethodBinding instanceof SearchMethodBinding) { + Search search = foundMethodBinding.getMethod().getAnnotation(Search.class); + return search.compartmentName(); + } else { + return ""; + } + } + + private void putIfAbsent(String key, BaseMethodBinding binding) { + operationBindings.putIfAbsent(binding.getRestOperationType(), new ConcurrentHashMap>()); + ConcurrentHashMap> map = operationBindings.get(binding.getRestOperationType()); if (map.containsKey(key)) { throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + map.get(key) + " -- " + binding.getMethod()); - } + } map.put(key, binding); } - private ConcurrentHashMap> getAllBindingsMap(final RestOperationTypeEnum restOperationTypeEnum) { - allBindings.putIfAbsent(restOperationTypeEnum, new ConcurrentHashMap>()); - return allBindings.get(restOperationTypeEnum); - } + public BaseMethodBinding getBinding(RestOperationTypeEnum operationType) { + return getBinding(operationType, ""); + } public BaseMethodBinding getBinding(RestOperationTypeEnum operationType, String qualifier) { - String nonEmptyQualifier = StringUtils.defaultIfBlank(qualifier, ""); - ConcurrentHashMap> map = getAllBindingsMap(operationType); - if(!map.containsKey(nonEmptyQualifier)) { + String nonEmptyQualifier = StringUtils.defaultIfBlank(qualifier, ""); + ConcurrentHashMap> map = operationBindings.get(operationType); + if(map == null || !map.containsKey(nonEmptyQualifier)) { throw new UnsupportedOperationException(); } else { return map.get(nonEmptyQualifier); } } - public BaseMethodBinding getBinding(RestOperationTypeEnum operationType) { - return getBinding(operationType, ""); - } + public static > MethodBindings getMethodBindings(T theProvider, Class clazz) { + if(!getClassBindings().containsKey(clazz)) { + MethodBindings foundBindings = new MethodBindings(theProvider, clazz); + getClassBindings().putIfAbsent(clazz, foundBindings); + } + return getClassBindings().get(clazz); + } + public static ConcurrentHashMap, MethodBindings> getClassBindings() { + return classBindings; + } + + } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/JaxRsPatientProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/JaxRsPatientProviderTest.java new file mode 100644 index 00000000000..3d4af441134 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/JaxRsPatientProviderTest.java @@ -0,0 +1,318 @@ +package ca.uhn.fhir.jaxrs.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; + +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.example.JaxRsPatientRestProvider; +import ca.uhn.fhir.jaxrs.server.example.RandomServerPortProvider; +import ca.uhn.fhir.model.api.BundleEntry; +import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Conformance; +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.BoundCodeDt; +import ca.uhn.fhir.model.primitive.DateDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.PreferReturnEnum; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.rest.method.SearchStyleEnum; +import ca.uhn.fhir.rest.server.EncodingEnum; + +public class JaxRsPatientProviderTest { + + private static IGenericClient client; + private static final FhirContext ourCtx = FhirContext.forDstu2(); + private static final String PATIENT_NAME = "Van Houte"; + private static int ourPort; + private static Server jettyServer; + + @BeforeClass + public static void setUpClass() + throws Exception { + ourPort = RandomServerPortProvider.findFreePort(); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + System.out.println(ourPort); + jettyServer = new Server(ourPort); + jettyServer.setHandler(context); + ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/*"); + jerseyServlet.setInitOrder(0); + jerseyServlet.setInitParameter("jersey.config.server.provider.classnames", JaxRsPatientRestProvider.class.getCanonicalName()); + jettyServer.start(); + + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); + client.setEncoding(EncodingEnum.JSON); + client.registerInterceptor(new LoggingInterceptor(true)); + } + + @AfterClass + public static void tearDownClass() + throws Exception { + try { + jettyServer.destroy(); + } + catch (Exception e) { + } + } + + /** Search/Query - Type */ + @Test + public void findUsingGenericClientBySearch() { + // Perform a search + final ca.uhn.fhir.model.api.Bundle results = client.search().forResource(Patient.class) + .where(Patient.NAME.matchesExactly().value(PATIENT_NAME)).execute(); + System.out.println(results.getEntries().get(0)); + assertEquals(results.getEntries().size(), 1); + } + + /** Search - Multi-valued Parameters (ANY/OR) */ + @Test + public void findUsingGenericClientBySearchWithMultiValues() { + final ca.uhn.fhir.model.api.Bundle response = client.search().forResource(Patient.class) + .where(Patient.ADDRESS.matches().values("Toronto")).and(Patient.ADDRESS.matches().values("Ontario")) + .and(Patient.ADDRESS.matches().values("Canada")) + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("SHORTNAME", "TOYS")).execute(); + System.out.println(response.getEntries().get(0)); + } + + /** Search - Paging */ + @Test + public void findWithPaging() { + // Perform a search + final Bundle results = client.search().forResource(Patient.class).limitTo(8).returnBundle(Bundle.class).execute(); + System.out.println(results.getEntry().size()); + + if (results.getLink(Bundle.LINK_NEXT) != null) { + + // load next page + final Bundle nextPage = client.loadPage().next(results).execute(); + System.out.println(nextPage.getEntry().size()); + } + } + + /** Search using other query options */ + public void testOther() { + //missing + } + + /** */ + @Test + public void testSearchPost() { + Bundle response = client.search() + .forResource("Patient") + .usingStyle(SearchStyleEnum.POST) + .returnBundle(Bundle.class) + .execute(); + assertTrue(response.getEntry().size() > 0); + } + + /** Search - Compartments */ + @Test + public void testSearchCompartements() { + Bundle response = client.search() + .forResource(Patient.class) + .withIdAndCompartment("1", "Condition") + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); + assertTrue(response.getEntry().size() > 0); + } + + /** Search - Subsetting (_summary and _elements) */ + @Test + @Ignore + public void testSummary() { + Object response = client.search() + .forResource(Patient.class) + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) + .execute(); + } + + @Test + public void testCreatePatient() { + final Patient existing = new Patient(); + existing.setId((IdDt) null); + existing.getNameFirstRep().addFamily("Created Patient 54"); + client.setEncoding(EncodingEnum.JSON); + final MethodOutcome results = client.create().resource(existing).prefer(PreferReturnEnum.REPRESENTATION).execute(); + System.out.println(results.getId()); + final Patient patient = (Patient) results.getResource(); + System.out.println(patient); + assertNotNull(client.read(patient.getId())); + client.setEncoding(EncodingEnum.JSON); + } + + + /** Conditional Creates */ + @Test + public void testConditionalCreate() { + final Patient existing = new Patient(); + existing.setId((IdDt) null); + existing.getNameFirstRep().addFamily("Created Patient 54"); + client.setEncoding(EncodingEnum.XML); + final MethodOutcome results = client.create().resource(existing).prefer(PreferReturnEnum.REPRESENTATION).execute(); + System.out.println(results.getId()); + final Patient patient = (Patient) results.getResource(); + + client.create() + .resource(patient) + .conditional() + .where(Patient.IDENTIFIER.exactly().identifier(patient.getIdentifierFirstRep())) + .execute(); + } + + + /** Find By Id */ + @Test + public void findUsingGenericClientById() { + final Patient results = client.read(Patient.class, "1"); + assertEquals(results.getId().getIdPartAsLong().longValue(), 1L); + } + + @Test + public void testUpdateById() { + final Patient existing = client.read(Patient.class, "1"); + final List name = existing.getName(); + name.get(0).addSuffix("The Second"); + existing.setName(name); + client.setEncoding(EncodingEnum.XML); + final MethodOutcome results = client.update("1", existing); + } + + @Test + public void testDeletePatient() { + final Patient existing = new Patient(); + existing.getNameFirstRep().addFamily("Created Patient XYZ"); + final MethodOutcome results = client.create().resource(existing).prefer(PreferReturnEnum.REPRESENTATION).execute(); + System.out.println(results.getId()); + final Patient patient = (Patient) results.getResource(); + client.delete(Patient.class, patient.getId()); + try { + client.read(patient.getId()); + fail(); + } + catch (final Exception e) { + //assertEquals(e.getStatusCode(), Constants.STATUS_HTTP_404_NOT_FOUND); + } + } + + /** Transaction - Server */ + @Ignore + @Test + public void testTransaction() { + ca.uhn.fhir.model.api.Bundle bundle = new ca.uhn.fhir.model.api.Bundle(); + BundleEntry entry = bundle.addEntry(); + final Patient existing = new Patient(); + existing.getNameFirstRep().addFamily("Created with bundle"); + entry.setResource(existing); + + BoundCodeDt theTransactionOperation = + new BoundCodeDt( + BundleEntryTransactionMethodEnum.VALUESET_BINDER, + BundleEntryTransactionMethodEnum.POST); + entry.setTransactionMethod(theTransactionOperation); + ca.uhn.fhir.model.api.Bundle response = client.transaction().withBundle(bundle).execute(); + } + + /** Conformance - Server */ + @Test + @Ignore + public void testConformance() { + final Conformance conf = client.fetchConformance().ofType(Conformance.class).execute(); + System.out.println(conf.getRest().get(0).getResource().get(0).getType()); + assertEquals(conf.getRest().get(0).getResource().get(0).getType().toString(), "Patient"); + } + + /** Extended Operations */ + // Create a client to talk to the HeathIntersections server + @Test + public void testExtendedOperations() { + client.registerInterceptor(new LoggingInterceptor(true)); + + // Create the input parameters to pass to the server + Parameters inParams = new Parameters(); + inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); + inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); + inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue")); + + // Invoke $everything on "Patient/1" + Parameters outParams = client + .operation() + .onInstance(new IdDt("Patient", "1")) + .named("$last") + .withParameters(inParams) + //.useHttpGet() // Use HTTP GET instead of POST + .execute(); + String resultValue = outParams.getParameter().get(0).getValue().toString(); + System.out.println(resultValue); + assertEquals("expected but found : "+ resultValue, resultValue.contains("myAwesomeDummyValue"), true); + } + + @Test + public void testExtendedOperationsUsingGet() { + client.registerInterceptor(new LoggingInterceptor(true)); + + // Create the input parameters to pass to the server + Parameters inParams = new Parameters(); + inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); + inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); + inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue")); + + // Invoke $everything on "Patient/1" + Parameters outParams = client + .operation() + .onInstance(new IdDt("Patient", "1")) + .named("$last") + .withParameters(inParams) + .useHttpGet() // Use HTTP GET instead of POST + .execute(); + String resultValue = outParams.getParameter().get(0).getValue().toString(); + System.out.println(resultValue); + assertEquals("expected but found : "+ resultValue, resultValue.contains("myAwesomeDummyValue"), true); + } + + @Test + public void testFindUnknownPatient() { + try { + final Patient existing = client.read(Patient.class, "999955541264"); + fail(); + } + catch (final Exception e) { + e.printStackTrace(); + //assertEquals(e.getStatusCode(), 404); + } + } + + @Test + public void testVRead() { + final Patient patient = client.vread(Patient.class, "1", "1"); + System.out.println(patient); + } + + @Test + public void testRead() { + final Patient patient = client.read(Patient.class, "1"); + System.out.println(patient); + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java new file mode 100644 index 00000000000..feb1b2295b6 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java @@ -0,0 +1,269 @@ +package ca.uhn.fhir.jaxrs.server.example; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ejb.Stateless; +import javax.interceptor.Interceptors; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu2.resource.Condition; +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +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.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.AddProfileTagEnum; +import ca.uhn.fhir.rest.server.BundleInclusionRule; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.ETagSupportEnum; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.IPagingProvider; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; + +/** + * Fhir Physician Rest Service + * @author axmpm + * + */ +@Path(JaxRsPatientRestProvider.PATH) +@Stateless +@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) +public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider /*implements IJaxRsPatientProvider*/ { + + static final String PATH = "/Patient"; + + private static Long counter = 1L; + private static final ConcurrentHashMap> patients = new ConcurrentHashMap>(); + + public JaxRsPatientRestProvider() throws Exception { + super(JaxRsPatientRestProvider.class); + } + + static { + patients.put(""+counter, createPatient("Van Houte")); + patients.put(""+(counter), createPatient("Agnew")); + for(int i = 0 ; i<20 ; i++) { + patients.put(""+(counter), createPatient("Random Patient " + counter)); + } + } + + private static List createPatient(final String name) { + final Patient patient = new Patient(); + patient.getNameFirstRep().addFamily(name); + return createPatient(patient); + } + + private static List createPatient(final Patient patient) { + patient.setId(createId(counter, 1L)); + final LinkedList list = new LinkedList(); + list.add(patient); + counter++; + return list ; + } + + private static IdDt createId(final Long id, final Long theVersionId) { + return new IdDt("Patient", "" + id, "" + theVersionId); + } + + @Search + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { + final List result = new LinkedList(); + for (final List patientIterator : patients.values()) { + Patient single = null; + for (Patient patient : patientIterator) { + if (name == null || patient.getNameFirstRep().getFamilyFirstRep().getValueNotNull().equals(name.getValueNotNull())) { + single = patient; + } + } + if (single != null) { + result.add(single); + } + } + return result; + } + + @Update + public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) + throws Exception { + final String idPart = theId.getIdPart(); + if(patients.containsKey(idPart)) { + final List patientList = patients.get(idPart); + final Patient lastPatient = getLast(patientList); + patient.setId(createId(theId.getIdPartAsLong(), lastPatient.getId().getVersionIdPartAsLong()+1)); + patientList.add(patient); + final MethodOutcome result = new MethodOutcome().setCreated(false); + result.setResource(patient); + result.setId(patient.getId()); + return result; + } else { + throw new ResourceNotFoundException(theId); + } + } + + @Read + public Patient find(@IdParam final IdDt theId) { + if(patients.containsKey(theId.getIdPart())) { + return getLast(patients.get(theId.getIdPart())); + } else { + throw new ResourceNotFoundException(theId); + } + } + + + private Patient getLast(final List list) { + return list.get(list.size()-1); + } + + @Read(version = true) + public Patient findHistory(@IdParam final IdDt theId) { + if (patients.containsKey(theId.getIdPart())) { + final List list = patients.get(theId.getIdPart()); + for (final Patient patient : list) { + if (patient.getId().getVersionIdPartAsLong().equals(theId.getVersionIdPartAsLong())) { + return patient; + } + } + } + throw new ResourceNotFoundException(theId); + } + + @Create + public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) + throws Exception { + patients.put(""+counter, createPatient(patient)); + final MethodOutcome result = new MethodOutcome().setCreated(true); + result.setResource(patient); + result.setId(patient.getId()); + return result; + } + + @Delete + public MethodOutcome delete(@IdParam final IdDt theId) { + final Patient deletedPatient = find(theId); + patients.remove(deletedPatient.getId().getIdPart()); + final MethodOutcome result = new MethodOutcome().setCreated(true); + result.setResource(deletedPatient); + return result; + } + + + @GET + @Path("/{id}/$last") + @Interceptors(JaxRsExceptionInterceptor.class) + public Response operationLastGet(@PathParam("id") String id) + throws Exception { + return customOperation(null, RequestTypeEnum.GET, id, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); + } + + @Search(compartmentName="Condition") + public List searchCompartment(@IdParam IdDt thePatientId) { + List retVal=new ArrayList(); + Condition condition = new Condition(); + condition.setId(new IdDt("665577")); + retVal.add(condition); + return retVal; + } + + @POST + @Path("/{id}/$last") + @Interceptors(JaxRsExceptionInterceptor.class) + public Response operationLast(final String resource) + throws Exception { + return customOperation(resource, RequestTypeEnum.POST, null, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); + } + +// @ca.uhn.fhir.rest.annotation.Validate +// public MethodOutcome validate( +// @ResourceParam T theResource, +// @ResourceParam String theRawResource, +// @ResourceParam EncodingEnum theEncoding, +// @ca.uhn.fhir.rest.annotation.Validate.Mode ValidationModeEnum theMode, +// @ca.uhn.fhir.rest.annotation.Validate.Profile String theProfile) { +// return validate(theResource, null, theRawResource, theEncoding, theMode, theProfile); +// } + + @Operation(name="last", idempotent=true, returnParameters= { + @OperationParam(name="return", type=StringDt.class) + }) + public Parameters last(@OperationParam(name = "dummy") StringDt dummyInput) { + System.out.println("inputparameter"); + Parameters parameters = new Parameters(); + Patient patient = find(new IdDt(counter.intValue()-1)); + parameters + .addParameter() + .setName("return") + .setResource(patient) + .setValue(new StringDt((counter-1)+"" + "inputVariable [ " + dummyInput.getValue()+ "]")); + return parameters; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + /** THE DEFAULTS */ + @Override + public List getInterceptors() { + return Collections.emptyList(); + } + + @Override + public ETagSupportEnum getETagSupport() { + return ETagSupportEnum.DISABLED; + } + + @Override + public boolean isDefaultPrettyPrint() { + return true; + } + + @Override + public AddProfileTagEnum getAddProfileTag() { + return AddProfileTagEnum.NEVER; + } + + @Override + public boolean isUseBrowserFriendlyContentTypes() { + return true; + } + + @Override + public IPagingProvider getPagingProvider() { + return new FifoMemoryPagingProvider(10); + } + + @Override + public BundleInclusionRule getBundleInclusionRule() { + return BundleInclusionRule.BASED_ON_INCLUDES; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java new file mode 100644 index 00000000000..4cfb0eb97d4 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java @@ -0,0 +1,36 @@ +package ca.uhn.fhir.jaxrs.server.example; + +import java.io.IOException; +import java.net.ServerSocket; +import java.util.ArrayList; +import java.util.List; + +/** + * Provides server ports + */ +public class RandomServerPortProvider { + + private static List ourPorts = new ArrayList(); + + public static int findFreePort() { + ServerSocket server; + try { + server = new ServerSocket(0); + int port = server.getLocalPort(); + ourPorts.add(port); + server.close(); + Thread.sleep(500); + return port; + } catch (IOException e) { + throw new Error(e); + } catch (InterruptedException e) { + throw new Error(e); + } + } + + public static List list() { + return ourPorts; + } + +} + \ No newline at end of file diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestDummyPatientProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestDummyPatientProvider.java new file mode 100644 index 00000000000..2d63381edca --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestDummyPatientProvider.java @@ -0,0 +1,12 @@ +package ca.uhn.fhir.jaxrs.server.example; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import ca.uhn.fhir.model.dstu2.resource.Patient; + +public class TestDummyPatientProvider extends AbstractJaxRsResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java new file mode 100644 index 00000000000..507d1f1afc6 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java @@ -0,0 +1,121 @@ +package ca.uhn.fhir.jaxrs.server.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.Arrays; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.MultivaluedMap; +import javax.ws.rs.core.UriInfo; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.server.ContainerRequest; +import org.junit.Before; +import org.junit.Test; + +import ca.uhn.fhir.jaxrs.server.example.TestDummyPatientProvider; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; + +public class JaxRsRequestTest { + + private static final String RESOURCE_STRING = ""; + private static final String BASEURI = "http://baseuri"; + private static final String REQUESTURI = "http://baseuri/test"; + + private JaxRsRequest details; + private MultivaluedMap queryParameters = new MultivaluedHashMap(); + private ContainerRequest headers; + private TestDummyPatientProvider provider; + + @Before + public void setUp() throws URISyntaxException { + details = createRequestDetails(); + } + + @Test + public void testGetHeader() { + String headerKey = "key"; + String headerValue = "location_value"; + String headerValue2 = "location_value_2"; + assertTrue(StringUtils.isBlank(details.getHeader(headerKey))); + headers.header(headerKey, headerValue); + assertEquals(headerValue, details.getHeader(headerKey)); + assertEquals(Arrays.asList(headerValue), details.getHeaders(headerKey)); + + headers.header(headerKey, headerValue2); + assertEquals(headerValue, details.getHeader(headerKey)); + assertEquals(Arrays.asList(headerValue, headerValue2), details.getHeaders(headerKey)); + } + + @Test + public void testGetByteStreamRequestContents() { + assertEquals(RESOURCE_STRING, new String(details.getByteStreamRequestContents())); + } + + @Test + public void testServerBaseForRequest() { + assertEquals(BASEURI, new String(details.getServerBaseForRequest())); + } + + @Test + public void testGetResponse() { + JaxRsResponse response = (JaxRsResponse) details.getResponse(); + assertEquals(details, response.getRequestDetails()); + assertTrue(response == details.getResponse()); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetReader() throws IOException { + details.getReader(); + } + + @Test(expected = UnsupportedOperationException.class) + public void testGetInputStream() { + details.getInputStream(); + } + + @Test + public void testGetServerBaseForRequest() { + assertEquals(JaxRsRequestTest.BASEURI, details.getFhirServerBase()); + } + + @Test + public void testGetServer() { + assertEquals(this.provider, details.getServer()); + } + + @Test + public void testJaxRsRequestDetails() { + Validate.notNull(new JaxRsRequest()); + } + + public JaxRsRequest createRequestDetails() throws URISyntaxException { + //headers + headers = new ContainerRequest(new URI(BASEURI), new URI(REQUESTURI), HttpMethod.GET, null, new MapPropertiesDelegate()); + + //uri info + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getQueryParameters()).thenReturn(queryParameters); + + //mocks + provider = spy(TestDummyPatientProvider.class); + doReturn(uriInfo).when(provider).getInfo(); + doReturn(BASEURI).when(provider).getBaseUri(); + doReturn(headers).when(provider).getHeaders(); + + return new JaxRsRequest(provider, RESOURCE_STRING, RequestTypeEnum.GET, RestOperationTypeEnum.HISTORY_TYPE); + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java new file mode 100644 index 00000000000..f75beddecad --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java @@ -0,0 +1,196 @@ +package ca.uhn.fhir.jaxrs.server.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +import java.io.ByteArrayInputStream; +import java.io.IOException; +import java.net.URISyntaxException; +import java.util.Collections; +import java.util.Set; +import java.util.zip.GZIPInputStream; + +import javax.ws.rs.core.Response; + +import org.hl7.fhir.instance.model.api.IBaseBinary; +import org.hl7.fhir.instance.model.api.IBaseResource; +import org.junit.Before; +import org.junit.Test; + +import ca.uhn.fhir.model.api.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Binary; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.SummaryEnum; +import ca.uhn.fhir.rest.method.ParseAction; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.RestfulServerUtils; + +public class JaxRsResponseTest { + + private JaxRsResponse response; + private JaxRsRequest request; + private Bundle bundle; + private Set theSummaryMode; + + @Before + public void setUp() throws URISyntaxException { + request = new JaxRsRequestTest().createRequestDetails(); + this.response = (JaxRsResponse) request.getResponse(); + bundle = getSinglePatientResource(); + theSummaryMode = Collections.emptySet(); + } + + @Test + public void testGetResponseWriterNoZipNoBrowser() throws IOException { + boolean theRequestIsBrowser = false; + boolean respondGzip = false; + Set theSummaryMode = Collections.emptySet(); + Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request); + assertEquals(200, result.getStatus()); + assertEquals(Constants.CT_FHIR_JSON, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertTrue(result.getEntity().toString().contains("Patient")); + assertTrue(result.getEntity().toString().contains("15")); + } + + @Test + public void testGetResponseWriterBrowserNoZip() throws IOException { + boolean theRequestIsBrowser = true; + boolean respondGzip = false; + Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request); + assertEquals(200, result.getStatus()); + assertEquals(Constants.CT_JSON, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertTrue(result.getEntity().toString().contains("Patient")); + assertTrue(result.getEntity().toString().contains("15")); + } + + @Test + public void testGetResponseWriterWithZip() throws IOException { + boolean theRequestIsBrowser = true; + boolean respondGzip = true; + Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request); + assertEquals(200, result.getStatus()); + assertEquals(Constants.CT_JSON, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertEquals(Constants.ENCODING_GZIP, result.getHeaderString(Constants.HEADER_CONTENT_ENCODING)); +// assertTrue(unzip(result.getEntity()).contains("Patient")); +// assertTrue(result.getEntity().toString().contains("15")); + } + + @Test + public void testSendAttachmentResponse() throws IOException { + boolean theRequestIsBrowser = true; + boolean respondGzip = true; + IBaseBinary binary = new Binary(); + String contentType = "foo"; + byte[] content = new byte[] { 1, 2, 3, 4 }; + binary.setContentType(contentType); + binary.setContent(content); + boolean theAddContentLocationHeader = false; + Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theRequestIsBrowser, theSummaryMode, 200, respondGzip, theAddContentLocationHeader, respondGzip, this.request); + assertEquals(200, result.getStatus()); + assertEquals(contentType, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertEquals(content, result.getEntity()); + } + + @Test + public void testSendAttachmentResponseNoContent() throws IOException { + boolean theRequestIsBrowser = true; + boolean respondGzip = true; + IBaseBinary binary = new Binary(); + binary.setContent(new byte[]{}); + boolean theAddContentLocationHeader = false; + Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theRequestIsBrowser, theSummaryMode, 200, respondGzip, theAddContentLocationHeader, respondGzip, this.request); + assertEquals(200, result.getStatus()); + assertEquals(null, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertEquals(null, result.getEntity()); + } + + @Test + public void testSendAttachmentResponseEmptyContent() throws IOException { + boolean theRequestIsBrowser = true; + boolean respondGzip = true; + IBaseBinary binary = new Binary(); + boolean theAddContentLocationHeader = false; + Response result = (Response) RestfulServerUtils.streamResponseAsResource(request.getServer(), binary, theRequestIsBrowser, theSummaryMode, 200, respondGzip, theAddContentLocationHeader, respondGzip, this.request); + assertEquals(200, result.getStatus()); + assertEquals(null, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertEquals(null, result.getEntity()); + } + + + @Test + public void testReturnResponse() throws IOException { + IdDt theId = new IdDt(15L); + ParseAction outcome = ParseAction.create(createPatient()); + int operationStatus = 200; + boolean allowPrefer = true; + String resourceName = "Patient"; + MethodOutcome methodOutcome = new MethodOutcome(theId); + Response result = response.returnResponse(outcome, operationStatus, allowPrefer, methodOutcome, resourceName); + assertEquals(200, result.getStatus()); + assertEquals(Constants.CT_JSON, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + System.out.println(result.getEntity().toString()); + assertTrue(result.getEntity().toString().contains("resourceType\":\"Patient")); + assertTrue(result.getEntity().toString().contains("15")); + + } + + @Test + public void testReturnResponseAsXml() throws IOException { + IdDt theId = new IdDt(15L); + ParseAction outcome = ParseAction.create(createPatient()); + int operationStatus = 200; + boolean allowPrefer = true; + String resourceName = "Patient"; + MethodOutcome methodOutcome = new MethodOutcome(theId); + response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML}); + Response result = response.returnResponse(outcome, operationStatus, allowPrefer, methodOutcome, resourceName); + assertEquals(200, result.getStatus()); + assertEquals(Constants.CT_XML, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertTrue(result.getEntity().toString().contains(" outcome = ParseAction.create((IBaseResource) null); + int operationStatus = Constants.STATUS_HTTP_204_NO_CONTENT; + boolean allowPrefer = true; + String resourceName = "Patient"; + MethodOutcome methodOutcome = new MethodOutcome(null); + response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML}); + Response result = response.returnResponse(outcome, operationStatus, allowPrefer, methodOutcome, resourceName); + assertEquals(204, result.getStatus()); + assertEquals(Constants.CT_XML, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + } + + private String unzip(Object entity) throws IOException { + byte[] compressed = ((String) entity).getBytes(Constants.CHARSET_NAME_UTF8); + final int BUFFER_SIZE = 32; + ByteArrayInputStream is = new ByteArrayInputStream(compressed); + GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE); + StringBuilder string = new StringBuilder(); + byte[] data = new byte[BUFFER_SIZE]; + int bytesRead; + while ((bytesRead = gis.read(data)) != -1) { + string.append(new String(data, 0, bytesRead)); + } + gis.close(); + is.close(); + return string.toString(); + } + + private Bundle getSinglePatientResource() { + Patient theResource = createPatient(); + Bundle bundle = Bundle.withSingleResource(theResource); + return bundle; + } + + private Patient createPatient() { + Patient theResource = new Patient(); + theResource.setId(new IdDt(15L)); + return theResource; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java new file mode 100644 index 00000000000..8649eac3ad8 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java @@ -0,0 +1,127 @@ +package ca.uhn.fhir.jaxrs.server.util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +import java.util.List; + +import org.junit.Before; +import org.junit.FixMethodOrder; +import org.junit.Test; +import org.junit.runners.MethodSorters; + +import ca.uhn.fhir.jaxrs.server.example.TestDummyPatientProvider; +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.param.StringParam; + +@FixMethodOrder(MethodSorters.DEFAULT) +public class MethodBindingsTest { + + @Before + public void setUp() { + MethodBindings.getClassBindings().clear(); + } + + @Test(expected = UnsupportedOperationException.class) + public void testFindMethodsForProviderNotDefinedMappingMethods() { + new TestDummyPatientProvider().getBindings().getBinding(RestOperationTypeEnum.UPDATE, ""); + } + + @Test + public void testFindMethodsForProviderWithMethods() { + class TestFindPatientProvider extends TestDummyPatientProvider { + @Search + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { + return null; + } + } + new TestFindPatientProvider(); + assertEquals(TestFindPatientProvider.class, new TestFindPatientProvider().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE).getMethod().getDeclaringClass()); + } + + @Test + public void testFindMethodsFor2ProvidersWithMethods() { + class TestFindPatientProvider extends TestDummyPatientProvider { + @Search + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { + return null; + } + } + class TestUpdatePatientProvider extends TestDummyPatientProvider { + @Update + public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) { + return null; + } + } + assertEquals(TestFindPatientProvider.class, new TestFindPatientProvider().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE).getMethod().getDeclaringClass()); + assertEquals(TestUpdatePatientProvider.class, new TestUpdatePatientProvider().getBindings().getBinding(RestOperationTypeEnum.UPDATE).getMethod().getDeclaringClass()); + } + + @Test + public void testFindMethodsWithDoubleMethodsDeclaration() { + class TestDoubleSearchProvider extends TestDummyPatientProvider { + @Search + public List search1(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { + return null; + } + + @Search + public List search2(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { + return null; + } + } + try { + new TestDoubleSearchProvider(); + fail(); + } catch(IllegalArgumentException e) { + assertTrue(e.getMessage().contains("search1")); + assertTrue(e.getMessage().contains("search2")); + } + } + + @Test + public void testFindMethodsWithMultipleMethods() { + class TestFindPatientProvider extends TestDummyPatientProvider { + @Search + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { + return null; + } + @Update + public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) { + return null; + } + @Operation(name = "firstMethod", idempotent = true, returnParameters = { @OperationParam(name = "return", type = StringDt.class) }) + public Parameters firstMethod(@OperationParam(name = "dummy") StringDt dummyInput) { + return null; + } + @Operation(name = "secondMethod", returnParameters = { @OperationParam(name = "return", type = StringDt.class) }) + public Parameters secondMethod(@OperationParam(name = "dummy") StringDt dummyInput) { + return null; + } + } + MethodBindings bindings = new TestFindPatientProvider().getBindings(); + assertEquals("search", bindings.getBinding(RestOperationTypeEnum.SEARCH_TYPE).getMethod().getName()); + assertEquals("update", bindings.getBinding(RestOperationTypeEnum.UPDATE).getMethod().getName()); + assertEquals("firstMethod", bindings.getBinding(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, "$firstMethod").getMethod().getName()); + assertEquals("secondMethod", bindings.getBinding(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, "$secondMethod").getMethod().getName()); + try { + bindings.getBinding(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, "$thirdMethod"); + fail(); + } catch(UnsupportedOperationException e){ + } + } + +} From d2cb35b6a16799d75e140ce39f48036c6f8c4649 Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Wed, 28 Oct 2015 16:27:45 +0100 Subject: [PATCH 05/13] add test for conformance --- .../AbstractJaxRsConformanceProvider.java | 48 ++++++--- .../jaxrs/server/AbstractJaxRsProvider.java | 39 +++++-- .../server/AbstractJaxRsResourceProvider.java | 21 ++-- .../server/IJaxRsConformanceProvider.java | 8 -- .../jaxrs/server/IJaxRsResourceProvider.java | 36 ------- .../fhir/jaxrs/server/util/JaxRsResponse.java | 4 +- .../AbstractJaxRsConformanceProviderTest.java | 100 ++++++++++++++++++ .../JaxRsPatientProviderTest.java | 9 +- .../TestJaxRsConformanceRestProvider.java | 35 ++++++ ...java => TestJaxRsPatientRestProvider.java} | 8 +- .../jaxrs/server/util/JaxRsRequestTest.java | 2 +- 11 files changed, 216 insertions(+), 94 deletions(-) delete mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsConformanceProvider.java delete mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java rename hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/{ => example}/JaxRsPatientProviderTest.java (98%) create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java rename hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/{JaxRsPatientRestProvider.java => TestJaxRsPatientRestProvider.java} (97%) diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java index e4d0a313dba..b3a9be6edbe 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java @@ -1,33 +1,40 @@ package ca.uhn.fhir.jaxrs.server; +import java.io.IOException; import java.lang.annotation.Annotation; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.LinkedList; import java.util.List; +import java.util.Map.Entry; import java.util.concurrent.ConcurrentHashMap; +import javax.annotation.PostConstruct; +import javax.ejb.Local; +import javax.ejb.Stateless; import javax.ws.rs.GET; import javax.ws.rs.OPTIONS; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.ResponseBuilder; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.slf4j.LoggerFactory; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.RuntimeResourceDefinition; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.model.dstu2.resource.Conformance; import ca.uhn.fhir.rest.annotation.IdParam; 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.method.ParseAction; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; +import ca.uhn.fhir.rest.server.IResourceProvider; +import ca.uhn.fhir.rest.server.IRestfulResponse; import ca.uhn.fhir.rest.server.ResourceBinding; import ca.uhn.fhir.rest.server.RestulfulServerConfiguration; import ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider; @@ -37,8 +44,11 @@ import ca.uhn.fhir.util.ReflectionUtil; * Conformance Rest Service * @author Peter Van Houte */ +@Local +@Path("") +@Stateless @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) -public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProvider implements IJaxRsConformanceProvider { +public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProvider { public static final String PATH = "/"; private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractJaxRsConformanceProvider.class); @@ -49,15 +59,19 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv private Conformance myConformance; - protected AbstractJaxRsConformanceProvider(String implementationDescription, String serverName, String serverVersion) { + public AbstractJaxRsConformanceProvider(String implementationDescription, String serverName, String serverVersion) { serverConfiguration.setFhirContext(getFhirContext()); serverConfiguration.setImplementationDescription(implementationDescription); serverConfiguration.setServerName(serverName); serverConfiguration.setServerVersion(serverVersion); } + @PostConstruct protected void setUpPostConstruct() throws Exception { + for (Entry, IResourceProvider> provider : getProviders().entrySet()) { + addProvider(provider.getValue(), provider.getKey()); + } List> serverBindings = new ArrayList>(); for (ResourceBinding baseMethodBinding : myResourceNameToBinding.values()) { serverBindings.addAll(baseMethodBinding.getMethodBindings()); @@ -72,20 +86,27 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv myConformance = serverConformanceProvider.getServerConformance(null); } - @GET + protected abstract ConcurrentHashMap, IResourceProvider> getProviders() throws Exception; + @OPTIONS @Path("/metadata") - public Response conformance(String string) { - String conformanceString = getParser(createRequestDetails(null, RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA)).encodeResourceToString(myConformance); - ResponseBuilder entity = Response.status(Constants.STATUS_HTTP_200_OK).entity(conformanceString); - entity.header("Access-Control-Allow-Origin", "*"); - return entity.build(); + public Response conformanceUsingOptions() throws IOException { + return conformance(); } - protected int findResourceMethods(Object theProvider, Class clazz) throws ConfigurationException { + @GET + @Path("/metadata") + public Response conformance() throws IOException { + JaxRsRequest request = createRequestDetails(null, RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA); + IRestfulResponse response = request.getResponse(); + response.addHeader(Constants.HEADER_CORS_ALLOW_ORIGIN, "*"); + return (Response) response.returnResponse(ParseAction.create(myConformance), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName()); + } + + public int addProvider(Object theProvider, Class theProviderInterface) throws ConfigurationException { int count = 0; - for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) { + for (Method m : ReflectionUtil.getDeclaredMethods(theProviderInterface)) { BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, getFhirContext(), theProvider); if (foundMethodBinding == null) { continue; @@ -144,9 +165,8 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv return count; } - @Override - public Class getResourceType() { + public Class getResourceType() { return Conformance.class; } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index 8974f4f60bc..88a4518fb96 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -1,6 +1,8 @@ package ca.uhn.fhir.jaxrs.server; import java.util.HashMap; +import java.util.List; +import java.util.Map.Entry; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; @@ -16,6 +18,7 @@ import ca.uhn.fhir.rest.server.AddProfileTagEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; +import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IRestfulServerDefaults; import ca.uhn.fhir.rest.server.IServerAddressStrategy; import ca.uhn.fhir.rest.server.RestfulServerUtils; @@ -25,7 +28,7 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils; * @author Peter Van Houte * */ -public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { +public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults, IResourceProvider { public static FhirContext CTX = FhirContext.forDstu2(); @@ -43,10 +46,10 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { * param and query methods */ public HashMap getQueryMap() { - MultivaluedMap queryParameters = getInfo().getQueryParameters(); + MultivaluedMap queryParameters = getUriInfo().getQueryParameters(); HashMap params = new HashMap(); - for (String key : queryParameters.keySet()) { - params.put(key, queryParameters.get(key).toArray(new String[] {})); + for (Entry> paramEntry : queryParameters.entrySet()) { + params.put(paramEntry.getKey(), paramEntry.getValue().toArray(new String[paramEntry.getValue().size()])); } return params; } @@ -58,7 +61,7 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { } public String getBaseUri() { - return getInfo().getBaseUri().toASCIIString(); + return getUriInfo().getBaseUri().toASCIIString(); } /** @@ -73,19 +76,35 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { } /** - * Get the info - * @return the info + * Get the uriInfo + * @return the uri info */ - public UriInfo getInfo() { - return theUriInfo; + public UriInfo getUriInfo() { + return this.theUriInfo; } + /** + * Set the Uri Info + * @param uriInfo the uri info + */ + public void setUriInfo(UriInfo uriInfo) { + this.theUriInfo = uriInfo; + } + /** * Get the headers * @return the headers */ public HttpHeaders getHeaders() { - return theHeaders; + return this.theHeaders; + } + + /** + * Set the headers + * @param headers the headers to set + */ + public void setHeaders(HttpHeaders headers) { + this.theHeaders = headers; } /** diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java index 93543dafee6..b49cddc82ca 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java @@ -31,6 +31,7 @@ import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.BundleInclusionRule; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IPagingProvider; +import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; @@ -43,7 +44,7 @@ import ca.uhn.fhir.util.UrlUtil; */ @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN}) @Consumes({MediaType.APPLICATION_FORM_URLENCODED,MediaType.APPLICATION_JSON, "application/json+fhir", "application/xml+fhir"}) -public abstract class AbstractJaxRsResourceProvider extends AbstractJaxRsProvider implements IJaxRsResourceProvider { +public abstract class AbstractJaxRsResourceProvider extends AbstractJaxRsProvider implements IRestfulServer { private final MethodBindings bindings; @@ -56,9 +57,9 @@ public abstract class AbstractJaxRsResourceProvider extends } @Override - public String getBaseUri() { + public String getBaseUri() { try { - return new URL(getInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); + return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); } catch(Exception e) { // cannot happen return null; @@ -66,7 +67,6 @@ public abstract class AbstractJaxRsResourceProvider extends } @POST - @Override @Interceptors(JaxRsExceptionInterceptor.class) public Response create(final String resourceString) throws Exception { @@ -76,20 +76,17 @@ public abstract class AbstractJaxRsResourceProvider extends @POST @Interceptors(JaxRsExceptionInterceptor.class) @Path("/_search") - @Override public Response searchWithPost() throws Exception { return executeMethod(null, RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE, null); } @GET - @Override @Interceptors(JaxRsExceptionInterceptor.class) public Response search() throws Exception { return executeMethod(null, RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE, null); } @PUT - @Override @Path("/{id}") @Interceptors(JaxRsExceptionInterceptor.class) public Response update(@PathParam("id") final String id, final String resourceString) @@ -98,7 +95,6 @@ public abstract class AbstractJaxRsResourceProvider extends } @DELETE - @Override @Path("/{id}") @Interceptors(JaxRsExceptionInterceptor.class) public Response delete(@PathParam("id") final String id) throws Exception { @@ -107,7 +103,6 @@ public abstract class AbstractJaxRsResourceProvider extends @GET - @Override @Path("/{id}") @Interceptors(JaxRsExceptionInterceptor.class) public Response find(@PathParam("id") final String id) throws Exception { @@ -120,7 +115,6 @@ public abstract class AbstractJaxRsResourceProvider extends } @GET - @Override @Path("/{id}/_history/{version}") @Interceptors(JaxRsExceptionInterceptor.class) public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String versionString) @@ -128,21 +122,20 @@ public abstract class AbstractJaxRsResourceProvider extends BaseMethodBinding method = getBindings().getBinding(RestOperationTypeEnum.VREAD); final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); if (id == null) { - throw new InvalidRequestException("Don't know how to handle request path: " + getInfo().getRequestUri().toASCIIString()); + throw new InvalidRequestException("Don't know how to handle request path: " + getUriInfo().getRequestUri().toASCIIString()); } theRequest.setId(new IdDt(getBaseUri(), id, UrlUtil.unescape(versionString))); return (Response) method.invokeServer(this, theRequest); } - @GET - @Override + @GET @Path("/{id}/{compartment}") @Interceptors(JaxRsExceptionInterceptor.class) public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) throws BaseServerResponseException, IOException { BaseMethodBinding method = getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE, compartment); final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); if (id == null) { - throw new InvalidRequestException("Don't know how to handle request path: " + getInfo().getRequestUri().toASCIIString()); + throw new InvalidRequestException("Don't know how to handle request path: " + getUriInfo().getRequestUri().toASCIIString()); } theRequest.setCompartmentName(compartment); theRequest.setId(new IdDt(getBaseUri(), id)); diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsConformanceProvider.java deleted file mode 100644 index 54bf5f8fa2f..00000000000 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsConformanceProvider.java +++ /dev/null @@ -1,8 +0,0 @@ -package ca.uhn.fhir.jaxrs.server; - -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.IRestfulServerDefaults; - -public interface IJaxRsConformanceProvider extends IResourceProvider, IRestfulServerDefaults { - -} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java deleted file mode 100644 index 21ac39a784f..00000000000 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/IJaxRsResourceProvider.java +++ /dev/null @@ -1,36 +0,0 @@ -package ca.uhn.fhir.jaxrs.server; - -import java.io.IOException; - -import javax.ws.rs.core.Response; - -import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.IRestfulServer; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; - -public interface IJaxRsResourceProvider extends IRestfulServer, IResourceProvider { - - Response search() - throws Exception; - - Response create(String resourceString) - throws Exception; - - Response searchWithPost() - throws Exception; - - Response find(String id) throws Exception; - - Response update(String id, String resourceString) - throws Exception; - - Response delete(String id) - throws Exception; - - Response findHistory(String id, String version) throws BaseServerResponseException, IOException; - - Response findCompartment(String id, String compartment) throws BaseServerResponseException, IOException; - - -} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java index df53b52927f..332d0150840 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java @@ -13,6 +13,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.ResponseBuilder; +import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseBinary; import ca.uhn.fhir.parser.IParser; @@ -43,7 +44,8 @@ public class JaxRsResponse extends RestfulResponse { @Override public Response sendWriterResponse(int status, String contentType, String charset, Writer writer) { Object entity = writer instanceof StringWriter ? writer.toString() : writer; - return buildResponse(status)/*.header(HttpHeaders.CONTENT_TYPE, charset)*/.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(entity).build(); + String charContentType = contentType+";charset="+StringUtils.defaultIfBlank(charset, Constants.CHARSET_NAME_UTF8); + return buildResponse(status).header(Constants.HEADER_CONTENT_TYPE, charContentType).entity(entity).build(); } @Override diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java new file mode 100644 index 00000000000..59911260c2a --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java @@ -0,0 +1,100 @@ +package ca.uhn.fhir.jaxrs.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import java.net.URI; +import java.util.Arrays; +import java.util.concurrent.ConcurrentHashMap; + +import javax.ws.rs.HttpMethod; +import javax.ws.rs.core.MultivaluedHashMap; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import org.glassfish.jersey.internal.MapPropertiesDelegate; +import org.glassfish.jersey.server.ContainerRequest; +import org.junit.Before; +import org.junit.Test; + +import ca.uhn.fhir.jaxrs.server.example.TestJaxRsPatientRestProvider; +import ca.uhn.fhir.jaxrs.server.example.TestDummyPatientProvider; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IResourceProvider; + +public class AbstractJaxRsConformanceProviderTest { + + private static final String BASEURI = "http://basiuri"; + private static final String REQUESTURI = BASEURI + "/metadata"; + AbstractJaxRsConformanceProvider provider; + private ConcurrentHashMap, IResourceProvider> providers; + private ContainerRequest headers; + private MultivaluedHashMap queryParameters; + + @Before + public void setUp() throws Exception { + // headers + headers = new ContainerRequest(new URI(BASEURI), new URI(REQUESTURI), HttpMethod.GET, null, + new MapPropertiesDelegate()); + // uri info + queryParameters = new MultivaluedHashMap(); + + + providers = new ConcurrentHashMap, IResourceProvider>(); + provider = createConformanceProvider(providers); + } + + @Test + public void testConformance() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestDummyPatientProvider.class, new TestDummyPatientProvider()); + Response response = createConformanceProvider(providers).conformance(); + System.out.println(response); + } + + @Test + public void testConformanceWithMethods() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsPatientRestProvider.class, new TestJaxRsPatientRestProvider()); + Response response = createConformanceProvider(providers).conformance(); + assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); + assertTrue(response.getEntity().toString().contains("\"type\":\"Patient\"")); + assertTrue(response.getEntity().toString().contains("\"$last")); + System.out.println(response); + System.out.println(response.getEntity()); + } + + @Test + public void testConformanceInXml() throws Exception { + queryParameters.put(Constants.PARAM_FORMAT, Arrays.asList(Constants.CT_XML)); + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsPatientRestProvider.class, new TestJaxRsPatientRestProvider()); + Response response = createConformanceProvider(providers).conformance(); + assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); + System.out.println(response.getEntity()); + assertTrue(response.getEntity().toString().contains(" ")); + assertTrue(response.getEntity().toString().contains("\"$last")); + System.out.println(response.getEntity()); + } + + private AbstractJaxRsConformanceProvider createConformanceProvider(final ConcurrentHashMap, IResourceProvider> providers) + throws Exception { + AbstractJaxRsConformanceProvider result = new AbstractJaxRsConformanceProvider(null, null, null) { + @Override + protected ConcurrentHashMap, IResourceProvider> getProviders() { + return providers; + } + }; + // mocks + UriInfo uriInfo = mock(UriInfo.class); + when(uriInfo.getQueryParameters()).thenReturn(queryParameters); + when(uriInfo.getBaseUri()).thenReturn(new URI(BASEURI)); + result.setUriInfo(uriInfo); + result.setHeaders(headers); + result.setUpPostConstruct(); + return result; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/JaxRsPatientProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java similarity index 98% rename from hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/JaxRsPatientProviderTest.java rename to hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java index 3d4af441134..71bafdfdf5c 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/JaxRsPatientProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jaxrs.server; +package ca.uhn.fhir.jaxrs.server.example; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; @@ -16,8 +16,6 @@ import org.junit.Ignore; import org.junit.Test; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jaxrs.server.example.JaxRsPatientRestProvider; -import ca.uhn.fhir.jaxrs.server.example.RandomServerPortProvider; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; @@ -56,7 +54,7 @@ public class JaxRsPatientProviderTest { jettyServer.setHandler(context); ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/*"); jerseyServlet.setInitOrder(0); - jerseyServlet.setInitParameter("jersey.config.server.provider.classnames", JaxRsPatientRestProvider.class.getCanonicalName()); + jerseyServlet.setInitParameter("jersey.config.server.provider.classnames", TestJaxRsPatientRestProvider.class.getCanonicalName() + "," +TestJaxRsConformanceRestProvider.class.getCanonicalName()); jettyServer.start(); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); @@ -75,7 +73,7 @@ public class JaxRsPatientProviderTest { catch (Exception e) { } } - + /** Search/Query - Type */ @Test public void findUsingGenericClientBySearch() { @@ -236,7 +234,6 @@ public class JaxRsPatientProviderTest { /** Conformance - Server */ @Test - @Ignore public void testConformance() { final Conformance conf = client.fetchConformance().ofType(Conformance.class).execute(); System.out.println(conf.getRest().get(0).getResource().get(0).getType()); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java new file mode 100644 index 00000000000..3570d8c62f9 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java @@ -0,0 +1,35 @@ +package ca.uhn.fhir.jaxrs.server.example; + +import java.util.concurrent.ConcurrentHashMap; + +import javax.ejb.Stateless; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsConformanceProvider; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IResourceProvider; + +/** + * Fhir Physician Rest Service + * @author axmpm + * + */ +@Path(TestJaxRsConformanceRestProvider.PATH) +@Stateless +@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) +public class TestJaxRsConformanceRestProvider extends AbstractJaxRsConformanceProvider { + + public TestJaxRsConformanceRestProvider() { + super("", "", ""); + } + + @Override + protected ConcurrentHashMap, IResourceProvider> getProviders() throws Exception { + ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>(); + map.put(TestJaxRsPatientRestProvider.class, new TestJaxRsPatientRestProvider()); + map.put(TestJaxRsConformanceRestProvider.class, new TestJaxRsConformanceRestProvider()); + return map; + } +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsPatientRestProvider.java similarity index 97% rename from hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java rename to hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsPatientRestProvider.java index feb1b2295b6..8da22fb88ec 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsPatientRestProvider.java @@ -53,18 +53,18 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; * @author axmpm * */ -@Path(JaxRsPatientRestProvider.PATH) +@Path(TestJaxRsPatientRestProvider.PATH) @Stateless @Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) -public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider /*implements IJaxRsPatientProvider*/ { +public class TestJaxRsPatientRestProvider extends AbstractJaxRsResourceProvider { static final String PATH = "/Patient"; private static Long counter = 1L; private static final ConcurrentHashMap> patients = new ConcurrentHashMap>(); - public JaxRsPatientRestProvider() throws Exception { - super(JaxRsPatientRestProvider.class); + public TestJaxRsPatientRestProvider() throws Exception { + super(TestJaxRsPatientRestProvider.class); } static { diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java index 507d1f1afc6..bfb789b78a9 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java @@ -111,7 +111,7 @@ public class JaxRsRequestTest { //mocks provider = spy(TestDummyPatientProvider.class); - doReturn(uriInfo).when(provider).getInfo(); + doReturn(uriInfo).when(provider).getUriInfo(); doReturn(BASEURI).when(provider).getBaseUri(); doReturn(headers).when(provider).getHeaders(); From a262bf64d05d167d39b99a159d77cc006e110686 Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Thu, 29 Oct 2015 15:54:09 +0100 Subject: [PATCH 06/13] fix paging --- .../rest/method/BaseResourceReturningMethodBinding.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java index 646d3cd484a..375a843df9e 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java @@ -337,9 +337,16 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding Date: Thu, 29 Oct 2015 15:55:37 +0100 Subject: [PATCH 07/13] fix return url --- .../src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java | 4 ++++ .../main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java | 2 +- .../fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java | 1 + 3 files changed, 6 insertions(+), 1 deletion(-) diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java index 79835e79967..4d99a73beb6 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/RequestDetails.java @@ -62,6 +62,10 @@ public abstract class RequestDetails { return myCompleteUrl; } + /** + * The fhir server base url, independant of the query being executed + * @return the fhir server base url + */ public String getFhirServerBase() { return myFhirServerBase; } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java index 92694476b96..7e09b7b061a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/RestfulServerUtils.java @@ -657,7 +657,7 @@ public class RestfulServerUtils { } - static Integer tryToExtractNamedParameter(RequestDetails theRequest, String name) { + public static Integer tryToExtractNamedParameter(RequestDetails theRequest, String name) { String[] countString = theRequest.getParameters().get(name); Integer count = null; if (countString != null && countString.length > 0 && isNotBlank(countString[0])) { diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java index 686cead9059..480be6d5aa7 100644 --- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java +++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/Dstu2BundleFactory.java @@ -382,6 +382,7 @@ public class Dstu2BundleFactory implements IVersionSpecificBundleFactory { addRootPropertiesToBundle(null, theServerBase, theCompleteUrl, theResult.size(), theBundleType, theResult.getPublished()); if (theServer.getPagingProvider() != null) { + theServerBase = theCompleteUrl.contains("?") ? theCompleteUrl.substring(0, theCompleteUrl.indexOf("?")) : theCompleteUrl; int limit; limit = theLimit != null ? theLimit : theServer.getPagingProvider().getDefaultPageSize(); limit = Math.min(limit, theServer.getPagingProvider().getMaximumPageSize()); From 54ad29edc108a0589097816e7e67e01f0e80d215 Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Thu, 29 Oct 2015 15:57:30 +0100 Subject: [PATCH 08/13] add test + drop implementation implementation for StaticJaxRsServer --- .../AbstractJaxRsConformanceProvider.java | 2 +- .../jaxrs/server/AbstractJaxRsProvider.java | 18 +- .../server/AbstractJaxRsResourceProvider.java | 12 +- .../fhir/jaxrs/server/StaticJaxRsServer.java | 46 -- .../fhir/jaxrs/server/util/JaxRsRequest.java | 103 ++--- .../fhir/jaxrs/server/util/JaxRsResponse.java | 95 ++-- .../AbstractJaxRsConformanceProviderTest.java | 10 +- .../AbstractJaxRsResourceProviderTest.java | 417 ++++++++++++++++++ .../example/JaxRsPatientProviderTest.java | 315 ------------- .../TestJaxRsConformanceRestProvider.java | 11 +- .../TestJaxRsMockPatientRestProvider.java | 134 ++++++ .../example/TestJaxRsPatientRestProvider.java | 269 ----------- .../jaxrs/server/util/JaxRsRequestTest.java | 3 +- .../jaxrs/server/util/JaxRsResponseTest.java | 22 +- hapi-fhir-jaxrsserver-example/pom.xml | 58 +-- .../server/example/IJaxRsPatientProvider.java | 45 -- .../example/JaxRsConformanceProvider.java | 53 +-- .../example/JaxRsPatientRestProvider.java | 396 ++++++++--------- .../example/JaxRsPatientProviderTest.java | 24 +- 19 files changed, 916 insertions(+), 1117 deletions(-) delete mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java delete mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsMockPatientRestProvider.java delete mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsPatientRestProvider.java delete mode 100644 hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IJaxRsPatientProvider.java diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java index b3a9be6edbe..513be4925fa 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java @@ -79,7 +79,7 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv serverConfiguration.setServerBindings(serverBindings); serverConfiguration.setResourceBindings(new LinkedList(myResourceNameToBinding.values())); HardcodedServerAddressStrategy hardcodedServerAddressStrategy = new HardcodedServerAddressStrategy(); - hardcodedServerAddressStrategy.setValue(getBaseUri()); + hardcodedServerAddressStrategy.setValue(getBaseForServer()); serverConfiguration.setServerAddressStrategy(hardcodedServerAddressStrategy); ServerConformanceProvider serverConformanceProvider = new ServerConformanceProvider(serverConfiguration); serverConformanceProvider.initializeOperations(); diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index 88a4518fb96..7ebeefc3ddd 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -11,7 +11,6 @@ import javax.ws.rs.core.UriInfo; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; -import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.AddProfileTagEnum; @@ -21,7 +20,6 @@ import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IRestfulServerDefaults; import ca.uhn.fhir.rest.server.IServerAddressStrategy; -import ca.uhn.fhir.rest.server.RestfulServerUtils; /** * Abstract Jax Rs Rest Server @@ -56,21 +54,21 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults, I public IServerAddressStrategy getServerAddressStrategy() { HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy(); - addressStrategy.setValue(getBaseUri()); + addressStrategy.setValue(getBaseForRequest()); return addressStrategy; - } - - public String getBaseUri() { + } + + public String getBaseForServer() { return getUriInfo().getBaseUri().toASCIIString(); } + + public String getBaseForRequest() { + return getBaseForServer(); + } /** * PARSING METHODS */ - public IParser getParser(JaxRsRequest theRequestDetails) { - return RestfulServerUtils.getNewParser(getFhirContext(), theRequestDetails); - } - protected JaxRsRequest createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { return new JaxRsRequest(this, resourceString, requestType, restOperation); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java index b49cddc82ca..5783119056f 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java @@ -18,6 +18,7 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.Validate; import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; @@ -43,7 +44,7 @@ import ca.uhn.fhir.util.UrlUtil; * */ @Produces({MediaType.APPLICATION_JSON,MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN}) -@Consumes({MediaType.APPLICATION_FORM_URLENCODED,MediaType.APPLICATION_JSON, "application/json+fhir", "application/xml+fhir"}) +@Consumes({MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) public abstract class AbstractJaxRsResourceProvider extends AbstractJaxRsProvider implements IRestfulServer { private final MethodBindings bindings; @@ -56,8 +57,8 @@ public abstract class AbstractJaxRsResourceProvider extends bindings = MethodBindings.getMethodBindings(this, subclass); } - @Override - public String getBaseUri() { + @Override + public String getBaseForRequest() { try { return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); } catch(Exception e) { @@ -111,6 +112,7 @@ public abstract class AbstractJaxRsResourceProvider extends protected Response customOperation(final String resource, RequestTypeEnum requestType, String id, String operationName, RestOperationTypeEnum operationType) throws Exception { + Validate.notNull(resource, "resource may not be null"); return executeMethod(resource, requestType, operationType, id, getBindings().getBinding(operationType, operationName)); } @@ -124,7 +126,7 @@ public abstract class AbstractJaxRsResourceProvider extends if (id == null) { throw new InvalidRequestException("Don't know how to handle request path: " + getUriInfo().getRequestUri().toASCIIString()); } - theRequest.setId(new IdDt(getBaseUri(), id, UrlUtil.unescape(versionString))); + theRequest.setId(new IdDt(getBaseForRequest(), id, UrlUtil.unescape(versionString))); return (Response) method.invokeServer(this, theRequest); } @@ -138,7 +140,7 @@ public abstract class AbstractJaxRsResourceProvider extends throw new InvalidRequestException("Don't know how to handle request path: " + getUriInfo().getRequestUri().toASCIIString()); } theRequest.setCompartmentName(compartment); - theRequest.setId(new IdDt(getBaseUri(), id)); + theRequest.setId(new IdDt(getBaseForRequest(), id)); return (Response) method.invokeServer(this, theRequest); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java deleted file mode 100644 index 14c95b52bc0..00000000000 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/StaticJaxRsServer.java +++ /dev/null @@ -1,46 +0,0 @@ -package ca.uhn.fhir.jaxrs.server; - -import javax.ejb.Local; -import javax.ejb.Stateless; -import javax.interceptor.Interceptors; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import org.slf4j.LoggerFactory; - -import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; -import ca.uhn.fhir.model.api.Bundle; -import ca.uhn.fhir.rest.annotation.Transaction; -import ca.uhn.fhir.rest.annotation.TransactionParam; - -/** - * Rest Service for static requests such as - * @author Peter Van Houte - */ -@Local -@Path(StaticJaxRsServer.PATH) -@Stateless -@Produces(MediaType.APPLICATION_JSON) -public class StaticJaxRsServer { - - private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(StaticJaxRsServer.class); - static final String PATH = "/"; - - @POST - @Path("/") - @Interceptors(JaxRsExceptionInterceptor.class) - public Response transaction(final String resource) { - ourLog.debug("calling transaction method"); - return null; - } - - @Transaction - public Bundle transaction(@TransactionParam Bundle theResources) { - ourLog.debug("transaction implemented"); - return theResources; - } - -} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java index 3bbf2b1833a..e41816aeb23 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java @@ -8,6 +8,8 @@ import java.util.List; import javax.ws.rs.core.HttpHeaders; +import org.apache.commons.lang3.StringUtils; + import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; @@ -17,73 +19,72 @@ import ca.uhn.fhir.rest.server.IRestfulResponse; public class JaxRsRequest extends RequestDetails { - private String theResourceString; - private HttpHeaders headers; - private AbstractJaxRsProvider myServer; + private String theResourceString; + private HttpHeaders headers; + private AbstractJaxRsProvider myServer; public JaxRsRequest() { - } - - public JaxRsRequest(AbstractJaxRsProvider server, String resourceString, - RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { - this.headers = server.getHeaders(); - this.theResourceString = resourceString; - this.setRestOperationType(restOperation); - setServer(server); - setFhirServerBase(server.getBaseUri()); - setParameters(server.getQueryMap()); - setRequestType(requestType); + } + + public JaxRsRequest(AbstractJaxRsProvider server, String resourceString, RequestTypeEnum requestType, + RestOperationTypeEnum restOperation) { + this.headers = server.getHeaders(); + this.theResourceString = resourceString; + this.setRestOperationType(restOperation); + setServer(server); + setFhirServerBase(server.getBaseForServer()); + setParameters(server.getQueryMap()); + setRequestType(requestType); } @Override public AbstractJaxRsProvider getServer() { - return myServer; - } + return myServer; + } - public void setServer(AbstractJaxRsProvider theServer) { - this.myServer = theServer; - } + public void setServer(AbstractJaxRsProvider theServer) { + this.myServer = theServer; + } - @Override - public String getHeader(String headerKey) { - List requestHeader = getHeaders(headerKey); - return requestHeader.isEmpty() ? null : requestHeader.get(0); - } + @Override + public String getHeader(String headerKey) { + List requestHeader = getHeaders(headerKey); + return requestHeader.isEmpty() ? null : requestHeader.get(0); + } - @Override - public List getHeaders(String name) { - List requestHeader = headers.getRequestHeader(name); - return requestHeader == null ? Collections. emptyList() : requestHeader; - } + @Override + public List getHeaders(String name) { + List requestHeader = headers.getRequestHeader(name); + return requestHeader == null ? Collections. emptyList() : requestHeader; + } - @Override - public String getServerBaseForRequest() { - return getServer().getServerAddressStrategy().determineServerBase(null, null); - } + @Override + public String getServerBaseForRequest() { + return getServer().getServerAddressStrategy().determineServerBase(null, null); + } @Override protected byte[] getByteStreamRequestContents() { - return theResourceString.getBytes(ResourceParameter.determineRequestCharset(this)); + return StringUtils.defaultIfEmpty(theResourceString, "").getBytes(ResourceParameter.determineRequestCharset(this)); } - - @Override + + @Override public IRestfulResponse getResponse() { - if(super.getResponse() == null) { - setResponse(new JaxRsResponse(this)); + if (super.getResponse() == null) { + setResponse(new JaxRsResponse(this)); } return super.getResponse(); - } - - @Override - public Reader getReader() - throws IOException { - // not yet implemented - throw new UnsupportedOperationException(); - } + } - @Override - public InputStream getInputStream() { - // not yet implemented - throw new UnsupportedOperationException(); - } + @Override + public Reader getReader() throws IOException { + // not yet implemented + throw new UnsupportedOperationException(); + } + + @Override + public InputStream getInputStream() { + // not yet implemented + throw new UnsupportedOperationException(); + } } \ No newline at end of file diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java index 332d0150840..74f2cc53551 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java @@ -1,13 +1,10 @@ package ca.uhn.fhir.jaxrs.server.util; -import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.OutputStreamWriter; import java.io.StringWriter; import java.io.UnsupportedEncodingException; import java.io.Writer; import java.util.Map.Entry; -import java.util.zip.GZIPOutputStream; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; @@ -26,60 +23,56 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils; public class JaxRsResponse extends RestfulResponse { - public JaxRsResponse(JaxRsRequest jaxRsRequestDetails) { - super(jaxRsRequestDetails); - } + public JaxRsResponse(JaxRsRequest jaxRsRequestDetails) { + super(jaxRsRequestDetails); + } - @Override - public Writer getResponseWriter(int statusCode, String contentType, String charset, boolean respondGzip) - throws UnsupportedEncodingException, IOException { - if (respondGzip) { - addHeader(Constants.HEADER_CONTENT_ENCODING, Constants.ENCODING_GZIP); - return new OutputStreamWriter(new GZIPOutputStream(new ByteArrayOutputStream()), Constants.CHARSET_NAME_UTF8); - } else { - return new StringWriter(); - } - } + @Override + public Writer getResponseWriter(int statusCode, String contentType, String charset, boolean respondGzip) + throws UnsupportedEncodingException, IOException { + return new StringWriter(); + } - @Override - public Response sendWriterResponse(int status, String contentType, String charset, Writer writer) { - Object entity = writer instanceof StringWriter ? writer.toString() : writer; - String charContentType = contentType+";charset="+StringUtils.defaultIfBlank(charset, Constants.CHARSET_NAME_UTF8); - return buildResponse(status).header(Constants.HEADER_CONTENT_TYPE, charContentType).entity(entity).build(); - } + @Override + public Response sendWriterResponse(int status, String contentType, String charset, Writer writer) { + String charContentType = contentType + "; charset=" + + StringUtils.defaultIfBlank(charset, Constants.CHARSET_NAME_UTF8); + return buildResponse(status).header(Constants.HEADER_CONTENT_TYPE, charContentType).entity(writer.toString()) + .build(); + } - @Override - public Response sendAttachmentResponse(IBaseBinary bin, int statusCode, String contentType) throws IOException { - ResponseBuilder response = buildResponse(statusCode); - if (bin.getContent() != null && bin.getContent().length > 0) { - response.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(bin.getContent()); - } - return response.build(); - } + @Override + public Response sendAttachmentResponse(IBaseBinary bin, int statusCode, String contentType) throws IOException { + ResponseBuilder response = buildResponse(statusCode); + if (bin.getContent() != null && bin.getContent().length > 0) { + response.header(Constants.HEADER_CONTENT_TYPE, contentType).entity(bin.getContent()); + } + return response.build(); + } + + @Override + public Response returnResponse(ParseAction outcome, int operationStatus, boolean allowPrefer, + MethodOutcome response, String resourceName) throws IOException { + StringWriter writer = new StringWriter(); + if (outcome != null) { + IParser parser = RestfulServerUtils.getNewParser(getRequestDetails().getServer().getFhirContext(), + getRequestDetails()); + outcome.execute(parser, writer); + } + return sendWriterResponse(operationStatus, getParserType(), null, writer); + } + + protected String getParserType() { + EncodingEnum encodingEnum = RestfulServerUtils.determineResponseEncodingWithDefault(getRequestDetails()); + return encodingEnum == EncodingEnum.JSON ? MediaType.APPLICATION_JSON : MediaType.APPLICATION_XML; + } - @Override - public Response returnResponse(ParseAction outcome, int operationStatus, boolean allowPrefer, MethodOutcome response, - String resourceName) - throws IOException { - StringWriter writer = new StringWriter(); - if(outcome != null) { - IParser parser = RestfulServerUtils.getNewParser(getRequestDetails().getServer().getFhirContext(), getRequestDetails()); - outcome.execute(parser, writer); - } - return sendWriterResponse(operationStatus, getParserType(), null, writer); - } - - protected String getParserType() { - EncodingEnum encodingEnum = RestfulServerUtils.determineResponseEncodingWithDefault(getRequestDetails()); - return encodingEnum == EncodingEnum.JSON ? MediaType.APPLICATION_JSON : MediaType.APPLICATION_XML; - } - private ResponseBuilder buildResponse(int statusCode) { ResponseBuilder response = Response.status(statusCode); - for (Entry header : getHeaders().entrySet()) { - response.header(header.getKey(), header.getValue()); - } + for (Entry header : getHeaders().entrySet()) { + response.header(header.getKey(), header.getValue()); + } return response; - } + } } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java index 59911260c2a..bd8b424a371 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java @@ -19,8 +19,8 @@ import org.glassfish.jersey.server.ContainerRequest; import org.junit.Before; import org.junit.Test; -import ca.uhn.fhir.jaxrs.server.example.TestJaxRsPatientRestProvider; import ca.uhn.fhir.jaxrs.server.example.TestDummyPatientProvider; +import ca.uhn.fhir.jaxrs.server.example.TestJaxRsMockPatientRestProvider; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IResourceProvider; @@ -57,11 +57,11 @@ public class AbstractJaxRsConformanceProviderTest { @Test public void testConformanceWithMethods() throws Exception { providers.put(AbstractJaxRsConformanceProvider.class, provider); - providers.put(TestJaxRsPatientRestProvider.class, new TestJaxRsPatientRestProvider()); + providers.put(TestJaxRsMockPatientRestProvider.class, new TestJaxRsMockPatientRestProvider()); Response response = createConformanceProvider(providers).conformance(); assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); assertTrue(response.getEntity().toString().contains("\"type\":\"Patient\"")); - assertTrue(response.getEntity().toString().contains("\"$last")); + assertTrue(response.getEntity().toString().contains("\"$someCustomOperation")); System.out.println(response); System.out.println(response.getEntity()); } @@ -70,12 +70,12 @@ public class AbstractJaxRsConformanceProviderTest { public void testConformanceInXml() throws Exception { queryParameters.put(Constants.PARAM_FORMAT, Arrays.asList(Constants.CT_XML)); providers.put(AbstractJaxRsConformanceProvider.class, provider); - providers.put(TestJaxRsPatientRestProvider.class, new TestJaxRsPatientRestProvider()); + providers.put(TestJaxRsMockPatientRestProvider.class, new TestJaxRsMockPatientRestProvider()); Response response = createConformanceProvider(providers).conformance(); assertEquals(Constants.STATUS_HTTP_200_OK, response.getStatus()); System.out.println(response.getEntity()); assertTrue(response.getEntity().toString().contains(" ")); - assertTrue(response.getEntity().toString().contains("\"$last")); + assertTrue(response.getEntity().toString().contains("\"$someCustomOperation")); System.out.println(response.getEntity()); } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java new file mode 100644 index 00000000000..8f384b3d229 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java @@ -0,0 +1,417 @@ +package ca.uhn.fhir.jaxrs.server; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNull; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.argThat; +import static org.mockito.Matchers.eq; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jetty.server.Server; +import org.eclipse.jetty.servlet.ServletContextHandler; +import org.eclipse.jetty.servlet.ServletHolder; +import org.hamcrest.BaseMatcher; +import org.hamcrest.Description; +import org.junit.AfterClass; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.FixMethodOrder; +import org.junit.Ignore; +import org.junit.Test; +import org.junit.runners.MethodSorters; +import org.mockito.ArgumentCaptor; +import org.mockito.Matchers; + +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jaxrs.server.example.RandomServerPortProvider; +import ca.uhn.fhir.jaxrs.server.example.TestJaxRsConformanceRestProvider; +import ca.uhn.fhir.jaxrs.server.example.TestJaxRsMockPatientRestProvider; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; +import ca.uhn.fhir.model.api.BundleEntry; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.base.resource.BaseOperationOutcome; +import ca.uhn.fhir.model.dstu2.resource.Bundle; +import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; +import ca.uhn.fhir.model.dstu2.resource.Conformance; +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.BoundCodeDt; +import ca.uhn.fhir.model.primitive.DateDt; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.model.primitive.UriDt; +import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.PreferReturnEnum; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.ServerValidationModeEnum; +import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; +import ca.uhn.fhir.rest.method.SearchStyleEnum; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.EncodingEnum; +import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; + +@FixMethodOrder(MethodSorters.NAME_ASCENDING) +public class AbstractJaxRsResourceProviderTest { + + private TestJaxRsMockPatientRestProvider mock; + + private ArgumentCaptor idCaptor; + private ArgumentCaptor patientCaptor; + + private static IGenericClient client; + private static final FhirContext ourCtx = FhirContext.forDstu2(); + private static final String PATIENT_NAME = "Van Houte"; + private static int ourPort; + private static Server jettyServer; + + @BeforeClass + public static void setUpClass() throws Exception { + ourPort = RandomServerPortProvider.findFreePort(); + ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); + context.setContextPath("/"); + System.out.println(ourPort); + jettyServer = new Server(ourPort); + jettyServer.setHandler(context); + ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/*"); + jerseyServlet.setInitOrder(0); + jerseyServlet.setInitParameter("jersey.config.server.provider.classnames", + StringUtils.join(Arrays.asList(TestJaxRsMockPatientRestProvider.class.getCanonicalName(), + JaxRsExceptionInterceptor.class.getCanonicalName(), + TestJaxRsConformanceRestProvider.class.getCanonicalName()), ";")); + jettyServer.start(); + + final FhirContext ctx = FhirContext.forDstu2(); + ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); + ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); + client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); + client.setEncoding(EncodingEnum.JSON); + client.registerInterceptor(new LoggingInterceptor(true)); + } + + @AfterClass + public static void tearDownClass() throws Exception { + try { + jettyServer.destroy(); + } catch (Exception e) { + + } + } + + @Before + public void setUp() { + this.mock = TestJaxRsMockPatientRestProvider.mock; + idCaptor = ArgumentCaptor.forClass(IdDt.class); + patientCaptor = ArgumentCaptor.forClass(Patient.class); + reset(mock); + } + + /** Search/Query - Type */ + @Test + public void testSearchUsingGenericClientBySearch() { + // Perform a search + when(mock.search(any(StringParam.class), Matchers.isNull(StringAndListParam.class))) + .thenReturn(Arrays.asList(createPatient(1))); + final ca.uhn.fhir.model.api.Bundle results = client.search().forResource(Patient.class) + .where(Patient.NAME.matchesExactly().value(PATIENT_NAME)).execute(); + verify(mock).search(any(StringParam.class), Matchers.isNull(StringAndListParam.class)); + IResource resource = results.getEntries().get(0).getResource(); + + compareResultId(1, resource); + compareResultUrl("/Patient/1", resource); + } + + /** Search - Multi-valued Parameters (ANY/OR) */ + @Test + public void testSearchUsingGenericClientBySearchWithMultiValues() { + when(mock.search(any(StringParam.class), Matchers.isNotNull(StringAndListParam.class))) + .thenReturn(Arrays.asList(createPatient(1))); + final ca.uhn.fhir.model.api.Bundle results = client.search().forResource(Patient.class) + .where(Patient.ADDRESS.matches().values("Toronto")).and(Patient.ADDRESS.matches().values("Ontario")) + .and(Patient.ADDRESS.matches().values("Canada")) + .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("SHORTNAME", "TOYS")).execute(); + IResource resource = results.getEntries().get(0).getResource(); + + compareResultId(1, resource); + compareResultUrl("/Patient/1", resource); + } + + /** Search - Paging */ + @Test + public void testSearchWithPaging() { + // Perform a search + when(mock.search(any(StringParam.class), Matchers.isNull(StringAndListParam.class))) + .thenReturn(createPatients(1, 13)); + final Bundle results = client.search().forResource(Patient.class).limitTo(8).returnBundle(Bundle.class) + .execute(); + + assertEquals(results.getEntry().size(), 8); + IResource resource = results.getEntry().get(0).getResource(); + compareResultId(1, resource); + compareResultUrl("/Patient/1", resource); + compareResultId(8, results.getEntry().get(7).getResource()); + // load next page + final Bundle nextPage = client.loadPage().next(results).execute(); + resource = nextPage.getEntry().get(0).getResource(); + compareResultId(9, resource); + compareResultUrl("/Patient/9", resource); + assertNull(nextPage.getLink(Bundle.LINK_NEXT)); + } + + /** Search using other query options */ + public void testOther() { + // missing + } + + /** */ + @Test + public void testSearchPost() { + when(mock.search(any(StringParam.class), Matchers.isNull(StringAndListParam.class))) + .thenReturn(createPatients(1, 13)); + Bundle result = client.search().forResource("Patient").usingStyle(SearchStyleEnum.POST) + .returnBundle(Bundle.class).execute(); + IResource resource = result.getEntry().get(0).getResource(); + compareResultId(1, resource); + compareResultUrl("/Patient/1", resource); + } + + /** Search - Compartments */ + @Test + public void testSearchCompartements() { + when(mock.searchCompartment(any(IdDt.class))).thenReturn(Arrays.asList((IResource) createPatient(1))); + Bundle response = client.search().forResource(Patient.class).withIdAndCompartment("1", "Condition") + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class).execute(); + IResource resource = response.getEntry().get(0).getResource(); + compareResultId(1, resource); + compareResultUrl("/Patient/1", resource); + } + + /** Search - Subsetting (_summary and _elements) */ + @Test + @Ignore + public void testSummary() { + Object response = client.search().forResource(Patient.class) + .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class).execute(); + } + + @Test + public void testCreatePatient() throws Exception { + Patient toCreate = createPatient(1); + MethodOutcome outcome = new MethodOutcome(); + toCreate.getIdentifierFirstRep().setValue("myIdentifier"); + outcome.setResource(toCreate); + + when(mock.create(patientCaptor.capture(), isNull(String.class))).thenReturn(outcome); + client.setEncoding(EncodingEnum.JSON); + final MethodOutcome response = client.create().resource(toCreate).prefer(PreferReturnEnum.REPRESENTATION) + .execute(); + IResource resource = (IResource) response.getResource(); + compareResultId(1, resource); + assertEquals("myIdentifier", patientCaptor.getValue().getIdentifierFirstRep().getValue()); + } + + /** Conditional Creates */ + @Test + public void testConditionalCreate() throws Exception { + Patient toCreate = createPatient(1); + MethodOutcome outcome = new MethodOutcome(); + toCreate.getIdentifierFirstRep().setValue("myIdentifier"); + outcome.setResource(toCreate); + + when(mock.create(patientCaptor.capture(), eq("Patient?_format=json&identifier=2"))).thenReturn(outcome); + client.setEncoding(EncodingEnum.JSON); + + MethodOutcome response = client.create().resource(toCreate).conditional() + .where(Patient.IDENTIFIER.exactly().identifier("2")).prefer(PreferReturnEnum.REPRESENTATION).execute(); + + assertEquals("myIdentifier", patientCaptor.getValue().getIdentifierFirstRep().getValue()); + IResource resource = (IResource) response.getResource(); + compareResultId(1, resource); + } + + /** Find By Id */ + @Test + public void findUsingGenericClientById() { + when(mock.find(any(IdDt.class))).thenReturn(createPatient(1)); + Patient result = client.read(Patient.class, "1"); + compareResultId(1, result); + compareResultUrl("/Patient/1", result); + reset(mock); + when(mock.find(withId(result.getId()))).thenReturn(createPatient(1)); + result = (Patient) client.read(new UriDt(result.getId().getValue())); + compareResultId(1, result); + compareResultUrl("/Patient/1", result); + } + + @Test + public void testUpdateById() throws Exception { + when(mock.update(idCaptor.capture(), patientCaptor.capture())).thenReturn(new MethodOutcome()); + client.update("1", createPatient(2)); + assertEquals("1", idCaptor.getValue().getIdPart()); + compareResultId(1, patientCaptor.getValue()); + } + + @Test + public void testDeletePatient() { + when(mock.delete(idCaptor.capture())).thenReturn(new MethodOutcome()); + final BaseOperationOutcome results = client.delete().resourceById("Patient", "1").execute(); + assertEquals("1", idCaptor.getValue().getIdPart()); + } + + /** Transaction - Server */ + @Ignore + @Test + public void testTransaction() { + ca.uhn.fhir.model.api.Bundle bundle = new ca.uhn.fhir.model.api.Bundle(); + BundleEntry entry = bundle.addEntry(); + final Patient existing = new Patient(); + existing.getNameFirstRep().addFamily("Created with bundle"); + entry.setResource(existing); + + BoundCodeDt theTransactionOperation = new BoundCodeDt( + BundleEntryTransactionMethodEnum.VALUESET_BINDER, BundleEntryTransactionMethodEnum.POST); + entry.setTransactionMethod(theTransactionOperation); + ca.uhn.fhir.model.api.Bundle response = client.transaction().withBundle(bundle).execute(); + } + + /** Conformance - Server */ + @Test + public void testConformance() { + final Conformance conf = client.fetchConformance().ofType(Conformance.class).execute(); + assertEquals(conf.getRest().get(0).getResource().get(0).getType().toString(), "Patient"); + } + + /** Extended Operations */ + @Test + public void testExtendedOperations() { + // prepare mock + Parameters resultParameters = new Parameters(); + resultParameters.addParameter().setName("return").setResource(createPatient(1)).setValue(new StringDt("outputValue")); + when(mock.someCustomOperation(eq(new StringDt("myAwesomeDummyValue")))).thenReturn(resultParameters); + // Create the input parameters to pass to the server + Parameters inParams = new Parameters(); + inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); + inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); + inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue")); + //invoke + Parameters outParams = client.operation().onInstance(new IdDt("Patient", "1")).named("$someCustomOperation") + .withParameters(inParams).execute(); + //verify + assertEquals("outputValue", ((StringDt)outParams.getParameter().get(0).getValue()).getValueAsString()); + } + + @Test + public void testExtendedOperationsUsingGet() { + // prepare mock + Parameters resultParameters = new Parameters(); + resultParameters.addParameter().setName("return").setResource(createPatient(1)).setValue(new StringDt("outputValue")); + when(mock.someCustomOperation(eq(new StringDt("myAwesomeDummyValue")))).thenReturn(resultParameters); + // Create the input parameters to pass to the server + Parameters inParams = new Parameters(); + inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); + inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); + inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue")); + + // invoke + Parameters outParams = client.operation().onInstance(new IdDt("Patient", "1")).named("$someCustomOperation") + .withParameters(inParams).useHttpGet().execute(); + // verify + assertEquals("outputValue", ((StringDt)outParams.getParameter().get(0).getValue()).getValueAsString()); + } + + @Test + public void testVRead() { + when(mock.findHistory(idCaptor.capture())).thenReturn(createPatient(1)); + final Patient patient = client.vread(Patient.class, "1", "2"); + compareResultId(1, patient); + compareResultUrl("/Patient/1", patient); + assertEquals("1", idCaptor.getValue().getIdPart()); + assertEquals("2", idCaptor.getValue().getVersionIdPart()); + } + + @Test + public void testRead() { + when(mock.find(idCaptor.capture())).thenReturn(createPatient(1)); + final Patient patient = client.read(Patient.class, "1"); + compareResultId(1, patient); + compareResultUrl("/Patient/1", patient); + assertEquals("1", idCaptor.getValue().getIdPart()); + } + + @Test + public void testXFindUnknownPatient() { + try { + when(mock.find(idCaptor.capture())).thenThrow(new ResourceNotFoundException(new IdDt("999955541264"))); + final Patient existing = client.read(Patient.class, "999955541264"); + fail(); + } catch (final Exception e) { + e.printStackTrace(); + // assertEquals(e.getStatusCode(), 404); + } + } + + private Bundle getPatientBundle(int size) { + Bundle result = new Bundle(); + for (long i = 0; i < size; i++) { + Patient patient = createPatient(i); + Entry entry = new Entry().setResource(patient); + result.addEntry(entry); + } + return result; + } + + private List createPatients(int firstId, int lastId) { + List result = new ArrayList(lastId - firstId); + for (long i = firstId; i <= lastId; i++) { + result.add(createPatient(i)); + } + return result; + } + + private Patient createPatient(long id) { + Patient theResource = new Patient(); + theResource.setId(new IdDt(id)); + return theResource; + } + + private void compareResultId(int id, IResource resource) { + assertEquals(id, resource.getId().getIdPartAsLong().intValue()); + } + + private void compareResultUrl(String url, IResource resource) { + assertEquals(url, resource.getId().getValueAsString().substring("http://localhost:55844".length())); + } + + private T withId(final T id) { + return argThat(new BaseMatcher() { + @Override + public boolean matches(Object other) { + IdDt thisId; + IdDt otherId; + if (id instanceof IdDt) { + thisId = (IdDt) id; + otherId = (IdDt) other; + } else { + thisId = ((IResource) id).getId(); + otherId = ((IResource) other).getId(); + } + return thisId.getIdPartAsLong().equals(otherId.getIdPartAsLong()); + } + + @Override + public void describeTo(Description arg0) { + } + }); + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java deleted file mode 100644 index 71bafdfdf5c..00000000000 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java +++ /dev/null @@ -1,315 +0,0 @@ -package ca.uhn.fhir.jaxrs.server.example; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; -import static org.junit.Assert.fail; - -import java.util.List; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; -import org.junit.AfterClass; -import org.junit.BeforeClass; -import org.junit.Ignore; -import org.junit.Test; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.api.BundleEntry; -import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; -import ca.uhn.fhir.model.dstu2.resource.Bundle; -import ca.uhn.fhir.model.dstu2.resource.Conformance; -import ca.uhn.fhir.model.dstu2.resource.Parameters; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.BoundCodeDt; -import ca.uhn.fhir.model.primitive.DateDt; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.model.valueset.BundleEntryTransactionMethodEnum; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.api.PreferReturnEnum; -import ca.uhn.fhir.rest.client.IGenericClient; -import ca.uhn.fhir.rest.client.ServerValidationModeEnum; -import ca.uhn.fhir.rest.client.interceptor.LoggingInterceptor; -import ca.uhn.fhir.rest.method.SearchStyleEnum; -import ca.uhn.fhir.rest.server.EncodingEnum; - -public class JaxRsPatientProviderTest { - - private static IGenericClient client; - private static final FhirContext ourCtx = FhirContext.forDstu2(); - private static final String PATIENT_NAME = "Van Houte"; - private static int ourPort; - private static Server jettyServer; - - @BeforeClass - public static void setUpClass() - throws Exception { - ourPort = RandomServerPortProvider.findFreePort(); - ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS); - context.setContextPath("/"); - System.out.println(ourPort); - jettyServer = new Server(ourPort); - jettyServer.setHandler(context); - ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/*"); - jerseyServlet.setInitOrder(0); - jerseyServlet.setInitParameter("jersey.config.server.provider.classnames", TestJaxRsPatientRestProvider.class.getCanonicalName() + "," +TestJaxRsConformanceRestProvider.class.getCanonicalName()); - jettyServer.start(); - - ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); - ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); - client.setEncoding(EncodingEnum.JSON); - client.registerInterceptor(new LoggingInterceptor(true)); - } - - @AfterClass - public static void tearDownClass() - throws Exception { - try { - jettyServer.destroy(); - } - catch (Exception e) { - } - } - - /** Search/Query - Type */ - @Test - public void findUsingGenericClientBySearch() { - // Perform a search - final ca.uhn.fhir.model.api.Bundle results = client.search().forResource(Patient.class) - .where(Patient.NAME.matchesExactly().value(PATIENT_NAME)).execute(); - System.out.println(results.getEntries().get(0)); - assertEquals(results.getEntries().size(), 1); - } - - /** Search - Multi-valued Parameters (ANY/OR) */ - @Test - public void findUsingGenericClientBySearchWithMultiValues() { - final ca.uhn.fhir.model.api.Bundle response = client.search().forResource(Patient.class) - .where(Patient.ADDRESS.matches().values("Toronto")).and(Patient.ADDRESS.matches().values("Ontario")) - .and(Patient.ADDRESS.matches().values("Canada")) - .where(Patient.IDENTIFIER.exactly().systemAndIdentifier("SHORTNAME", "TOYS")).execute(); - System.out.println(response.getEntries().get(0)); - } - - /** Search - Paging */ - @Test - public void findWithPaging() { - // Perform a search - final Bundle results = client.search().forResource(Patient.class).limitTo(8).returnBundle(Bundle.class).execute(); - System.out.println(results.getEntry().size()); - - if (results.getLink(Bundle.LINK_NEXT) != null) { - - // load next page - final Bundle nextPage = client.loadPage().next(results).execute(); - System.out.println(nextPage.getEntry().size()); - } - } - - /** Search using other query options */ - public void testOther() { - //missing - } - - /** */ - @Test - public void testSearchPost() { - Bundle response = client.search() - .forResource("Patient") - .usingStyle(SearchStyleEnum.POST) - .returnBundle(Bundle.class) - .execute(); - assertTrue(response.getEntry().size() > 0); - } - - /** Search - Compartments */ - @Test - public void testSearchCompartements() { - Bundle response = client.search() - .forResource(Patient.class) - .withIdAndCompartment("1", "Condition") - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .execute(); - assertTrue(response.getEntry().size() > 0); - } - - /** Search - Subsetting (_summary and _elements) */ - @Test - @Ignore - public void testSummary() { - Object response = client.search() - .forResource(Patient.class) - .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) - .execute(); - } - - @Test - public void testCreatePatient() { - final Patient existing = new Patient(); - existing.setId((IdDt) null); - existing.getNameFirstRep().addFamily("Created Patient 54"); - client.setEncoding(EncodingEnum.JSON); - final MethodOutcome results = client.create().resource(existing).prefer(PreferReturnEnum.REPRESENTATION).execute(); - System.out.println(results.getId()); - final Patient patient = (Patient) results.getResource(); - System.out.println(patient); - assertNotNull(client.read(patient.getId())); - client.setEncoding(EncodingEnum.JSON); - } - - - /** Conditional Creates */ - @Test - public void testConditionalCreate() { - final Patient existing = new Patient(); - existing.setId((IdDt) null); - existing.getNameFirstRep().addFamily("Created Patient 54"); - client.setEncoding(EncodingEnum.XML); - final MethodOutcome results = client.create().resource(existing).prefer(PreferReturnEnum.REPRESENTATION).execute(); - System.out.println(results.getId()); - final Patient patient = (Patient) results.getResource(); - - client.create() - .resource(patient) - .conditional() - .where(Patient.IDENTIFIER.exactly().identifier(patient.getIdentifierFirstRep())) - .execute(); - } - - - /** Find By Id */ - @Test - public void findUsingGenericClientById() { - final Patient results = client.read(Patient.class, "1"); - assertEquals(results.getId().getIdPartAsLong().longValue(), 1L); - } - - @Test - public void testUpdateById() { - final Patient existing = client.read(Patient.class, "1"); - final List name = existing.getName(); - name.get(0).addSuffix("The Second"); - existing.setName(name); - client.setEncoding(EncodingEnum.XML); - final MethodOutcome results = client.update("1", existing); - } - - @Test - public void testDeletePatient() { - final Patient existing = new Patient(); - existing.getNameFirstRep().addFamily("Created Patient XYZ"); - final MethodOutcome results = client.create().resource(existing).prefer(PreferReturnEnum.REPRESENTATION).execute(); - System.out.println(results.getId()); - final Patient patient = (Patient) results.getResource(); - client.delete(Patient.class, patient.getId()); - try { - client.read(patient.getId()); - fail(); - } - catch (final Exception e) { - //assertEquals(e.getStatusCode(), Constants.STATUS_HTTP_404_NOT_FOUND); - } - } - - /** Transaction - Server */ - @Ignore - @Test - public void testTransaction() { - ca.uhn.fhir.model.api.Bundle bundle = new ca.uhn.fhir.model.api.Bundle(); - BundleEntry entry = bundle.addEntry(); - final Patient existing = new Patient(); - existing.getNameFirstRep().addFamily("Created with bundle"); - entry.setResource(existing); - - BoundCodeDt theTransactionOperation = - new BoundCodeDt( - BundleEntryTransactionMethodEnum.VALUESET_BINDER, - BundleEntryTransactionMethodEnum.POST); - entry.setTransactionMethod(theTransactionOperation); - ca.uhn.fhir.model.api.Bundle response = client.transaction().withBundle(bundle).execute(); - } - - /** Conformance - Server */ - @Test - public void testConformance() { - final Conformance conf = client.fetchConformance().ofType(Conformance.class).execute(); - System.out.println(conf.getRest().get(0).getResource().get(0).getType()); - assertEquals(conf.getRest().get(0).getResource().get(0).getType().toString(), "Patient"); - } - - /** Extended Operations */ - // Create a client to talk to the HeathIntersections server - @Test - public void testExtendedOperations() { - client.registerInterceptor(new LoggingInterceptor(true)); - - // Create the input parameters to pass to the server - Parameters inParams = new Parameters(); - inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); - inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); - inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue")); - - // Invoke $everything on "Patient/1" - Parameters outParams = client - .operation() - .onInstance(new IdDt("Patient", "1")) - .named("$last") - .withParameters(inParams) - //.useHttpGet() // Use HTTP GET instead of POST - .execute(); - String resultValue = outParams.getParameter().get(0).getValue().toString(); - System.out.println(resultValue); - assertEquals("expected but found : "+ resultValue, resultValue.contains("myAwesomeDummyValue"), true); - } - - @Test - public void testExtendedOperationsUsingGet() { - client.registerInterceptor(new LoggingInterceptor(true)); - - // Create the input parameters to pass to the server - Parameters inParams = new Parameters(); - inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); - inParams.addParameter().setName("end").setValue(new DateDt("2015-03-01")); - inParams.addParameter().setName("dummy").setValue(new StringDt("myAwesomeDummyValue")); - - // Invoke $everything on "Patient/1" - Parameters outParams = client - .operation() - .onInstance(new IdDt("Patient", "1")) - .named("$last") - .withParameters(inParams) - .useHttpGet() // Use HTTP GET instead of POST - .execute(); - String resultValue = outParams.getParameter().get(0).getValue().toString(); - System.out.println(resultValue); - assertEquals("expected but found : "+ resultValue, resultValue.contains("myAwesomeDummyValue"), true); - } - - @Test - public void testFindUnknownPatient() { - try { - final Patient existing = client.read(Patient.class, "999955541264"); - fail(); - } - catch (final Exception e) { - e.printStackTrace(); - //assertEquals(e.getStatusCode(), 404); - } - } - - @Test - public void testVRead() { - final Patient patient = client.vread(Patient.class, "1", "1"); - System.out.println(patient); - } - - @Test - public void testRead() { - final Patient patient = client.read(Patient.class, "1"); - System.out.println(patient); - } - -} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java index 3570d8c62f9..ad99dd03696 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java @@ -13,22 +13,23 @@ import ca.uhn.fhir.rest.server.IResourceProvider; /** * Fhir Physician Rest Service + * * @author axmpm * */ @Path(TestJaxRsConformanceRestProvider.PATH) @Stateless -@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) +@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) public class TestJaxRsConformanceRestProvider extends AbstractJaxRsConformanceProvider { - - public TestJaxRsConformanceRestProvider() { + + public TestJaxRsConformanceRestProvider() { super("", "", ""); - } + } @Override protected ConcurrentHashMap, IResourceProvider> getProviders() throws Exception { ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>(); - map.put(TestJaxRsPatientRestProvider.class, new TestJaxRsPatientRestProvider()); + map.put(TestJaxRsMockPatientRestProvider.class, new TestJaxRsMockPatientRestProvider()); map.put(TestJaxRsConformanceRestProvider.class, new TestJaxRsConformanceRestProvider()); return map; } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsMockPatientRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsMockPatientRestProvider.java new file mode 100644 index 00000000000..ae23b4fe6ec --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsMockPatientRestProvider.java @@ -0,0 +1,134 @@ +package ca.uhn.fhir.jaxrs.server.example; + +import java.util.List; + +import javax.ejb.Stateless; +import javax.interceptor.Interceptors; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import org.mockito.Mockito; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; +import ca.uhn.fhir.model.api.IResource; +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +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.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.Read; +import ca.uhn.fhir.rest.annotation.RequiredParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.annotation.Update; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.param.StringAndListParam; +import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; +import ca.uhn.fhir.rest.server.IPagingProvider; + +/** + * Fhir Physician Rest Service + * + * @author axmpm + * + */ +@Path(TestJaxRsMockPatientRestProvider.PATH) +@Stateless +@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) +public class TestJaxRsMockPatientRestProvider extends AbstractJaxRsResourceProvider { + + static final String PATH = "/Patient"; + private FifoMemoryPagingProvider pp; + + public static final TestJaxRsMockPatientRestProvider mock = Mockito.mock(TestJaxRsMockPatientRestProvider.class); + + public TestJaxRsMockPatientRestProvider() { + pp = new FifoMemoryPagingProvider(10); + pp.setDefaultPageSize(10); + pp.setMaximumPageSize(100); + } + + @Search + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name, @RequiredParam(name=Patient.SP_ADDRESS) StringAndListParam theAddressParts) { + return mock.search(name, theAddressParts); + } + + @Update + public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) throws Exception { + return mock.update(theId, patient); + } + + @Read + public Patient find(@IdParam final IdDt theId) { + return mock.find(theId); + } + + @Read(version = true) + public Patient findHistory(@IdParam final IdDt theId) { + return mock.findHistory(theId); + } + + @Create + public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) + throws Exception { + return mock.create(patient, theConditional); + } + + @Delete + public MethodOutcome delete(@IdParam final IdDt theId) { + return mock.delete(theId); + } + + @GET + @Path("/{id}/$someCustomOperation") + @Interceptors(JaxRsExceptionInterceptor.class) + public Response someCustomOperationUsingGet(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.GET, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); + } + + @Search(compartmentName = "Condition") + public List searchCompartment(@IdParam IdDt thePatientId) { + return mock.searchCompartment(thePatientId); + } + + @POST + @Path("/{id}/$someCustomOperation") + @Interceptors(JaxRsExceptionInterceptor.class) + public Response someCustomOperationUsingPost(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.POST, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); + } + + @Operation(name = "someCustomOperation", idempotent = true, returnParameters = { + @OperationParam(name = "return", type = StringDt.class) }) + public Parameters someCustomOperation(@OperationParam(name = "dummy") StringDt dummyInput) { + return mock.someCustomOperation(dummyInput); + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Override + public IPagingProvider getPagingProvider() { + return pp; + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsPatientRestProvider.java deleted file mode 100644 index 8da22fb88ec..00000000000 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsPatientRestProvider.java +++ /dev/null @@ -1,269 +0,0 @@ -package ca.uhn.fhir.jaxrs.server.example; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.LinkedList; -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; - -import javax.ejb.Stateless; -import javax.interceptor.Interceptors; -import javax.ws.rs.GET; -import javax.ws.rs.POST; -import javax.ws.rs.Path; -import javax.ws.rs.PathParam; -import javax.ws.rs.Produces; -import javax.ws.rs.core.MediaType; -import javax.ws.rs.core.Response; - -import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; -import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu2.resource.Condition; -import ca.uhn.fhir.model.dstu2.resource.Parameters; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; -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.Operation; -import ca.uhn.fhir.rest.annotation.OperationParam; -import ca.uhn.fhir.rest.annotation.Read; -import ca.uhn.fhir.rest.annotation.RequiredParam; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.annotation.Update; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.api.RequestTypeEnum; -import ca.uhn.fhir.rest.api.RestOperationTypeEnum; -import ca.uhn.fhir.rest.param.StringParam; -import ca.uhn.fhir.rest.server.AddProfileTagEnum; -import ca.uhn.fhir.rest.server.BundleInclusionRule; -import ca.uhn.fhir.rest.server.Constants; -import ca.uhn.fhir.rest.server.ETagSupportEnum; -import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; -import ca.uhn.fhir.rest.server.IPagingProvider; -import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; - -/** - * Fhir Physician Rest Service - * @author axmpm - * - */ -@Path(TestJaxRsPatientRestProvider.PATH) -@Stateless -@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) -public class TestJaxRsPatientRestProvider extends AbstractJaxRsResourceProvider { - - static final String PATH = "/Patient"; - - private static Long counter = 1L; - private static final ConcurrentHashMap> patients = new ConcurrentHashMap>(); - - public TestJaxRsPatientRestProvider() throws Exception { - super(TestJaxRsPatientRestProvider.class); - } - - static { - patients.put(""+counter, createPatient("Van Houte")); - patients.put(""+(counter), createPatient("Agnew")); - for(int i = 0 ; i<20 ; i++) { - patients.put(""+(counter), createPatient("Random Patient " + counter)); - } - } - - private static List createPatient(final String name) { - final Patient patient = new Patient(); - patient.getNameFirstRep().addFamily(name); - return createPatient(patient); - } - - private static List createPatient(final Patient patient) { - patient.setId(createId(counter, 1L)); - final LinkedList list = new LinkedList(); - list.add(patient); - counter++; - return list ; - } - - private static IdDt createId(final Long id, final Long theVersionId) { - return new IdDt("Patient", "" + id, "" + theVersionId); - } - - @Search - public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { - final List result = new LinkedList(); - for (final List patientIterator : patients.values()) { - Patient single = null; - for (Patient patient : patientIterator) { - if (name == null || patient.getNameFirstRep().getFamilyFirstRep().getValueNotNull().equals(name.getValueNotNull())) { - single = patient; - } - } - if (single != null) { - result.add(single); - } - } - return result; - } - - @Update - public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) - throws Exception { - final String idPart = theId.getIdPart(); - if(patients.containsKey(idPart)) { - final List patientList = patients.get(idPart); - final Patient lastPatient = getLast(patientList); - patient.setId(createId(theId.getIdPartAsLong(), lastPatient.getId().getVersionIdPartAsLong()+1)); - patientList.add(patient); - final MethodOutcome result = new MethodOutcome().setCreated(false); - result.setResource(patient); - result.setId(patient.getId()); - return result; - } else { - throw new ResourceNotFoundException(theId); - } - } - - @Read - public Patient find(@IdParam final IdDt theId) { - if(patients.containsKey(theId.getIdPart())) { - return getLast(patients.get(theId.getIdPart())); - } else { - throw new ResourceNotFoundException(theId); - } - } - - - private Patient getLast(final List list) { - return list.get(list.size()-1); - } - - @Read(version = true) - public Patient findHistory(@IdParam final IdDt theId) { - if (patients.containsKey(theId.getIdPart())) { - final List list = patients.get(theId.getIdPart()); - for (final Patient patient : list) { - if (patient.getId().getVersionIdPartAsLong().equals(theId.getVersionIdPartAsLong())) { - return patient; - } - } - } - throw new ResourceNotFoundException(theId); - } - - @Create - public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) - throws Exception { - patients.put(""+counter, createPatient(patient)); - final MethodOutcome result = new MethodOutcome().setCreated(true); - result.setResource(patient); - result.setId(patient.getId()); - return result; - } - - @Delete - public MethodOutcome delete(@IdParam final IdDt theId) { - final Patient deletedPatient = find(theId); - patients.remove(deletedPatient.getId().getIdPart()); - final MethodOutcome result = new MethodOutcome().setCreated(true); - result.setResource(deletedPatient); - return result; - } - - - @GET - @Path("/{id}/$last") - @Interceptors(JaxRsExceptionInterceptor.class) - public Response operationLastGet(@PathParam("id") String id) - throws Exception { - return customOperation(null, RequestTypeEnum.GET, id, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); - } - - @Search(compartmentName="Condition") - public List searchCompartment(@IdParam IdDt thePatientId) { - List retVal=new ArrayList(); - Condition condition = new Condition(); - condition.setId(new IdDt("665577")); - retVal.add(condition); - return retVal; - } - - @POST - @Path("/{id}/$last") - @Interceptors(JaxRsExceptionInterceptor.class) - public Response operationLast(final String resource) - throws Exception { - return customOperation(resource, RequestTypeEnum.POST, null, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); - } - -// @ca.uhn.fhir.rest.annotation.Validate -// public MethodOutcome validate( -// @ResourceParam T theResource, -// @ResourceParam String theRawResource, -// @ResourceParam EncodingEnum theEncoding, -// @ca.uhn.fhir.rest.annotation.Validate.Mode ValidationModeEnum theMode, -// @ca.uhn.fhir.rest.annotation.Validate.Profile String theProfile) { -// return validate(theResource, null, theRawResource, theEncoding, theMode, theProfile); -// } - - @Operation(name="last", idempotent=true, returnParameters= { - @OperationParam(name="return", type=StringDt.class) - }) - public Parameters last(@OperationParam(name = "dummy") StringDt dummyInput) { - System.out.println("inputparameter"); - Parameters parameters = new Parameters(); - Patient patient = find(new IdDt(counter.intValue()-1)); - parameters - .addParameter() - .setName("return") - .setResource(patient) - .setValue(new StringDt((counter-1)+"" + "inputVariable [ " + dummyInput.getValue()+ "]")); - return parameters; - } - - @Override - public Class getResourceType() { - return Patient.class; - } - - /** THE DEFAULTS */ - @Override - public List getInterceptors() { - return Collections.emptyList(); - } - - @Override - public ETagSupportEnum getETagSupport() { - return ETagSupportEnum.DISABLED; - } - - @Override - public boolean isDefaultPrettyPrint() { - return true; - } - - @Override - public AddProfileTagEnum getAddProfileTag() { - return AddProfileTagEnum.NEVER; - } - - @Override - public boolean isUseBrowserFriendlyContentTypes() { - return true; - } - - @Override - public IPagingProvider getPagingProvider() { - return new FifoMemoryPagingProvider(10); - } - - @Override - public BundleInclusionRule getBundleInclusionRule() { - return BundleInclusionRule.BASED_ON_INCLUDES; - } - -} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java index bfb789b78a9..fb3af725355 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java @@ -112,7 +112,8 @@ public class JaxRsRequestTest { //mocks provider = spy(TestDummyPatientProvider.class); doReturn(uriInfo).when(provider).getUriInfo(); - doReturn(BASEURI).when(provider).getBaseUri(); + doReturn(BASEURI).when(provider).getBaseForRequest(); + doReturn(BASEURI).when(provider).getBaseForServer(); doReturn(headers).when(provider).getHeaders(); return new JaxRsRequest(provider, RESOURCE_STRING, RequestTypeEnum.GET, RestOperationTypeEnum.HISTORY_TYPE); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java index f75beddecad..89d7d2ec645 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java @@ -49,7 +49,7 @@ public class JaxRsResponseTest { Set theSummaryMode = Collections.emptySet(); Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request); assertEquals(200, result.getStatus()); - assertEquals(Constants.CT_FHIR_JSON, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertEquals(Constants.CT_FHIR_JSON+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); assertTrue(result.getEntity().toString().contains("Patient")); assertTrue(result.getEntity().toString().contains("15")); } @@ -60,23 +60,11 @@ public class JaxRsResponseTest { boolean respondGzip = false; Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request); assertEquals(200, result.getStatus()); - assertEquals(Constants.CT_JSON, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertEquals(Constants.CT_JSON+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); assertTrue(result.getEntity().toString().contains("Patient")); assertTrue(result.getEntity().toString().contains("15")); } - @Test - public void testGetResponseWriterWithZip() throws IOException { - boolean theRequestIsBrowser = true; - boolean respondGzip = true; - Response result = (Response) RestfulServerUtils.streamResponseAsBundle(request.getServer(), bundle, theSummaryMode, theRequestIsBrowser, respondGzip, request); - assertEquals(200, result.getStatus()); - assertEquals(Constants.CT_JSON, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); - assertEquals(Constants.ENCODING_GZIP, result.getHeaderString(Constants.HEADER_CONTENT_ENCODING)); -// assertTrue(unzip(result.getEntity()).contains("Patient")); -// assertTrue(result.getEntity().toString().contains("15")); - } - @Test public void testSendAttachmentResponse() throws IOException { boolean theRequestIsBrowser = true; @@ -129,7 +117,7 @@ public class JaxRsResponseTest { MethodOutcome methodOutcome = new MethodOutcome(theId); Response result = response.returnResponse(outcome, operationStatus, allowPrefer, methodOutcome, resourceName); assertEquals(200, result.getStatus()); - assertEquals(Constants.CT_JSON, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertEquals(Constants.CT_JSON+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); System.out.println(result.getEntity().toString()); assertTrue(result.getEntity().toString().contains("resourceType\":\"Patient")); assertTrue(result.getEntity().toString().contains("15")); @@ -147,7 +135,7 @@ public class JaxRsResponseTest { response.getRequestDetails().getParameters().put(Constants.PARAM_FORMAT, new String[]{Constants.CT_XML}); Response result = response.returnResponse(outcome, operationStatus, allowPrefer, methodOutcome, resourceName); assertEquals(200, result.getStatus()); - assertEquals(Constants.CT_XML, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); + assertEquals(Constants.CT_XML+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); assertTrue(result.getEntity().toString().contains(" 4.0.0 hapi-fhir-jaxrsserver-example @@ -90,36 +100,6 @@ - - maven-dependency-plugin - - - unpack - package - - unpack - - - - - ${project.groupId} - ${project.artifactId} - ${project.version} - war - *.war - - - C:\Agfa\ORBIS-AS\server\orbis-as-08.05.07.00.0009200-UK\standalone\deployments - true - - - - - - org.mortbay.jetty - maven-jetty-plugin - 8.1.16.v20140903 - diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IJaxRsPatientProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IJaxRsPatientProvider.java deleted file mode 100644 index 8d51661bbbd..00000000000 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/IJaxRsPatientProvider.java +++ /dev/null @@ -1,45 +0,0 @@ -package ca.uhn.fhir.jaxrs.server.example; - -import java.util.List; - -import javax.ws.rs.core.Response; - -import ca.uhn.fhir.jaxrs.server.IJaxRsResourceProvider; -import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.dstu2.resource.Parameters; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.model.primitive.IdDt; -import ca.uhn.fhir.model.primitive.StringDt; -import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.param.StringParam; - -public interface IJaxRsPatientProvider extends IJaxRsResourceProvider { - - public static final String JNDI_NAME = "IJaxRsPatientProvider"; - - List search(StringParam name); - - MethodOutcome update(IdDt theId, Patient patient) - throws Exception; - - Patient find(IdDt theId); - - Patient findHistory(IdDt theId); - - MethodOutcome create(Patient patient, String theConditional) - throws Exception; - - MethodOutcome delete(IdDt theId); - - Response operationLastGet(String resource) - throws Exception; - - Response operationLast(String resource) - throws Exception; - - Parameters last(StringDt dummyInput); - - - List searchCompartment(IdDt thePatientId); - -} diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java index 09786fdcec5..a1e3c0d0a55 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java @@ -1,43 +1,38 @@ package ca.uhn.fhir.jaxrs.server.example; -import javax.annotation.PostConstruct; -import javax.ejb.EJB; -import javax.ejb.Local; +import java.util.concurrent.ConcurrentHashMap; + import javax.ejb.Stateless; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsConformanceProvider; import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IResourceProvider; /** * Conformance Rest Service + * * @author Peter Van Houte */ -//@Local -//@Path(ConformanceRestServer.PATH) -//@Stateless -//@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) -public class JaxRsConformanceProvider -{ - private static final String SERVER_VERSION = "1.0.0"; - private static final String SERVER_DESCRIPTION = "Jax-Rs Test Example Description"; - private static final String SERVER_NAME = "Jax-Rs Test Example"; - - @EJB - private JaxRsConformanceProvider conformanceRestServer; - @EJB - private IJaxRsPatientProvider patientRestServer; - -// public ConformanceRestServer() { -// super(SERVER_DESCRIPTION, SERVER_NAME, SERVER_VERSION); -// } -// -// @PostConstruct -// public void createMethod() -// throws Exception { -// findResourceMethods(conformanceRestServer, ConformanceRestServer.class); -// findResourceMethods(patientRestServer, FhirPatientRestServer.class); -// super.setUpPostConstruct(); -// } +@Path("") +@Stateless +@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) +public class JaxRsConformanceProvider extends AbstractJaxRsConformanceProvider { + private static final String SERVER_VERSION = "1.0.0"; + private static final String SERVER_DESCRIPTION = "Jax-Rs Test Example Description"; + private static final String SERVER_NAME = "Jax-Rs Test Example"; + + public JaxRsConformanceProvider() { + super(SERVER_VERSION, SERVER_DESCRIPTION, SERVER_NAME); + } + + @Override + protected ConcurrentHashMap, IResourceProvider> getProviders() throws Exception { + ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>(); + map.put(JaxRsConformanceProvider.class, new JaxRsConformanceProvider()); + map.put(JaxRsPatientRestProvider.class, new JaxRsPatientRestProvider()); + return map; + } } diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java index 710f89ee87a..93a7c67b007 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java @@ -51,231 +51,207 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; /** * Fhir Physician Rest Service + * * @author axmpm * */ -@Local(IJaxRsPatientProvider.class) +@Local @Path(JaxRsPatientRestProvider.PATH) -@Stateless(name = IJaxRsPatientProvider.JNDI_NAME, mappedName=IJaxRsPatientProvider.JNDI_NAME) -@Produces({MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML}) -public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider implements IJaxRsPatientProvider { - - static final String PATH = "/Patient"; - - private static Long counter = 1L; - private static final ConcurrentHashMap> patients = new ConcurrentHashMap>(); - - public JaxRsPatientRestProvider() throws Exception { - super(JaxRsPatientRestProvider.class); - } - - static { - patients.put(""+counter, createPatient("Van Houte")); - patients.put(""+(counter), createPatient("Agnew")); - for(int i = 0 ; i<20 ; i++) { - patients.put(""+(counter), createPatient("Random Patient " + counter)); - } - } +@Stateless +@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) +public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider { - private static List createPatient(final String name) { - final Patient patient = new Patient(); - patient.getNameFirstRep().addFamily(name); - return createPatient(patient); - } - - private static List createPatient(final Patient patient) { - patient.setId(createId(counter, 1L)); - final LinkedList list = new LinkedList(); - list.add(patient); - counter++; - return list ; - } + static final String PATH = "/Patient"; - private static IdDt createId(final Long id, final Long theVersionId) { - return new IdDt("Patient", "" + id, "" + theVersionId); - } - - @Search - @Override - public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { - final List result = new LinkedList(); - for (final List patientIterator : patients.values()) { - Patient single = null; - for (Patient patient : patientIterator) { - if (name == null || patient.getNameFirstRep().getFamilyFirstRep().getValueNotNull().equals(name.getValueNotNull())) { - single = patient; - } - } - if (single != null) { - result.add(single); - } - } - return result; - } - - @Update - @Override - public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) - throws Exception { - final String idPart = theId.getIdPart(); - if(patients.containsKey(idPart)) { - final List patientList = patients.get(idPart); - final Patient lastPatient = getLast(patientList); - patient.setId(createId(theId.getIdPartAsLong(), lastPatient.getId().getVersionIdPartAsLong()+1)); - patientList.add(patient); - final MethodOutcome result = new MethodOutcome().setCreated(false); - result.setResource(patient); - result.setId(patient.getId()); - return result; - } else { - throw new ResourceNotFoundException(theId); - } - } + private static Long counter = 1L; + private static final ConcurrentHashMap> patients = new ConcurrentHashMap>(); - @Override - @Read - public Patient find(@IdParam final IdDt theId) { - if(patients.containsKey(theId.getIdPart())) { - return getLast(patients.get(theId.getIdPart())); - } else { - throw new ResourceNotFoundException(theId); - } - } - - - private Patient getLast(final List list) { - return list.get(list.size()-1); - } + public JaxRsPatientRestProvider() throws Exception { + super(JaxRsPatientRestProvider.class); + } - @Override - @Read(version = true) - public Patient findHistory(@IdParam final IdDt theId) { - if (patients.containsKey(theId.getIdPart())) { - final List list = patients.get(theId.getIdPart()); - for (final Patient patient : list) { - if (patient.getId().getVersionIdPartAsLong().equals(theId.getVersionIdPartAsLong())) { - return patient; - } - } - } - throw new ResourceNotFoundException(theId); - } + static { + patients.put("" + counter, createPatient("Van Houte")); + patients.put("" + (counter), createPatient("Agnew")); + for (int i = 0; i < 20; i++) { + patients.put("" + (counter), createPatient("Random Patient " + counter)); + } + } - @Create - @Override - public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) - throws Exception { - patients.put(""+counter, createPatient(patient)); - final MethodOutcome result = new MethodOutcome().setCreated(true); - result.setResource(patient); - result.setId(patient.getId()); - return result; - } - - @Delete - @Override - public MethodOutcome delete(@IdParam final IdDt theId) { - final Patient deletedPatient = find(theId); - patients.remove(deletedPatient.getId().getIdPart()); - final MethodOutcome result = new MethodOutcome().setCreated(true); - result.setResource(deletedPatient); - return result; - } - - - @GET - @Path("/{id}/$last") - @Interceptors(JaxRsExceptionInterceptor.class) - @Override - public Response operationLastGet(@PathParam("id") String id) - throws Exception { - return customOperation(null, RequestTypeEnum.GET, id, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); - } - - @Search(compartmentName="Condition") - @Override - public List searchCompartment(@IdParam IdDt thePatientId) { - List retVal=new ArrayList(); - Condition condition = new Condition(); - condition.setId(new IdDt("665577")); - retVal.add(condition); - return retVal; - } - - @POST - @Path("/{id}/$last") - @Interceptors(JaxRsExceptionInterceptor.class) - @Override - public Response operationLast(final String resource) - throws Exception { - return customOperation(resource, RequestTypeEnum.POST, null, "$last", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); - } - -// @ca.uhn.fhir.rest.annotation.Validate -// public MethodOutcome validate( -// @ResourceParam T theResource, -// @ResourceParam String theRawResource, -// @ResourceParam EncodingEnum theEncoding, -// @ca.uhn.fhir.rest.annotation.Validate.Mode ValidationModeEnum theMode, -// @ca.uhn.fhir.rest.annotation.Validate.Profile String theProfile) { -// return validate(theResource, null, theRawResource, theEncoding, theMode, theProfile); -// } + private static List createPatient(final String name) { + final Patient patient = new Patient(); + patient.getNameFirstRep().addFamily(name); + return createPatient(patient); + } - @Operation(name="last", idempotent=true, returnParameters= { - @OperationParam(name="return", type=StringDt.class) - }) - @Override - public Parameters last(@OperationParam(name = "dummy") StringDt dummyInput) { - System.out.println("inputparameter"); - Parameters parameters = new Parameters(); - Patient patient = find(new IdDt(counter.intValue()-1)); - parameters - .addParameter() - .setName("return") - .setResource(patient) - .setValue(new StringDt((counter-1)+"" + "inputVariable [ " + dummyInput.getValue()+ "]")); - return parameters; - } - - @Override - public Class getResourceType() { - return Patient.class; - } - - /** THE DEFAULTS */ - @Override - public List getInterceptors() { - return Collections.emptyList(); - } + private static List createPatient(final Patient patient) { + patient.setId(createId(counter, 1L)); + final LinkedList list = new LinkedList(); + list.add(patient); + counter++; + return list; + } - @Override - public ETagSupportEnum getETagSupport() { - return ETagSupportEnum.DISABLED; - } + private static IdDt createId(final Long id, final Long theVersionId) { + return new IdDt("Patient", "" + id, "" + theVersionId); + } - @Override - public boolean isDefaultPrettyPrint() { - return true; - } + @Search + public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { + final List result = new LinkedList(); + for (final List patientIterator : patients.values()) { + Patient single = null; + for (Patient patient : patientIterator) { + if (name == null || patient.getNameFirstRep().getFamilyFirstRep().getValueNotNull() + .equals(name.getValueNotNull())) { + single = patient; + } + } + if (single != null) { + result.add(single); + } + } + return result; + } - @Override - public AddProfileTagEnum getAddProfileTag() { - return AddProfileTagEnum.NEVER; - } + @Update + public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) throws Exception { + final String idPart = theId.getIdPart(); + if (patients.containsKey(idPart)) { + final List patientList = patients.get(idPart); + final Patient lastPatient = getLast(patientList); + patient.setId(createId(theId.getIdPartAsLong(), lastPatient.getId().getVersionIdPartAsLong() + 1)); + patientList.add(patient); + final MethodOutcome result = new MethodOutcome().setCreated(false); + result.setResource(patient); + result.setId(patient.getId()); + return result; + } else { + throw new ResourceNotFoundException(theId); + } + } - @Override - public boolean isUseBrowserFriendlyContentTypes() { - return true; - } + @Read + public Patient find(@IdParam final IdDt theId) { + if (patients.containsKey(theId.getIdPart())) { + return getLast(patients.get(theId.getIdPart())); + } else { + throw new ResourceNotFoundException(theId); + } + } - @Override - public IPagingProvider getPagingProvider() { - return new FifoMemoryPagingProvider(10); - } + private Patient getLast(final List list) { + return list.get(list.size() - 1); + } - @Override - public BundleInclusionRule getBundleInclusionRule() { - return BundleInclusionRule.BASED_ON_INCLUDES; - } + @Read(version = true) + public Patient findHistory(@IdParam final IdDt theId) { + if (patients.containsKey(theId.getIdPart())) { + final List list = patients.get(theId.getIdPart()); + for (final Patient patient : list) { + if (patient.getId().getVersionIdPartAsLong().equals(theId.getVersionIdPartAsLong())) { + return patient; + } + } + } + throw new ResourceNotFoundException(theId); + } + + @Create + public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) + throws Exception { + patients.put("" + counter, createPatient(patient)); + final MethodOutcome result = new MethodOutcome().setCreated(true); + result.setResource(patient); + result.setId(patient.getId()); + return result; + } + + @Delete + public MethodOutcome delete(@IdParam final IdDt theId) { + final Patient deletedPatient = find(theId); + patients.remove(deletedPatient.getId().getIdPart()); + final MethodOutcome result = new MethodOutcome().setCreated(true); + result.setResource(deletedPatient); + return result; + } + + @GET + @Path("/{id}/$last") + @Interceptors(JaxRsExceptionInterceptor.class) + public Response operationLastGet(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.GET, id, "$last", + RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); + } + + @Search(compartmentName = "Condition") + public List searchCompartment(@IdParam IdDt thePatientId) { + List retVal = new ArrayList(); + Condition condition = new Condition(); + condition.setId(new IdDt("665577")); + retVal.add(condition); + return retVal; + } + + @POST + @Path("/{id}/$last") + @Interceptors(JaxRsExceptionInterceptor.class) + public Response operationLast(final String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.POST, null, "$last", + RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); + } + + @Operation(name = "last", idempotent = true, returnParameters = { + @OperationParam(name = "return", type = StringDt.class) }) + public Parameters last(@OperationParam(name = "dummy") StringDt dummyInput) { + System.out.println("inputparameter"); + Parameters parameters = new Parameters(); + Patient patient = find(new IdDt(counter.intValue() - 1)); + parameters.addParameter().setName("return").setResource(patient) + .setValue(new StringDt((counter - 1) + "" + "inputVariable [ " + dummyInput.getValue() + "]")); + return parameters; + } + + @Override + public Class getResourceType() { + return Patient.class; + } + + /** THE DEFAULTS */ + + @Override + public List getInterceptors() { + return Collections.emptyList(); + } + + @Override + public ETagSupportEnum getETagSupport() { + return ETagSupportEnum.DISABLED; + } + + @Override + public boolean isDefaultPrettyPrint() { + return true; + } + + @Override + public AddProfileTagEnum getAddProfileTag() { + return AddProfileTagEnum.NEVER; + } + + @Override + public boolean isUseBrowserFriendlyContentTypes() { + return true; + } + + @Override + public IPagingProvider getPagingProvider() { + return new FifoMemoryPagingProvider(10); + } + + @Override + public BundleInclusionRule getBundleInclusionRule() { + return BundleInclusionRule.BASED_ON_INCLUDES; + } } diff --git a/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java b/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java index f52ae0cc426..004e404a16a 100644 --- a/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java +++ b/hapi-fhir-jaxrsserver-example/src/test/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientProviderTest.java @@ -5,8 +5,10 @@ import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import java.util.Arrays; import java.util.List; +import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletHolder; @@ -16,7 +18,6 @@ import org.junit.Ignore; import org.junit.Test; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jaxrs.server.example.JaxRsPatientRestProvider; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.dstu2.composite.HumanNameDt; import ca.uhn.fhir.model.dstu2.resource.Bundle; @@ -55,10 +56,11 @@ public class JaxRsPatientProviderTest { jettyServer.setHandler(context); ServletHolder jerseyServlet = context.addServlet(org.glassfish.jersey.servlet.ServletContainer.class, "/*"); jerseyServlet.setInitOrder(0); - jerseyServlet.setInitParameter("jersey.config.server.provider.classnames", JaxRsPatientRestProvider.class.getCanonicalName()); + jerseyServlet.setInitParameter("jersey.config.server.provider.classnames", + StringUtils.join(Arrays.asList(JaxRsConformanceProvider.class.getCanonicalName(), + JaxRsPatientRestProvider.class.getCanonicalName()), ";")); jettyServer.start(); - final FhirContext ctx = FhirContext.forDstu2(); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); @@ -142,7 +144,7 @@ public class JaxRsPatientProviderTest { @Test @Ignore public void testSummary() { - Object response = client.search() + client.search() .forResource(Patient.class) .returnBundle(ca.uhn.fhir.model.dstu2.resource.Bundle.class) .execute(); @@ -270,8 +272,6 @@ public class JaxRsPatientProviderTest { @Test public void testExtendedOperationsUsingGet() { - client.registerInterceptor(new LoggingInterceptor(true)); - // Create the input parameters to pass to the server Parameters inParams = new Parameters(); inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); @@ -291,18 +291,6 @@ public class JaxRsPatientProviderTest { assertEquals("expected but found : "+ resultValue, resultValue.contains("myAwesomeDummyValue"), true); } - @Test - public void testFindUnknownPatient() { - try { - final Patient existing = client.read(Patient.class, "999955541264"); - fail(); - } - catch (final Exception e) { - e.printStackTrace(); - //assertEquals(e.getStatusCode(), 404); - } - } - @Test public void testVRead() { final Patient patient = client.vread(Patient.class, "1", "1"); From 0aafd37397ad9a751248873c8f3355a62f6528d6 Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Thu, 29 Oct 2015 16:00:23 +0100 Subject: [PATCH 09/13] commit poms --- hapi-fhir-jaxrsserver-base/pom.xml | 6 ++++++ pom.xml | 3 ++- 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index 8a24553c727..d64447b1736 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -89,6 +89,12 @@ ${jersey_version} test + + org.glassfish.jersey.media + jersey-media-moxy + ${jersey_version} + + diff --git a/pom.xml b/pom.xml index 1e496203038..3b87d122053 100644 --- a/pom.xml +++ b/pom.xml @@ -217,7 +217,8 @@ for that plugin... --> 5.0.1.Final 5.2.1.Final - 9.2.6.v20141205 + 9.2.6.v20141205 + 2.7 1.9.1 2.5.3 2.18.1 From 63db1b646f38af963f31294f892a88a4a8dee06b Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Fri, 30 Oct 2015 11:35:11 +0100 Subject: [PATCH 10/13] test in weblogic: fix interceptor + stateless on abstract --- .../ExceptionHandlingInterceptor.java | 15 +- .../servlet/ServletRestfulResponse.java | 2 +- .../AbstractJaxRsConformanceProvider.java | 10 +- .../jaxrs/server/AbstractJaxRsProvider.java | 3 - .../server/AbstractJaxRsResourceProvider.java | 12 +- .../BaseServerRuntimeResponseException.java | 16 ++ .../JaxRsExceptionInterceptor.java | 194 +++++------------- .../jaxrs/server/util/MethodBindings.java | 3 +- .../AbstractJaxRsResourceProviderTest.java | 17 +- .../TestJaxRsConformanceRestProvider.java | 2 +- .../JaxRsExceptionInterceptorTest.java | 105 ++++++++++ .../jaxrs/server/util/MethodBindingsTest.java | 5 +- .../example/JaxRsConformanceProvider.java | 2 +- .../example/JaxRsPatientRestProvider.java | 10 +- .../example/JaxRsPatientProviderTest.java | 3 +- 15 files changed, 220 insertions(+), 179 deletions(-) create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/BaseServerRuntimeResponseException.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java index 2fed137b4ea..a66e68cca2b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/interceptor/ExceptionHandlingInterceptor.java @@ -36,6 +36,7 @@ import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.rest.api.SummaryEnum; import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.server.IRestfulResponse; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.util.OperationOutcomeUtil; @@ -47,6 +48,13 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter { @Override public boolean handleException(RequestDetails theRequestDetails, BaseServerResponseException theException, HttpServletRequest theRequest, HttpServletResponse theResponse) throws ServletException, IOException { + handleException(theRequestDetails, theException); + return false; + } + + public Object handleException(RequestDetails theRequestDetails, BaseServerResponseException theException) + throws ServletException, IOException { + IRestfulResponse response = theRequestDetails.getResponse(); FhirContext ctx = theRequestDetails.getServer().getFhirContext(); @@ -64,22 +72,19 @@ public class ExceptionHandlingInterceptor extends InterceptorAdapter { if (isNotBlank(next.getKey()) && next.getValue() != null) { String nextKey = next.getKey(); for (String nextValue : next.getValue()) { - theResponse.addHeader(nextKey, nextValue); + response.addHeader(nextKey, nextValue); } } } } - theRequestDetails.getResponse().streamResponseAsResource(oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, false, false); - + return response.streamResponseAsResource(oo, true, Collections.singleton(SummaryEnum.FALSE), statusCode, false, false); // theResponse.setStatus(statusCode); // theRequestDetails.getServer().addHeadersToResponse(theResponse); // theResponse.setContentType("text/plain"); // theResponse.setCharacterEncoding("UTF-8"); // theResponse.getWriter().append(theException.getMessage()); // theResponse.getWriter().close(); - - return false; } @Override diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java index d19375d3142..97ce9f5ebfb 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/server/servlet/ServletRestfulResponse.java @@ -64,7 +64,7 @@ public class ServletRestfulResponse extends RestfulResponse, IResourceProvider> provider : getProviders().entrySet()) { addProvider(provider.getValue(), provider.getKey()); } @@ -86,7 +80,7 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv myConformance = serverConformanceProvider.getServerConformance(null); } - protected abstract ConcurrentHashMap, IResourceProvider> getProviders() throws Exception; + protected abstract ConcurrentHashMap, IResourceProvider> getProviders(); @OPTIONS @Path("/metadata") diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index 7ebeefc3ddd..a5cb5034643 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -66,9 +66,6 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults, I return getBaseForServer(); } - /** - * PARSING METHODS - */ protected JaxRsRequest createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { return new JaxRsRequest(this, resourceString, requestType, restOperation); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java index 5783119056f..bd22c2552e7 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java @@ -20,6 +20,7 @@ import javax.ws.rs.core.Response; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.Validate; +import ca.uhn.fhir.jaxrs.server.interceptor.BaseServerRuntimeResponseException; import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.jaxrs.server.util.MethodBindings; @@ -35,6 +36,7 @@ import ca.uhn.fhir.rest.server.IPagingProvider; import ca.uhn.fhir.rest.server.IRestfulServer; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; import ca.uhn.fhir.util.UrlUtil; @@ -45,8 +47,10 @@ import ca.uhn.fhir.util.UrlUtil; */ @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 AbstractJaxRsResourceProvider extends AbstractJaxRsProvider implements IRestfulServer { + private static final ExceptionHandlingInterceptor EXCEPTION_HANDLING_INTERCEPTOR = new ExceptionHandlingInterceptor(); private final MethodBindings bindings; protected AbstractJaxRsResourceProvider() { @@ -153,8 +157,12 @@ public abstract class AbstractJaxRsResourceProvider extends private Response executeMethod(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id, BaseMethodBinding method) throws IOException { - final RequestDetails theRequest = createRequestDetails(resourceString, requestType, restOperation, id); - return (Response) method.invokeServer(this, theRequest); + final JaxRsRequest theRequest = createRequestDetails(resourceString, requestType, restOperation, id); + try { + return (Response) method.invokeServer(this, theRequest); + } catch(BaseServerRuntimeResponseException theException) { + return new JaxRsExceptionInterceptor().handleException(theRequest, theException); + } } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/BaseServerRuntimeResponseException.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/BaseServerRuntimeResponseException.java new file mode 100644 index 00000000000..dd66a9f2596 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/BaseServerRuntimeResponseException.java @@ -0,0 +1,16 @@ +package ca.uhn.fhir.jaxrs.server.interceptor; + +import javax.ejb.ApplicationException; + +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; + +@ApplicationException(rollback=false) +public class BaseServerRuntimeResponseException extends BaseServerResponseException { + + private static final long serialVersionUID = 1L; + + public BaseServerRuntimeResponseException(BaseServerResponseException base) { + super(base.getStatusCode(), base.getMessage(), base.getCause(), base.getOperationOutcome()); + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java index 55e70a4ca95..bd94a0cd1ef 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java @@ -1,169 +1,77 @@ package ca.uhn.fhir.jaxrs.server.interceptor; -import static org.apache.commons.lang3.StringUtils.isNotBlank; - -import java.util.Arrays; -import java.util.Map; -import java.util.Map.Entry; +import java.io.IOException; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; +import javax.servlet.ServletException; import javax.ws.rs.core.Context; import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.ResponseBuilder; import javax.ws.rs.core.UriInfo; -import org.apache.commons.lang3.exception.ExceptionUtils; -import org.hl7.fhir.instance.model.api.IBaseOperationOutcome; -import org.slf4j.LoggerFactory; - import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; -import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; -import ca.uhn.fhir.util.OperationOutcomeUtil; +import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; public class JaxRsExceptionInterceptor { - private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(JaxRsExceptionInterceptor.class); - private Class[] myReturnStackTracesForExceptionTypes; - + private ExceptionHandlingInterceptor exceptionHandler; @Context private UriInfo info; @Context private HttpHeaders headers; + AbstractJaxRsProvider theServer; + FhirContext fhirContext = AbstractJaxRsProvider.CTX; - @AroundInvoke - public Object intercept(final InvocationContext ctx) throws Exception { - try { - if(!ourLog.isDebugEnabled() || ctx.getMethod().getName().contains("getResourceType")) { - return ctx.proceed(); - } else { - ourLog.debug("METHOD_CALL : " + ctx.getMethod().getName() + " [ " + Arrays.asList(ctx.getParameters()) + "] "); - Object proceed = ctx.proceed(); - ourLog.debug("RESULT : " + proceed.toString()); - return proceed; - } - } catch(final Exception theException) { - return handleException(theException); - } - } - - public Response handleException(final Throwable theException) - { - IBaseOperationOutcome oo = null; - int statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR; - - if (theException instanceof BaseServerResponseException) { - oo = ((BaseServerResponseException) theException).getOperationOutcome(); - statusCode = ((BaseServerResponseException) theException).getStatusCode(); - } - - /* - * Generate an OperationOutcome to return, unless the exception throw by the resource provider had one - */ - if (oo == null) { - try { - final RuntimeResourceDefinition ooDef = fhirContext.getResourceDefinition("OperationOutcome"); - oo = (IBaseOperationOutcome) ooDef.getImplementingClass().newInstance(); - - if (theException instanceof InternalErrorException) { - ourLog.error("Failure during REST processing", theException); - populateDetails(fhirContext, theException, oo); - } else if (theException instanceof BaseServerResponseException) { - ourLog.warn("Failure during REST processing: {}", theException); - final BaseServerResponseException baseServerResponseException = (BaseServerResponseException) theException; - statusCode = baseServerResponseException.getStatusCode(); - populateDetails(fhirContext, theException, oo); - if (baseServerResponseException.getAdditionalMessages() != null) { - for (final String next : baseServerResponseException.getAdditionalMessages()) { - OperationOutcomeUtil.addIssue(fhirContext, oo, "error", next); - } - } - } else { - ourLog.error("Failure during REST processing: " + theException.toString(), theException); - populateDetails(fhirContext, theException, oo); - statusCode = Constants.STATUS_HTTP_500_INTERNAL_ERROR; - } - } catch (final Exception e1) { - ourLog.error("Failed to instantiate OperationOutcome resource instance", e1); - final ResponseBuilder result = Response.status(Constants.STATUS_HTTP_500_INTERNAL_ERROR); - result.header(Constants.HEADER_CONTENT_TYPE, Constants.CT_TEXT_WITH_UTF8); - result.header(Constants.HEADER_CONTENT_ENCODING, Constants.CHARSET_NAME_UTF8); - result.entity(theException.getMessage()); - return result.build(); - } - } else { - ourLog.error("Unknown error during processing", theException); - } - - // Add headers associated with the specific error code - if (theException instanceof BaseServerResponseException) { - final Map additional = ((BaseServerResponseException) theException).getAssociatedHeaders(); - if (additional != null) { - for (final Entry next : additional.entrySet()) { - if (isNotBlank(next.getKey()) && next.getValue() != null) { - final String nextKey = next.getKey(); - for (final String nextValue : next.getValue()) { - addHeader(nextKey, nextValue); - } - } - } - } - } - - final boolean requestIsBrowser = false; // RestfulServer.requestIsBrowser(theRequest); - final String fhirServerBase = ""; // theRequestDetails.getFhirServerBase(); - - // theResponse.setStatus(statusCode); - // theRequestDetails.getServer().addHeadersToResponse(theResponse); - // theResponse.setContentType("text/plain"); - // theResponse.setCharacterEncoding("UTF-8"); - // theResponse.getWriter().append(theException.getMessage()); - // theResponse.getWriter().close(); - - final ResponseBuilder result = Response.status(statusCode); - //final String resName = ctx.getResourceDefinition(oo).getName(); - result.header(Constants.HEADER_CONTENT_TYPE, Constants.CT_TEXT_WITH_UTF8); - result.entity(theException.getMessage()); - return result.build(); - } - - private void addHeader(final String nextKey, final String nextValue) { - throw new UnsupportedOperationException(); - } - - private void populateDetails(final FhirContext theCtx, final Throwable theException, final IBaseOperationOutcome theOo) { - if (myReturnStackTracesForExceptionTypes != null) { - for (final Class next : myReturnStackTracesForExceptionTypes) { - if (next.isAssignableFrom(theException.getClass())) { - final String detailsValue = theException.getMessage() + "\n\n" + ExceptionUtils.getStackTrace(theException); - OperationOutcomeUtil.addIssue(theCtx, theOo, "error", detailsValue); - return; - } - } - } - - OperationOutcomeUtil.addIssue(theCtx, theOo, "error", theException.getMessage()); - } - - /** - * If any server methods throw an exception which extends any of the given exception types, the exception stack trace - * will be returned to the user. This can be useful for helping to diagnose issues, but may not be desirable for - * production situations. - * - * @param theExceptionTypes - * The exception types for which to return the stack trace to the user. - * @return Returns an instance of this interceptor, to allow for easy method chaining. - */ - public JaxRsExceptionInterceptor setReturnStackTracesForExceptionTypes(final Class... theExceptionTypes) { - myReturnStackTracesForExceptionTypes = theExceptionTypes; - return this; - } + public JaxRsExceptionInterceptor() { + this.exceptionHandler = new ExceptionHandlingInterceptor(); + } + public JaxRsExceptionInterceptor(ExceptionHandlingInterceptor exceptionHandler) { + this.exceptionHandler = exceptionHandler; + } + + @AroundInvoke + public Object intercept(final InvocationContext ctx) throws Throwable { + try { + return ctx.proceed(); + } catch(final Exception theException) { + theServer = (AbstractJaxRsProvider) ctx.getTarget(); + throw convertException(theServer, theException); + } + } + + public BaseServerRuntimeResponseException convertException(final AbstractJaxRsProvider theServer, final Exception theException) { + JaxRsRequest requestDetails = new JaxRsRequest(theServer, null, null, null); + BaseServerResponseException convertedException = preprocessException(theException, requestDetails); + return new BaseServerRuntimeResponseException(convertedException); + } + + public Response handleException(JaxRsRequest theRequest, BaseServerRuntimeResponseException theException) + throws IOException { + return handleExceptionWithoutServletError(theRequest, theException); + } + + private BaseServerResponseException preprocessException(final Exception theException, JaxRsRequest requestDetails) { + try { + return exceptionHandler.preProcessOutgoingException(requestDetails, theException, null); + } catch(ServletException e) { + return new InternalErrorException(e); + } + } + + private Response handleExceptionWithoutServletError(JaxRsRequest theRequest, BaseServerResponseException theException) throws IOException { + try { + return (Response) exceptionHandler.handleException(theRequest, theException); + } catch (ServletException e) { + BaseServerResponseException newException = preprocessException(new InternalErrorException(e), theRequest); + return handleExceptionWithoutServletError(theRequest, newException); + } + } } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java index 0a8623da6e9..3d659b0b114 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java @@ -11,6 +11,7 @@ import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.OperationMethodBinding; import ca.uhn.fhir.rest.method.SearchMethodBinding; +import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; import ca.uhn.fhir.util.ReflectionUtil; /** @@ -63,7 +64,7 @@ public class MethodBindings { String nonEmptyQualifier = StringUtils.defaultIfBlank(qualifier, ""); ConcurrentHashMap> map = operationBindings.get(operationType); if(map == null || !map.containsKey(nonEmptyQualifier)) { - throw new UnsupportedOperationException(); + throw new NotImplementedOperationException("Operation not implemented"); } else { return map.get(nonEmptyQualifier); } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java index 8f384b3d229..441203498e4 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jaxrs.server; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; +import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; import static org.mockito.Matchers.any; import static org.mockito.Matchers.argThat; @@ -35,6 +36,7 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jaxrs.server.example.RandomServerPortProvider; import ca.uhn.fhir.jaxrs.server.example.TestJaxRsConformanceRestProvider; import ca.uhn.fhir.jaxrs.server.example.TestJaxRsMockPatientRestProvider; +import ca.uhn.fhir.jaxrs.server.interceptor.BaseServerRuntimeResponseException; import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; @@ -351,15 +353,16 @@ public class AbstractJaxRsResourceProviderTest { @Test public void testXFindUnknownPatient() { try { - when(mock.find(idCaptor.capture())).thenThrow(new ResourceNotFoundException(new IdDt("999955541264"))); - final Patient existing = client.read(Patient.class, "999955541264"); + BaseServerRuntimeResponseException notFoundException = new BaseServerRuntimeResponseException(new ResourceNotFoundException(new IdDt("999955541264"))); + when(mock.find(idCaptor.capture())).thenThrow(notFoundException); + client.read(Patient.class, "999955541264"); fail(); - } catch (final Exception e) { - e.printStackTrace(); - // assertEquals(e.getStatusCode(), 404); + } catch (final ResourceNotFoundException e) { + assertEquals(ResourceNotFoundException.STATUS_CODE, e.getStatusCode()); + assertTrue(e.getMessage().contains("999955541264")); } - } - + } + private Bundle getPatientBundle(int size) { Bundle result = new Bundle(); for (long i = 0; i < size; i++) { diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java index ad99dd03696..7c68212dedf 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java @@ -27,7 +27,7 @@ public class TestJaxRsConformanceRestProvider extends AbstractJaxRsConformancePr } @Override - protected ConcurrentHashMap, IResourceProvider> getProviders() throws Exception { + protected ConcurrentHashMap, IResourceProvider> getProviders() { ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>(); map.put(TestJaxRsMockPatientRestProvider.class, new TestJaxRsMockPatientRestProvider()); map.put(TestJaxRsConformanceRestProvider.class, new TestJaxRsConformanceRestProvider()); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java new file mode 100644 index 00000000000..25ff1e829ac --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java @@ -0,0 +1,105 @@ +package ca.uhn.fhir.jaxrs.server.interceptor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertSame; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import static org.mockito.Matchers.any; +import static org.mockito.Matchers.isNull; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.doThrow; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.when; + +import java.util.HashMap; + +import javax.interceptor.InvocationContext; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.ws.rs.core.HttpHeaders; +import javax.ws.rs.core.Response; + +import org.junit.Before; +import org.junit.Test; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; +import ca.uhn.fhir.jaxrs.server.example.TestDummyPatientProvider; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; +import ca.uhn.fhir.rest.method.RequestDetails; +import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; +import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; +import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; + +public class JaxRsExceptionInterceptorTest { + + JaxRsExceptionInterceptor interceptor = new JaxRsExceptionInterceptor(); + private InvocationContext context; + + @Before + public void setUp() { + interceptor = new JaxRsExceptionInterceptor(); + context = mock(InvocationContext.class); + TestDummyPatientProvider provider = spy(TestDummyPatientProvider.class); + when(context.getTarget()).thenReturn(provider); + doReturn("http://baseUri").when(provider).getBaseForServer(); + doReturn(new HashMap()).when(provider).getQueryMap(); + doReturn(mock(HttpHeaders.class)).when(provider).getHeaders(); + } + + @Test + public void testInterceptWithBaseServerError() throws Throwable { + NotImplementedOperationException thrownException = new NotImplementedOperationException("not implemented"); + when(context.proceed()).thenThrow(thrownException); + try { + interceptor.intercept(context); + fail(); + } catch (BaseServerResponseException e) { + assertEquals(e.getMessage(), thrownException.getMessage()); + } + } + + @Test + public void testIntercepWithServletError() throws Throwable { + ExceptionHandlingInterceptor exceptionHandler = mock(ExceptionHandlingInterceptor.class); + when(exceptionHandler.preProcessOutgoingException(any(RequestDetails.class), any(Throwable.class), + isNull(HttpServletRequest.class))).thenThrow(new ServletException("someMessage")); + interceptor = new JaxRsExceptionInterceptor(exceptionHandler); + when(context.proceed()).thenThrow(new ServletException()); + try { + interceptor.intercept(context); + fail(); + } catch (BaseServerResponseException e) { + assertTrue(e.getMessage().contains("someMessage")); + } + } + + @Test + public void testInterceptServletWithoutError() throws Throwable { + Object expected = new Object(); + when(context.proceed()).thenReturn(expected); + Object result = interceptor.intercept(context); + assertSame(expected, result); + } + + @Test + public void testHandleExceptionWithServletError() throws Throwable { + JaxRsRequest request = new JaxRsRequest((AbstractJaxRsProvider) context.getTarget(), null, null, null); + + ExceptionHandlingInterceptor exceptionHandler = spy(ExceptionHandlingInterceptor.class); + doThrow(new ServletException("someMessage")).when(exceptionHandler).preProcessOutgoingException(any(RequestDetails.class), any(Throwable.class), + isNull(HttpServletRequest.class)); + + interceptor = new JaxRsExceptionInterceptor(exceptionHandler); + + when(context.proceed()).thenThrow(new ServletException()); + + BaseServerRuntimeResponseException thrownException = new BaseServerRuntimeResponseException(new NotImplementedOperationException("not implemented")); + doThrow(new javax.servlet.ServletException("someMessage")).when(exceptionHandler).handleException(request, thrownException); + BaseServerRuntimeResponseException internalException = thrownException; + Response result = interceptor.handleException(request, internalException); + assertEquals(InternalErrorException.STATUS_CODE, result.getStatus()); + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java index 8649eac3ad8..0c32db77039 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java @@ -26,6 +26,7 @@ import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.param.StringParam; +import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; @FixMethodOrder(MethodSorters.DEFAULT) public class MethodBindingsTest { @@ -35,7 +36,7 @@ public class MethodBindingsTest { MethodBindings.getClassBindings().clear(); } - @Test(expected = UnsupportedOperationException.class) + @Test(expected = NotImplementedOperationException.class) public void testFindMethodsForProviderNotDefinedMappingMethods() { new TestDummyPatientProvider().getBindings().getBinding(RestOperationTypeEnum.UPDATE, ""); } @@ -120,7 +121,7 @@ public class MethodBindingsTest { try { bindings.getBinding(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, "$thirdMethod"); fail(); - } catch(UnsupportedOperationException e){ + } catch(NotImplementedOperationException e){ } } diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java index a1e3c0d0a55..049952661ce 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java @@ -29,7 +29,7 @@ public class JaxRsConformanceProvider extends AbstractJaxRsConformanceProvider { } @Override - protected ConcurrentHashMap, IResourceProvider> getProviders() throws Exception { + protected ConcurrentHashMap, IResourceProvider> getProviders() { ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>(); map.put(JaxRsConformanceProvider.class, new JaxRsConformanceProvider()); map.put(JaxRsPatientRestProvider.class, new JaxRsPatientRestProvider()); diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java index 93a7c67b007..a5190c9b4cf 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jaxrs.server.example; +import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; import java.util.LinkedList; @@ -66,7 +67,7 @@ public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider> patients = new ConcurrentHashMap>(); - public JaxRsPatientRestProvider() throws Exception { + public JaxRsPatientRestProvider() { super(JaxRsPatientRestProvider.class); } @@ -132,7 +133,8 @@ public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider Date: Fri, 30 Oct 2015 19:13:20 +0100 Subject: [PATCH 11/13] add javadoc + create builder for jaxrsrequest --- .../AbstractJaxRsConformanceProvider.java | 63 ++- .../jaxrs/server/AbstractJaxRsProvider.java | 68 +++- .../server/AbstractJaxRsResourceProvider.java | 374 +++++++++++------- .../JaxRsExceptionInterceptor.java | 50 ++- ...ption.java => JaxRsResponseException.java} | 13 +- .../server/util/JaxRsMethodBindings.java | 133 +++++++ .../fhir/jaxrs/server/util/JaxRsRequest.java | 108 ++++- .../fhir/jaxrs/server/util/JaxRsResponse.java | 23 +- .../jaxrs/server/util/MethodBindings.java | 86 ---- .../AbstractJaxRsConformanceProviderTest.java | 14 +- .../AbstractJaxRsResourceProviderTest.java | 10 +- .../JaxRsExceptionInterceptorTest.java | 15 +- .../JaxRsResponseExceptionTest.java | 23 ++ .../RandomServerPortProvider.java | 2 +- .../TestJaxRsConformanceRestProvider.java | 11 +- .../TestJaxRsDummyPatientProvider.java} | 7 +- .../TestJaxRsMockPatientRestProvider.java | 11 +- ...Test.java => JaxRsMethodBindingsTest.java} | 30 +- .../jaxrs/server/util/JaxRsRequestTest.java | 12 +- .../jaxrs/server/util/JaxRsResponseTest.java | 18 - .../example/FhirPatientDemoApplication.java | 1 + .../example/JaxRsPatientRestProvider.java | 21 +- .../example/JaxRsPatientProviderTest.java | 3 +- 23 files changed, 716 insertions(+), 380 deletions(-) rename hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/{BaseServerRuntimeResponseException.java => JaxRsResponseException.java} (53%) create mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java delete mode 100644 hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java create mode 100644 hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseExceptionTest.java rename hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/{example => test}/RandomServerPortProvider.java (93%) rename hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/{example => test}/TestJaxRsConformanceRestProvider.java (86%) rename hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/{example/TestDummyPatientProvider.java => test/TestJaxRsDummyPatientProvider.java} (51%) rename hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/{example => test}/TestJaxRsMockPatientRestProvider.java (93%) rename hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/{MethodBindingsTest.java => JaxRsMethodBindingsTest.java} (81%) diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java index e44ef576d65..e324ea7177b 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java @@ -18,11 +18,12 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; +import org.apache.commons.lang3.StringUtils; import org.slf4j.LoggerFactory; import ca.uhn.fhir.context.ConfigurationException; import ca.uhn.fhir.context.RuntimeResourceDefinition; -import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder; import ca.uhn.fhir.model.dstu2.resource.Conformance; import ca.uhn.fhir.rest.annotation.IdParam; import ca.uhn.fhir.rest.api.RequestTypeEnum; @@ -39,28 +40,43 @@ import ca.uhn.fhir.rest.server.provider.dstu2.ServerConformanceProvider; import ca.uhn.fhir.util.ReflectionUtil; /** - * Conformance Rest Service + * This is the conformance provider for the jax rs servers. It requires all providers to be registered + * during startup because the conformance profile is generated during the postconstruct phase. * @author Peter Van Houte */ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) -public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProvider { +public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProvider implements IResourceProvider { - public static final String PATH = "/"; + /** the logger */ private static final org.slf4j.Logger ourLog = LoggerFactory.getLogger(AbstractJaxRsConformanceProvider.class); - + /** the server bindings */ private ResourceBinding myServerBinding = new ResourceBinding(); + /** the resource bindings */ private ConcurrentHashMap myResourceNameToBinding = new ConcurrentHashMap(); + /** the server configuration */ private RestulfulServerConfiguration serverConfiguration = new RestulfulServerConfiguration(); - + + /** the conformance. It is created once during startup */ private Conformance myConformance; - public AbstractJaxRsConformanceProvider(String implementationDescription, String serverName, String serverVersion) { + /** + * Constructor allowing the description, servername and server to be set + * @param implementationDescription the implementation description. If null, "" is used + * @param serverName the server name. If null, "" is used + * @param serverVersion the server version. If null, "" is used + */ + protected AbstractJaxRsConformanceProvider(String implementationDescription, String serverName, String serverVersion) { serverConfiguration.setFhirContext(getFhirContext()); - serverConfiguration.setImplementationDescription(implementationDescription); - serverConfiguration.setServerName(serverName); - serverConfiguration.setServerVersion(serverVersion); + serverConfiguration.setImplementationDescription(StringUtils.defaultIfEmpty(implementationDescription, "")); + serverConfiguration.setServerName(StringUtils.defaultIfEmpty(serverName, "")); + serverConfiguration.setServerVersion(StringUtils.defaultIfEmpty(serverVersion, "")); } + /** + * This method will set the conformance during the postconstruct phase. The + * method {@link AbstractJaxRsConformanceProvider#getProviders()} is used to + * get all the resource providers include in the conformance + */ @PostConstruct protected void setUpPostConstruct() { for (Entry, IResourceProvider> provider : getProviders().entrySet()) { @@ -80,24 +96,45 @@ public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProv myConformance = serverConformanceProvider.getServerConformance(null); } + /** + * This method must return all the resource providers which need to be included in the conformance + * @return a map of the resource providers and their corresponding classes. This class needs to be given + * explicitly because retrieving the interface using {@link Object#getClass()} may not give the correct + * interface in a jee environment. + */ protected abstract ConcurrentHashMap, IResourceProvider> getProviders(); + /** + * This method will retrieve the conformance using the http OPTIONS method + * @return the response containing the conformance + */ @OPTIONS @Path("/metadata") public Response conformanceUsingOptions() throws IOException { return conformance(); } + /** + * This method will retrieve the conformance using the http GET method + * @return the response containing the conformance + */ @GET @Path("/metadata") public Response conformance() throws IOException { - JaxRsRequest request = createRequestDetails(null, RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA); - IRestfulResponse response = request.getResponse(); + Builder request = getRequest(RequestTypeEnum.OPTIONS, RestOperationTypeEnum.METADATA); + IRestfulResponse response = request.build().getResponse(); response.addHeader(Constants.HEADER_CORS_ALLOW_ORIGIN, "*"); return (Response) response.returnResponse(ParseAction.create(myConformance), Constants.STATUS_HTTP_200_OK, true, null, getResourceType().getSimpleName()); } - public int addProvider(Object theProvider, Class theProviderInterface) throws ConfigurationException { + /** + * This method will add a provider to the conformance. This method is almost an exact copy of {@link ca.uhn.fhir.rest.server.RestfulServer#findResourceMethods } + * @param theProvider an instance of the provider interface + * @param theProviderInterface the class describing the providers interface + * @return the numbers of basemethodbindings added + * @see ca.uhn.fhir.rest.server.RestfulServer#findResourceMethods + */ + public int addProvider(IResourceProvider theProvider, Class theProviderInterface) throws ConfigurationException { int count = 0; for (Method m : ReflectionUtil.getDeclaredMethods(theProviderInterface)) { diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index a5cb5034643..58089966483 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jaxrs.server; import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Map.Entry; import javax.ws.rs.core.Context; @@ -11,27 +12,30 @@ import javax.ws.rs.core.UriInfo; import ca.uhn.fhir.context.FhirContext; 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.server.AddProfileTagEnum; import ca.uhn.fhir.rest.server.ETagSupportEnum; import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.HardcodedServerAddressStrategy; -import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IRestfulServerDefaults; import ca.uhn.fhir.rest.server.IServerAddressStrategy; /** - * Abstract Jax Rs Rest Server + * This is the abstract superclass for all jaxrs providers. It contains some defaults implementing + * the IRestfulServerDefaults interface and exposes the uri and headers. * @author Peter Van Houte - * */ -public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults, IResourceProvider { +public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { - public static FhirContext CTX = FhirContext.forDstu2(); + /** a static initialization for the fhircontext. Only DSTU2 is supported */ + private static final FhirContext CTX = FhirContext.forDstu2(); + /** the uri info */ @Context private UriInfo theUriInfo; + /** the http headers */ @Context private HttpHeaders theHeaders; @@ -40,10 +44,11 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults, I return CTX; } - /** - * param and query methods + /** + * This method returns the query parameters + * @return the query parameters */ - public HashMap getQueryMap() { + public Map getParameters() { MultivaluedMap queryParameters = getUriInfo().getQueryParameters(); HashMap params = new HashMap(); for (Entry> paramEntry : queryParameters.entrySet()) { @@ -52,25 +57,36 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults, I return params; } + /** + * This method returns the default server address strategy. The default strategy return the + * base uri for the request {@link AbstractJaxRsProvider#getBaseForRequest() getBaseForRequest()} + * @return + */ public IServerAddressStrategy getServerAddressStrategy() { HardcodedServerAddressStrategy addressStrategy = new HardcodedServerAddressStrategy(); addressStrategy.setValue(getBaseForRequest()); return addressStrategy; } - + + /** + * This method returns the server base, independent of the request or resource. + * @see javax.ws.rs.core.UriInfo#getBaseUri() + * @return the ascii string for the server base + */ public String getBaseForServer() { return getUriInfo().getBaseUri().toASCIIString(); } + /** + * This method returns the server base, including the resource path. + * {@link javax.ws.rs.core.UriInfo#getBaseUri() UriInfo#getBaseUri()} + * @return the ascii string for the base resource provider path + */ public String getBaseForRequest() { return getBaseForServer(); } - protected JaxRsRequest createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { - return new JaxRsRequest(this, resourceString, requestType, restOperation); - } - - /** + /** * Get the uriInfo * @return the uri info */ @@ -101,30 +117,52 @@ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults, I public void setHeaders(HttpHeaders headers) { this.theHeaders = headers; } + + /** + * 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(RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { + return new JaxRsRequest.Builder(this, requestType, restOperation); + } /** - * DEFAULT VALUES + * DEFAULT = EncodingEnum.JSON */ @Override public EncodingEnum getDefaultResponseEncoding() { return EncodingEnum.JSON; } + /** + * DEFAULT = true + */ @Override public boolean isDefaultPrettyPrint() { return true; } + /** + * DEFAULT = ETagSupportEnum.DISABLED + */ @Override public ETagSupportEnum getETagSupport() { return ETagSupportEnum.DISABLED; } + /** + * DEFAULT = AddProfileTagEnum.NEVER + */ @Override public AddProfileTagEnum getAddProfileTag() { return AddProfileTagEnum.NEVER; } + /** + * DEFAULT = false + */ @Override public boolean isUseBrowserFriendlyContentTypes() { return true; diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java index bd22c2552e7..270f66bf41b 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java @@ -17,187 +17,279 @@ import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; -import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; - -import ca.uhn.fhir.jaxrs.server.interceptor.BaseServerRuntimeResponseException; 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.JaxRsRequest; -import ca.uhn.fhir.jaxrs.server.util.MethodBindings; +import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest.Builder; import ca.uhn.fhir.model.api.IResource; -import ca.uhn.fhir.model.primitive.IdDt; 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.method.RequestDetails; import ca.uhn.fhir.rest.server.BundleInclusionRule; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IPagingProvider; +import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IRestfulServer; -import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; -import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; -import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.util.UrlUtil; /** - * Fhir Physician Rest Service - * @author axmpm - * + * This server is the abstract superclass for all resource providers. It exposes + * a large amount of the fhir api functionality using JAXRS + * @author Peter Van Houte */ -@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}) +@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 AbstractJaxRsResourceProvider extends AbstractJaxRsProvider implements IRestfulServer { - - private static final ExceptionHandlingInterceptor EXCEPTION_HANDLING_INTERCEPTOR = new ExceptionHandlingInterceptor(); - private final MethodBindings bindings; - +public abstract class AbstractJaxRsResourceProvider extends AbstractJaxRsProvider + implements IRestfulServer, IResourceProvider { + + /** the method bindings for this class */ + private final JaxRsMethodBindings theBindings; + + /** + * The default constructor. The method bindings are retrieved from the class + * being constructed. + */ protected AbstractJaxRsResourceProvider() { - bindings = MethodBindings.getMethodBindings(this, getClass()); - } + theBindings = JaxRsMethodBindings.getMethodBindings(this, getClass()); + } - protected AbstractJaxRsResourceProvider(Class subclass) { - bindings = MethodBindings.getMethodBindings(this, subclass); - } + /** + * 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 AbstractJaxRsResourceProvider(Class theProviderClass) { + theBindings = JaxRsMethodBindings.getMethodBindings(this, theProviderClass); + } + /** + * The base for request for a resource provider has the following form:
+ * {@link AbstractJaxRsResourceProvider#getBaseForServer() + * getBaseForServer()} + "/" + + * {@link AbstractJaxRsResourceProvider#getResourceType() getResourceType()} + * .{@link java.lang.Class#getSimpleName() getSimpleName()} + */ @Override public String getBaseForRequest() { - try { - return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); - } catch(Exception e) { - // cannot happen - return null; - } - } + try { + return new URL(getUriInfo().getBaseUri().toURL(), getResourceType().getSimpleName()).toExternalForm(); + } catch (Exception e) { + // cannot happen + return null; + } + } - @POST - @Interceptors(JaxRsExceptionInterceptor.class) - public Response create(final String resourceString) - throws Exception { - return executeMethod(resourceString, RequestTypeEnum.POST, RestOperationTypeEnum.CREATE, null); - } + /** + * Create a new resource with a server assigned id + * + * @param resource the body of the post method containing resource being created in a xml/json form + * @return the response + * @see https://www.hl7. org/fhir/http.html#create + */ + @POST + public Response create(final String resource) throws Exception { + return execute(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.CREATE).resource(resource)); + } - @POST - @Interceptors(JaxRsExceptionInterceptor.class) - @Path("/_search") - public Response searchWithPost() throws Exception { - return executeMethod(null, RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE, null); - } - - @GET - @Interceptors(JaxRsExceptionInterceptor.class) - public Response search() throws Exception { - return executeMethod(null, RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE, null); - } - - @PUT - @Path("/{id}") - @Interceptors(JaxRsExceptionInterceptor.class) - public Response update(@PathParam("id") final String id, final String resourceString) - throws Exception { - return executeMethod(resourceString, RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE, id); - } - - @DELETE - @Path("/{id}") - @Interceptors(JaxRsExceptionInterceptor.class) - public Response delete(@PathParam("id") final String id) throws Exception { - return executeMethod(null, RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE, id); - } - - - @GET - @Path("/{id}") - @Interceptors(JaxRsExceptionInterceptor.class) - public Response find(@PathParam("id") final String id) throws Exception { - return executeMethod(null, RequestTypeEnum.GET, RestOperationTypeEnum.READ, id); - } - - protected Response customOperation(final String resource, RequestTypeEnum requestType, String id, String operationName, RestOperationTypeEnum operationType) - throws Exception { - Validate.notNull(resource, "resource may not be null"); - return executeMethod(resource, requestType, operationType, id, getBindings().getBinding(operationType, operationName)); - } - - @GET - @Path("/{id}/_history/{version}") - @Interceptors(JaxRsExceptionInterceptor.class) - public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String versionString) - throws BaseServerResponseException, IOException { - BaseMethodBinding method = getBindings().getBinding(RestOperationTypeEnum.VREAD); - final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); - if (id == null) { - throw new InvalidRequestException("Don't know how to handle request path: " + getUriInfo().getRequestUri().toASCIIString()); - } - theRequest.setId(new IdDt(getBaseForRequest(), id, UrlUtil.unescape(versionString))); - return (Response) method.invokeServer(this, theRequest); - } + /** + * Search the resource type based on some filter criteria + * + * @return the response + * @see https://www.hl7.org/fhir/http.html#search + */ + @POST + @Path("/_search") + public Response searchWithPost() throws Exception { + return execute(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE)); + } + /** + * Search the resource type based on some filter criteria + * + * @return the response + * @see https://www.hl7.org/fhir/http.html#search + */ @GET - @Path("/{id}/{compartment}") - @Interceptors(JaxRsExceptionInterceptor.class) - public Response findCompartment(@PathParam("id") final String id, @PathParam("compartment") final String compartment) throws BaseServerResponseException, IOException { - BaseMethodBinding method = getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE, compartment); - final RequestDetails theRequest = createRequestDetails(null, RequestTypeEnum.GET, RestOperationTypeEnum.VREAD); - if (id == null) { - throw new InvalidRequestException("Don't know how to handle request path: " + getUriInfo().getRequestUri().toASCIIString()); - } - theRequest.setCompartmentName(compartment); - theRequest.setId(new IdDt(getBaseForRequest(), id)); - return (Response) method.invokeServer(this, theRequest); - } - - private > Response executeMethod(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id) - throws BaseServerResponseException, IOException { - BaseMethodBinding method = getBindings().getBinding(restOperation); - return executeMethod(resourceString, requestType, restOperation, id, method); - } + public Response search() throws Exception { + return execute(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE)); + } - private Response executeMethod(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id, - BaseMethodBinding method) - throws IOException { - final JaxRsRequest theRequest = createRequestDetails(resourceString, requestType, restOperation, id); - try { - return (Response) method.invokeServer(this, theRequest); - } catch(BaseServerRuntimeResponseException theException) { - return new JaxRsExceptionInterceptor().handleException(theRequest, theException); - } - } - - - protected JaxRsRequest createRequestDetails(final String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation, String id) { - JaxRsRequest theRequest = super.createRequestDetails(resourceString, requestType, restOperation); - theRequest.setId(StringUtils.isBlank(id) ? null : new IdDt(getResourceType().getName(), UrlUtil.unescape(id))); - if(restOperation == RestOperationTypeEnum.UPDATE) { - String contentLocation = theRequest.getHeader(Constants.HEADER_CONTENT_LOCATION); - if (contentLocation != null) { - theRequest.setId(new IdDt(contentLocation)); - } - } - return theRequest; - } - - @Override + /** + * Update an existing resource by its id (or create it if it is new) + * + * @param id the id of the resource + * @param resource the body contents for the put method + * @return the response + * @see https://www.hl7.org/fhir/http.html#update + */ + @PUT + @Path("/{id}") + public Response update(@PathParam("id") final String id, final String resource) throws Exception { + return execute(getRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).id(id).resource(resource)); + } + + /** + * Delete a resource + * + * @param id the id of the resource to delete + * @return the response + * @see https://www.hl7.org/fhir/http.html#delete + */ + @DELETE + @Path("/{id}") + public Response delete(@PathParam("id") final String id) throws Exception { + return execute(getRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE).id(id)); + } + + /** + * Read the current state of the resource + * + * @param id the id of the resource to read + * @return the response + * @see https://www.hl7.org/fhir/http.html#read + */ + @GET + @Path("/{id}") + public Response find(@PathParam("id") final String id) throws Exception { + return execute(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).id(id)); + } + + /** + * Execute a custom operation + * + * @param resource the resource to create + * @param requestType the type of request + * @param id the id of the resource on which to perform the operation + * @param operationName the name of the operation to execute + * @param operationType the rest operation type + * @return the response + * @see https://www.hl7.org/fhir/operations.html + */ + protected Response customOperation(final String resource, RequestTypeEnum requestType, String id, + String operationName, RestOperationTypeEnum operationType) throws Exception { + Builder request = getRequest(requestType, operationType).resource(resource).id(id); + return execute(request, operationName); + } + + /** + * Retrieve the update history for a particular resource + * + * @param id the id of the resource + * @param version the version of the resource + * @return the response + * @see https://www.hl7.org/fhir/http.html#history + */ + @GET + @Path("/{id}/_history/{version}") + public Response findHistory(@PathParam("id") final String id, @PathParam("version") final String version) + throws IOException { + Builder theRequest = getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.VREAD).id(id) + .version(version); + return execute(theRequest); + } + + /** + * Compartment Based Access + * + * @param id the resource to which the compartment belongs + * @param compartment the compartment + * @return the repsonse + * @see https://www.hl7.org/fhir/http.html#search + * @see https://www.hl7.org/fhir/compartments.html#compartment + */ + @GET + @Path("/{id}/{compartment}") + public Response findCompartment(@PathParam("id") final String 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 + * + * @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(Builder theRequestBuilder, String methodKey) throws IOException { + JaxRsRequest theRequest = theRequestBuilder.build(); + BaseMethodBinding method = getBinding(theRequest.getRestOperationType(), methodKey); + try { + return (Response) method.invokeServer(this, theRequest); + } catch (JaxRsResponseException theException) { + return new JaxRsExceptionInterceptor().convertExceptionIntoResponse(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(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(RestOperationTypeEnum restOperation, String theBindingKey) { + return getBindings().getBinding(restOperation, theBindingKey); + } + + /** + * Default: an empty list of interceptors + * + * @see ca.uhn.fhir.rest.server.IRestfulServer#getInterceptors() + */ + @Override public List 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; - } + } + /** + * The resource type should return conform to the generic resource included + * in the topic + */ @Override - public abstract Class getResourceType(); + public abstract Class getResourceType(); - public MethodBindings getBindings() { - return bindings; + /** + * Return the bindings defined in this resource provider + * + * @return the jax-rs method bindings + */ + public JaxRsMethodBindings getBindings() { + return theBindings; } } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java index bd94a0cd1ef..adfe7ce0ed8 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java @@ -5,55 +5,67 @@ import java.io.IOException; import javax.interceptor.AroundInvoke; import javax.interceptor.InvocationContext; import javax.servlet.ServletException; -import javax.ws.rs.core.Context; -import javax.ws.rs.core.HttpHeaders; import javax.ws.rs.core.Response; -import javax.ws.rs.core.UriInfo; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; +/** + * An interceptor that catches the jax-rs exceptions + * @author Peter Van Houte + */ public class JaxRsExceptionInterceptor { + /** the existing exception handler which is able to convert exception into responses*/ private ExceptionHandlingInterceptor exceptionHandler; - @Context - private UriInfo info; - @Context - private HttpHeaders headers; - - AbstractJaxRsProvider theServer; - - FhirContext fhirContext = AbstractJaxRsProvider.CTX; + /** + * The default constructor + */ public JaxRsExceptionInterceptor() { this.exceptionHandler = new ExceptionHandlingInterceptor(); } - public JaxRsExceptionInterceptor(ExceptionHandlingInterceptor exceptionHandler) { + /** + * A utility constructor for unit testing + * @param exceptionHandler the handler for the exception conversion + */ + JaxRsExceptionInterceptor(ExceptionHandlingInterceptor exceptionHandler) { this.exceptionHandler = exceptionHandler; } + /** + * This interceptor will catch all exception and convert them using the exceptionhandler + * @param ctx the invocation context + * @return the result + * @throws JaxRsResponseException an exception that can be handled by a jee container + */ @AroundInvoke - public Object intercept(final InvocationContext ctx) throws Throwable { + public Object intercept(final InvocationContext ctx) throws JaxRsResponseException { try { return ctx.proceed(); } catch(final Exception theException) { - theServer = (AbstractJaxRsProvider) ctx.getTarget(); + AbstractJaxRsProvider theServer = (AbstractJaxRsProvider) ctx.getTarget(); throw convertException(theServer, theException); } } - public BaseServerRuntimeResponseException convertException(final AbstractJaxRsProvider theServer, final Exception theException) { - JaxRsRequest requestDetails = new JaxRsRequest(theServer, null, null, null); + private JaxRsResponseException convertException(final AbstractJaxRsProvider theServer, final Exception theException) { + JaxRsRequest requestDetails = theServer.getRequest(null, null).build(); BaseServerResponseException convertedException = preprocessException(theException, requestDetails); - return new BaseServerRuntimeResponseException(convertedException); + return new JaxRsResponseException(convertedException); } - public Response handleException(JaxRsRequest theRequest, BaseServerRuntimeResponseException theException) + /** + * This method converts an exception into a response + * @param theRequest the request + * @param theException the thrown exception + * @return the response describing the error + */ + public Response convertExceptionIntoResponse(JaxRsRequest theRequest, JaxRsResponseException theException) throws IOException { return handleExceptionWithoutServletError(theRequest, theException); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/BaseServerRuntimeResponseException.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java similarity index 53% rename from hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/BaseServerRuntimeResponseException.java rename to hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java index dd66a9f2596..e34c4b4519b 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/BaseServerRuntimeResponseException.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java @@ -4,12 +4,21 @@ import javax.ejb.ApplicationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; +/** + * A JEE wrapper exception that will not force a rollback. + * @author Peter Van Houte + */ @ApplicationException(rollback=false) -public class BaseServerRuntimeResponseException extends BaseServerResponseException { +public class JaxRsResponseException extends BaseServerResponseException { private static final long serialVersionUID = 1L; - public BaseServerRuntimeResponseException(BaseServerResponseException base) { + /** + * Utility constructor + * + * @param base the base exception + */ + public JaxRsResponseException(BaseServerResponseException base) { super(base.getStatusCode(), base.getMessage(), base.getCause(), base.getOperationOutcome()); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java new file mode 100644 index 00000000000..bdb7d4acd41 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java @@ -0,0 +1,133 @@ +package ca.uhn.fhir.jaxrs.server.util; + +import java.lang.reflect.Method; +import java.util.concurrent.ConcurrentHashMap; + +import org.apache.commons.lang3.StringUtils; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; +import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.method.BaseMethodBinding; +import ca.uhn.fhir.rest.method.OperationMethodBinding; +import ca.uhn.fhir.rest.method.SearchMethodBinding; +import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; +import ca.uhn.fhir.util.ReflectionUtil; + +/** + * Class that contains the method bindings defined by a ResourceProvider + * @author Peter Van Houte + */ +public class JaxRsMethodBindings { + + /** DEFAULT_METHOD_KEY="" */ + public static final String DEFAULT_METHOD_KEY = ""; + /** Static collection of bindings mapped to a class*/ + private static final ConcurrentHashMap, JaxRsMethodBindings> classBindings = new ConcurrentHashMap, JaxRsMethodBindings>(); + /** Static collection of operationBindings mapped to a class */ + private ConcurrentHashMap>> operationBindings = new ConcurrentHashMap>>(); + + /** + * The constructor + * @param theProvider the provider which is an implementation of the theProviderClass + * @param theProviderClass the class definition contaning the operations + */ + public JaxRsMethodBindings(AbstractJaxRsProvider theProvider, Class theProviderClass) { + for (final Method m : ReflectionUtil.getDeclaredMethods(theProviderClass)) { + final BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, theProvider.getFhirContext(), theProvider); + if (foundMethodBinding == null) { + continue; + } + String bindingKey = getBindingKey(foundMethodBinding); + addMethodBinding(bindingKey, foundMethodBinding); + } + } + + /** + * Get the key for the baseMethodBinding. This is: + *
    + *
  • the compartName for SearchMethodBindings + *
  • the methodName for OperationMethodBindings + *
  • {@link #DEFAULT_METHOD_KEY} for all other MethodBindings + *
+ * @param theBinding the methodbinding + * @return the key for the methodbinding. + */ + private String getBindingKey(final BaseMethodBinding theBinding) { + if (theBinding instanceof OperationMethodBinding) { + return ((OperationMethodBinding) theBinding).getName(); + } else if (theBinding instanceof SearchMethodBinding) { + Search search = theBinding.getMethod().getAnnotation(Search.class); + return search.compartmentName(); + } else { + return DEFAULT_METHOD_KEY; + } + } + + private void addMethodBinding(String key, BaseMethodBinding binding) { + ConcurrentHashMap> mapByOperation = getMapForOperation(binding.getRestOperationType()); + if (mapByOperation.containsKey(key)) { + throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + mapByOperation.get(key) + " -- " + binding.getMethod()); + } + mapByOperation.put(key, binding); + } + + /** + * Get the map for the given operation type. If no map exists for this operation type, create a new hashmap for this + * operation type and add it to the operation bindings. + * + * @param operationType the operation type. + * @return the map defined in the operation bindings + */ + private ConcurrentHashMap> getMapForOperation(RestOperationTypeEnum operationType) { + ConcurrentHashMap> result = operationBindings.get(operationType); + if(result == null) { + operationBindings.putIfAbsent(operationType, new ConcurrentHashMap>()); + return getMapForOperation(operationType); + } else { + return result; + } + } + + /** + * Get the binding + * + * @param operationType the type of operation + * @param theBindingKey the binding key + * @return the binding defined + * @throws NotImplementedOperationException cannot be found + */ + public BaseMethodBinding getBinding(RestOperationTypeEnum operationType, String theBindingKey) { + String bindingKey = StringUtils.defaultIfBlank(theBindingKey, DEFAULT_METHOD_KEY); + ConcurrentHashMap> map = operationBindings.get(operationType); + if(map == null || !map.containsKey(bindingKey)) { + throw new NotImplementedOperationException("Operation not implemented"); + } else { + return map.get(bindingKey); + } + } + + /** + * Get the method bindings for the given class. If this class is not yet contained in the classBindings, they will be added for this class + * + * @param theProvider the implementation class + * @param theProviderClass the provider class + * @return the methodBindings for this class + */ + public static JaxRsMethodBindings getMethodBindings(AbstractJaxRsProvider theProvider, Class theProviderClass) { + if(!getClassBindings().containsKey(theProviderClass)) { + JaxRsMethodBindings foundBindings = new JaxRsMethodBindings(theProvider, theProviderClass); + getClassBindings().putIfAbsent(theProviderClass, foundBindings); + } + return getClassBindings().get(theProviderClass); + } + + /** + * @return the classBindings + */ + static ConcurrentHashMap, JaxRsMethodBindings> getClassBindings() { + return classBindings; + } + + +} diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java index e41816aeb23..4d67e1947a2 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java @@ -11,21 +11,118 @@ import javax.ws.rs.core.HttpHeaders; import org.apache.commons.lang3.StringUtils; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; +import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.param.ResourceParameter; +import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IRestfulResponse; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; +import ca.uhn.fhir.util.UrlUtil; +/** + * The JaxRsRequest is a jax-rs specific implementation of the RequestDetails. + * + * @author Peter Van Houte + */ public class JaxRsRequest extends RequestDetails { + /** + * An implementation of the builder pattern for the JaxRsRequest + */ + public static class Builder { + private String theResource; + private AbstractJaxRsProvider theServer; + private RequestTypeEnum theRequestType; + private RestOperationTypeEnum theRestOperation; + private String theId; + private String theVersion; + private String theCompartment; + + public Builder(AbstractJaxRsProvider theServer, RequestTypeEnum theRequestType, + RestOperationTypeEnum theRestOperation) { + this.theServer = theServer; + this.theRequestType = theRequestType; + this.theRestOperation = theRestOperation; + } + + /** + * Set the resource + * @param resource the body contents of an http method + * @return the builder + */ + public Builder resource(String resource) { + this.theResource = resource; + return this; + } + + /** + * Set the id + * @param id the resource id + * @return the builder + */ + public Builder id(String id) { + this.theId = id; + return this; + } + + /** + * Set the id version + * @param version the version of the resource + * @return the builder + */ + public Builder version(String version) { + this.theVersion = version; + return this; + } + + /** + * Set the compartment + * @param compartment the compartment + * @return the builder + */ + public Builder compartment(String compartment) { + this.theCompartment = compartment; + return this; + } + + /** + * Create the jax-rs request + * @return the jax-rs request + */ + public JaxRsRequest build() { + JaxRsRequest result = new JaxRsRequest(theServer, theResource, theRequestType, theRestOperation); + if ((StringUtils.isNotBlank(theVersion) || StringUtils.isNotBlank(theCompartment)) + && StringUtils.isBlank(theId)) { + throw new InvalidRequestException("Don't know how to handle request path: " + + theServer.getUriInfo().getRequestUri().toASCIIString()); + } + + if (StringUtils.isNotBlank(theVersion)) { + result.setId( + new IdDt(theServer.getBaseForRequest(), UrlUtil.unescape(theId), UrlUtil.unescape(theVersion))); + } else if (StringUtils.isNotBlank(theId)) { + result.setId(new IdDt(theServer.getBaseForRequest(), UrlUtil.unescape(theId))); + } + + if (theRestOperation == RestOperationTypeEnum.UPDATE) { + String contentLocation = result.getHeader(Constants.HEADER_CONTENT_LOCATION); + if (contentLocation != null) { + result.setId(new IdDt(contentLocation)); + } + } + + result.setCompartmentName(theCompartment); + + return result; + } + } + private String theResourceString; private HttpHeaders headers; private AbstractJaxRsProvider myServer; - public JaxRsRequest() { - } - public JaxRsRequest(AbstractJaxRsProvider server, String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { this.headers = server.getHeaders(); @@ -33,7 +130,7 @@ public class JaxRsRequest extends RequestDetails { this.setRestOperationType(restOperation); setServer(server); setFhirServerBase(server.getBaseForServer()); - setParameters(server.getQueryMap()); + setParameters(server.getParameters()); setRequestType(requestType); } @@ -65,7 +162,8 @@ public class JaxRsRequest extends RequestDetails { @Override protected byte[] getByteStreamRequestContents() { - return StringUtils.defaultIfEmpty(theResourceString, "").getBytes(ResourceParameter.determineRequestCharset(this)); + return StringUtils.defaultIfEmpty(theResourceString, "") + .getBytes(ResourceParameter.determineRequestCharset(this)); } @Override diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java index 74f2cc53551..78e04bac846 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java @@ -13,6 +13,7 @@ import javax.ws.rs.core.Response.ResponseBuilder; import org.apache.commons.lang3.StringUtils; import org.hl7.fhir.instance.model.api.IBaseBinary; +import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.parser.IParser; import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.method.ParseAction; @@ -21,12 +22,26 @@ import ca.uhn.fhir.rest.server.EncodingEnum; import ca.uhn.fhir.rest.server.RestfulResponse; import ca.uhn.fhir.rest.server.RestfulServerUtils; +/** + * The JaxRsResponse is a jax-rs specific implementation of the RestfulResponse. + * + * @author Peter Van Houte + */ public class JaxRsResponse extends RestfulResponse { - public JaxRsResponse(JaxRsRequest jaxRsRequestDetails) { - super(jaxRsRequestDetails); + /** + * The constructor + * + * @param request the JaxRs Request + */ + public JaxRsResponse(JaxRsRequest request) { + super(request); } + /** + * The response writer is a simple String Writer. All output is configured + * by the server. + */ @Override public Writer getResponseWriter(int statusCode, String contentType, String charset, boolean respondGzip) throws UnsupportedEncodingException, IOException { @@ -55,8 +70,8 @@ public class JaxRsResponse extends RestfulResponse { MethodOutcome response, String resourceName) throws IOException { StringWriter writer = new StringWriter(); if (outcome != null) { - IParser parser = RestfulServerUtils.getNewParser(getRequestDetails().getServer().getFhirContext(), - getRequestDetails()); + FhirContext fhirContext = getRequestDetails().getServer().getFhirContext(); + IParser parser = RestfulServerUtils.getNewParser(fhirContext, getRequestDetails()); outcome.execute(parser, writer); } return sendWriterResponse(operationStatus, getParserType(), null, writer); diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java deleted file mode 100644 index 3d659b0b114..00000000000 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/MethodBindings.java +++ /dev/null @@ -1,86 +0,0 @@ -package ca.uhn.fhir.jaxrs.server.util; - -import java.lang.reflect.Method; -import java.util.concurrent.ConcurrentHashMap; - -import org.apache.commons.lang3.StringUtils; - -import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; -import ca.uhn.fhir.rest.annotation.Search; -import ca.uhn.fhir.rest.api.RestOperationTypeEnum; -import ca.uhn.fhir.rest.method.BaseMethodBinding; -import ca.uhn.fhir.rest.method.OperationMethodBinding; -import ca.uhn.fhir.rest.method.SearchMethodBinding; -import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; -import ca.uhn.fhir.util.ReflectionUtil; - -/** - * @author Peter Van Houte - * Class that contains the method bindings defined by a ResourceProvider - */ -public class MethodBindings { - - /** Static collection of bindings mapped to a class*/ - private static final ConcurrentHashMap, MethodBindings> classBindings = new ConcurrentHashMap, MethodBindings>(); - /** Static collection of operationBindings mapped to a class */ - private ConcurrentHashMap>> operationBindings = new ConcurrentHashMap>>(); - - public > MethodBindings(T theProvider, Class clazz) { - for (final Method m : ReflectionUtil.getDeclaredMethods(clazz)) { - final BaseMethodBinding foundMethodBinding = BaseMethodBinding.bindMethod(m, theProvider.getFhirContext(), theProvider); - if (foundMethodBinding == null) { - continue; - } - String bindingKey = getBindingKey(foundMethodBinding); - putIfAbsent(bindingKey, foundMethodBinding); - } - } - - private String getBindingKey(final BaseMethodBinding foundMethodBinding) { - if (foundMethodBinding instanceof OperationMethodBinding) { - return ((OperationMethodBinding) foundMethodBinding).getName(); - } else if (foundMethodBinding instanceof SearchMethodBinding) { - Search search = foundMethodBinding.getMethod().getAnnotation(Search.class); - return search.compartmentName(); - } else { - return ""; - } - } - - private void putIfAbsent(String key, BaseMethodBinding binding) { - operationBindings.putIfAbsent(binding.getRestOperationType(), new ConcurrentHashMap>()); - ConcurrentHashMap> map = operationBindings.get(binding.getRestOperationType()); - if (map.containsKey(key)) { - throw new IllegalArgumentException("Multiple Search Method Bindings Found : " + map.get(key) + " -- " + binding.getMethod()); - } - map.put(key, binding); - } - - public BaseMethodBinding getBinding(RestOperationTypeEnum operationType) { - return getBinding(operationType, ""); - } - - public BaseMethodBinding getBinding(RestOperationTypeEnum operationType, String qualifier) { - String nonEmptyQualifier = StringUtils.defaultIfBlank(qualifier, ""); - ConcurrentHashMap> map = operationBindings.get(operationType); - if(map == null || !map.containsKey(nonEmptyQualifier)) { - throw new NotImplementedOperationException("Operation not implemented"); - } else { - return map.get(nonEmptyQualifier); - } - } - - public static > MethodBindings getMethodBindings(T theProvider, Class clazz) { - if(!getClassBindings().containsKey(clazz)) { - MethodBindings foundBindings = new MethodBindings(theProvider, clazz); - getClassBindings().putIfAbsent(clazz, foundBindings); - } - return getClassBindings().get(clazz); - } - - public static ConcurrentHashMap, MethodBindings> getClassBindings() { - return classBindings; - } - - -} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java index bd8b424a371..604cc00c228 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProviderTest.java @@ -19,8 +19,8 @@ import org.glassfish.jersey.server.ContainerRequest; import org.junit.Before; import org.junit.Test; -import ca.uhn.fhir.jaxrs.server.example.TestDummyPatientProvider; -import ca.uhn.fhir.jaxrs.server.example.TestJaxRsMockPatientRestProvider; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProvider; import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IResourceProvider; @@ -49,10 +49,18 @@ public class AbstractJaxRsConformanceProviderTest { @Test public void testConformance() throws Exception { providers.put(AbstractJaxRsConformanceProvider.class, provider); - providers.put(TestDummyPatientProvider.class, new TestDummyPatientProvider()); + providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); Response response = createConformanceProvider(providers).conformance(); System.out.println(response); } + + @Test + public void testConformanceUsingOptions() throws Exception { + providers.put(AbstractJaxRsConformanceProvider.class, provider); + providers.put(TestJaxRsDummyPatientProvider.class, new TestJaxRsDummyPatientProvider()); + Response response = createConformanceProvider(providers).conformanceUsingOptions(); + System.out.println(response); + } @Test public void testConformanceWithMethods() throws Exception { diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java index 441203498e4..d489168bc0a 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java @@ -33,10 +33,10 @@ import org.mockito.ArgumentCaptor; import org.mockito.Matchers; import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jaxrs.server.example.RandomServerPortProvider; -import ca.uhn.fhir.jaxrs.server.example.TestJaxRsConformanceRestProvider; -import ca.uhn.fhir.jaxrs.server.example.TestJaxRsMockPatientRestProvider; -import ca.uhn.fhir.jaxrs.server.interceptor.BaseServerRuntimeResponseException; +import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsResponseException; +import ca.uhn.fhir.jaxrs.server.test.RandomServerPortProvider; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsConformanceRestProvider; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsMockPatientRestProvider; import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.model.api.BundleEntry; import ca.uhn.fhir.model.api.IResource; @@ -353,7 +353,7 @@ public class AbstractJaxRsResourceProviderTest { @Test public void testXFindUnknownPatient() { try { - BaseServerRuntimeResponseException notFoundException = new BaseServerRuntimeResponseException(new ResourceNotFoundException(new IdDt("999955541264"))); + JaxRsResponseException notFoundException = new JaxRsResponseException(new ResourceNotFoundException(new IdDt("999955541264"))); when(mock.find(idCaptor.capture())).thenThrow(notFoundException); client.read(Patient.class, "999955541264"); fail(); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java index 25ff1e829ac..0d9982ec105 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptorTest.java @@ -24,7 +24,7 @@ import org.junit.Before; import org.junit.Test; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; -import ca.uhn.fhir.jaxrs.server.example.TestDummyPatientProvider; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider; import ca.uhn.fhir.jaxrs.server.util.JaxRsRequest; import ca.uhn.fhir.rest.method.RequestDetails; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; @@ -41,10 +41,10 @@ public class JaxRsExceptionInterceptorTest { public void setUp() { interceptor = new JaxRsExceptionInterceptor(); context = mock(InvocationContext.class); - TestDummyPatientProvider provider = spy(TestDummyPatientProvider.class); + TestJaxRsDummyPatientProvider provider = spy(TestJaxRsDummyPatientProvider.class); when(context.getTarget()).thenReturn(provider); doReturn("http://baseUri").when(provider).getBaseForServer(); - doReturn(new HashMap()).when(provider).getQueryMap(); + doReturn(new HashMap()).when(provider).getParameters(); doReturn(mock(HttpHeaders.class)).when(provider).getHeaders(); } @@ -85,20 +85,17 @@ public class JaxRsExceptionInterceptorTest { @Test public void testHandleExceptionWithServletError() throws Throwable { - JaxRsRequest request = new JaxRsRequest((AbstractJaxRsProvider) context.getTarget(), null, null, null); + JaxRsRequest request = ((AbstractJaxRsProvider) context.getTarget()).getRequest(null, null).build(); ExceptionHandlingInterceptor exceptionHandler = spy(ExceptionHandlingInterceptor.class); - doThrow(new ServletException("someMessage")).when(exceptionHandler).preProcessOutgoingException(any(RequestDetails.class), any(Throwable.class), - isNull(HttpServletRequest.class)); interceptor = new JaxRsExceptionInterceptor(exceptionHandler); when(context.proceed()).thenThrow(new ServletException()); - BaseServerRuntimeResponseException thrownException = new BaseServerRuntimeResponseException(new NotImplementedOperationException("not implemented")); + JaxRsResponseException thrownException = new JaxRsResponseException(new NotImplementedOperationException("not implemented")); doThrow(new javax.servlet.ServletException("someMessage")).when(exceptionHandler).handleException(request, thrownException); - BaseServerRuntimeResponseException internalException = thrownException; - Response result = interceptor.handleException(request, internalException); + Response result = interceptor.convertExceptionIntoResponse(request, thrownException); assertEquals(InternalErrorException.STATUS_CODE, result.getStatus()); } diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseExceptionTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseExceptionTest.java new file mode 100644 index 00000000000..52dacb73e74 --- /dev/null +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseExceptionTest.java @@ -0,0 +1,23 @@ +package ca.uhn.fhir.jaxrs.server.interceptor; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; + +import javax.ejb.ApplicationException; + +import org.junit.Test; + +import ca.uhn.fhir.rest.server.exceptions.ForbiddenOperationException; + +public class JaxRsResponseExceptionTest { + + @Test + public void testException() { + ForbiddenOperationException wrappedException = new ForbiddenOperationException("someMessage"); + JaxRsResponseException response = new JaxRsResponseException(wrappedException); + assertEquals(response.getMessage(), wrappedException.getMessage()); + assertEquals(response.getStatusCode(), wrappedException.getStatusCode()); + assertNotNull(response.getClass().getAnnotation(ApplicationException.class)); + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/RandomServerPortProvider.java similarity index 93% rename from hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java rename to hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/RandomServerPortProvider.java index 4cfb0eb97d4..e9e137a6b3e 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/RandomServerPortProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/RandomServerPortProvider.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jaxrs.server.example; +package ca.uhn.fhir.jaxrs.server.test; import java.io.IOException; import java.net.ServerSocket; diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsConformanceRestProvider.java similarity index 86% rename from hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java rename to hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsConformanceRestProvider.java index 7c68212dedf..ccbf8182461 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsConformanceRestProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsConformanceRestProvider.java @@ -1,4 +1,4 @@ -package ca.uhn.fhir.jaxrs.server.example; +package ca.uhn.fhir.jaxrs.server.test; import java.util.concurrent.ConcurrentHashMap; @@ -12,18 +12,15 @@ import ca.uhn.fhir.rest.server.Constants; import ca.uhn.fhir.rest.server.IResourceProvider; /** - * Fhir Physician Rest Service - * - * @author axmpm - * + * A conformance provider exposes the mock patient and this provider */ -@Path(TestJaxRsConformanceRestProvider.PATH) +@Path("") @Stateless @Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) public class TestJaxRsConformanceRestProvider extends AbstractJaxRsConformanceProvider { public TestJaxRsConformanceRestProvider() { - super("", "", ""); + super("description", "name", "version"); } @Override diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestDummyPatientProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProvider.java similarity index 51% rename from hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestDummyPatientProvider.java rename to hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProvider.java index 2d63381edca..70907adf73c 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestDummyPatientProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsDummyPatientProvider.java @@ -1,9 +1,12 @@ -package ca.uhn.fhir.jaxrs.server.example; +package ca.uhn.fhir.jaxrs.server.test; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; import ca.uhn.fhir.model.dstu2.resource.Patient; -public class TestDummyPatientProvider extends AbstractJaxRsResourceProvider { +/** + * A dummy patient provider exposing no methods + */ +public class TestJaxRsDummyPatientProvider extends AbstractJaxRsResourceProvider { @Override public Class getResourceType() { diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsMockPatientRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java similarity index 93% rename from hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsMockPatientRestProvider.java rename to hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java index ae23b4fe6ec..09781cf1c4a 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/example/TestJaxRsMockPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java @@ -1,9 +1,8 @@ -package ca.uhn.fhir.jaxrs.server.example; +package ca.uhn.fhir.jaxrs.server.test; import java.util.List; import javax.ejb.Stateless; -import javax.interceptor.Interceptors; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -15,7 +14,6 @@ import javax.ws.rs.core.Response; import org.mockito.Mockito; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; -import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.dstu2.resource.Patient; @@ -42,10 +40,7 @@ import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider; import ca.uhn.fhir.rest.server.IPagingProvider; /** - * Fhir Physician Rest Service - * - * @author axmpm - * + * A test server delegating each call to a mock */ @Path(TestJaxRsMockPatientRestProvider.PATH) @Stateless @@ -96,7 +91,6 @@ public class TestJaxRsMockPatientRestProvider extends AbstractJaxRsResourceProvi @GET @Path("/{id}/$someCustomOperation") - @Interceptors(JaxRsExceptionInterceptor.class) public Response someCustomOperationUsingGet(@PathParam("id") String id, String resource) throws Exception { return customOperation(resource, RequestTypeEnum.GET, id, "$someCustomOperation", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); @@ -109,7 +103,6 @@ public class TestJaxRsMockPatientRestProvider extends AbstractJaxRsResourceProvi @POST @Path("/{id}/$someCustomOperation") - @Interceptors(JaxRsExceptionInterceptor.class) public Response someCustomOperationUsingPost(@PathParam("id") String id, String resource) throws Exception { return customOperation(resource, RequestTypeEnum.POST, id, "$someCustomOperation", RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsTest.java similarity index 81% rename from hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java rename to hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsTest.java index 0c32db77039..e70c77f1df2 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/MethodBindingsTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindingsTest.java @@ -11,7 +11,7 @@ import org.junit.FixMethodOrder; import org.junit.Test; import org.junit.runners.MethodSorters; -import ca.uhn.fhir.jaxrs.server.example.TestDummyPatientProvider; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider; import ca.uhn.fhir.model.dstu2.resource.Parameters; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.primitive.IdDt; @@ -29,51 +29,51 @@ import ca.uhn.fhir.rest.param.StringParam; import ca.uhn.fhir.rest.server.exceptions.NotImplementedOperationException; @FixMethodOrder(MethodSorters.DEFAULT) -public class MethodBindingsTest { +public class JaxRsMethodBindingsTest { @Before public void setUp() { - MethodBindings.getClassBindings().clear(); + JaxRsMethodBindings.getClassBindings().clear(); } @Test(expected = NotImplementedOperationException.class) public void testFindMethodsForProviderNotDefinedMappingMethods() { - new TestDummyPatientProvider().getBindings().getBinding(RestOperationTypeEnum.UPDATE, ""); + new TestJaxRsDummyPatientProvider().getBindings().getBinding(RestOperationTypeEnum.UPDATE, ""); } @Test public void testFindMethodsForProviderWithMethods() { - class TestFindPatientProvider extends TestDummyPatientProvider { + class TestFindPatientProvider extends TestJaxRsDummyPatientProvider { @Search public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { return null; } } new TestFindPatientProvider(); - assertEquals(TestFindPatientProvider.class, new TestFindPatientProvider().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE).getMethod().getDeclaringClass()); + assertEquals(TestFindPatientProvider.class, new TestFindPatientProvider().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE, "").getMethod().getDeclaringClass()); } @Test public void testFindMethodsFor2ProvidersWithMethods() { - class TestFindPatientProvider extends TestDummyPatientProvider { + class TestFindPatientProvider extends TestJaxRsDummyPatientProvider { @Search public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { return null; } } - class TestUpdatePatientProvider extends TestDummyPatientProvider { + class TestUpdatePatientProvider extends TestJaxRsDummyPatientProvider { @Update public MethodOutcome update(@IdParam final IdDt theId, @ResourceParam final Patient patient) { return null; } } - assertEquals(TestFindPatientProvider.class, new TestFindPatientProvider().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE).getMethod().getDeclaringClass()); - assertEquals(TestUpdatePatientProvider.class, new TestUpdatePatientProvider().getBindings().getBinding(RestOperationTypeEnum.UPDATE).getMethod().getDeclaringClass()); + assertEquals(TestFindPatientProvider.class, new TestFindPatientProvider().getBindings().getBinding(RestOperationTypeEnum.SEARCH_TYPE, "").getMethod().getDeclaringClass()); + assertEquals(TestUpdatePatientProvider.class, new TestUpdatePatientProvider().getBindings().getBinding(RestOperationTypeEnum.UPDATE, "").getMethod().getDeclaringClass()); } @Test public void testFindMethodsWithDoubleMethodsDeclaration() { - class TestDoubleSearchProvider extends TestDummyPatientProvider { + class TestDoubleSearchProvider extends TestJaxRsDummyPatientProvider { @Search public List search1(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { return null; @@ -95,7 +95,7 @@ public class MethodBindingsTest { @Test public void testFindMethodsWithMultipleMethods() { - class TestFindPatientProvider extends TestDummyPatientProvider { + class TestFindPatientProvider extends TestJaxRsDummyPatientProvider { @Search public List search(@RequiredParam(name = Patient.SP_NAME) final StringParam name) { return null; @@ -113,9 +113,9 @@ public class MethodBindingsTest { return null; } } - MethodBindings bindings = new TestFindPatientProvider().getBindings(); - assertEquals("search", bindings.getBinding(RestOperationTypeEnum.SEARCH_TYPE).getMethod().getName()); - assertEquals("update", bindings.getBinding(RestOperationTypeEnum.UPDATE).getMethod().getName()); + JaxRsMethodBindings bindings = new TestFindPatientProvider().getBindings(); + assertEquals("search", bindings.getBinding(RestOperationTypeEnum.SEARCH_TYPE, "").getMethod().getName()); + assertEquals("update", bindings.getBinding(RestOperationTypeEnum.UPDATE, "").getMethod().getName()); assertEquals("firstMethod", bindings.getBinding(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, "$firstMethod").getMethod().getName()); assertEquals("secondMethod", bindings.getBinding(RestOperationTypeEnum.EXTENDED_OPERATION_TYPE, "$secondMethod").getMethod().getName()); try { diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java index fb3af725355..7be1a639290 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequestTest.java @@ -18,13 +18,12 @@ import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.UriInfo; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.Validate; import org.glassfish.jersey.internal.MapPropertiesDelegate; import org.glassfish.jersey.server.ContainerRequest; import org.junit.Before; import org.junit.Test; -import ca.uhn.fhir.jaxrs.server.example.TestDummyPatientProvider; +import ca.uhn.fhir.jaxrs.server.test.TestJaxRsDummyPatientProvider; import ca.uhn.fhir.rest.api.RequestTypeEnum; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; @@ -37,7 +36,7 @@ public class JaxRsRequestTest { private JaxRsRequest details; private MultivaluedMap queryParameters = new MultivaluedHashMap(); private ContainerRequest headers; - private TestDummyPatientProvider provider; + private TestJaxRsDummyPatientProvider provider; @Before public void setUp() throws URISyntaxException { @@ -96,11 +95,6 @@ public class JaxRsRequestTest { assertEquals(this.provider, details.getServer()); } - @Test - public void testJaxRsRequestDetails() { - Validate.notNull(new JaxRsRequest()); - } - public JaxRsRequest createRequestDetails() throws URISyntaxException { //headers headers = new ContainerRequest(new URI(BASEURI), new URI(REQUESTURI), HttpMethod.GET, null, new MapPropertiesDelegate()); @@ -110,7 +104,7 @@ public class JaxRsRequestTest { when(uriInfo.getQueryParameters()).thenReturn(queryParameters); //mocks - provider = spy(TestDummyPatientProvider.class); + provider = spy(TestJaxRsDummyPatientProvider.class); doReturn(uriInfo).when(provider).getUriInfo(); doReturn(BASEURI).when(provider).getBaseForRequest(); doReturn(BASEURI).when(provider).getBaseForServer(); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java index 89d7d2ec645..8e26760539a 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponseTest.java @@ -3,12 +3,10 @@ package ca.uhn.fhir.jaxrs.server.util; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertTrue; -import java.io.ByteArrayInputStream; import java.io.IOException; import java.net.URISyntaxException; import java.util.Collections; import java.util.Set; -import java.util.zip.GZIPInputStream; import javax.ws.rs.core.Response; @@ -153,22 +151,6 @@ public class JaxRsResponseTest { assertEquals(Constants.CT_XML+Constants.CHARSET_UTF8_CTSUFFIX, result.getHeaderString(Constants.HEADER_CONTENT_TYPE)); } - private String unzip(Object entity) throws IOException { - byte[] compressed = ((String) entity).getBytes(Constants.CHARSET_NAME_UTF8); - final int BUFFER_SIZE = 32; - ByteArrayInputStream is = new ByteArrayInputStream(compressed); - GZIPInputStream gis = new GZIPInputStream(is, BUFFER_SIZE); - StringBuilder string = new StringBuilder(); - byte[] data = new byte[BUFFER_SIZE]; - int bytesRead; - while ((bytesRead = gis.read(data)) != -1) { - string.append(new String(data, 0, bytesRead)); - } - gis.close(); - is.close(); - return string.toString(); - } - private Bundle getSinglePatientResource() { Patient theResource = createPatient(); Bundle bundle = Bundle.withSingleResource(theResource); diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java index 6be1bdb3754..3a126b7d446 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java @@ -5,6 +5,7 @@ import javax.ws.rs.core.Application; /** * Fhir Patient Demo Application + * * @author Peter Van Houte */ @ApplicationPath(value=FhirPatientDemoApplication.PATH) diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java index a5190c9b4cf..aeddcc21914 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java @@ -9,7 +9,6 @@ import java.util.concurrent.ConcurrentHashMap; import javax.ejb.Local; import javax.ejb.Stateless; -import javax.interceptor.Interceptors; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.Path; @@ -19,7 +18,6 @@ import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; -import ca.uhn.fhir.jaxrs.server.interceptor.JaxRsExceptionInterceptor; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.resource.Condition; import ca.uhn.fhir.model.dstu2.resource.Parameters; @@ -51,10 +49,7 @@ import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException; import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; /** - * Fhir Physician Rest Service - * - * @author axmpm - * + * A demo JaxRs Patient Rest Provider */ @Local @Path(JaxRsPatientRestProvider.PATH) @@ -72,10 +67,10 @@ public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider Date: Tue, 24 Nov 2015 17:35:37 +0100 Subject: [PATCH 12/13] include agfa in author + fix test in case port is shorter + inject services into eample conformance provider + set moxy in scope test --- hapi-fhir-jaxrsserver-base/pom.xml | 1 + .../AbstractJaxRsConformanceProvider.java | 3 ++- .../jaxrs/server/AbstractJaxRsProvider.java | 3 ++- .../server/AbstractJaxRsResourceProvider.java | 16 ++++++++-------- .../JaxRsExceptionInterceptor.java | 4 +++- .../interceptor/JaxRsResponseException.java | 3 ++- .../server/util/JaxRsMethodBindings.java | 3 ++- .../fhir/jaxrs/server/util/JaxRsRequest.java | 19 ++++++++++++++++++- .../fhir/jaxrs/server/util/JaxRsResponse.java | 2 +- .../AbstractJaxRsResourceProviderTest.java | 7 ++++--- .../example/FhirPatientDemoApplication.java | 3 ++- .../example/JaxRsConformanceProvider.java | 13 ++++++++++--- .../example/JaxRsPatientRestProvider.java | 2 ++ 13 files changed, 57 insertions(+), 22 deletions(-) diff --git a/hapi-fhir-jaxrsserver-base/pom.xml b/hapi-fhir-jaxrsserver-base/pom.xml index d64447b1736..be9a8a4e927 100644 --- a/hapi-fhir-jaxrsserver-base/pom.xml +++ b/hapi-fhir-jaxrsserver-base/pom.xml @@ -93,6 +93,7 @@ org.glassfish.jersey.media jersey-media-moxy ${jersey_version} + test diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java index e324ea7177b..bbef3d408a4 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsConformanceProvider.java @@ -42,7 +42,8 @@ import ca.uhn.fhir.util.ReflectionUtil; /** * This is the conformance provider for the jax rs servers. It requires all providers to be registered * during startup because the conformance profile is generated during the postconstruct phase. - * @author Peter Van Houte + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML }) public abstract class AbstractJaxRsConformanceProvider extends AbstractJaxRsProvider implements IResourceProvider { diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java index 58089966483..dbd64ae11b9 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsProvider.java @@ -25,7 +25,8 @@ import ca.uhn.fhir.rest.server.IServerAddressStrategy; /** * This is the abstract superclass for all jaxrs providers. It contains some defaults implementing * the IRestfulServerDefaults interface and exposes the uri and headers. - * @author Peter Van Houte + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ public abstract class AbstractJaxRsProvider implements IRestfulServerDefaults { diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java index 270f66bf41b..0a13cfb697a 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProvider.java @@ -36,7 +36,7 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; /** * This server is the abstract superclass for all resource providers. It exposes * a large amount of the fhir api functionality using JAXRS - * @author Peter Van Houte + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_PLAIN }) @Consumes({ MediaType.APPLICATION_FORM_URLENCODED, MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, @@ -93,7 +93,7 @@ public abstract class AbstractJaxRsResourceProvider extends * @see https://www.hl7. org/fhir/http.html#create */ @POST - public Response create(final String resource) throws Exception { + public Response create(final String resource) throws IOException { return execute(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.CREATE).resource(resource)); } @@ -105,7 +105,7 @@ public abstract class AbstractJaxRsResourceProvider extends */ @POST @Path("/_search") - public Response searchWithPost() throws Exception { + public Response searchWithPost() throws IOException { return execute(getRequest(RequestTypeEnum.POST, RestOperationTypeEnum.SEARCH_TYPE)); } @@ -116,7 +116,7 @@ public abstract class AbstractJaxRsResourceProvider extends * @see https://www.hl7.org/fhir/http.html#search */ @GET - public Response search() throws Exception { + public Response search() throws IOException { return execute(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.SEARCH_TYPE)); } @@ -130,7 +130,7 @@ public abstract class AbstractJaxRsResourceProvider extends */ @PUT @Path("/{id}") - public Response update(@PathParam("id") final String id, final String resource) throws Exception { + public Response update(@PathParam("id") final String id, final String resource) throws IOException { return execute(getRequest(RequestTypeEnum.PUT, RestOperationTypeEnum.UPDATE).id(id).resource(resource)); } @@ -143,7 +143,7 @@ public abstract class AbstractJaxRsResourceProvider extends */ @DELETE @Path("/{id}") - public Response delete(@PathParam("id") final String id) throws Exception { + public Response delete(@PathParam("id") final String id) throws IOException { return execute(getRequest(RequestTypeEnum.DELETE, RestOperationTypeEnum.DELETE).id(id)); } @@ -156,7 +156,7 @@ public abstract class AbstractJaxRsResourceProvider extends */ @GET @Path("/{id}") - public Response find(@PathParam("id") final String id) throws Exception { + public Response find(@PathParam("id") final String id) throws IOException { return execute(getRequest(RequestTypeEnum.GET, RestOperationTypeEnum.READ).id(id)); } @@ -172,7 +172,7 @@ public abstract class AbstractJaxRsResourceProvider extends * @see https://www.hl7.org/fhir/operations.html */ protected Response customOperation(final String resource, RequestTypeEnum requestType, String id, - String operationName, RestOperationTypeEnum operationType) throws Exception { + String operationName, RestOperationTypeEnum operationType) throws IOException { Builder request = getRequest(requestType, operationType).resource(resource).id(id); return execute(request, operationName); } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java index adfe7ce0ed8..f4665f8d0b1 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsExceptionInterceptor.java @@ -15,7 +15,8 @@ import ca.uhn.fhir.rest.server.interceptor.ExceptionHandlingInterceptor; /** * An interceptor that catches the jax-rs exceptions - * @author Peter Van Houte + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ public class JaxRsExceptionInterceptor { @@ -64,6 +65,7 @@ public class JaxRsExceptionInterceptor { * @param theRequest the request * @param theException the thrown exception * @return the response describing the error + * @throws IOException */ public Response convertExceptionIntoResponse(JaxRsRequest theRequest, JaxRsResponseException theException) throws IOException { diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java index e34c4b4519b..893fd23c1f1 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/interceptor/JaxRsResponseException.java @@ -6,7 +6,8 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; /** * A JEE wrapper exception that will not force a rollback. - * @author Peter Van Houte + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ @ApplicationException(rollback=false) public class JaxRsResponseException extends BaseServerResponseException { diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java index bdb7d4acd41..1113ddb5fa6 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsMethodBindings.java @@ -16,7 +16,8 @@ import ca.uhn.fhir.util.ReflectionUtil; /** * Class that contains the method bindings defined by a ResourceProvider - * @author Peter Van Houte + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ public class JaxRsMethodBindings { diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java index 4d67e1947a2..add9998d497 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsRequest.java @@ -24,7 +24,7 @@ import ca.uhn.fhir.util.UrlUtil; /** * The JaxRsRequest is a jax-rs specific implementation of the RequestDetails. * - * @author Peter Van Houte + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ public class JaxRsRequest extends RequestDetails { @@ -40,6 +40,12 @@ public class JaxRsRequest extends RequestDetails { private String theVersion; private String theCompartment; + /** + * Utility Constructor + * @param theServer the server + * @param theRequestType the request type + * @param theRestOperation the rest operation + */ public Builder(AbstractJaxRsProvider theServer, RequestTypeEnum theRequestType, RestOperationTypeEnum theRestOperation) { this.theServer = theServer; @@ -123,6 +129,13 @@ public class JaxRsRequest extends RequestDetails { private HttpHeaders headers; private AbstractJaxRsProvider myServer; + /** + * Utility Constructor + * @param server the server + * @param resourceString the resource body + * @param requestType the request type + * @param restOperation the operation type + */ public JaxRsRequest(AbstractJaxRsProvider server, String resourceString, RequestTypeEnum requestType, RestOperationTypeEnum restOperation) { this.headers = server.getHeaders(); @@ -139,6 +152,10 @@ public class JaxRsRequest extends RequestDetails { return myServer; } + /** + * Set the server + * @param theServer the server to set + */ public void setServer(AbstractJaxRsProvider theServer) { this.myServer = theServer; } diff --git a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java index 78e04bac846..7239427e5ba 100644 --- a/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java +++ b/hapi-fhir-jaxrsserver-base/src/main/java/ca/uhn/fhir/jaxrs/server/util/JaxRsResponse.java @@ -25,7 +25,7 @@ import ca.uhn.fhir.rest.server.RestfulServerUtils; /** * The JaxRsResponse is a jax-rs specific implementation of the RestfulResponse. * - * @author Peter Van Houte + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ public class JaxRsResponse extends RestfulResponse { diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java index d489168bc0a..d3934165152 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java @@ -75,6 +75,7 @@ public class AbstractJaxRsResourceProviderTest { private static final FhirContext ourCtx = FhirContext.forDstu2(); private static final String PATIENT_NAME = "Van Houte"; private static int ourPort; + private static String serverBase; private static Server jettyServer; @BeforeClass @@ -93,10 +94,10 @@ public class AbstractJaxRsResourceProviderTest { TestJaxRsConformanceRestProvider.class.getCanonicalName()), ";")); jettyServer.start(); - final FhirContext ctx = FhirContext.forDstu2(); ourCtx.getRestfulClientFactory().setServerValidationMode(ServerValidationModeEnum.NEVER); ourCtx.getRestfulClientFactory().setSocketTimeout(1200 * 1000); - client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); + serverBase = "http://localhost:" + ourPort + "/"; + client = ourCtx.newRestfulGenericClient(serverBase); client.setEncoding(EncodingEnum.JSON); client.registerInterceptor(new LoggingInterceptor(true)); } @@ -392,7 +393,7 @@ public class AbstractJaxRsResourceProviderTest { } private void compareResultUrl(String url, IResource resource) { - assertEquals(url, resource.getId().getValueAsString().substring("http://localhost:55844".length())); + assertEquals(url, resource.getId().getValueAsString().substring(serverBase.length() - 1)); } private T withId(final T id) { diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java index 3a126b7d446..3123ed6969f 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/FhirPatientDemoApplication.java @@ -6,9 +6,10 @@ import javax.ws.rs.core.Application; /** * Fhir Patient Demo Application * - * @author Peter Van Houte + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ @ApplicationPath(value=FhirPatientDemoApplication.PATH) public class FhirPatientDemoApplication extends Application { + /** The demo application path */ public final static String PATH = "/jaxrs-demo"; } diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java index 049952661ce..c38ef890a34 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsConformanceProvider.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jaxrs.server.example; import java.util.concurrent.ConcurrentHashMap; import javax.ejb.Stateless; +import javax.inject.Inject; import javax.ws.rs.Path; import javax.ws.rs.Produces; import javax.ws.rs.core.MediaType; @@ -14,7 +15,7 @@ import ca.uhn.fhir.rest.server.IResourceProvider; /** * Conformance Rest Service * - * @author Peter Van Houte + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ @Path("") @Stateless @@ -23,7 +24,13 @@ public class JaxRsConformanceProvider extends AbstractJaxRsConformanceProvider { private static final String SERVER_VERSION = "1.0.0"; private static final String SERVER_DESCRIPTION = "Jax-Rs Test Example Description"; private static final String SERVER_NAME = "Jax-Rs Test Example"; + + @Inject + private JaxRsPatientRestProvider patientProvider; + /** + * Standard Constructor + */ public JaxRsConformanceProvider() { super(SERVER_VERSION, SERVER_DESCRIPTION, SERVER_NAME); } @@ -31,8 +38,8 @@ public class JaxRsConformanceProvider extends AbstractJaxRsConformanceProvider { @Override protected ConcurrentHashMap, IResourceProvider> getProviders() { ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>(); - map.put(JaxRsConformanceProvider.class, new JaxRsConformanceProvider()); - map.put(JaxRsPatientRestProvider.class, new JaxRsPatientRestProvider()); + map.put(JaxRsConformanceProvider.class, this); + map.put(JaxRsPatientRestProvider.class, patientProvider); return map; } } diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java index aeddcc21914..af09a407fab 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java @@ -50,6 +50,8 @@ import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; /** * A demo JaxRs Patient Rest Provider + * + * @author Peter Van Houte | peter.vanhoute@agfa.com | Agfa Healthcare */ @Local @Path(JaxRsPatientRestProvider.PATH) From 94f309114be8e4bc2b218ac5b6fe14acf260bd2f Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Wed, 25 Nov 2015 15:34:41 +0100 Subject: [PATCH 13/13] - add site-documentation for jaxrs base - set example for operation on instance level - remove not thrown exceptions --- examples/pom.xml | 18 ++++ .../example/JaxRsConformanceProvider.java | 41 ++++++++++ .../example/JaxRsPatientRestProvider.java | 82 +++++++++++++++++++ .../AbstractJaxRsResourceProviderTest.java | 4 +- .../TestJaxRsMockPatientRestProvider.java | 18 ++-- .../example/JaxRsPatientRestProvider.java | 37 ++++----- .../example/JaxRsPatientProviderTest.java | 4 +- pom.xml | 1 + src/site/xdoc/doc_rest_server.xml | 69 ++++++++++++++++ 9 files changed, 242 insertions(+), 32 deletions(-) create mode 100644 examples/src/main/java/example/JaxRsConformanceProvider.java create mode 100644 examples/src/main/java/example/JaxRsPatientRestProvider.java diff --git a/examples/pom.xml b/examples/pom.xml index a0e3908996b..44aa96c1379 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -40,6 +40,24 @@ javax.servlet-api provided + + + ca.uhn.hapi.fhir + hapi-fhir-jaxrsserver-base + 1.3-SNAPSHOT + + + javax.ws.rs + javax.ws.rs-api + 2.0 + provided + + + javax.ejb + ejb-api + 3.0 + provided + diff --git a/examples/src/main/java/example/JaxRsConformanceProvider.java b/examples/src/main/java/example/JaxRsConformanceProvider.java new file mode 100644 index 00000000000..24bac3ee5e1 --- /dev/null +++ b/examples/src/main/java/example/JaxRsConformanceProvider.java @@ -0,0 +1,41 @@ +package example; + +import java.util.concurrent.ConcurrentHashMap; + +import javax.ejb.EJB; +import javax.ejb.Stateless; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsConformanceProvider; +import ca.uhn.fhir.rest.server.Constants; +import ca.uhn.fhir.rest.server.IResourceProvider; + +/** + * Conformance Rest Service + * + * @author Peter Van Houte + */ + // START SNIPPET: jax-rs-conformance +@Path("") +@Stateless +@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) +public class JaxRsConformanceProvider extends AbstractJaxRsConformanceProvider { + + @EJB + private JaxRsPatientRestProvider provider; + + public JaxRsConformanceProvider() { + super("My Server Version", "My Server Description", "My Server Name"); + } + + @Override + protected ConcurrentHashMap, IResourceProvider> getProviders() { + ConcurrentHashMap, IResourceProvider> map = new ConcurrentHashMap, IResourceProvider>(); + map.put(JaxRsConformanceProvider.class, this); + map.put(JaxRsPatientRestProvider.class, provider); + return map; + } +} +// END SNIPPET: jax-rs-conformance diff --git a/examples/src/main/java/example/JaxRsPatientRestProvider.java b/examples/src/main/java/example/JaxRsPatientRestProvider.java new file mode 100644 index 00000000000..d23f5dc9513 --- /dev/null +++ b/examples/src/main/java/example/JaxRsPatientRestProvider.java @@ -0,0 +1,82 @@ +package example; + +import java.lang.reflect.InvocationTargetException; + +import javax.ejb.Local; +import javax.ejb.Stateless; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; + +import ca.uhn.fhir.jaxrs.server.AbstractJaxRsResourceProvider; +import ca.uhn.fhir.model.dstu2.resource.Parameters; +import ca.uhn.fhir.model.dstu2.resource.Patient; +import ca.uhn.fhir.model.primitive.IdDt; +import ca.uhn.fhir.model.primitive.StringDt; +import ca.uhn.fhir.rest.annotation.ConditionalUrlParam; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.IdParam; +import ca.uhn.fhir.rest.annotation.Operation; +import ca.uhn.fhir.rest.annotation.OperationParam; +import ca.uhn.fhir.rest.annotation.ResourceParam; +import ca.uhn.fhir.rest.api.MethodOutcome; +import ca.uhn.fhir.rest.api.RequestTypeEnum; +import ca.uhn.fhir.rest.api.RestOperationTypeEnum; +import ca.uhn.fhir.rest.server.Constants; + +/** + * A demo JaxRs Patient Rest Provider + */ +@Local +@Stateless +// START SNIPPET: jax-rs-provider-construction +@Path("/Patient") +@Produces({ MediaType.APPLICATION_JSON, Constants.CT_FHIR_JSON, Constants.CT_FHIR_XML }) +public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider { + + public JaxRsPatientRestProvider() { + super(JaxRsPatientRestProvider.class); + } +// END SNIPPET: jax-rs-provider-construction + + @Override + public Class getResourceType() { + return Patient.class; + } + + + @Create + public MethodOutcome create(@ResourceParam final Patient patient, @ConditionalUrlParam String theConditional) { + // create the patient ... + return new MethodOutcome(new IdDt(1L)).setCreated(true); + } + +// START SNIPPET: jax-rs-provider-operation + @GET + @Path("/{id}/$someCustomOperation") + public Response someCustomOperationUsingGet(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.GET, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); + } + + @Operation(name = "someCustomOperation", idempotent = true, returnParameters = { + @OperationParam(name = "return", type = StringDt.class) }) + public Parameters someCustomOperation(@IdParam IdDt myId, @OperationParam(name = "dummy") StringDt dummyInput) { + Parameters parameters = new Parameters(); + parameters.addParameter().setName("return").setValue(new StringDt("My Dummy Result")); + return parameters; + } + // END SNIPPET: jax-rs-provider-operation + + @POST + @Path("/{id}/$someCustomOperation") + public Response someCustomOperationUsingPost(@PathParam("id") String id, String resource) throws Exception { + return customOperation(resource, RequestTypeEnum.POST, id, "$someCustomOperation", + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); + } + +} diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java index d3934165152..24f27d3799e 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/AbstractJaxRsResourceProviderTest.java @@ -300,7 +300,7 @@ public class AbstractJaxRsResourceProviderTest { // prepare mock Parameters resultParameters = new Parameters(); resultParameters.addParameter().setName("return").setResource(createPatient(1)).setValue(new StringDt("outputValue")); - when(mock.someCustomOperation(eq(new StringDt("myAwesomeDummyValue")))).thenReturn(resultParameters); + when(mock.someCustomOperation(any(IdDt.class), eq(new StringDt("myAwesomeDummyValue")))).thenReturn(resultParameters); // Create the input parameters to pass to the server Parameters inParams = new Parameters(); inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); @@ -318,7 +318,7 @@ public class AbstractJaxRsResourceProviderTest { // prepare mock Parameters resultParameters = new Parameters(); resultParameters.addParameter().setName("return").setResource(createPatient(1)).setValue(new StringDt("outputValue")); - when(mock.someCustomOperation(eq(new StringDt("myAwesomeDummyValue")))).thenReturn(resultParameters); + when(mock.someCustomOperation(any(IdDt.class), eq(new StringDt("myAwesomeDummyValue")))).thenReturn(resultParameters); // Create the input parameters to pass to the server Parameters inParams = new Parameters(); inParams.addParameter().setName("start").setValue(new DateDt("2001-01-01")); diff --git a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java index 09781cf1c4a..ca04618c208 100644 --- a/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-base/src/test/java/ca/uhn/fhir/jaxrs/server/test/TestJaxRsMockPatientRestProvider.java @@ -89,29 +89,29 @@ public class TestJaxRsMockPatientRestProvider extends AbstractJaxRsResourceProvi return mock.delete(theId); } + @Search(compartmentName = "Condition") + public List searchCompartment(@IdParam IdDt thePatientId) { + return mock.searchCompartment(thePatientId); + } + @GET @Path("/{id}/$someCustomOperation") public Response someCustomOperationUsingGet(@PathParam("id") String id, String resource) throws Exception { return customOperation(resource, RequestTypeEnum.GET, id, "$someCustomOperation", - RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); } - - @Search(compartmentName = "Condition") - public List searchCompartment(@IdParam IdDt thePatientId) { - return mock.searchCompartment(thePatientId); - } @POST @Path("/{id}/$someCustomOperation") public Response someCustomOperationUsingPost(@PathParam("id") String id, String resource) throws Exception { return customOperation(resource, RequestTypeEnum.POST, id, "$someCustomOperation", - RestOperationTypeEnum.EXTENDED_OPERATION_TYPE); + RestOperationTypeEnum.EXTENDED_OPERATION_INSTANCE); } @Operation(name = "someCustomOperation", idempotent = true, returnParameters = { @OperationParam(name = "return", type = StringDt.class) }) - public Parameters someCustomOperation(@OperationParam(name = "dummy") StringDt dummyInput) { - return mock.someCustomOperation(dummyInput); + public Parameters someCustomOperation(@IdParam IdDt myId, @OperationParam(name = "dummy") StringDt dummyInput) { + return mock.someCustomOperation(myId, dummyInput); } @Override diff --git a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java index af09a407fab..b33d0b35cad 100644 --- a/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java +++ b/hapi-fhir-jaxrsserver-example/src/main/java/ca/uhn/fhir/jaxrs/server/example/JaxRsPatientRestProvider.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jaxrs.server.example; +import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; import java.util.Collections; @@ -113,7 +114,7 @@ public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider patientList = patients.get(idPart); @@ -130,7 +131,7 @@ public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProvider searchCompartment(@IdParam IdDt thePatientId) { List retVal = new ArrayList(); @@ -190,18 +183,24 @@ public class JaxRsPatientRestProvider extends AbstractJaxRsResourceProviderhapi-fhir-structures-dstu2 hapi-fhir-structures-hl7org-dstu2 hapi-fhir-jpaserver-base + hapi-fhir-jaxrsserver-base examples diff --git a/src/site/xdoc/doc_rest_server.xml b/src/site/xdoc/doc_rest_server.xml index bdf36673ca0..2531e2cc7d7 100644 --- a/src/site/xdoc/doc_rest_server.xml +++ b/src/site/xdoc/doc_rest_server.xml @@ -598,6 +598,75 @@ +
+

+ The standard server is implemented using Servlet technology. A module + exists which implements the server using JAX-RS technology. + This enables the usage of existing Java EE interceptors and annotations. This module does not provide the full set of features. + + The server currently supports + Conformance Statements, + @Read, + @Search, + @Create, + @Update, + @Delete and + @Operation. +

+

+ The primary intention for this project is to ensure that other web technologies (JAX-RS in this case) can be used together with the base-server functionality. + An example server can be found in the Git repo here. +

+ + +

+ The set-up of a JAX-RS server goes beyond the scope of this documentation. The implementation of the server follows the same pattern as the standard server. It is required + to put the correct annotation on the methods in the Resource Providers in order to be able to call them. +

+ +

+ Implementing a JAX-RS Resource Provider requires some JAX-RS annotations. The @Path + annotation needs to define the resource path. The @Produces annotation + needs to declare the produced formats. The constructor needs to pass the class of the object explicitely in order to avoid problems with proxy classes in a Java EE environment. + It is necessary to extend the abstract class + AbstractJaxRsResourceProvider. + + + + +

+ +

+ + Extended Operations require the correct JAX-RS ( + @Path, + @GET or + @POST) annotations. The body of the method needs to call the + method AbstractJaxRsResourceProvider#customOperation + with the correct parameters. The server will then call the method with corresponding name. + + + + +

+ +

+ In order to create the conformance profile, a conformance provider class needs to be deployed which exports the provider's conformance statements. + These providers need to be returned as the result of + the method AbstractJaxRsResourceProvider#getProviders. + This method is called once, during PostConstruct. + + + + +

+ + +
+ +
+ +