Add $get-resource-counts operation to JPA server

This commit is contained in:
jamesagnew 2015-07-17 18:28:47 -04:00
parent ddbe79cb86
commit 41283d4ed4
15 changed files with 625 additions and 167 deletions

View File

@ -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. * This is your chance to do any cleanup.
*/ */
@Retention(RetentionPolicy.RUNTIME) @Retention(RetentionPolicy.RUNTIME)

View File

@ -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 {
}

View File

@ -25,7 +25,8 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target; 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.IBasicClient;
import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.rest.client.api.IRestfulClient;
import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.IResourceProvider;
@ -57,7 +58,7 @@ public @interface Read {
* </p> * </p>
*/ */
// NB: Read, Search (maybe others) share this annotation, so update the javadocs everywhere // 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 * If set to true (default is false), this method supports vread operation as well as read

View File

@ -128,6 +128,9 @@ public class OperationMethodBinding extends BaseResourceReturningMethodBinding {
return myDescription; return myDescription;
} }
/**
* Returns the name of the operation, starting with "$"
*/
public String getName() { public String getName() {
return myName; return myName;
} }

View File

@ -57,6 +57,7 @@ import ca.uhn.fhir.model.api.Include;
import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.IdDt;
import ca.uhn.fhir.rest.annotation.Destroy; import ca.uhn.fhir.rest.annotation.Destroy;
import ca.uhn.fhir.rest.annotation.IdParam; 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.api.RequestTypeEnum;
import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.ConformanceMethodBinding; import ca.uhn.fhir.rest.method.ConformanceMethodBinding;
@ -91,7 +92,7 @@ public class RestfulServer extends HttpServlet {
private final List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>(); private final List<IServerInterceptor> myInterceptors = new ArrayList<IServerInterceptor>();
private IPagingProvider myPagingProvider; private IPagingProvider myPagingProvider;
private Collection<Object> myPlainProviders; private Collection<Object> myPlainProviders;
private Map<String, ResourceBinding> myResourceNameToProvider = new HashMap<String, ResourceBinding>(); private Map<String, ResourceBinding> myResourceNameToBinding = new HashMap<String, ResourceBinding>();
private Collection<IResourceProvider> myResourceProviders; private Collection<IResourceProvider> myResourceProviders;
private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy(); private IServerAddressStrategy myServerAddressStrategy = new IncomingRequestAddressStrategy();
private ResourceBinding myServerBinding = new ResourceBinding(); 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 * 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 {@link #setFhirContext(FhirContext)}) the server will determine which version of FHIR to support through
* through classpath scanning. This is brittle, and it is highly recommended to explicitly specify * classpath scanning. This is brittle, and it is highly recommended to explicitly specify a FHIR version.
* a FHIR version.
*/ */
public RestfulServer() { public RestfulServer() {
this(null); 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. * This method is called prior to sending a response to incoming requests. It is used to add custom headers.
* <p> * <p>
* Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid inadvertantly disabling functionality. * Use caution if overriding this method: it is recommended to call <code>super.addHeadersToResponse</code> to avoid
* inadvertantly disabling functionality.
* </p> * </p>
*/ */
public void addHeadersToResponse(HttpServletResponse theHttpResponse) { public void addHeadersToResponse(HttpServletResponse theHttpResponse) {
@ -143,6 +144,14 @@ public class RestfulServer extends HttpServlet {
invokeDestroy(iResourceProvider); invokeDestroy(iResourceProvider);
} }
} }
if (myServerConformanceProvider != null) {
invokeDestroy(myServerConformanceProvider);
}
if (getPlainProviders() != null) {
for (Object next : getPlainProviders()) {
invokeDestroy(next);
}
}
} }
@Override @Override
@ -228,12 +237,12 @@ public class RestfulServer extends HttpServlet {
resourceBinding = myServerBinding; resourceBinding = myServerBinding;
} else { } else {
RuntimeResourceDefinition definition = getFhirContext().getResourceDefinition(resourceName); RuntimeResourceDefinition definition = getFhirContext().getResourceDefinition(resourceName);
if (myResourceNameToProvider.containsKey(definition.getName())) { if (myResourceNameToBinding.containsKey(definition.getName())) {
resourceBinding = myResourceNameToProvider.get(definition.getName()); resourceBinding = myResourceNameToBinding.get(definition.getName());
} else { } else {
resourceBinding = new ResourceBinding(); resourceBinding = new ResourceBinding();
resourceBinding.setResourceName(resourceName); resourceBinding.setResourceName(resourceName);
myResourceNameToProvider.put(resourceName, resourceBinding); myResourceNameToBinding.put(resourceName, resourceBinding);
} }
} }
@ -284,7 +293,7 @@ public class RestfulServer extends HttpServlet {
} else { } else {
myServerBinding.addMethod(foundMethodBinding); myServerBinding.addMethod(foundMethodBinding);
} }
ourLog.info(" * Method: {}#{} is a handler", theSystemProvider.getClass(), m.getName()); ourLog.debug(" * Method: {}#{} is a handler", theSystemProvider.getClass(), m.getName());
} else { } else {
ourLog.debug(" * Method: {}#{} is not a handler", theSystemProvider.getClass(), m.getName()); 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 <code>_format</code> URL parameter, or with an <code>Accept</code> header * Returns the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either
* in the request. The default is {@link EncodingEnum#XML}. * with the <code>_format</code> URL parameter, or with an <code>Accept</code> header in the request. The default is
* {@link EncodingEnum#XML}.
*/ */
public EncodingEnum getDefaultResponseEncoding() { public EncodingEnum getDefaultResponseEncoding() {
return myDefaultResponseEncoding; 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 * Gets the {@link FhirContext} associated with this server. For efficient processing, resource providers and plain
* creating their own. * providers should generally use this context if one is needed, as opposed to creating their own.
*/ */
public FhirContext getFhirContext() { public FhirContext getFhirContext() {
if (myFhirContext == null) { if (myFhirContext == null) {
@ -356,7 +366,8 @@ 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 * @param requestFullPath
* the full request path * the full request path
@ -371,7 +382,7 @@ public class RestfulServer extends HttpServlet {
} }
public Collection<ResourceBinding> getResourceBindings() { public Collection<ResourceBinding> 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() { public IServerAddressStrategy getServerAddressStrategy() {
return myServerAddressStrategy; 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 * Returns the method bindings for this server which are not specific to any particular resource type. This method is
* with caution, as it may change. * internal to HAPI and developers generally do not need to interact with it. Use with caution, as it may change.
*/ */
public List<BaseMethodBinding<?>> getServerBindings() { public List<BaseMethodBinding<?>> getServerBindings() {
return myServerBinding.getMethodBindings(); 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.
* <p> * <p>
* By default, the ServerConformanceProvider for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> 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 <code>null</code> to use the appropriate one for the given FHIR version.
* </p> * </p>
*/ */
public Object getServerConformanceProvider() { 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) * @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() { public String getServerVersion() {
return myServerVersion; return myServerVersion;
@ -508,8 +524,7 @@ public class RestfulServer extends HttpServlet {
return; return;
} }
} }
RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, Constants.STATUS_HTTP_200_OK, RestfulServerUtils.streamResponseAsResource(this, theResponse, resBundle, responseEncoding, prettyPrint, requestIsBrowser, narrativeMode, Constants.STATUS_HTTP_200_OK, theRequest.isRespondGzip(), theRequest.getFhirServerBase(), false);
theRequest.isRespondGzip(), theRequest.getFhirServerBase(), false);
} }
} }
@ -582,9 +597,9 @@ public class RestfulServer extends HttpServlet {
} else if (resourceName == null) { } else if (resourceName == null) {
resourceBinding = myServerBinding; resourceBinding = myServerBinding;
} else { } else {
resourceBinding = myResourceNameToProvider.get(resourceName); resourceBinding = myResourceNameToBinding.get(resourceName);
if (resourceBinding == null) { 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); requestDetails.setOtherOperationType(OtherOperationTypeEnum.GET_PAGE);
if (theRequestType != RequestTypeEnum.GET) { 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 * We reconstruct the link-self URL using the request parameters, and this would break if the parameters
* unless someone comes up with a reason for needing it. * 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")); 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 * Initializes the server. Note that this method is final to avoid accidentally introducing bugs in implementations,
* called immediately before beginning initialization of the restful server's internal init. * but subclasses may put initialization code in {@link #initialize()}, which is called immediately before beginning
* initialization of the restful server's internal init.
*/ */
@Override @Override
public final void init() throws ServletException { public final void init() throws ServletException {
initialize(); initialize();
Object confProvider;
try { try {
ourLog.info("Initializing HAPI FHIR restful server running in " + getFhirContext().getVersion().getVersion().name() + " mode"); 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(); String resourceName = getFhirContext().getResourceDefinition(resourceType).getName();
if (typeToProvider.containsKey(resourceName)) { if (typeToProvider.containsKey(resourceName)) {
throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName() throw new ServletException("Multiple resource providers return resource type[" + resourceName + "]: First[" + typeToProvider.get(resourceName).getClass().getCanonicalName() + "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
+ "] and Second[" + nextProvider.getClass().getCanonicalName() + "]");
} }
typeToProvider.put(resourceName, nextProvider); typeToProvider.put(resourceName, nextProvider);
providedResourceScanner.scanForProvidedResources(nextProvider); providedResourceScanner.scanForProvidedResources(nextProvider);
@ -800,36 +818,56 @@ public class RestfulServer extends HttpServlet {
findResourceMethods(getServerProfilesProvider()); findResourceMethods(getServerProfilesProvider());
Object confProvider = getServerConformanceProvider(); confProvider = getServerConformanceProvider();
if (confProvider == null) { if (confProvider == null) {
confProvider = getFhirContext().getVersion().createServerConformanceProvider(this); confProvider = getFhirContext().getVersion().createServerConformanceProvider(this);
} }
findSystemMethods(confProvider); findSystemMethods(confProvider);
findResourceMethods(confProvider);
} catch (Exception ex) { } catch (Exception ex) {
ourLog.error("An error occurred while loading request handlers!", ex); ourLog.error("An error occurred while loading request handlers!", ex);
throw new ServletException("Failed to initialize FHIR Restful server", 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; myStarted = true;
ourLog.info("A FHIR has been lit on this server"); 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 * @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 * If the initialization failed. Note that you should consider throwing {@link UnavailableException}
* container that the servlet is not usable. * (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 { protected void initialize() throws ServletException {
// nothing by default // nothing by default
} }
private void invokeDestroy(Object theProvider) { private void invokeDestroy(Object theProvider) {
Class<?> clazz = theProvider.getClass(); invokeDestroy(theProvider, theProvider.getClass());
invokeDestroy(theProvider, clazz); }
private void invokeInitialize(Object theProvider) {
invokeInitialize(theProvider, theProvider.getClass());
} }
private void invokeDestroy(Object theProvider, Class<?> clazz) { 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 <code>Accept</code> header in the request, or a <code>_pretty</code> * Should the server "pretty print" responses by default (requesting clients can always override this default by
* parameter in the request URL. * supplying an <code>Accept</code> header in the request, or a <code>_pretty</code> parameter in the request URL.
* <p> * <p>
* The default is <code>false</code> * The default is <code>false</code>
* </p> * </p>
@ -876,8 +935,9 @@ 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 * Sets the profile tagging behaviour for the server. When set to a value other than {@link AddProfileTagEnum#NEVER}
* on the class of the resource(s) being returned. * (which is the default), the server will automatically add a profile tag based on the class of the resource(s)
* being returned.
* *
* @param theAddProfileTag * @param theAddProfileTag
* The behaviour enum (must not be null) * The behaviour enum (must not be null)
@ -898,8 +958,8 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an <code>Accept</code> header in the request, or a <code>_pretty</code> * Should the server "pretty print" responses by default (requesting clients can always override this default by
* parameter in the request URL. * supplying an <code>Accept</code> header in the request, or a <code>_pretty</code> parameter in the request URL.
* <p> * <p>
* The default is <code>false</code> * The default is <code>false</code>
* </p> * </p>
@ -912,8 +972,9 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with the <code>_format</code> URL parameter, or with an <code>Accept</code> header in * Sets the default encoding to return (XML/JSON) if an incoming request does not specify a preference (either with
* the request. The default is {@link EncodingEnum#XML}. * the <code>_format</code> URL parameter, or with an <code>Accept</code> header in the request. The default is
* {@link EncodingEnum#XML}.
*/ */
public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) { public void setDefaultResponseEncoding(EncodingEnum theDefaultResponseEncoding) {
Validate.notNull(theDefaultResponseEncoding, "theDefaultResponseEncoding can not be null"); Validate.notNull(theDefaultResponseEncoding, "theDefaultResponseEncoding can not be null");
@ -921,7 +982,8 @@ public class RestfulServer extends HttpServlet {
} }
/** /**
* Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is {@link #DEFAULT_ETAG_SUPPORT} * Sets (enables/disables) the server support for ETags. Must not be <code>null</code>. Default is
* {@link #DEFAULT_ETAG_SUPPORT}
* *
* @param theETagSupport * @param theETagSupport
* The ETag support mode * The ETag support mode
@ -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) { public void setServerAddressStrategy(IServerAddressStrategy theServerAddressStrategy) {
Validate.notNull(theServerAddressStrategy, "Server address strategy can not be null"); 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.
* <p> * <p>
* By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be changed, or set to <code>null</code> if you do not wish to export a * By default, the ServerConformanceProvider implementation for the declared version of FHIR is used, but this can be
* conformance statement. * changed, or set to <code>null</code> if you do not wish to export a conformance statement.
* </p> * </p>
* Note that this method can only be called before the server is initialized. * Note that this method can only be called before the server is initialized.
* *
* @throws IllegalStateException * @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) { public void setServerConformanceProvider(Object theServerConformanceProvider) {
if (myStarted) { 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) { public void setServerName(String theServerName) {
myServerName = 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) { public void setServerVersion(String theServerVersion) {
myServerVersion = theServerVersion; myServerVersion = theServerVersion;
} }
/** /**
* If set to <code>true</code> (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 * If set to <code>true</code> (default is false), the server will use browser friendly content-types (instead of
* instead of a FHIR * standard FHIR ones) when it detects that the request is coming from a browser instead of a FHIR
*/ */
public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) { public void setUseBrowserFriendlyContentTypes(boolean theUseBrowserFriendlyContentTypes) {
myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes; myUseBrowserFriendlyContentTypes = theUseBrowserFriendlyContentTypes;

View File

@ -403,6 +403,10 @@
<packageBase>ca.uhn.fhir.jpa.rp.dstu</packageBase> <packageBase>ca.uhn.fhir.jpa.rp.dstu</packageBase>
<targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu1.xml</targetResourceSpringBeansFile> <targetResourceSpringBeansFile>hapi-fhir-server-resourceproviders-dstu1.xml</targetResourceSpringBeansFile>
<baseResourceNames></baseResourceNames> <baseResourceNames></baseResourceNames>
<excludeResourceNames>
<excludeResourceName>Conformance</excludeResourceName>
<excludeResourceName>OperationDefinition</excludeResourceName>
</excludeResourceNames>
</configuration> </configuration>
</execution> </execution>
<execution> <execution>

View File

@ -20,11 +20,22 @@ package ca.uhn.fhir.jpa.provider;
* #L% * #L%
*/ */
import java.util.Map;
import java.util.Map.Entry;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest; 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.composite.MetaDt;
import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle;
import ca.uhn.fhir.model.dstu2.resource.Parameters; 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.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Transaction; import ca.uhn.fhir.rest.annotation.Transaction;
@ -32,6 +43,120 @@ import ca.uhn.fhir.rest.annotation.TransactionParam;
public class JpaSystemProviderDstu2 extends BaseJpaSystemProvider<Bundle> { public class JpaSystemProviderDstu2 extends BaseJpaSystemProvider<Bundle> {
@Autowired()
@Qualifier("mySystemDaoDstu2")
private IFhirSystemDao<Bundle> 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<String, Long> counts = mySystemDao.getResourceCounts();
counts = new TreeMap<String, Long>(counts);
for (Entry<String, Long> nextEntry : counts.entrySet()) {
retVal.addParameter().setName(new StringDt(nextEntry.getKey())).setValue(new IntegerDt(nextEntry.getValue().intValue()));
}
return retVal;
}
//@formatter:off //@formatter:off
@Operation(name="$meta", idempotent=true, returnParameters= { @Operation(name="$meta", idempotent=true, returnParameters= {
@OperationParam(name="return", type=MetaDt.class) @OperationParam(name="return", type=MetaDt.class)

View File

@ -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("<parameter><name value=\"Patient\"/><valueInteger value=\""));
} finally {
response.close();
}
}
@Test @Test
public void testCreateResourceConditional() throws IOException { public void testCreateResourceConditional() throws IOException {
String methodName = "testCreateResourceConditional"; String methodName = "testCreateResourceConditional";

View File

@ -23,7 +23,9 @@ package ca.uhn.fhir.rest.server.provider.dstu2;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -53,7 +55,10 @@ import ca.uhn.fhir.model.dstu2.valueset.SystemRestfulInteractionEnum;
import ca.uhn.fhir.model.dstu2.valueset.TypeRestfulInteractionEnum; import ca.uhn.fhir.model.dstu2.valueset.TypeRestfulInteractionEnum;
import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.DateTimeDt;
import ca.uhn.fhir.model.primitive.IdDt; 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.Metadata;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding; import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding;
import ca.uhn.fhir.rest.method.IParameter; import ca.uhn.fhir.rest.method.IParameter;
@ -66,6 +71,7 @@ import ca.uhn.fhir.rest.server.IServerConformanceProvider;
import ca.uhn.fhir.rest.server.ResourceBinding; import ca.uhn.fhir.rest.server.ResourceBinding;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 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 * Server FHIR Provider which serves the conformance statement for a RESTful server implementation
@ -83,6 +89,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
private volatile Conformance myConformance; private volatile Conformance myConformance;
private String myPublisher = "Not provided"; private String myPublisher = "Not provided";
private final RestfulServer myRestfulServer; private final RestfulServer myRestfulServer;
private IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName;
private HashMap<String, OperationMethodBinding> myOperationNameToBinding;
public ServerConformanceProvider(RestfulServer theRestfulServer) { public ServerConformanceProvider(RestfulServer theRestfulServer) {
myRestfulServer = theRestfulServer; myRestfulServer = theRestfulServer;
@ -97,6 +105,73 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
return myPublisher; return myPublisher;
} }
@Initialize
public void initializeOperations() {
Set<String> allNames = new HashSet<String>();
myOperationBindingToName = new IdentityHashMap<OperationMethodBinding, String>();
myOperationNameToBinding = new HashMap<String, OperationMethodBinding>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings();
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
List<BaseMethodBinding<?>> 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 @Override
@Metadata @Metadata
public Conformance getServerConformance(HttpServletRequest theRequest) { public Conformance getServerConformance(HttpServletRequest theRequest) {
@ -123,24 +198,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
Set<SystemRestfulInteractionEnum> systemOps = new HashSet<SystemRestfulInteractionEnum>(); Set<SystemRestfulInteractionEnum> systemOps = new HashSet<SystemRestfulInteractionEnum>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = new TreeMap<String, List<BaseMethodBinding<?>>>(); Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings();
for (ResourceBinding next : myRestfulServer.getResourceBindings()) {
String resourceName = next.getResourceName();
for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) {
if (resourceToMethods.containsKey(resourceName) == false) {
resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>());
}
resourceToMethods.get(resourceName).add(nextMethodBinding);
}
}
for (BaseMethodBinding<?> nextMethodBinding : myRestfulServer.getServerBindings()) {
String resourceName = "";
if (resourceToMethods.containsKey(resourceName) == false) {
resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>());
}
resourceToMethods.get(resourceName).add(nextMethodBinding);
}
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) { for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
if (nextEntry.getKey().isEmpty() == false) { if (nextEntry.getKey().isEmpty() == false) {
@ -202,30 +260,8 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding); handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding);
} else if (nextMethodBinding instanceof OperationMethodBinding) { } else if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
OperationDefinition op = new OperationDefinition(); String opName = myOperationBindingToName.get(methodBinding);
rest.addOperation().setName(methodBinding.getName()).getDefinition().setResource(op); rest.addOperation().setName(methodBinding.getName()).getDefinition().setReference("OperationDefinition/" + opName);
;
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());
}
}
} }
Collections.sort(resource.getInteraction(), new Comparator<RestResourceInteraction>() { Collections.sort(resource.getInteraction(), new Comparator<RestResourceInteraction>() {
@ -262,6 +298,27 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
return retVal; return retVal;
} }
private Map<String, List<BaseMethodBinding<?>>> collectMethodBindings() {
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = new TreeMap<String, List<BaseMethodBinding<?>>>();
for (ResourceBinding next : myRestfulServer.getResourceBindings()) {
String resourceName = next.getResourceName();
for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) {
if (resourceToMethods.containsKey(resourceName) == false) {
resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>());
}
resourceToMethods.get(resourceName).add(nextMethodBinding);
}
}
for (BaseMethodBinding<?> nextMethodBinding : myRestfulServer.getServerBindings()) {
String resourceName = "";
if (resourceToMethods.containsKey(resourceName) == false) {
resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>());
}
resourceToMethods.get(resourceName).add(nextMethodBinding);
}
return resourceToMethods;
}
private void checkBindingForSystemOps(Rest rest, Set<SystemRestfulInteractionEnum> systemOps, BaseMethodBinding<?> nextMethodBinding) { private void checkBindingForSystemOps(Rest rest, Set<SystemRestfulInteractionEnum> systemOps, BaseMethodBinding<?> nextMethodBinding) {
if (nextMethodBinding.getSystemOperationType() != null) { if (nextMethodBinding.getSystemOperationType() != null) {
String sysOpCode = nextMethodBinding.getSystemOperationType().getCode(); String sysOpCode = nextMethodBinding.getSystemOperationType().getCode();

View File

@ -1,7 +1,6 @@
package ca.uhn.fhir.rest.server; package ca.uhn.fhir.rest.server;
import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.*;
import static org.hamcrest.Matchers.startsWith;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNull; import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertThat; import static org.junit.Assert.assertThat;
@ -32,6 +31,9 @@ import org.junit.Test;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.api.IResource;
import ca.uhn.fhir.model.dstu2.resource.Bundle; 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.Parameters;
import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.dstu2.resource.Patient;
import ca.uhn.fhir.model.primitive.DateTimeDt; 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.Operation;
import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.annotation.OperationParam;
import ca.uhn.fhir.rest.annotation.Read; import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.client.IGenericClient;
import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.PortUtil;
/** /**
@ -312,6 +315,20 @@ public class OperationServerTest {
assertEquals("instance $everything", ourLastMethod); assertEquals("instance $everything", ourLastMethod);
assertEquals("Patient/123", ourLastId.toUnqualifiedVersionless().getValue()); 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<RestOperation> 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 @Test

View File

@ -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.Rest;
import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource; import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResource;
import ca.uhn.fhir.model.dstu2.resource.DiagnosticReport; 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.resource.Patient;
import ca.uhn.fhir.model.dstu2.valueset.SystemRestfulInteractionEnum; import ca.uhn.fhir.model.dstu2.valueset.SystemRestfulInteractionEnum;
import ca.uhn.fhir.model.dstu2.valueset.TypeRestfulInteractionEnum; import ca.uhn.fhir.model.dstu2.valueset.TypeRestfulInteractionEnum;
@ -86,6 +87,29 @@ public class ServerConformanceProviderDstu2Test {
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf); 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 @Test

View File

@ -20,13 +20,13 @@ package org.hl7.fhir.instance.conf;
* #L% * #L%
*/ */
import static org.apache.commons.lang3.StringUtils.*;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Comparator; import java.util.Comparator;
import java.util.Date; import java.util.Date;
import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; 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.RuntimeResourceDefinition;
import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.context.RuntimeSearchParam;
import ca.uhn.fhir.model.api.IResource; 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.Metadata;
import ca.uhn.fhir.rest.annotation.Read;
import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.BaseMethodBinding;
import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding; import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding;
import ca.uhn.fhir.rest.method.IParameter; 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.ResourceBinding;
import ca.uhn.fhir.rest.server.RestfulServer; import ca.uhn.fhir.rest.server.RestfulServer;
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; 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 * 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;
* </p> * </p>
*/ */
public class ServerConformanceProvider implements IServerConformanceProvider<Conformance> { public class ServerConformanceProvider implements IServerConformanceProvider<Conformance> {
private boolean myCache = true; private boolean myCache = true;
private volatile Conformance myConformance; private volatile Conformance myConformance;
private String myPublisher = "Not provided"; private String myPublisher = "Not provided";
private final RestfulServer myRestfulServer; private final RestfulServer myRestfulServer;
private IdentityHashMap<OperationMethodBinding, String> myOperationBindingToName;
private HashMap<String, OperationMethodBinding> myOperationNameToBinding;
public ServerConformanceProvider(RestfulServer theRestfulServer) { public ServerConformanceProvider(RestfulServer theRestfulServer) {
myRestfulServer = theRestfulServer; myRestfulServer = theRestfulServer;
@ -97,6 +103,73 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
return myPublisher; return myPublisher;
} }
@Initialize
public void initializeOperations() {
Set<String> allNames = new HashSet<String>();
myOperationBindingToName = new IdentityHashMap<OperationMethodBinding, String>();
myOperationNameToBinding = new HashMap<String, OperationMethodBinding>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings();
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
List<BaseMethodBinding<?>> 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 @Override
@Metadata @Metadata
public Conformance getServerConformance(HttpServletRequest theRequest) { public Conformance getServerConformance(HttpServletRequest theRequest) {
@ -123,24 +196,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
Set<SystemRestfulInteraction> systemOps = new HashSet<SystemRestfulInteraction>(); Set<SystemRestfulInteraction> systemOps = new HashSet<SystemRestfulInteraction>();
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = new TreeMap<String, List<BaseMethodBinding<?>>>(); Map<String, List<BaseMethodBinding<?>>> resourceToMethods = collectMethodBindings();
for (ResourceBinding next : myRestfulServer.getResourceBindings()) {
String resourceName = next.getResourceName();
for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) {
if (resourceToMethods.containsKey(resourceName) == false) {
resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>());
}
resourceToMethods.get(resourceName).add(nextMethodBinding);
}
}
for (BaseMethodBinding<?> nextMethodBinding : myRestfulServer.getServerBindings()) {
String resourceName = "";
if (resourceToMethods.containsKey(resourceName) == false) {
resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>());
}
resourceToMethods.get(resourceName).add(nextMethodBinding);
}
for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) { for (Entry<String, List<BaseMethodBinding<?>>> nextEntry : resourceToMethods.entrySet()) {
if (nextEntry.getKey().isEmpty() == false) { if (nextEntry.getKey().isEmpty() == false) {
@ -149,7 +205,7 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
String resourceName = nextEntry.getKey(); String resourceName = nextEntry.getKey();
RuntimeResourceDefinition def = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); RuntimeResourceDefinition def = myRestfulServer.getFhirContext().getResourceDefinition(resourceName);
resource.getTypeElement().setValue(def.getName()); resource.getTypeElement().setValue(def.getName());
resource.getProfile().setReference(def.getResourceProfile(myRestfulServer.getServerBaseForRequest(theRequest))); resource.getProfile().setReference((def.getResourceProfile(myRestfulServer.getServerBaseForRequest(theRequest))));
TreeSet<String> includes = new TreeSet<String>(); TreeSet<String> includes = new TreeSet<String>();
@ -162,8 +218,10 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
TypeRestfulInteraction resOp; TypeRestfulInteraction resOp;
try { try {
resOp = TypeRestfulInteraction.fromCode(resOpCode); resOp = TypeRestfulInteraction.fromCode(resOpCode);
assert resOp != null;
} catch (Exception e) { } catch (Exception e) {
resOp = null;
}
if (resOp == null) {
throw new InternalErrorException("Unknown type-restful-interaction: " + resOpCode); throw new InternalErrorException("Unknown type-restful-interaction: " + resOpCode);
} }
if (resourceOps.contains(resOp) == false) { if (resourceOps.contains(resOp) == false) {
@ -205,37 +263,15 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding); handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding);
} else if (nextMethodBinding instanceof OperationMethodBinding) { } else if (nextMethodBinding instanceof OperationMethodBinding) {
OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding;
OperationDefinition op = new OperationDefinition(); String opName = myOperationBindingToName.get(methodBinding);
rest.addOperation().setName(methodBinding.getName()).getDefinition().setResource(op); rest.addOperation().setName(methodBinding.getName()).getDefinition().setReference("OperationDefinition/" + opName);
;
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());
}
}
} }
Collections.sort(resource.getInteraction(), new Comparator<ResourceInteractionComponent>() { Collections.sort(resource.getInteraction(), new Comparator<ResourceInteractionComponent>() {
@Override @Override
public int compare(ResourceInteractionComponent theO1, ResourceInteractionComponent theO2) { public int compare(ResourceInteractionComponent theO1, ResourceInteractionComponent theO2) {
TypeRestfulInteraction o1 = theO1.getCode(); TypeRestfulInteraction o1 = theO1.getCodeElement().getValue();
TypeRestfulInteraction o2 = theO2.getCode(); TypeRestfulInteraction o2 = theO2.getCodeElement().getValue();
if (o1 == null && o2 == null) { if (o1 == null && o2 == null) {
return 0; return 0;
} }
@ -265,6 +301,27 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
return retVal; return retVal;
} }
private Map<String, List<BaseMethodBinding<?>>> collectMethodBindings() {
Map<String, List<BaseMethodBinding<?>>> resourceToMethods = new TreeMap<String, List<BaseMethodBinding<?>>>();
for (ResourceBinding next : myRestfulServer.getResourceBindings()) {
String resourceName = next.getResourceName();
for (BaseMethodBinding<?> nextMethodBinding : next.getMethodBindings()) {
if (resourceToMethods.containsKey(resourceName) == false) {
resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>());
}
resourceToMethods.get(resourceName).add(nextMethodBinding);
}
}
for (BaseMethodBinding<?> nextMethodBinding : myRestfulServer.getServerBindings()) {
String resourceName = "";
if (resourceToMethods.containsKey(resourceName) == false) {
resourceToMethods.put(resourceName, new ArrayList<BaseMethodBinding<?>>());
}
resourceToMethods.get(resourceName).add(nextMethodBinding);
}
return resourceToMethods;
}
private void checkBindingForSystemOps(ConformanceRestComponent rest, Set<SystemRestfulInteraction> systemOps, BaseMethodBinding<?> nextMethodBinding) { private void checkBindingForSystemOps(ConformanceRestComponent rest, Set<SystemRestfulInteraction> systemOps, BaseMethodBinding<?> nextMethodBinding) {
if (nextMethodBinding.getSystemOperationType() != null) { if (nextMethodBinding.getSystemOperationType() != null) {
String sysOpCode = nextMethodBinding.getSystemOperationType().getCode(); String sysOpCode = nextMethodBinding.getSystemOperationType().getCode();
@ -272,8 +329,10 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
SystemRestfulInteraction sysOp; SystemRestfulInteraction sysOp;
try { try {
sysOp = SystemRestfulInteraction.fromCode(sysOpCode); sysOp = SystemRestfulInteraction.fromCode(sysOpCode);
assert sysOp != null;
} catch (Exception e) { } catch (Exception e) {
sysOp=null;
}
if (sysOp == null) {
throw new InternalErrorException("Unknown system-restful-interaction: " + sysOpCode); throw new InternalErrorException("Unknown system-restful-interaction: " + sysOpCode);
} }
if (systemOps.contains(sysOp) == false) { if (systemOps.contains(sysOp) == false) {
@ -390,8 +449,14 @@ public class ServerConformanceProvider implements IServerConformanceProvider<Con
for (Class<? extends IResource> nextTarget : nextParameter.getDeclaredTypes()) { for (Class<? extends IResource> nextTarget : nextParameter.getDeclaredTypes()) {
RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget); RuntimeResourceDefinition targetDef = myRestfulServer.getFhirContext().getResourceDefinition(nextTarget);
if (targetDef != null) { if (targetDef != null) {
if (isNotBlank(targetDef.getName())) { org.hl7.fhir.instance.model.Enumerations.ResourceType code;
param.addTarget(targetDef.getName()); try {
code = org.hl7.fhir.instance.model.Enumerations.ResourceType.fromCode(targetDef.getName());
} catch (Exception e) {
code = null;
}
if (code != null) {
param.addTarget(code.toCode());
} }
} }
} }

View File

@ -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.SystemRestfulInteraction;
import org.hl7.fhir.instance.model.Conformance.TypeRestfulInteraction; import org.hl7.fhir.instance.model.Conformance.TypeRestfulInteraction;
import org.hl7.fhir.instance.model.DiagnosticReport; 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.Patient;
import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IBaseResource;
import org.junit.Test; import org.junit.Test;
@ -86,6 +87,29 @@ public class ServerConformanceProviderHl7OrgDstu2Test {
String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance);
ourLog.info(conf); 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 @Test

View File

@ -8,7 +8,6 @@ import java.io.InputStreamReader;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.Properties; import java.util.Properties;
import java.util.Set;
import java.util.TreeSet; import java.util.TreeSet;
import org.apache.commons.lang.WordUtils; import org.apache.commons.lang.WordUtils;
@ -49,6 +48,9 @@ public class TinderJpaRestServerMojo extends AbstractMojo {
@Parameter(required = false) @Parameter(required = false)
private List<String> baseResourceNames; private List<String> baseResourceNames;
@Parameter(required = false)
private List<String> excludeResourceNames;
@Parameter(required = true, defaultValue = "${project.build.directory}/..") @Parameter(required = true, defaultValue = "${project.build.directory}/..")
private String baseDir; 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); ourLog.info("Including the following resources: {}", baseResourceNames);
File directoryBase = new File(targetDirectory, packageBase.replace(".", File.separatorChar + "")); File directoryBase = new File(targetDirectory, packageBase.replace(".", File.separatorChar + ""));

View File

@ -19,6 +19,15 @@
actually point to any codes (e.g. Observation.interpretation). Thanks actually point to any codes (e.g. Observation.interpretation). Thanks
to GitHub user @steve1medix for reporting! to GitHub user @steve1medix for reporting!
</action> </action>
<action type="add">
Server now exports operations as separate resources instead of as contained resources
within Conformance
</action>
<action type="add">
Add new operation $get-resource-counts which will replace the resource
count extensions exported in the Conformance statement by the JPA
server.
</action>
</release> </release>
<release version="1.1" date="2015-07-13"> <release version="1.1" date="2015-07-13">
<action type="add"> <action type="add">