diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ProvidedResourceScanner.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ProvidedResourceScanner.java
deleted file mode 100644
index 45827c3402a..00000000000
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/context/ProvidedResourceScanner.java
+++ /dev/null
@@ -1,100 +0,0 @@
-package ca.uhn.fhir.context;
-
-/*
- * #%L
- * HAPI FHIR - Core Library
- * %%
- * Copyright (C) 2014 - 2019 University Health Network
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-
-import org.hl7.fhir.instance.model.api.IBaseResource;
-
-import ca.uhn.fhir.model.api.annotation.ProvidesResources;
-
-/**
- * Scans a class tagged with {@code ProvidesResources} and adds any resources listed to its FhirContext's resource
- * definition list. This makes the profile generator find the classes.
- *
- * @see ca.uhn.fhir.model.api.annotation.ProvidesResources
- */
-public class ProvidedResourceScanner {
- private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ProvidedResourceScanner.class);
- private FhirContext myContext;
-
- /**
- * Constructor
- *
- * @param theContext
- * - context whose resource definition list is to be updated by the scanner
- */
- public ProvidedResourceScanner(FhirContext theContext) {
- myContext = theContext;
- }
-
- /**
- * If {@code theProvider} is tagged with the {@code ProvidesResources} annotation, this method will add every
- * resource listed by the {@code resources} method.
- *
- * Notes:
- *
- *
- * if {@code theProvider} isn't annotated with {@code resources} nothing is done; it's expected that most
- * RestfulServers and ResourceProviders won't be annotated.
- * any object listed in {@code resources} that doesn't implement {@code IResource} will generate a warning in the
- * log.
- *
- *
- * @param theProvider
- * - Normally, either a {@link ca.uhn.fhir.rest.server.RestfulServer} or a
- * {@link ca.uhn.fhir.rest.server.IResourceProvider} that might be annotated with
- * {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
- */
- @SuppressWarnings("unchecked")
- public void scanForProvidedResources(Object theProvider) {
- ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
- if (annotation == null)
- return;
- for (Class> clazz : annotation.resources()) {
- if (IBaseResource.class.isAssignableFrom(clazz)) {
- myContext.getResourceDefinition((Class extends IBaseResource>) clazz);
- } else {
- ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
- }
- }
- }
-
- /**
- * Remove any metadata that was added by any {@code ProvidesResources} annotation
- * present in {@code theProvider}. This method is callled from {@code RestfulService}
- * when it is unregistering a Resource Provider.
- *
- * @param theProvider
- * - Normally a {@link ca.uhn.fhir.rest.server.IResourceProvider} that might
- * be annotated with {@link ca.uhn.fhir.model.api.annotation.ProvidesResources}
- */
- public void removeProvidedResources(Object theProvider) {
- ProvidesResources annotation = theProvider.getClass().getAnnotation(ProvidesResources.class);
- if (annotation == null)
- return;
- for (Class> clazz : annotation.resources()) {
- if (IBaseResource.class.isAssignableFrom(clazz)) {
- // TODO -- not currently used but should be finished for completeness
- } else {
- ourLog.warn(clazz.getSimpleName() + "is not assignable from IResource");
- }
- }
- }
-}
diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/ProvidesResources.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/ProvidesResources.java
deleted file mode 100644
index a1e625fd8a7..00000000000
--- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/model/api/annotation/ProvidesResources.java
+++ /dev/null
@@ -1,43 +0,0 @@
-package ca.uhn.fhir.model.api.annotation;
-
-/*
- * #%L
- * HAPI FHIR - Core Library
- * %%
- * Copyright (C) 2014 - 2019 University Health Network
- * %%
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- * #L%
- */
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * IResourceProvider and RestfulServer subclasses can use this annotation to designate which custom resources they can provide.
- * These resources will automatically be added to the resource list used for profile generation.
- *
- * Examples:
- * {@literal @}ProvidesResources(resource=CustomObservation.class)
- * class CustomObservationResourceProvider implements IResourceProvider{...}
- *
- * {@literal @}ProvidesResources(resource={CustomPatient.class,CustomObservation.class}){...}
- * class FhirServer extends RestfulServer
- * }
- *
- * Note that you needn't annotate both the IResourceProvider and the RestfulServer for a given resource; either one will suffice.
- */
-@Retention(RetentionPolicy.RUNTIME)
-public @interface ProvidesResources {
- Class>[] resources();
-}
diff --git a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistryTest.java b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistryTest.java
index c46bf68998f..87c04555963 100644
--- a/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistryTest.java
+++ b/hapi-fhir-jpaserver-searchparam/src/test/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistryTest.java
@@ -53,7 +53,7 @@ public class BaseSearchParamRegistryTest {
}
@Test
- public void testRefreshCacheIfNeccessary() {
+ public void testRefreshCacheIfNecessary() {
SearchParamRegistryR4 registry = new SearchParamRegistryR4();
when(mySearchParamProvider.search(any())).thenReturn(new SimpleBundleProvider());
@@ -70,6 +70,9 @@ public class BaseSearchParamRegistryTest {
assertTrue(registry.refreshCacheIfNecessary());
assertFalse(registry.refreshCacheIfNecessary());
+
+ registry.requestRefresh();
+ assertTrue(registry.refreshCacheIfNecessary());
}
@Test
diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
index b8a4ba6b618..3a523b22a12 100644
--- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
+++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/RestfulServer.java
@@ -22,7 +22,6 @@ package ca.uhn.fhir.rest.server;
import ca.uhn.fhir.context.ConfigurationException;
import ca.uhn.fhir.context.FhirContext;
-import ca.uhn.fhir.context.ProvidedResourceScanner;
import ca.uhn.fhir.context.RuntimeResourceDefinition;
import ca.uhn.fhir.context.api.AddProfileTagEnum;
import ca.uhn.fhir.context.api.BundleInclusionRule;
@@ -106,7 +105,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer myPlainProviders = new ArrayList<>();
private final List myResourceProviders = new ArrayList<>();
private IInterceptorService myInterceptorService;
@@ -667,9 +665,7 @@ public class RestfulServer extends HttpServlet implements IRestfulServer resourceProvider = getResourceProviders();
// 'true' tells registerProviders() that
// this call is part of initialization
@@ -1552,7 +1544,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer newResourceProviders = new ArrayList<>();
List newPlainProviders = new ArrayList<>();
- ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
if (theProviders != null) {
for (Object provider : theProviders) {
@@ -1565,7 +1556,6 @@ public class RestfulServer extends HttpServlet implements IRestfulServer providers) {
- ProvidedResourceScanner providedResourceScanner = new ProvidedResourceScanner(getFhirContext());
if (providers != null) {
for (Object provider : providers) {
removeResourceMethods(provider);
if (provider instanceof IResourceProvider) {
myResourceProviders.remove(provider);
- IResourceProvider rsrcProvider = (IResourceProvider) provider;
- Class extends IBaseResource> resourceType = rsrcProvider.getResourceType();
- providedResourceScanner.removeProvidedResources(rsrcProvider);
} else {
myPlainProviders.remove(provider);
}
diff --git a/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderR4Test.java b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderR4Test.java
new file mode 100644
index 00000000000..f9df334542b
--- /dev/null
+++ b/hapi-fhir-structures-r4/src/test/java/ca/uhn/fhir/rest/server/ServerConformanceProviderR4Test.java
@@ -0,0 +1,923 @@
+package ca.uhn.fhir.rest.server;
+
+import ca.uhn.fhir.context.FhirContext;
+import ca.uhn.fhir.model.api.Include;
+import ca.uhn.fhir.model.api.annotation.Description;
+import ca.uhn.fhir.rest.annotation.*;
+import ca.uhn.fhir.rest.api.MethodOutcome;
+import ca.uhn.fhir.rest.api.server.IBundleProvider;
+import ca.uhn.fhir.rest.api.server.RequestDetails;
+import ca.uhn.fhir.rest.param.*;
+import ca.uhn.fhir.rest.server.method.BaseMethodBinding;
+import ca.uhn.fhir.rest.server.method.IParameter;
+import ca.uhn.fhir.rest.server.method.SearchMethodBinding;
+import ca.uhn.fhir.rest.server.method.SearchParameter;
+import ca.uhn.fhir.rest.server.servlet.ServletRequestDetails;
+import ca.uhn.fhir.util.TestUtil;
+import ca.uhn.fhir.validation.FhirValidator;
+import ca.uhn.fhir.validation.ValidationResult;
+import com.google.common.collect.Lists;
+import org.hl7.fhir.instance.model.api.IBaseResource;
+import org.hl7.fhir.r4.hapi.rest.server.ServerCapabilityStatementProvider;
+import org.hl7.fhir.r4.model.*;
+import org.junit.AfterClass;
+import org.junit.Test;
+
+import javax.servlet.ServletConfig;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServletRequest;
+import java.util.*;
+
+import static org.hamcrest.Matchers.*;
+import static org.junit.Assert.*;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.when;
+
+public class ServerConformanceProviderR4Test {
+
+ private static FhirContext ourCtx;
+ private static FhirValidator ourValidator;
+ private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ServerConformanceProviderR4Test.class);
+
+ static {
+ ourCtx = FhirContext.forR4();
+ ourValidator = ourCtx.newValidator();
+ ourValidator.setValidateAgainstStandardSchema(true);
+ ourValidator.setValidateAgainstStandardSchematron(true);
+ }
+
+ private void validate(OperationDefinition theOpDef) {
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(theOpDef);
+ ourLog.info("Def: {}", conf);
+
+ ValidationResult result = ourValidator.validateWithResult(theOpDef);
+ String outcome = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(result.toOperationOutcome());
+ ourLog.info("Outcome: {}", outcome);
+
+ assertTrue(outcome, result.isSuccessful());
+ }
+
+ private HttpServletRequest createHttpServletRequest() {
+ HttpServletRequest req = mock(HttpServletRequest.class);
+ when(req.getRequestURI()).thenReturn("/FhirStorm/fhir/Patient/_search");
+ when(req.getServletPath()).thenReturn("/fhir");
+ when(req.getRequestURL()).thenReturn(new StringBuffer().append("http://fhirstorm.dyndns.org:8080/FhirStorm/fhir/Patient/_search"));
+ when(req.getContextPath()).thenReturn("/FhirStorm");
+ return req;
+ }
+ private ServletConfig createServletConfig() {
+ ServletConfig sc = mock(ServletConfig.class);
+ when(sc.getServletContext()).thenReturn(null);
+ return sc;
+ }
+
+ private CapabilityStatement.CapabilityStatementRestResourceComponent findRestResource(CapabilityStatement capabilityStatement, String wantResource) throws Exception {
+ CapabilityStatement.CapabilityStatementRestResourceComponent resource = null;
+ for (CapabilityStatement.CapabilityStatementRestResourceComponent next : capabilityStatement.getRest().get(0).getResource()) {
+ if (next.getType().equals(wantResource)) {
+ resource = next;
+ }
+ }
+ if (resource == null) {
+ throw new Exception("Could not find resource: " + wantResource);
+ }
+ return resource;
+ }
+
+ @Test
+ public void testConditionalOperations() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new ConditionalProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ CapabilityStatement.CapabilityStatementRestResourceComponent res = capabilityStatement.getRest().get(0).getResource().get(1);
+ assertEquals("Patient", res.getType());
+
+ assertTrue(res.getConditionalCreate());
+ assertEquals(CapabilityStatement.ConditionalDeleteStatus.MULTIPLE, res.getConditionalDeleteElement().getValue());
+ assertTrue(res.getConditionalUpdate());
+ }
+
+ @Test
+ public void testExtendedOperationReturningBundle() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ assertEquals(1, capabilityStatement.getRest().get(0).getOperation().size());
+ assertEquals("everything", capabilityStatement.getRest().get(0).getOperation().get(0).getName());
+ assertEquals("OperationDefinition/Patient-i-everything", capabilityStatement.getRest().get(0).getOperation().get(0).getDefinition());
+ }
+
+ @Test
+ public void testExtendedOperationReturningBundleOperation() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new ProviderWithExtendedOperationReturningBundle());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
+ };
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-everything"), createRequestDetails(rs));
+ validate(opDef);
+
+ assertEquals("everything", opDef.getCode());
+ assertEquals(false, opDef.getAffectsState());
+ }
+
+ @Test
+ public void testInstanceHistorySupported() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new InstanceHistoryProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(capabilityStatement);
+ assertThat(conf, containsString("
"));
+ }
+
+
+ @Test
+ public void testMultiOptionalDocumentation() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new MultiOptionalProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ boolean found = false;
+ Collection resourceBindings = rs.getResourceBindings();
+ for (ResourceBinding resourceBinding : resourceBindings) {
+ if (resourceBinding.getResourceName().equals("Patient")) {
+ List> methodBindings = resourceBinding.getMethodBindings();
+ SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
+ SearchParameter param = (SearchParameter) binding.getParameters().iterator().next();
+ assertEquals("The patient's identifier", param.getDescription());
+ found = true;
+ }
+ }
+
+ assertTrue(found);
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ assertThat(conf, containsString(" "));
+ assertThat(conf, containsString(" "));
+ assertThat(conf, containsString(""));
+ }
+
+ @Test
+ public void testNonConditionalOperations() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new NonConditionalProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ CapabilityStatement.CapabilityStatementRestResourceComponent res = capabilityStatement.getRest().get(0).getResource().get(1);
+ assertEquals("Patient", res.getType());
+
+ assertFalse(res.getConditionalCreate());
+ assertEquals(null, res.getConditionalDelete());
+ assertFalse(res.getConditionalUpdate());
+ }
+
+ /** See #379 */
+ @Test
+ public void testOperationAcrossMultipleTypes() throws Exception {
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new MultiTypePatientProvider(), new MultiTypeEncounterProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ assertEquals(4, capabilityStatement.getRest().get(0).getOperation().size());
+ List operationNames = toOperationNames(capabilityStatement.getRest().get(0).getOperation());
+ assertThat(operationNames, containsInAnyOrder("someOp", "validate", "someOp", "validate"));
+
+ List operationIdParts = toOperationIdParts(capabilityStatement.getRest().get(0).getOperation());
+
+ {
+ OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-someOp"), createRequestDetails(rs));
+ validate(opDef);
+
+ assertEquals("someOp", opDef.getCode());
+ assertEquals(true, opDef.getInstance());
+ assertEquals(false, opDef.getSystem());
+ assertEquals(2, opDef.getParameter().size());
+ assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
+ assertEquals("string", opDef.getParameter().get(0).getType());
+ assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
+ assertEquals("Patient", opDef.getParameter().get(1).getType());
+ }
+ {
+ OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Encounter-i-someOp"), createRequestDetails(rs));
+ validate(opDef);
+
+ assertEquals("someOp", opDef.getCode());
+ assertEquals(true, opDef.getInstance());
+ assertEquals(false, opDef.getSystem());
+ assertEquals(2, opDef.getParameter().size());
+ assertEquals("someOpParam1", opDef.getParameter().get(0).getName());
+ assertEquals("string", opDef.getParameter().get(0).getType());
+ assertEquals("someOpParam2", opDef.getParameter().get(1).getName());
+ assertEquals("Encounter", opDef.getParameter().get(1).getType());
+ }
+ {
+ OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/Patient-i-validate"), createRequestDetails(rs));
+ validate(opDef);
+
+ assertEquals("validate", opDef.getCode());
+ assertEquals(true, opDef.getInstance());
+ assertEquals(false, opDef.getSystem());
+ assertEquals(1, opDef.getParameter().size());
+ assertEquals("resource", opDef.getParameter().get(0).getName());
+ assertEquals("Patient", opDef.getParameter().get(0).getType());
+ }
+ }
+
+ @Test
+ public void testOperationDocumentation() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setServerName("MY NAME");
+ rs.setServerVersion("MY VERSION");
+ rs.setProviders(new SearchProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info("AAAAAA" + conf);
+
+ assertThat(conf, containsString(" "));
+ assertThat(conf, containsString(""));
+ assertEquals("MY NAME", capabilityStatement.getSoftware().getName());
+ assertEquals("MY VERSION", capabilityStatement.getSoftware().getVersion());
+
+ }
+
+ @Test
+ public void testOperationOnNoTypes() throws Exception {
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new PlainProviderWithExtendedOperationOnNoType());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs) {
+ @Override
+ public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
+ return super.getServerConformance(theRequest, theRequestDetails);
+ }
+ };
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement sconf = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+ OperationDefinition opDef = sc.readOperationDefinition(new IdType("OperationDefinition/-is-plain"), createRequestDetails(rs));
+ validate(opDef);
+
+ assertEquals("plain", opDef.getCode());
+ assertEquals(3, opDef.getParameter().size());
+ assertEquals("start", opDef.getParameter().get(0).getName());
+ assertEquals("in", opDef.getParameter().get(0).getUse().toCode());
+ assertEquals("0", opDef.getParameter().get(0).getMinElement().getValueAsString());
+ assertEquals("date", opDef.getParameter().get(0).getTypeElement().getValueAsString());
+
+ assertEquals("out1", opDef.getParameter().get(2).getName());
+ assertEquals("out", opDef.getParameter().get(2).getUse().toCode());
+ assertEquals("1", opDef.getParameter().get(2).getMinElement().getValueAsString());
+ assertEquals("2", opDef.getParameter().get(2).getMaxElement().getValueAsString());
+ assertEquals("string", opDef.getParameter().get(2).getTypeElement().getValueAsString());
+ }
+
+ @Test
+ public void testProviderForSmart() throws ServletException {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.createConfiguration();
+ rs.setProviders(new ProviderWithRequiredAndOptional());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider() {
+ @Override
+ public CapabilityStatement getServerConformance(HttpServletRequest theRequest, RequestDetails theRequestDetails) {
+ CapabilityStatement capabilityStatement = super.getServerConformance(theRequest, theRequestDetails);
+ Extension extension = new Extension();
+ Extension extensionDtToken = new Extension();
+ Extension extensionDtAuthorize = new Extension();
+ CapabilityStatement.CapabilityStatementRestComponent rest = capabilityStatement.getRestFirstRep();
+ CapabilityStatement.CapabilityStatementRestSecurityComponent restSecurity = rest.getSecurity();
+
+ extension.setUrl("http://fhir-registry.smarthealthit.org/StructureDefinition/oauth-uris");
+ extensionDtToken.setUrl("token");
+ extensionDtToken.setValue(new UriType("https://SERVERNAME/token"));
+ extensionDtAuthorize.setUrl("authorize");
+ extensionDtAuthorize.setValue(new UriType("https://SERVERNAME/authorize"));
+ extension.addExtension(extensionDtToken);
+ extension.addExtension(extensionDtAuthorize);
+ restSecurity.addExtension(extension);
+
+ return capabilityStatement;
+ }
+ };
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ CapabilityStatement parsed = ourCtx.newJsonParser().parseResource(CapabilityStatement.class, conf);
+ }
+
+ @Test
+ public void testProviderWithRequiredAndOptional() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new ProviderWithRequiredAndOptional());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ CapabilityStatement.CapabilityStatementRestComponent rest = capabilityStatement.getRestFirstRep();
+ CapabilityStatement.CapabilityStatementRestResourceComponent res = rest.getResourceFirstRep();
+ assertEquals("DiagnosticReport", res.getType());
+
+ assertEquals(DiagnosticReport.SP_SUBJECT, res.getSearchParam().get(0).getName());
+
+ assertEquals(DiagnosticReport.SP_CODE, res.getSearchParam().get(1).getName());
+
+ assertEquals(DiagnosticReport.SP_DATE, res.getSearchParam().get(2).getName());
+
+ assertEquals(1, res.getSearchInclude().size());
+ }
+
+ @Test
+ public void testReadAndVReadSupported() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new VreadProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(capabilityStatement);
+ assertThat(conf, containsString("
"));
+ assertThat(conf, containsString("
"));
+ }
+
+ @Test
+ public void testReadSupported() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new ReadProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(capabilityStatement);
+ assertThat(conf, not(containsString("
")));
+ assertThat(conf, containsString("
"));
+ }
+
+ @Test
+ public void testSearchParameterDocumentation() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new SearchProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ boolean found = false;
+ Collection resourceBindings = rs.getResourceBindings();
+ for (ResourceBinding resourceBinding : resourceBindings) {
+ if (resourceBinding.getResourceName().equals("Patient")) {
+ List> methodBindings = resourceBinding.getMethodBindings();
+ SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
+ for (IParameter next : binding.getParameters()) {
+ SearchParameter param = (SearchParameter) next;
+ if (param.getDescription().contains("The patient's identifier (MRN or other card number")) {
+ found = true;
+ }
+ }
+ found = true;
+ }
+ }
+ assertTrue(found);
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ assertThat(conf, containsString(" "));
+ assertThat(conf, containsString(""));
+
+ }
+
+ /**
+ * See #286
+ */
+ @Test
+ public void testSearchReferenceParameterDocumentation() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new PatientResourceProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ boolean found = false;
+ Collection resourceBindings = rs.getResourceBindings();
+ for (ResourceBinding resourceBinding : resourceBindings) {
+ if (resourceBinding.getResourceName().equals("Patient")) {
+ List> methodBindings = resourceBinding.getMethodBindings();
+ SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
+ SearchParameter param = (SearchParameter) binding.getParameters().get(25);
+ assertEquals("The organization at which this person is a patient", param.getDescription());
+ found = true;
+ }
+ }
+ assertTrue(found);
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ }
+
+
+ /**
+ * See #286
+ */
+ @Test
+ public void testSearchReferenceParameterWithWhitelistDocumentation() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new SearchProviderWithWhitelist());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ boolean found = false;
+ Collection resourceBindings = rs.getResourceBindings();
+ for (ResourceBinding resourceBinding : resourceBindings) {
+ if (resourceBinding.getResourceName().equals("Patient")) {
+ List> methodBindings = resourceBinding.getMethodBindings();
+ SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
+ SearchParameter param = (SearchParameter) binding.getParameters().get(0);
+ assertEquals("The organization at which this person is a patient", param.getDescription());
+ found = true;
+ }
+ }
+ assertTrue(found);
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ CapabilityStatement.CapabilityStatementRestResourceComponent resource = findRestResource(capabilityStatement, "Patient");
+
+ }
+
+ @Test
+ public void testSearchReferenceParameterWithExplicitChainsDocumentation() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new SearchProviderWithExplicitChains());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ boolean found = false;
+ Collection resourceBindings = rs.getResourceBindings();
+ for (ResourceBinding resourceBinding : resourceBindings) {
+ if (resourceBinding.getResourceName().equals("Patient")) {
+ List> methodBindings = resourceBinding.getMethodBindings();
+ SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0);
+ SearchParameter param = (SearchParameter) binding.getParameters().get(0);
+ assertEquals("The organization at which this person is a patient", param.getDescription());
+ found = true;
+ }
+ }
+ assertTrue(found);
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ CapabilityStatement.CapabilityStatementRestResourceComponent resource = findRestResource(capabilityStatement, "Patient");
+
+ assertEquals(3, resource.getSearchParam().size());
+ CapabilityStatement.CapabilityStatementRestResourceSearchParamComponent param = resource.getSearchParam().get(0);
+ assertEquals("organization", param.getName());
+ }
+
+ @Test
+ public void testSystemHistorySupported() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new SystemHistoryProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(capabilityStatement);
+ assertThat(conf, containsString("
"));
+ }
+
+ @Test
+ public void testTypeHistorySupported() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new TypeHistoryProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement);
+ ourLog.info(conf);
+
+ conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(capabilityStatement);
+ assertThat(conf, containsString("
"));
+ }
+
+ @Test
+ public void testValidateGeneratedStatement() throws Exception {
+
+ RestfulServer rs = new RestfulServer(ourCtx);
+ rs.setProviders(new MultiOptionalProvider());
+
+ ServerCapabilityStatementProvider sc = new ServerCapabilityStatementProvider(rs);
+ rs.setServerConformanceProvider(sc);
+
+ rs.init(createServletConfig());
+
+ CapabilityStatement capabilityStatement = sc.getServerConformance(createHttpServletRequest(), createRequestDetails(rs));
+ ourLog.info(ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(capabilityStatement));
+
+ ValidationResult result = ourCtx.newValidator().validateWithResult(capabilityStatement);
+ assertTrue(result.getMessages().toString(), result.isSuccessful());
+ }
+
+ private List toOperationIdParts(List theOperation) {
+ ArrayList retVal = Lists.newArrayList();
+ for (CapabilityStatement.CapabilityStatementRestResourceOperationComponent next : theOperation) {
+ retVal.add(next.getDefinitionElement().getValue());
+ }
+ return retVal;
+ }
+
+ private List toOperationNames(List theOperation) {
+ ArrayList retVal = Lists.newArrayList();
+ for (CapabilityStatement.CapabilityStatementRestResourceOperationComponent next : theOperation) {
+ retVal.add(next.getName());
+ }
+ return retVal;
+ }
+
+ private Set toStrings(List extends CodeType> theType) {
+ HashSet retVal = new HashSet();
+ for (CodeType next : theType) {
+ retVal.add(next.getValueAsString());
+ }
+ return retVal;
+ }
+
+ @AfterClass
+ public static void afterClassClearContext() {
+ TestUtil.clearAllStaticFieldsForUnitTest();
+ }
+
+ public static class ConditionalProvider implements IResourceProvider {
+
+ @Create
+ public MethodOutcome create(@ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) {
+ return null;
+ }
+
+ @Delete
+ public MethodOutcome delete(@IdParam IdType theId, @ConditionalUrlParam(supportsMultiple = true) String theConditionalUrl) {
+ return null;
+ }
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Patient.class;
+ }
+
+ @Update
+ public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) {
+ return null;
+ }
+
+ }
+
+ public static class InstanceHistoryProvider implements IResourceProvider {
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Patient.class;
+ }
+
+ @History
+ public List history(@IdParam IdType theId) {
+ return null;
+ }
+
+ }
+
+ public static class MultiOptionalProvider {
+
+ @Search(type = Patient.class)
+ public Patient findPatient(@Description(shortDefinition = "The patient's identifier") @OptionalParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier, @Description(shortDefinition = "The patient's name") @OptionalParam(name = Patient.SP_NAME) StringParam theName) {
+ return null;
+ }
+
+ }
+
+ public static class MultiTypeEncounterProvider implements IResourceProvider {
+
+ @Operation(name = "someOp")
+ public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId,
+ @OperationParam(name = "someOpParam1") DateParam theStart, @OperationParam(name = "someOpParam2") Encounter theEnd) {
+ return null;
+ }
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Encounter.class;
+ }
+
+ @Validate
+ public IBundleProvider validate(HttpServletRequest theServletRequest, @IdParam IdType theId, @ResourceParam Encounter thePatient) {
+ return null;
+ }
+
+ }
+
+ public static class MultiTypePatientProvider implements IResourceProvider {
+
+ @Operation(name = "someOp")
+ public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId,
+ @OperationParam(name = "someOpParam1") DateParam theStart, @OperationParam(name = "someOpParam2") Patient theEnd) {
+ return null;
+ }
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Patient.class;
+ }
+
+ @Validate
+ public IBundleProvider validate(HttpServletRequest theServletRequest, @IdParam IdType theId, @ResourceParam Patient thePatient) {
+ return null;
+ }
+
+ }
+
+ public static class NonConditionalProvider implements IResourceProvider {
+
+ @Create
+ public MethodOutcome create(@ResourceParam Patient thePatient) {
+ return null;
+ }
+
+ @Delete
+ public MethodOutcome delete(@IdParam IdType theId) {
+ return null;
+ }
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Patient.class;
+ }
+
+ @Update
+ public MethodOutcome update(@IdParam IdType theId, @ResourceParam Patient thePatient) {
+ return null;
+ }
+
+ }
+
+ public static class PlainProviderWithExtendedOperationOnNoType {
+
+ @Operation(name = "plain", idempotent = true, returnParameters = { @OperationParam(min = 1, max = 2, name = "out1", type = StringType.class) })
+ public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId, @OperationParam(name = "start") DateType theStart, @OperationParam(name = "end") DateType theEnd) {
+ return null;
+ }
+
+ }
+
+ public static class ProviderWithExtendedOperationReturningBundle implements IResourceProvider {
+
+ @Operation(name = "everything", idempotent = true)
+ public IBundleProvider everything(HttpServletRequest theServletRequest, @IdParam IdType theId, @OperationParam(name = "start") DateType theStart, @OperationParam(name = "end") DateType theEnd) {
+ return null;
+ }
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Patient.class;
+ }
+
+ }
+
+ public static class ProviderWithRequiredAndOptional {
+
+ @Description(shortDefinition = "This is a search for stuff!")
+ @Search
+ public List findDiagnosticReportsByPatient(@RequiredParam(name = DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) TokenParam thePatientId, @OptionalParam(name = DiagnosticReport.SP_CODE) TokenOrListParam theNames,
+ @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange, @IncludeParam(allow = { "DiagnosticReport.result" }) Set theIncludes) throws Exception {
+ return null;
+ }
+
+ }
+
+ public static class ReadProvider {
+
+ @Search(type = Patient.class)
+ public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
+ return null;
+ }
+
+ @Read(version = false)
+ public Patient readPatient(@IdParam IdType theId) {
+ return null;
+ }
+
+ }
+
+ public static class SearchProvider {
+
+ @Search(type = Patient.class)
+ public Patient findPatient1(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
+ return null;
+ }
+
+ @Search(type = Patient.class)
+ public Patient findPatient2(@Description(shortDefinition = "All patients linked to the given patient") @OptionalParam(name = "link", targetTypes = { Patient.class }) ReferenceAndListParam theLink) {
+ return null;
+ }
+
+ }
+
+ public static class SearchProviderWithWhitelist {
+
+ @Search(type = Patient.class)
+ public Patient findPatient1(
+ @Description(shortDefinition = "The organization at which this person is a patient")
+ @RequiredParam(name = Patient.SP_ORGANIZATION, chainWhitelist= {"foo", "bar"})
+ ReferenceAndListParam theIdentifier) {
+ return null;
+ }
+
+ }
+
+ public static class SearchProviderWithExplicitChains {
+
+ @Search(type = Patient.class)
+ public Patient findPatient1(
+ @Description(shortDefinition = "The organization at which this person is a patient")
+ @RequiredParam(name = "organization.foo") ReferenceAndListParam theFoo,
+ @RequiredParam(name = "organization.bar") ReferenceAndListParam theBar,
+ @RequiredParam(name = "organization.baz.bob") ReferenceAndListParam theBazbob) {
+ return null;
+ }
+
+ }
+
+ public static class SystemHistoryProvider {
+
+ @History
+ public List history() {
+ return null;
+ }
+
+ }
+
+ public static class TypeHistoryProvider implements IResourceProvider {
+
+ @Override
+ public Class extends IBaseResource> getResourceType() {
+ return Patient.class;
+ }
+
+ @History
+ public List history() {
+ return null;
+ }
+
+ }
+
+ /**
+ * Created by dsotnikov on 2/25/2014.
+ */
+ public static class VreadProvider {
+
+ @Search(type = Patient.class)
+ public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) TokenParam theIdentifier) {
+ return null;
+ }
+
+ @Read(version = true)
+ public Patient readPatient(@IdParam IdType theId) {
+ return null;
+ }
+
+ }
+
+ private RequestDetails createRequestDetails(RestfulServer theServer) {
+ ServletRequestDetails retVal = new ServletRequestDetails(null);
+ retVal.setServer(theServer);
+ return retVal;
+ }
+
+
+}
diff --git a/src/changes/changes.xml b/src/changes/changes.xml
index 255304fbcc8..6ef82cb5aae 100644
--- a/src/changes/changes.xml
+++ b/src/changes/changes.xml
@@ -458,6 +458,10 @@
Header and automatically place it in
Resource.meta.source]]>
+
+ The @ProvidesResources annotation has been removed from HAPI FHIR, as it was not documented
+ and did not do anything useful. Please get in touch if this causes any issues.
+