From b4df6f9612cbbd6b25fe524ce8d99d20d24ce6fb Mon Sep 17 00:00:00 2001 From: petervanhoute Date: Fri, 2 Oct 2015 11:27:21 +0200 Subject: [PATCH] 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) {