diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java index 330f02d5636..df3aace9a4a 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/FhirContext.java @@ -251,6 +251,8 @@ public class FhirContext { */ @SuppressWarnings("unchecked") public RuntimeResourceDefinition getResourceDefinition(String theResourceName) { + Validate.notBlank(theResourceName, "theResourceName must not be blank"); + String resourceName = theResourceName; /* 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 34a5f0a0514..702c257b55e 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 @@ -109,15 +109,14 @@ public class RestfulServer extends HttpServlet { this(new FhirContext()); } - public RestfulServer(FhirContext theCtx) { + public RestfulServer(FhirContext theCtx) { myFhirContext = theCtx; } /** * 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) { @@ -296,13 +295,12 @@ public class RestfulServer extends HttpServlet { } public BundleInclusionRule getBundleInclusionRule() { - return myBundleInclusionRule; - } + return myBundleInclusionRule; + } /** - * 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; @@ -316,8 +314,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() { return myFhirContext; @@ -348,8 +346,7 @@ 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 @@ -375,8 +372,7 @@ 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; @@ -396,11 +392,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 if one has been explicitly defined. + * 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. *

- * 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() { @@ -408,8 +410,7 @@ 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) */ @@ -422,8 +423,7 @@ 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; @@ -474,9 +474,8 @@ public class RestfulServer extends HttpServlet { String linkSelfBase = getServerAddressStrategy().determineServerBase(getServletContext(), theRequest.getServletRequest()); String linkSelf = linkSelfBase + theRequest.getCompleteUrl().substring(theRequest.getCompleteUrl().indexOf('?')); - - bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), linkSelf, prettyPrint, start, count, thePagingAction, - null, includes); + + bundleFactory.initializeBundleFromBundleProvider(this, resultList, responseEncoding, theRequest.getFhirServerBase(), linkSelf, prettyPrint, start, count, thePagingAction, null, includes); Bundle bundle = bundleFactory.getDstu1Bundle(); if (bundle != null) { @@ -499,7 +498,8 @@ 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); } } @@ -658,10 +658,8 @@ 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")); } @@ -740,14 +738,13 @@ public class RestfulServer extends HttpServlet { } new ExceptionHandlingInterceptor().handleException(requestDetails, e, theRequest, theResponse); - + } } /** - * 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 { @@ -770,7 +767,8 @@ public class RestfulServer extends HttpServlet { String resourceName = myFhirContext.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); @@ -808,14 +806,13 @@ public class RestfulServer extends HttpServlet { } /** - * 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 } @@ -847,12 +844,12 @@ public class RestfulServer extends HttpServlet { } /** - * 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 + * Should the server "pretty print" responses by default (requesting clients can always override this default by supplying an Accept header in the request, or a _pretty * parameter in the request URL. *

* The default is false *

