diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Destroy.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Destroy.java
index 5d110e76036..40010f84985 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Destroy.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Destroy.java
@@ -26,7 +26,7 @@ import java.lang.annotation.Target;
*/
/**
- * ResourceProvider methods tagged with @Destroy will be invoked when the RestfulServer is shut down.
+ * ResourceProvider methods tagged with {@link Destroy} will be invoked when the RestfulServer is shut down.
* This is your chance to do any cleanup.
*/
@Retention(RetentionPolicy.RUNTIME)
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Initialize.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Initialize.java
new file mode 100644
index 00000000000..adf4f50beeb
--- /dev/null
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Initialize.java
@@ -0,0 +1,35 @@
+package ca.uhn.fhir.rest.annotation;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/*
+ * #%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%
+ */
+
+/**
+ * ResourceProvider methods tagged with {@link Initialize} will be invoked when the RestfulServer is starting up.
+ * This is your chance to do any initialization prior to the server taking any requests.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.METHOD)
+public @interface Initialize {
+}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Read.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Read.java
index 0cd335842fa..269e203fc54 100644
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Read.java
+++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/Read.java
@@ -25,7 +25,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
-import ca.uhn.fhir.model.api.IResource;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+
import ca.uhn.fhir.rest.client.api.IBasicClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.server.IResourceProvider;
@@ -57,7 +58,7 @@ public @interface Read {
*
*/
// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere
- Class extends IResource> type() default IResource.class;
+ Class extends IBaseResource> type() default IBaseResource.class;
/**
* If set to true (default is false), this method supports vread operation as well as read
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 4ce8358b86d..a545ded437f 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
@@ -128,6 +128,9 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
return myDescription;
}
+ /**
+ * Returns the name of the operation, starting with "$"
+ */
public String getName() {
return myName;
}
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 b3679bb0c0e..c8c592c2f8c 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
@@ -57,6 +57,7 @@ import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.primitive.IdDt;
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.RequestTypeEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
@@ -91,7 +92,7 @@ public class RestfulServer extends HttpServlet {
private final List myInterceptors = new ArrayList();
private IPagingProvider myPagingProvider;
private Collection myPlainProviders;
- private Map myResourceNameToProvider = new HashMap();
+ private Map myResourceNameToBinding = new HashMap();
private Collection myResourceProviders;
private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy();
private ResourceBinding myServerBinding = new ResourceBinding();
@@ -105,9 +106,8 @@ public class RestfulServer extends HttpServlet {
/**
* Constructor. Note that if no {@link FhirContext} is passed in to the server (either through the constructor, or
- * through {@link #setFhirContext(FhirContext)}) the server will determine which version of FHIR to support
- * through classpath scanning. This is brittle, and it is highly recommended to explicitly specify
- * a FHIR version.
+ * through {@link #setFhirContext(FhirContext)}) the server will determine which version of FHIR to support through
+ * classpath scanning. This is brittle, and it is highly recommended to explicitly specify a FHIR version.
*/
public RestfulServer() {
this(null);
@@ -123,7 +123,8 @@ public class RestfulServer extends HttpServlet {
/**
* This method is called prior to sending a response to incoming requests. It is used to add custom headers.
*
- * Use caution if overriding this method: it is recommended to call super.addHeadersToResponse
to avoid inadvertantly disabling functionality.
+ * Use caution if overriding this method: it is recommended to call super.addHeadersToResponse
to avoid
+ * inadvertantly disabling functionality.
*
*/
public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
@@ -143,6 +144,14 @@ public class RestfulServer extends HttpServlet {
invokeDestroy(iResourceProvider);
}
}
+ if (myServerConformanceProvider != null) {
+ invokeDestroy(myServerConformanceProvider);
+ }
+ if (getPlainProviders() != null) {
+ for (Object next : getPlainProviders()) {
+ invokeDestroy(next);
+ }
+ }
}
@Override
@@ -228,12 +237,12 @@ public class RestfulServer extends HttpServlet {
resourceBinding = myServerBinding;
} else {
RuntimeResourceDefinition definition = getFhirContext().getResourceDefinition(resourceName);
- if (myResourceNameToProvider.containsKey(definition.getName())) {
- resourceBinding = myResourceNameToProvider.get(definition.getName());
+ if (myResourceNameToBinding.containsKey(definition.getName())) {
+ resourceBinding = myResourceNameToBinding.get(definition.getName());
} else {
resourceBinding = new ResourceBinding();
resourceBinding.setResourceName(resourceName);
- myResourceNameToProvider.put(resourceName, resourceBinding);
+ myResourceNameToBinding.put(resourceName, resourceBinding);
}
}
@@ -284,7 +293,7 @@ public class RestfulServer extends HttpServlet {
} else {
myServerBinding.addMethod(foundMethodBinding);
}
- ourLog.info(" * Method: {}#{} is a handler", theSystemProvider.getClass(), m.getName());
+ ourLog.debug(" * Method: {}#{} is a handler", theSystemProvider.getClass(), m.getName());
} else {
ourLog.debug(" * Method: {}#{} is not a handler", theSystemProvider.getClass(), m.getName());
}
@@ -306,8 +315,9 @@ public class RestfulServer extends HttpServlet {
}
/**
- * 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}.
+ * 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}.
*/
public EncodingEnum getDefaultResponseEncoding() {
return myDefaultResponseEncoding;
@@ -321,8 +331,8 @@ public class RestfulServer extends HttpServlet {
}
/**
- * 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.
+ * 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() {
if (myFhirContext == null) {
@@ -356,14 +366,15 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path implementation
+ * Allows users of RestfulServer to override the getRequestPath method to let them build their custom request path
+ * implementation
*
* @param requestFullPath
- * the full request path
+ * the full request path
* @param servletContextPath
- * the servelet context path
+ * the servelet context path
* @param servletPath
- * the servelet path
+ * the servelet path
* @return created resource path
*/
protected String getRequestPath(String requestFullPath, String servletContextPath, String servletPath) {
@@ -371,7 +382,7 @@ public class RestfulServer extends HttpServlet {
}
public Collection getResourceBindings() {
- return myResourceNameToProvider.values();
+ return myResourceNameToBinding.values();
}
/**
@@ -382,7 +393,8 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Get the server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
+ * Get the server address strategy, which is used to determine what base URL to provide clients to refer to this
+ * server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public IServerAddressStrategy getServerAddressStrategy() {
return myServerAddressStrategy;
@@ -402,17 +414,19 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Returns the method bindings for this server which are not specific to any particular resource type. This method is internal to HAPI and developers generally do not need to interact with it. Use
- * with caution, as it may change.
+ * Returns the method bindings for this server which are not specific to any particular resource type. This method is
+ * internal to HAPI and developers generally do not need to interact with it. Use with caution, as it may change.
*/
public List> getServerBindings() {
return myServerBinding.getMethodBindings();
}
/**
- * Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement if one has been explicitly defined.
+ * Returns the server conformance provider, which is the provider that is used to generate the server's conformance
+ * (metadata) statement if one has been explicitly defined.
*
- * By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or set to null
to use the appropriate one for the given FHIR version.
+ * By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or
+ * set to null
to use the appropriate one for the given FHIR version.
*
*/
public Object getServerConformanceProvider() {
@@ -420,7 +434,8 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
+ * Gets the server's name, as exported in conformance profiles exported by the server. This is informational only,
+ * but can be helpful to set with something appropriate.
*
* @see RestfulServer#setServerName(String)
*/
@@ -433,7 +448,8 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
+ * Gets the server's version, as exported in conformance profiles exported by the server. This is informational only,
+ * but can be helpful to set with something appropriate.
*/
public String getServerVersion() {
return myServerVersion;
@@ -508,8 +524,7 @@ public class RestfulServer extends HttpServlet {
return;
}
}
- RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, Constants.STATUS_HTTP_200_OK,
- theRequest.isRespondGzip(), theRequest.getFhirServerBase(), false);
+ RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase(), false);
}
}
@@ -582,9 +597,9 @@ public class RestfulServer extends HttpServlet {
} else if (resourceName == null) {
resourceBinding = myServerBinding;
} else {
- resourceBinding = myResourceNameToProvider.get(resourceName);
+ resourceBinding = myResourceNameToBinding.get(resourceName);
if (resourceBinding == null) {
- throw new InvalidRequestException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToProvider.keySet());
+ throw new InvalidRequestException("Unknown resource type '" + resourceName + "' - Server knows how to handle: " + myResourceNameToBinding.keySet());
}
}
@@ -668,8 +683,9 @@ public class RestfulServer extends HttpServlet {
requestDetails.setOtherOperationType(OtherOperationTypeEnum.GET_PAGE);
if (theRequestType != RequestTypeEnum.GET) {
/*
- * We reconstruct the link-self URL using the request parameters, and this would break if the parameters came in using a POST. We could probably work around that but why bother
- * unless someone comes up with a reason for needing it.
+ * We reconstruct the link-self URL using the request parameters, and this would break if the parameters
+ * came in using a POST. We could probably work around that but why bother unless someone comes up with a
+ * reason for needing it.
*/
throw new InvalidRequestException(getFhirContext().getLocalizer().getMessage(RestfulServer.class, "getPagesNonHttpGet"));
}
@@ -753,12 +769,15 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations, but subclasses may put initialization code in {@link #initialize()}, which is
- * called immediately before beginning initialization of the restful server's internal init.
+ * Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
+ * but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
+ * initialization of the restful server's internal init.
*/
@Override
public final void init() throws ServletException {
initialize();
+
+ Object confProvider;
try {
ourLog.info("Initializing HAPI FHIR restful server running in " + getFhirContext().getVersion().getVersion().name() + " mode");
@@ -777,8 +796,7 @@ public class RestfulServer extends HttpServlet {
String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
if (typeToProvider.containsKey(resourceName)) {
- throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName()
- + "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
+ throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName() + "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
}
typeToProvider.put(resourceName, nextProvider);
providedResourceScanner.scanForProvidedResources(nextProvider);
@@ -800,36 +818,56 @@ public class RestfulServer extends HttpServlet {
findResourceMethods(getServerProfilesProvider());
- Object confProvider = getServerConformanceProvider();
+ confProvider = getServerConformanceProvider();
if (confProvider == null) {
confProvider = getFhirContext().getVersion().createServerConformanceProvider(this);
}
findSystemMethods(confProvider);
+ findResourceMethods(confProvider);
} catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", ex);
}
+ ourLog.trace("Invoking provider initialize methods");
+ if (getResourceProviders() != null) {
+ for (IResourceProvider iResourceProvider : getResourceProviders()) {
+ invokeInitialize(iResourceProvider);
+ }
+ }
+ if (confProvider != null) {
+ invokeInitialize(confProvider);
+ }
+ if (getPlainProviders() != null) {
+ for (Object next : getPlainProviders()) {
+ invokeInitialize(next);
+ }
+ }
+
myStarted = true;
ourLog.info("A FHIR has been lit on this server");
}
/**
- * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the server being used.
+ * This method may be overridden by subclasses to do perform initialization that needs to be performed prior to the
+ * server being used.
*
* @throws ServletException
- * If the initialization failed. Note that you should consider throwing {@link UnavailableException} (which extends {@link ServletException}), as this is a flag to the servlet
- * container that the servlet is not usable.
+ * If the initialization failed. Note that you should consider throwing {@link UnavailableException}
+ * (which extends {@link ServletException}), as this is a flag to the servlet container that the servlet
+ * is not usable.
*/
- @SuppressWarnings("unused")
protected void initialize() throws ServletException {
// nothing by default
}
private void invokeDestroy(Object theProvider) {
- Class> clazz = theProvider.getClass();
- invokeDestroy(theProvider, clazz);
+ invokeDestroy(theProvider, theProvider.getClass());
+ }
+
+ private void invokeInitialize(Object theProvider) {
+ invokeInitialize(theProvider, theProvider.getClass());
}
private void invokeDestroy(Object theProvider, Class> clazz) {
@@ -853,9 +891,30 @@ public class RestfulServer extends HttpServlet {
}
}
+ private void invokeInitialize(Object theProvider, Class> clazz) {
+ for (Method m : ReflectionUtil.getDeclaredMethods(clazz)) {
+ Initialize initialize = m.getAnnotation(Initialize.class);
+ if (initialize != null) {
+ try {
+ m.invoke(theProvider);
+ } catch (IllegalAccessException e) {
+ ourLog.error("Exception occurred in destroy ", e);
+ } catch (InvocationTargetException e) {
+ ourLog.error("Exception occurred in destroy ", e);
+ }
+ return;
+ }
+ }
+
+ Class> supertype = clazz.getSuperclass();
+ if (!Object.class.equals(supertype)) {
+ invokeInitialize(theProvider, supertype);
+ }
+ }
+
/**
- * 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.
+ * 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
*
@@ -876,11 +935,12 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER} (which is the default), the server will automatically add a profile tag based
- * on the class of the resource(s) being returned.
+ * Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
+ * (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
+ * being returned.
*
* @param theAddProfileTag
- * The behaviour enum (must not be null)
+ * The behaviour enum (must not be null)
*/
public void setAddProfileTag(AddProfileTagEnum theAddProfileTag) {
Validate.notNull(theAddProfileTag, "theAddProfileTag must not be null");
@@ -891,29 +951,30 @@ public class RestfulServer extends HttpServlet {
* Set how bundle factory should decide whether referenced resources should be included in bundles
*
* @param theBundleInclusionRule
- * - inclusion rule (@see BundleInclusionRule for behaviors)
+ * - inclusion rule (@see BundleInclusionRule for behaviors)
*/
public void setBundleInclusionRule(BundleInclusionRule theBundleInclusionRule) {
myBundleInclusionRule = theBundleInclusionRule;
}
/**
- * 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.
+ * 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
*
*
* @param theDefaultPrettyPrint
- * The default pretty print setting
+ * The default pretty print setting
*/
public void setDefaultPrettyPrint(boolean theDefaultPrettyPrint) {
myDefaultPrettyPrint = theDefaultPrettyPrint;
}
/**
- * Sets 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}.
+ * Sets 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}.
*/
public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
Validate.notNull(theDefaultResponseEncoding, "theDefaultResponseEncoding can not be null");
@@ -921,10 +982,11 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Sets (enables/disables) the server support for ETags. Must not be null
. Default is {@link #DEFAULT_ETAG_SUPPORT}
+ * Sets (enables/disables) the server support for ETags. Must not be null
. Default is
+ * {@link #DEFAULT_ETAG_SUPPORT}
*
* @param theETagSupport
- * The ETag support mode
+ * The ETag support mode
*/
public void setETagSupport(ETagSupportEnum theETagSupport) {
if (theETagSupport == null) {
@@ -946,7 +1008,7 @@ public class RestfulServer extends HttpServlet {
* Sets (or clears) the list of interceptors
*
* @param theList
- * The list of interceptors (may be null)
+ * The list of interceptors (may be null)
*/
public void setInterceptors(IServerInterceptor... theList) {
myInterceptors.clear();
@@ -959,7 +1021,7 @@ public class RestfulServer extends HttpServlet {
* Sets (or clears) the list of interceptors
*
* @param theList
- * The list of interceptors (may be null)
+ * The list of interceptors (may be null)
*/
public void setInterceptors(List theList) {
myInterceptors.clear();
@@ -1017,7 +1079,8 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
+ * Provide a server address strategy, which is used to determine what base URL to provide clients to refer to this
+ * server. Defaults to an instance of {@link IncomingRequestAddressStrategy}
*/
public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null");
@@ -1025,15 +1088,17 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Returns the server conformance provider, which is the provider that is used to generate the server's conformance (metadata) statement.
+ * Returns the server conformance provider, which is the provider that is used to generate the server's conformance
+ * (metadata) statement.
*
- * By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be changed, or set to null
if you do not wish to export a
- * conformance statement.
+ * By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be
+ * changed, or set to null
if you do not wish to export a conformance statement.
*
* Note that this method can only be called before the server is initialized.
*
* @throws IllegalStateException
- * Note that this method can only be called prior to {@link #init() initialization} and will throw an {@link IllegalStateException} if called after that.
+ * Note that this method can only be called prior to {@link #init() initialization} and will throw an
+ * {@link IllegalStateException} if called after that.
*/
public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) {
@@ -1043,22 +1108,24 @@ public class RestfulServer extends HttpServlet {
}
/**
- * Sets the server's name, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
+ * Sets the server's name, as exported in conformance profiles exported by the server. This is informational only,
+ * but can be helpful to set with something appropriate.
*/
public void setServerName(String theServerName) {
myServerName = theServerName;
}
/**
- * Gets the server's version, as exported in conformance profiles exported by the server. This is informational only, but can be helpful to set with something appropriate.
+ * Gets the server's version, as exported in conformance profiles exported by the server. This is informational only,
+ * but can be helpful to set with something appropriate.
*/
public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion;
}
/**
- * If set to true
(default is false), 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
+ * If set to true
(default is false), 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
*/
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;
diff --git a/hapi-fhir-jpaserver-base/pom.xml b/hapi-fhir-jpaserver-base/pom.xml
index 77b70336e35..121c1de2eca 100644
--- a/hapi-fhir-jpaserver-base/pom.xml
+++ b/hapi-fhir-jpaserver-base/pom.xml
@@ -403,6 +403,10 @@
ca.uhn.fhir.jpa.rp.dstu
hapi-fhir-server-resourceproviders-dstu1.xml
+
+ Conformance
+ OperationDefinition
+
diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java
index 742ded53dd0..afb759c1285 100644
--- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java
+++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/provider/JpaSystemProviderDstu2.java
@@ -20,11 +20,22 @@ package ca.uhn.fhir.jpa.provider;
* #L%
*/
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.TreeMap;
+
import javax.servlet.http.HttpServletRequest;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.beans.factory.annotation.Qualifier;
+
+import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
+import ca.uhn.fhir.model.api.annotation.Description;
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
+import ca.uhn.fhir.model.primitive.IntegerDt;
+import ca.uhn.fhir.model.primitive.StringDt;
import ca.uhn.fhir.rest.annotation.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Transaction;
@@ -32,6 +43,120 @@ import ca.uhn.fhir.rest.annotation.TransactionParam;
public class JpaSystemProviderDstu2 extends BaseJpaSystemProvider {
+ @Autowired()
+ @Qualifier("mySystemDaoDstu2")
+ private IFhirSystemDao mySystemDao;
+
+ //@formatter:off
+ // This is generated by hand:
+ // gls hapi-fhir-structures-dstu2/target/generated-sources/tinder/ca/uhn/fhir/model/dstu2/resource/ | sort | sed "s/.java//" | sed "s/^/@OperationParam(name=\"/" | sed "s/$/\", type=IntegerDt.class, min=0, max=1),/"
+ @Operation(name="$get-resource-counts", idempotent=true, returnParameters= {
+ @OperationParam(name="AllergyIntolerance", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Appointment", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="AppointmentResponse", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="AuditEvent", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Basic", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Binary", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="BodySite", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Bundle", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="CarePlan", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="CarePlan2", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Claim", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ClaimResponse", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ClinicalImpression", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Communication", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="CommunicationRequest", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Composition", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ConceptMap", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Condition", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Conformance", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Contract", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Contraindication", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Coverage", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="DataElement", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Device", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="DeviceComponent", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="DeviceMetric", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="DeviceUseRequest", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="DeviceUseStatement", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="DiagnosticOrder", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="DiagnosticReport", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="DocumentManifest", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="DocumentReference", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="EligibilityRequest", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="EligibilityResponse", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Encounter", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="EnrollmentRequest", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="EnrollmentResponse", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="EpisodeOfCare", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ExplanationOfBenefit", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="FamilyMemberHistory", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Flag", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Goal", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Group", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="HealthcareService", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ImagingObjectSelection", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ImagingStudy", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Immunization", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ImmunizationRecommendation", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ListResource", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Location", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Media", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Medication", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="MedicationAdministration", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="MedicationDispense", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="MedicationPrescription", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="MedicationStatement", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="MessageHeader", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="NamingSystem", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="NutritionOrder", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Observation", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="OperationDefinition", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="OperationOutcome", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Order", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="OrderResponse", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Organization", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Parameters", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Patient", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="PaymentNotice", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="PaymentReconciliation", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Person", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Practitioner", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Procedure", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ProcedureRequest", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ProcessRequest", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ProcessResponse", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Provenance", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Questionnaire", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="QuestionnaireAnswers", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ReferralRequest", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="RelatedPerson", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="RiskAssessment", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Schedule", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="SearchParameter", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Slot", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Specimen", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="StructureDefinition", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Subscription", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Substance", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="Supply", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="ValueSet", type=IntegerDt.class, min=0, max=1),
+ @OperationParam(name="VisionPrescription", type=IntegerDt.class, min=0, max=1)
+ })
+ @Description(shortDefinition="Provides the number of resources currently stored on the server, broken down by resource type")
+ //@formatter:on
+ public Parameters getResourceCounts() {
+ Parameters retVal = new Parameters();
+
+ Map counts = mySystemDao.getResourceCounts();
+ counts = new TreeMap(counts);
+ for (Entry nextEntry : counts.entrySet()) {
+ retVal.addParameter().setName(new StringDt(nextEntry.getKey())).setValue(new IntegerDt(nextEntry.getValue().intValue()));
+ }
+
+ return retVal;
+ }
+
//@formatter:off
@Operation(name="$meta", idempotent=true, returnParameters= {
@OperationParam(name="return", type=MetaDt.class)
diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java
index db4c94a4c97..84ca4fe20ca 100644
--- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java
+++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/ResourceProviderDstu2Test.java
@@ -212,6 +212,27 @@ public class ResourceProviderDstu2Test extends BaseJpaTest {
}
+ @Test
+ public void testGetResourceCountsOperation() throws Exception {
+ String methodName = "testMetaOperations";
+
+ Patient pt = new Patient();
+ pt.addName().addFamily(methodName);
+ ourClient.create().resource(pt).execute().getId().toUnqualifiedVersionless();
+
+ HttpGet get = new HttpGet(ourServerBase + "/$get-resource-counts");
+ CloseableHttpResponse response = ourHttpClient.execute(get);
+ try {
+ assertEquals(200, response.getStatusLine().getStatusCode());
+ String output = IOUtils.toString(response.getEntity().getContent());
+ IOUtils.closeQuietly(response.getEntity().getContent());
+ ourLog.info(output);
+ assertThat(output, containsString(" myOperationBindingToName;
+ private HashMap myOperationNameToBinding;
public ServerConformanceProvider(RestfulServer theRestfulServer) {
myRestfulServer = theRestfulServer;
@@ -97,6 +105,73 @@ public class ServerConformanceProvider implements IServerConformanceProvider allNames = new HashSet();
+ myOperationBindingToName = new IdentityHashMap();
+ myOperationNameToBinding = new HashMap();
+
+ Map>> resourceToMethods = collectMethodBindings();
+ for (Entry>> nextEntry : resourceToMethods.entrySet()) {
+ List> nextMethodBindings = nextEntry.getValue();
+ for (BaseMethodBinding> nextMethodBinding : nextMethodBindings) {
+ if (nextMethodBinding instanceof OperationMethodBinding) {
+ OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
+ String nextName = methodBinding.getName().substring(1);
+ int count = 1;
+ while (allNames.add(createOperationName(nextName, count)) == false) {
+ count++;
+ }
+ String name = createOperationName(nextName, count);
+ myOperationBindingToName.put(methodBinding, name);
+ myOperationNameToBinding.put(name, methodBinding);
+ }
+ }
+ }
+ }
+
+ private String createOperationName(String theName, int theCount) {
+ if (theCount < 2) {
+ return theName;
+ }
+ return theName + '-' + theCount;
+ }
+
+ @Read(type = OperationDefinition.class)
+ public OperationDefinition readOperationDefinition(@IdParam IdDt theId) {
+ if (theId == null || theId.hasIdPart() == false) {
+ throw new ResourceNotFoundException(theId);
+ }
+ OperationMethodBinding methodBinding = myOperationNameToBinding.get(theId.getIdPart());
+ if (methodBinding == null) {
+ throw new ResourceNotFoundException(theId);
+ }
+
+ OperationDefinition op = new OperationDefinition();
+ op.setStatus(ConformanceResourceStatusEnum.ACTIVE);
+ op.setDescription(methodBinding.getDescription());
+ op.setIdempotent(methodBinding.isIdempotent());
+ op.setCode(methodBinding.getName());
+ op.setInstance(methodBinding.isInstanceLevel());
+ op.addType().setValue(methodBinding.getResourceName());
+
+ for (IParameter nextParamUntyped : methodBinding.getParameters()) {
+ if (nextParamUntyped instanceof OperationParameter) {
+ OperationParameter nextParam = (OperationParameter) nextParamUntyped;
+ Parameter param = op.addParameter();
+ param.setUse(OperationParameterUseEnum.IN);
+ if (nextParam.getParamType() != null) {
+ param.setType(nextParam.getParamType().getCode());
+ }
+ param.setMin(nextParam.getMin());
+ param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
+ param.setName(nextParam.getName());
+ }
+ }
+
+ return op;
+ }
+
@Override
@Metadata
public Conformance getServerConformance(HttpServletRequest theRequest) {
@@ -123,24 +198,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider systemOps = new HashSet();
- Map>> resourceToMethods = new TreeMap>>();
- for (ResourceBinding next : myRestfulServer.getResourceBindings()) {
- String resourceName = next.getResourceName();
- for (BaseMethodBinding> nextMethodBinding : next.getMethodBindings()) {
- if (resourceToMethods.containsKey(resourceName) == false) {
- resourceToMethods.put(resourceName, new ArrayList>());
- }
- resourceToMethods.get(resourceName).add(nextMethodBinding);
- }
- }
- for (BaseMethodBinding> nextMethodBinding : myRestfulServer.getServerBindings()) {
- String resourceName = "";
- if (resourceToMethods.containsKey(resourceName) == false) {
- resourceToMethods.put(resourceName, new ArrayList>());
- }
- resourceToMethods.get(resourceName).add(nextMethodBinding);
- }
-
+ Map>> resourceToMethods = collectMethodBindings();
for (Entry>> nextEntry : resourceToMethods.entrySet()) {
if (nextEntry.getKey().isEmpty() == false) {
@@ -202,30 +260,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider() {
@@ -262,6 +298,27 @@ public class ServerConformanceProvider implements IServerConformanceProvider>> collectMethodBindings() {
+ Map>> resourceToMethods = new TreeMap>>();
+ for (ResourceBinding next : myRestfulServer.getResourceBindings()) {
+ String resourceName = next.getResourceName();
+ for (BaseMethodBinding> nextMethodBinding : next.getMethodBindings()) {
+ if (resourceToMethods.containsKey(resourceName) == false) {
+ resourceToMethods.put(resourceName, new ArrayList>());
+ }
+ resourceToMethods.get(resourceName).add(nextMethodBinding);
+ }
+ }
+ for (BaseMethodBinding> nextMethodBinding : myRestfulServer.getServerBindings()) {
+ String resourceName = "";
+ if (resourceToMethods.containsKey(resourceName) == false) {
+ resourceToMethods.put(resourceName, new ArrayList>());
+ }
+ resourceToMethods.get(resourceName).add(nextMethodBinding);
+ }
+ return resourceToMethods;
+ }
+
private void checkBindingForSystemOps(Rest rest, Set systemOps, BaseMethodBinding> nextMethodBinding) {
if (nextMethodBinding.getSystemOperationType() != null) {
String sysOpCode = nextMethodBinding.getSystemOperationType().getCode();
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerTest.java
index 2ac55c23945..0e8a4ebd656 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerTest.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/OperationServerTest.java
@@ -1,7 +1,6 @@
package ca.uhn.fhir.rest.server;
-import static org.hamcrest.Matchers.containsString;
-import static org.hamcrest.Matchers.startsWith;
+import static org.hamcrest.Matchers.*;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat;
@@ -32,6 +31,9 @@ import org.junit.Test;
import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Bundle;
+import ca.uhn.fhir.model.dstu2.resource.Conformance;
+import ca.uhn.fhir.model.dstu2.resource.OperationDefinition;
+import ca.uhn.fhir.model.dstu2.resource.Conformance.RestOperation;
import ca.uhn.fhir.model.dstu2.resource.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.DateTimeDt;
@@ -42,6 +44,7 @@ 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.client.IGenericClient;
import ca.uhn.fhir.util.PortUtil;
/**
@@ -312,6 +315,20 @@ public class OperationServerTest {
assertEquals("instance $everything", ourLastMethod);
assertEquals("Patient/123", ourLastId.toUnqualifiedVersionless().getValue());
+
+ }
+
+ @Test
+ public void testConformance() throws Exception {
+ IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort);
+ Conformance p = client.fetchConformance().ofType(Conformance.class).execute();
+ List ops = p.getRest().get(0).getOperation();
+ assertThat(ops.size(), greaterThan(1));
+ assertNull(ops.get(0).getDefinition().getReference().getBaseUrl());
+ assertThat(ops.get(0).getDefinition().getReference().getValue(), startsWith("OperationDefinition/"));
+
+ OperationDefinition def = client.read().resource(OperationDefinition.class).withId(ops.get(0).getDefinition().getReference()).execute();
+ assertThat(def.getCode(), not(blankOrNullString()));
}
@Test
diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderDstu2Test.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderDstu2Test.java
index d65a063c4a5..2db3bcfc6ee 100644
--- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderDstu2Test.java
+++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderDstu2Test.java
@@ -22,6 +22,7 @@ import ca.uhn.fhir.model.dstu2.resource.Conformance;
import ca.uhn.fhir.model.dstu2.resource.Conformance.Rest;
import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport;
+import ca.uhn.fhir.model.dstu2.resource.OperationDefinition;
import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.SystemRestfulInteractionEnum;
import ca.uhn.fhir.model.dstu2.valueset.TypeRestfulInteractionEnum;
@@ -86,6 +87,29 @@ public class ServerConformanceProviderDstu2Test {
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
+ assertEquals(1, conformance.getRest().get(0).getOperation().size());
+ assertEquals("$everything", conformance.getRest().get(0).getOperation().get(0).getName());
+ assertEquals("OperationDefinition/everything", conformance.getRest().get(0).getOperation().get(0).getDefinition().getReference().getValue());
+ }
+
+ @Test
+ public void testExtendedOperationReturningBundleOperation() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
+
+ ServerConformanceProvider sc = new ServerConformanceProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ OperationDefinition opDef = sc.readOperationDefinition(new IdDt("OperationDefinition/everything"));
+
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
+ ourLog.info(conf);
+
+ assertEquals("$everything", opDef.getCode());
+ assertEquals(true, opDef.getIdempotent().booleanValue());
}
@Test
diff --git a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java
index 9d322a68343..2db61c07fd5 100644
--- a/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java
+++ b/hapi-fhir-structures-hl7org-dstu2/src/main/java/org/hl7/fhir/instance/conf/ServerConformanceProvider.java
@@ -20,13 +20,13 @@ package org.hl7.fhir.instance.conf;
* #L%
*/
-import static org.apache.commons.lang3.StringUtils.*;
-
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
+import java.util.HashMap;
import java.util.HashSet;
+import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
@@ -53,7 +53,11 @@ import org.hl7.fhir.instance.model.OperationDefinition.OperationParameterUse;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.IResource;
+import ca.uhn.fhir.model.primitive.IdDt;
+import ca.uhn.fhir.rest.annotation.IdParam;
+import ca.uhn.fhir.rest.annotation.Initialize;
import ca.uhn.fhir.rest.annotation.Metadata;
+import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding;
import ca.uhn.fhir.rest.method.IParameter;
@@ -66,6 +70,7 @@ 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.exceptions.InternalErrorException;
+import ca.uhn.fhir.rest.server.exceptions.ResourceNotFoundException;
/**
* Server FHIR Provider which serves the conformance statement for a RESTful server implementation
@@ -78,11 +83,12 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
*
*/
public class ServerConformanceProvider implements IServerConformanceProvider {
-
private boolean myCache = true;
private volatile Conformance myConformance;
private String myPublisher = "Not provided";
private final RestfulServer myRestfulServer;
+ private IdentityHashMap myOperationBindingToName;
+ private HashMap myOperationNameToBinding;
public ServerConformanceProvider(RestfulServer theRestfulServer) {
myRestfulServer = theRestfulServer;
@@ -97,6 +103,73 @@ public class ServerConformanceProvider implements IServerConformanceProvider allNames = new HashSet();
+ myOperationBindingToName = new IdentityHashMap();
+ myOperationNameToBinding = new HashMap();
+
+ Map>> resourceToMethods = collectMethodBindings();
+ for (Entry>> nextEntry : resourceToMethods.entrySet()) {
+ List> nextMethodBindings = nextEntry.getValue();
+ for (BaseMethodBinding> nextMethodBinding : nextMethodBindings) {
+ if (nextMethodBinding instanceof OperationMethodBinding) {
+ OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
+ String nextName = methodBinding.getName().substring(1);
+ int count = 1;
+ while (allNames.add(createOperationName(nextName, count)) == false) {
+ count++;
+ }
+ String name = createOperationName(nextName, count);
+ myOperationBindingToName.put(methodBinding, name);
+ myOperationNameToBinding.put(name, methodBinding);
+ }
+ }
+ }
+ }
+
+ private String createOperationName(String theName, int theCount) {
+ if (theCount < 2) {
+ return theName;
+ }
+ return theName + '-' + theCount;
+ }
+
+ @Read(type = OperationDefinition.class)
+ public OperationDefinition readOperationDefinition(@IdParam IdDt theId) {
+ if (theId == null || theId.hasIdPart() == false) {
+ throw new ResourceNotFoundException(theId);
+ }
+ OperationMethodBinding methodBinding = myOperationNameToBinding.get(theId.getIdPart());
+ if (methodBinding == null) {
+ throw new ResourceNotFoundException(theId);
+ }
+
+ OperationDefinition op = new OperationDefinition();
+ op.setStatus(ConformanceResourceStatus.ACTIVE);
+ op.setDescription(methodBinding.getDescription());
+ op.setIdempotent(methodBinding.isIdempotent());
+ op.setCode(methodBinding.getName());
+ op.setInstance(methodBinding.isInstanceLevel());
+ op.addType(methodBinding.getResourceName());
+
+ for (IParameter nextParamUntyped : methodBinding.getParameters()) {
+ if (nextParamUntyped instanceof OperationParameter) {
+ OperationParameter nextParam = (OperationParameter) nextParamUntyped;
+ OperationDefinitionParameterComponent param = op.addParameter();
+ param.setUse(OperationParameterUse.IN);
+ if (nextParam.getParamType() != null) {
+ param.setType(nextParam.getParamType().getCode());
+ }
+ param.setMin(nextParam.getMin());
+ param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax()));
+ param.setName(nextParam.getName());
+ }
+ }
+
+ return op;
+ }
+
@Override
@Metadata
public Conformance getServerConformance(HttpServletRequest theRequest) {
@@ -123,24 +196,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider systemOps = new HashSet();
- Map>> resourceToMethods = new TreeMap>>();
- for (ResourceBinding next : myRestfulServer.getResourceBindings()) {
- String resourceName = next.getResourceName();
- for (BaseMethodBinding> nextMethodBinding : next.getMethodBindings()) {
- if (resourceToMethods.containsKey(resourceName) == false) {
- resourceToMethods.put(resourceName, new ArrayList>());
- }
- resourceToMethods.get(resourceName).add(nextMethodBinding);
- }
- }
- for (BaseMethodBinding> nextMethodBinding : myRestfulServer.getServerBindings()) {
- String resourceName = "";
- if (resourceToMethods.containsKey(resourceName) == false) {
- resourceToMethods.put(resourceName, new ArrayList>());
- }
- resourceToMethods.get(resourceName).add(nextMethodBinding);
- }
-
+ Map>> resourceToMethods = collectMethodBindings();
for (Entry>> nextEntry : resourceToMethods.entrySet()) {
if (nextEntry.getKey().isEmpty() == false) {
@@ -149,7 +205,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider includes = new TreeSet();
@@ -162,8 +218,10 @@ public class ServerConformanceProvider implements IServerConformanceProvider() {
@Override
public int compare(ResourceInteractionComponent theO1, ResourceInteractionComponent theO2) {
- TypeRestfulInteraction o1 = theO1.getCode();
- TypeRestfulInteraction o2 = theO2.getCode();
+ TypeRestfulInteraction o1 = theO1.getCodeElement().getValue();
+ TypeRestfulInteraction o2 = theO2.getCodeElement().getValue();
if (o1 == null && o2 == null) {
return 0;
}
@@ -265,6 +301,27 @@ public class ServerConformanceProvider implements IServerConformanceProvider>> collectMethodBindings() {
+ Map>> resourceToMethods = new TreeMap>>();
+ for (ResourceBinding next : myRestfulServer.getResourceBindings()) {
+ String resourceName = next.getResourceName();
+ for (BaseMethodBinding> nextMethodBinding : next.getMethodBindings()) {
+ if (resourceToMethods.containsKey(resourceName) == false) {
+ resourceToMethods.put(resourceName, new ArrayList>());
+ }
+ resourceToMethods.get(resourceName).add(nextMethodBinding);
+ }
+ }
+ for (BaseMethodBinding> nextMethodBinding : myRestfulServer.getServerBindings()) {
+ String resourceName = "";
+ if (resourceToMethods.containsKey(resourceName) == false) {
+ resourceToMethods.put(resourceName, new ArrayList>());
+ }
+ resourceToMethods.get(resourceName).add(nextMethodBinding);
+ }
+ return resourceToMethods;
+ }
+
private void checkBindingForSystemOps(ConformanceRestComponent rest, Set systemOps, BaseMethodBinding> nextMethodBinding) {
if (nextMethodBinding.getSystemOperationType() != null) {
String sysOpCode = nextMethodBinding.getSystemOperationType().getCode();
@@ -272,8 +329,10 @@ public class ServerConformanceProvider implements IServerConformanceProvider nextTarget : nextParameter.getDeclaredTypes()) {
RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget);
if (targetDef != null) {
- if (isNotBlank(targetDef.getName())) {
- param.addTarget(targetDef.getName());
+ org.hl7.fhir.instance.model.Enumerations.ResourceType code;
+ try {
+ code = org.hl7.fhir.instance.model.Enumerations.ResourceType.fromCode(targetDef.getName());
+ } catch (Exception e) {
+ code = null;
+ }
+ if (code != null) {
+ param.addTarget(code.toCode());
}
}
}
diff --git a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderHl7OrgDstu2Test.java b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderHl7OrgDstu2Test.java
index 4e1cefad039..967e59ea7f1 100644
--- a/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderHl7OrgDstu2Test.java
+++ b/hapi-fhir-structures-hl7org-dstu2/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderHl7OrgDstu2Test.java
@@ -18,6 +18,7 @@ import org.hl7.fhir.instance.model.Conformance.ConformanceRestResourceComponent;
import org.hl7.fhir.instance.model.Conformance.SystemRestfulInteraction;
import org.hl7.fhir.instance.model.Conformance.TypeRestfulInteraction;
import org.hl7.fhir.instance.model.DiagnosticReport;
+import org.hl7.fhir.instance.model.OperationDefinition;
import org.hl7.fhir.instance.model.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.Test;
@@ -86,6 +87,29 @@ public class ServerConformanceProviderHl7OrgDstu2Test {
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf);
+ assertEquals(1, conformance.getRest().get(0).getOperation().size());
+ assertEquals("$everything", conformance.getRest().get(0).getOperation().get(0).getName());
+ assertEquals("OperationDefinition/everything", conformance.getRest().get(0).getOperation().get(0).getDefinition().getReference());
+ }
+
+ @Test
+ public void testExtendedOperationReturningBundleOperation() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
+
+ ServerConformanceProvider sc = new ServerConformanceProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ OperationDefinition opDef = sc.readOperationDefinition(new IdDt("OperationDefinition/everything"));
+
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(opDef);
+ ourLog.info(conf);
+
+ assertEquals("$everything", opDef.getCode());
+ assertEquals(true, opDef.getIdempotent());
}
@Test
diff --git a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java
index f99711471b7..27a9e9491fc 100644
--- a/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java
+++ b/hapi-tinder-plugin/src/main/java/ca/uhn/fhir/tinder/TinderJpaRestServerMojo.java
@@ -8,7 +8,6 @@ import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
-import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang.WordUtils;
@@ -49,6 +48,9 @@ public class TinderJpaRestServerMojo extends AbstractMojo {
@Parameter(required = false)
private List baseResourceNames;
+ @Parameter(required = false)
+ private List excludeResourceNames;
+
@Parameter(required = true, defaultValue = "${project.build.directory}/..")
private String baseDir;
@@ -97,6 +99,10 @@ public class TinderJpaRestServerMojo extends AbstractMojo {
}
}
+ if (excludeResourceNames != null) {
+ baseResourceNames.removeAll(excludeResourceNames);
+ }
+
ourLog.info("Including the following resources: {}", baseResourceNames);
File directoryBase = new File(targetDirectory, packageBase.replace(".", File.separatorChar + ""));
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 51c820713c2..2733a86f1f5 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -19,6 +19,15 @@
actually point to any codes (e.g. Observation.interpretation). Thanks
to GitHub user @steve1medix for reporting!
+
+ Server now exports operations as separate resources instead of as contained resources
+ within Conformance
+
+
+ Add new operation $get-resource-counts which will replace the resource
+ count extensions exported in the Conformance statement by the JPA
+ server.
+