+ * * @return Returns the default pretty print setting */ public boolean isDefaultPrettyPrint() { @@ -868,15 +865,9 @@ public class RestfulServer extends HttpServlet { myInterceptors.add(theInterceptor); } - public static boolean requestIsBrowser(HttpServletRequest theRequest) { - String userAgent = theRequest.getHeader("User-Agent"); - return userAgent != null && userAgent.contains("Mozilla"); - } - /** - * 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) @@ -887,31 +878,32 @@ 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) - */ - public void setBundleInclusionRule(BundleInclusionRule theBundleInclusionRule) { - myBundleInclusionRule = theBundleInclusionRule; - } + * Set how bundle factory should decide whether referenced resources should be included in bundles + * + * @param theBundleInclusionRule + * - 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 + * 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 + * + * @param theDefaultPrettyPrint + * 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"); @@ -919,8 +911,7 @@ 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 @@ -1016,8 +1007,7 @@ 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,17 +1015,15 @@ 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) { @@ -1045,24 +1033,22 @@ 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; @@ -1081,11 +1067,16 @@ public class RestfulServer extends HttpServlet { theResponse.getWriter().write(theException.getMessage()); } - private static boolean partIsOperation(String nextString) { + private static boolean partIsOperation(String nextString) { return nextString.length() > 0 && (nextString.charAt(0) == '_' || nextString.charAt(0) == '$'); } - public enum NarrativeModeEnum { + public static boolean requestIsBrowser(HttpServletRequest theRequest) { + String userAgent = theRequest.getHeader("User-Agent"); + return userAgent != null && userAgent.contains("Mozilla"); + } + + public enum NarrativeModeEnum { NORMAL, ONLY, SUPPRESS; public static NarrativeModeEnum valueOfCaseInsensitive(String theCode) { diff --git a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java index d90238a210d..a9a90fc5967 100644 --- a/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java +++ b/hapi-fhir-structures-dstu2/src/main/java/ca/uhn/fhir/rest/server/provider/dstu2/ServerConformanceProvider.java @@ -25,7 +25,10 @@ import java.util.Collections; import java.util.Comparator; import java.util.HashSet; import java.util.List; +import java.util.Map; +import java.util.Map.Entry; import java.util.Set; +import java.util.TreeMap; import java.util.TreeSet; import javax.servlet.http.HttpServletRequest; @@ -36,11 +39,11 @@ import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.model.api.IResource; import ca.uhn.fhir.model.dstu2.resource.Conformance; -import ca.uhn.fhir.model.dstu2.resource.OperationDefinition; 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.RestResourceInteraction; import ca.uhn.fhir.model.dstu2.resource.Conformance.RestResourceSearchParam; +import ca.uhn.fhir.model.dstu2.resource.OperationDefinition; import ca.uhn.fhir.model.dstu2.resource.OperationDefinition.Parameter; import ca.uhn.fhir.model.dstu2.valueset.ConformanceResourceStatusEnum; import ca.uhn.fhir.model.dstu2.valueset.OperationParameterUseEnum; @@ -51,7 +54,6 @@ import ca.uhn.fhir.model.dstu2.valueset.TypeRestfulInteractionEnum; import ca.uhn.fhir.model.primitive.DateTimeDt; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.rest.annotation.Metadata; -import ca.uhn.fhir.rest.annotation.OperationParam; import ca.uhn.fhir.rest.method.BaseMethodBinding; import ca.uhn.fhir.rest.method.DynamicSearchMethodBinding; import ca.uhn.fhir.rest.method.IParameter; @@ -118,118 +120,137 @@ public class ServerConformanceProvider implements IServerConformanceProvider systemOps = new HashSet(); - List bindings = new ArrayList(myRestfulServer.getResourceBindings()); - Collections.sort(bindings, new Comparator() { - @Override - public int compare(ResourceBinding theArg0, ResourceBinding theArg1) { - return theArg0.getResourceName().compareToIgnoreCase(theArg1.getResourceName()); - } - }); - - for (ResourceBinding next : bindings) { - - Set resourceOps = new HashSet(); - RestResource resource = rest.addResource(); - + Map>> resourceToMethods = new TreeMap>>(); + for (ResourceBinding next : myRestfulServer.getResourceBindings()) { String resourceName = next.getResourceName(); - RuntimeResourceDefinition def = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); - resource.getTypeElement().setValue(def.getName()); - resource.getProfile().setReference(new IdDt(def.getResourceProfile(myRestfulServer.getServerBaseForRequest(theRequest)))); - - TreeSet includes = new TreeSet(); - - // Map nameToSearchParam = new HashMap(); for (BaseMethodBinding nextMethodBinding : next.getMethodBindings()) { - if (nextMethodBinding.getResourceOperationType() != null) { - String resOpCode = nextMethodBinding.getResourceOperationType().getCode(); - if (resOpCode != null) { - TypeRestfulInteractionEnum resOp = TypeRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(resOpCode); - if (resOp == null) { - throw new InternalErrorException("Unknown type-restful-interaction: " + resOpCode); - } - if (resourceOps.contains(resOp) == false) { - resourceOps.add(resOp); - resource.addInteraction().setCode(resOp); - } - } + 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); + } - if (nextMethodBinding.getSystemOperationType() != null) { - String sysOpCode = nextMethodBinding.getSystemOperationType().getCode(); - if (sysOpCode != null) { - SystemRestfulInteractionEnum sysOp = SystemRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(sysOpCode); - if (sysOp == null) { - throw new InternalErrorException("Unknown system-restful-interaction: " + sysOpCode); - } - if (systemOps.contains(sysOp) == false) { - systemOps.add(sysOp); - rest.addInteraction().setCode(sysOp); - } - } - } + for (Entry>> nextEntry : resourceToMethods.entrySet()) { - if (nextMethodBinding instanceof SearchMethodBinding) { - handleSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding); - } else if (nextMethodBinding instanceof DynamicSearchMethodBinding) { - handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding); - } else if (nextMethodBinding instanceof OperationMethodBinding) { - OperationMethodBinding methodBinding = (OperationMethodBinding)nextMethodBinding; - OperationDefinition op = new OperationDefinition(); - rest.addOperation().setName(methodBinding.getName()).getDefinition().setResource(op);; - - 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()); + if (nextEntry.getKey().isEmpty() == false) { + Set resourceOps = new HashSet(); + RestResource resource = rest.addResource(); + String resourceName = nextEntry.getKey(); + RuntimeResourceDefinition def = myRestfulServer.getFhirContext().getResourceDefinition(resourceName); + resource.getTypeElement().setValue(def.getName()); + resource.getProfile().setReference(new IdDt(def.getResourceProfile(myRestfulServer.getServerBaseForRequest(theRequest)))); + + TreeSet includes = new TreeSet(); + + // Map nameToSearchParam = new HashMap(); + for (BaseMethodBinding nextMethodBinding : nextEntry.getValue()) { + if (nextMethodBinding.getResourceOperationType() != null) { + String resOpCode = nextMethodBinding.getResourceOperationType().getCode(); + if (resOpCode != null) { + TypeRestfulInteractionEnum resOp = TypeRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(resOpCode); + if (resOp == null) { + throw new InternalErrorException("Unknown type-restful-interaction: " + resOpCode); + } + if (resourceOps.contains(resOp) == false) { + resourceOps.add(resOp); + resource.addInteraction().setCode(resOp); } - param.setMin(nextParam.getMin()); - param.setMax(nextParam.getMax() == -1 ? "*" : Integer.toString(nextParam.getMax())); - param.setName(nextParam.getName()); } } + + checkBindingForSystemOps(rest, systemOps, nextMethodBinding); + + if (nextMethodBinding instanceof SearchMethodBinding) { + handleSearchMethodBinding(rest, resource, resourceName, def, includes, (SearchMethodBinding) nextMethodBinding); + } else if (nextMethodBinding instanceof DynamicSearchMethodBinding) { + handleDynamicSearchMethodBinding(resource, def, includes, (DynamicSearchMethodBinding) nextMethodBinding); + } else if (nextMethodBinding instanceof OperationMethodBinding) { + OperationMethodBinding methodBinding = (OperationMethodBinding) nextMethodBinding; + OperationDefinition op = new OperationDefinition(); + rest.addOperation().setName(methodBinding.getName()).getDefinition().setResource(op); + ; + + 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() { + @Override + public int compare(RestResourceInteraction theO1, RestResourceInteraction theO2) { + TypeRestfulInteractionEnum o1 = theO1.getCodeElement().getValueAsEnum(); + TypeRestfulInteractionEnum o2 = theO2.getCodeElement().getValueAsEnum(); + if (o1 == null && o2 == null) { + return 0; + } + if (o1 == null) { + return 1; + } + if (o2 == null) { + return -1; + } + return o1.ordinal() - o2.ordinal(); + } + }); + } - Collections.sort(resource.getInteraction(), new Comparator() { - @Override - public int compare(RestResourceInteraction theO1, RestResourceInteraction theO2) { - TypeRestfulInteractionEnum o1 = theO1.getCodeElement().getValueAsEnum(); - TypeRestfulInteractionEnum o2 = theO2.getCodeElement().getValueAsEnum(); - if (o1 == null && o2 == null) { - return 0; - } - if (o1 == null) { - return 1; - } - if (o2 == null) { - return -1; - } - return o1.ordinal() - o2.ordinal(); - } - }); - + for (String nextInclude : includes) { + resource.addSearchInclude(nextInclude); + } + } else { + for (BaseMethodBinding nextMethodBinding : nextEntry.getValue()) { + checkBindingForSystemOps(rest, systemOps, nextMethodBinding); + } } - - for (String nextInclude : includes) { - resource.addSearchInclude(nextInclude); - } - } myConformance = retVal; return retVal; } + private void checkBindingForSystemOps(Rest rest, Set systemOps, BaseMethodBinding nextMethodBinding) { + if (nextMethodBinding.getSystemOperationType() != null) { + String sysOpCode = nextMethodBinding.getSystemOperationType().getCode(); + if (sysOpCode != null) { + SystemRestfulInteractionEnum sysOp = SystemRestfulInteractionEnum.VALUESET_BINDER.fromCodeString(sysOpCode); + if (sysOp == null) { + throw new InternalErrorException("Unknown system-restful-interaction: " + sysOpCode); + } + if (systemOps.contains(sysOp) == false) { + systemOps.add(sysOp); + rest.addInteraction().setCode(sysOp); + } + } + } + } + private void handleDynamicSearchMethodBinding(RestResource resource, RuntimeResourceDefinition def, TreeSet includes, DynamicSearchMethodBinding searchMethodBinding) { includes.addAll(searchMethodBinding.getIncludes()); diff --git a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/TransactionWithBundleResourceParamTest.java b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/TransactionWithBundleResourceParamTest.java index dce0d9c8bcb..b8deec6db73 100644 --- a/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/TransactionWithBundleResourceParamTest.java +++ b/hapi-fhir-structures-dstu2/src/test/java/ca/uhn/fhir/rest/server/TransactionWithBundleResourceParamTest.java @@ -22,13 +22,19 @@ import org.junit.Test; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.model.dstu2.resource.Bundle; import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry; +import ca.uhn.fhir.model.dstu2.resource.Conformance; +import ca.uhn.fhir.model.dstu2.resource.Conformance.RestInteraction; +import ca.uhn.fhir.model.dstu2.resource.Conformance.RestOperation; import ca.uhn.fhir.model.dstu2.resource.OperationOutcome; import ca.uhn.fhir.model.dstu2.resource.Patient; import ca.uhn.fhir.model.dstu2.valueset.HTTPVerbEnum; +import ca.uhn.fhir.model.dstu2.valueset.SystemRestfulInteractionEnum; import ca.uhn.fhir.model.primitive.IdDt; import ca.uhn.fhir.model.primitive.InstantDt; import ca.uhn.fhir.rest.annotation.Transaction; import ca.uhn.fhir.rest.annotation.TransactionParam; +import ca.uhn.fhir.rest.client.IGenericClient; +import ca.uhn.fhir.rest.client.api.IRestfulClient; import ca.uhn.fhir.util.PortUtil; /** @@ -54,6 +60,21 @@ public class TransactionWithBundleResourceParamTest { ourReturnOperationOutcome = false; } + @Test + public void testConformance() { + ourCtx.getRestfulClientFactory().setSocketTimeout(500000); + IGenericClient client = ourCtx.newRestfulGenericClient("http://localhost:" + ourPort + "/"); + Conformance rest = client.fetchConformance().ofType(Conformance.class).execute(); + boolean supportsTransaction = false; + for (RestInteraction next : rest.getRest().get(0).getInteraction()) { + if (next.getCodeElement().getValueAsEnum() == SystemRestfulInteractionEnum.TRANSACTION) { + supportsTransaction = true; + } + } + + assertTrue(supportsTransaction); + } + @Test public void testTransactionWithXmlRequest() throws Exception { Bundle b = new Bundle(); diff --git a/src/changes/changes.xml b/src/changes/changes.xml index a4af61f97b6..ed5d47967ff 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -102,6 +102,9 @@ Server now supports complete Accept header content negotiation, including q values specifying order of preference. Previously the q value was ignored. + + Server in DSTU2 mode now indicates that whether it has support for Transaction operation or not. Thanks to Kevin Paschke for pointing out that this wasn't working! +