diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java index b53951aa047..2154d68a1b9 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseMethodBinding.java @@ -85,10 +85,15 @@ import ca.uhn.fhir.util.ReflectionUtil; public abstract class BaseMethodBinding implements IClientResponseHandler { private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseMethodBinding.class); + /** + * @see BaseMethodBinding#loadRequestContents(RequestDetails) + */ + private static volatile IRequestReader ourRequestReader; private FhirContext myContext; private Method myMethod; private List myParameters; private Object myProvider; + private boolean mySupportsConditional; public BaseMethodBinding(Method theMethod, FhirContext theContext, Object theProvider) { assert theMethod != null; @@ -98,6 +103,14 @@ public abstract class BaseMethodBinding implements IClientResponseHandler myContext = theContext; myProvider = theProvider; myParameters = MethodUtil.getResourceParameters(theContext, theMethod, theProvider, getResourceOperationType()); + + for (IParameter next : myParameters) { + if (next instanceof ConditionalParamBinder) { + mySupportsConditional = true; + break; + } + } + } protected IParser createAppropriateParserForParsingResponse(String theResponseMimeType, Reader theResponseReader, int theResponseStatusCode) { @@ -223,7 +236,8 @@ public abstract class BaseMethodBinding implements IClientResponseHandler public abstract RestfulOperationTypeEnum getResourceOperationType(); /** - * Returns the value of {@link #getResourceOperationType()} or {@link #getSystemOperationType()} or {@link #getOtherOperationType()} + * Returns the value of {@link #getResourceOperationType()} or {@link #getSystemOperationType()} or + * {@link #getOtherOperationType()} */ public String getResourceOrSystemOperationType() { Enum retVal = getResourceOperationType(); @@ -264,11 +278,20 @@ public abstract class BaseMethodBinding implements IClientResponseHandler } } + /** + * Does this method have a parameter annotated with {@link ConditionalParamBinder}. Note that many operations don't + * actually support this paramter, so this will only return true occasionally. + */ + public boolean isSupportsConditional() { + return mySupportsConditional; + } + protected byte[] loadRequestContents(RequestDetails theRequest) throws IOException { /* - * This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on servlet-api in clients since there is no point. So we dynamically load a class - * that does the servlet processing in servers. Down the road it may make sense to just split the method binding classes into server and client versions, but this isn't actually a huge deal I - * don't think. + * This is weird, but this class is used both in clients and in servers, and we want to avoid needing to depend on + * servlet-api in clients since there is no point. So we dynamically load a class that does the servlet processing + * in servers. Down the road it may make sense to just split the method binding classes into server and client + * versions, but this isn't actually a huge deal I don't think. */ IRequestReader reader = ourRequestReader; if (reader == null) { @@ -385,16 +408,14 @@ public abstract class BaseMethodBinding implements IClientResponseHandler if (theProvider instanceof IResourceProvider) { returnTypeFromRp = ((IResourceProvider) theProvider).getResourceType(); if (!verifyIsValidResourceReturnType(returnTypeFromRp)) { - throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned " - + toLogString(returnTypeFromRp) + " - Must return a resource type"); + throw new ConfigurationException("getResourceType() from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returned " + toLogString(returnTypeFromRp) + " - Must return a resource type"); } } Class returnTypeFromMethod = theMethod.getReturnType(); if (getTags != null) { if (!TagList.class.equals(returnTypeFromMethod)) { - throw new ConfigurationException("Method '" + theMethod.getName() + "' from type " + theMethod.getDeclaringClass().getCanonicalName() + " is annotated with @" - + GetTags.class.getSimpleName() + " but does not return type " + TagList.class.getName()); + throw new ConfigurationException("Method '" + theMethod.getName() + "' from type " + theMethod.getDeclaringClass().getCanonicalName() + " is annotated with @" + GetTags.class.getSimpleName() + " but does not return type " + TagList.class.getName()); } } else if (MethodOutcome.class.equals(returnTypeFromMethod)) { // returns a method outcome @@ -407,15 +428,13 @@ public abstract class BaseMethodBinding implements IClientResponseHandler } else if (Collection.class.isAssignableFrom(returnTypeFromMethod)) { returnTypeFromMethod = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod); if (!verifyIsValidResourceReturnType(returnTypeFromMethod) && !IResource.class.equals(returnTypeFromMethod)) { - throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() - + " returns a collection with generic type " + toLogString(returnTypeFromMethod) + throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns a collection with generic type " + toLogString(returnTypeFromMethod) + " - Must return a resource type or a collection (List, Set) with a resource type parameter (e.g. List or List )"); } } else { if (!IResource.class.equals(returnTypeFromMethod) && !verifyIsValidResourceReturnType(returnTypeFromMethod)) { - throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() - + " returns " + toLogString(returnTypeFromMethod) + " - Must return a resource type (eg Patient, " + Bundle.class.getSimpleName() + ", " + IBundleProvider.class.getSimpleName() - + ", etc., see the documentation for more details)"); + throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromMethod) + " - Must return a resource type (eg Patient, " + + Bundle.class.getSimpleName() + ", " + IBundleProvider.class.getSimpleName() + ", etc., see the documentation for more details)"); } } @@ -445,13 +464,12 @@ public abstract class BaseMethodBinding implements IClientResponseHandler if (returnTypeFromRp != null) { if (returnTypeFromAnnotation != null && returnTypeFromAnnotation != IResource.class) { if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) { - throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type " - + returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract"); + throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " returns type " + returnTypeFromMethod.getCanonicalName() + " - Must return " + returnTypeFromRp.getCanonicalName() + + " (or a subclass of it) per IResourceProvider contract"); } if (!returnTypeFromRp.isAssignableFrom(returnTypeFromAnnotation)) { - throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type " - + returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return " + returnTypeFromRp.getCanonicalName() - + " (or a subclass of it) per IResourceProvider contract"); + throw new ConfigurationException("Method '" + theMethod.getName() + "' in type " + theMethod.getDeclaringClass().getCanonicalName() + " claims to return type " + returnTypeFromAnnotation.getCanonicalName() + " per method annotation - Must return " + + returnTypeFromRp.getCanonicalName() + " (or a subclass of it) per IResourceProvider contract"); } returnType = returnTypeFromAnnotation; } else { @@ -460,8 +478,8 @@ public abstract class BaseMethodBinding implements IClientResponseHandler } else { if (returnTypeFromAnnotation != IResource.class) { if (!verifyIsValidResourceReturnType(returnTypeFromAnnotation)) { - throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() - + " returns " + toLogString(returnTypeFromAnnotation) + " according to annotation - Must return a resource type"); + throw new ConfigurationException("Method '" + theMethod.getName() + "' from " + IResourceProvider.class.getSimpleName() + " type " + theMethod.getDeclaringClass().getCanonicalName() + " returns " + toLogString(returnTypeFromAnnotation) + + " according to annotation - Must return a resource type"); } returnType = returnTypeFromAnnotation; } else { @@ -572,8 +590,7 @@ public abstract class BaseMethodBinding implements IClientResponseHandler if (obj1 == null) { obj1 = object; } else { - throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @" - + obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName() + ". Can not have both."); + throw new ConfigurationException("Method " + theNextMethod.getName() + " on type '" + theNextMethod.getDeclaringClass().getSimpleName() + " has annotations @" + obj1.getClass().getSimpleName() + " and @" + object.getClass().getSimpleName() + ". Can not have both."); } } @@ -588,18 +605,6 @@ public abstract class BaseMethodBinding implements IClientResponseHandler return true; } - /** - * @see BaseMethodBinding#loadRequestContents(RequestDetails) - */ - private static volatile IRequestReader ourRequestReader; - - /** - * @see BaseMethodBinding#loadRequestContents(RequestDetails) - */ - private static interface IRequestReader { - InputStream getInputStream(RequestDetails theRequestDetails) throws IOException; - } - /** * @see BaseMethodBinding#loadRequestContents(RequestDetails) */ @@ -620,4 +625,11 @@ public abstract class BaseMethodBinding implements IClientResponseHandler } } + /** + * @see BaseMethodBinding#loadRequestContents(RequestDetails) + */ + private static interface IRequestReader { + InputStream getInputStream(RequestDetails theRequestDetails) throws IOException; + } + } diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java index 55245dd579d..38a53709529 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseOutcomeReturningMethodBindingWithResourceParam.java @@ -35,10 +35,10 @@ import ca.uhn.fhir.rest.server.IResourceProvider; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOutcomeReturningMethodBinding { - private int myResourceParameterIndex; - private String myResourceName; - private Class myResourceType; private Integer myIdParamIndex; + private String myResourceName; + private int myResourceParameterIndex; + private Class myResourceType; public BaseOutcomeReturningMethodBindingWithResourceParam(Method theMethod, FhirContext theContext, Class theMethodAnnotation, Object theProvider) { super(theMethod, theContext, theMethodAnnotation, theProvider); @@ -55,7 +55,7 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu if (myResourceType != null) { throw new ConfigurationException("Method " + theMethod.getName() + " on type " + theMethod.getDeclaringClass() + " has more than one @ResourceParam. Only one is allowed."); } - + myResourceType = resourceParameter.getResourceType(); myResourceParameterIndex = index; diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java index 98afe221ad5..757286e83e1 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/method/BaseResourceReturningMethodBinding.java @@ -99,7 +99,7 @@ abstract class BaseResourceReturningMethodBinding extends BaseMethodBinding collectionType = ReflectionUtil.getGenericCollectionTypeOfMethodReturnType(theMethod); if (collectionType != null) { - if (!Object.class.equals(collectionType) && !IResource.class.isAssignableFrom(collectionType)) { + if (!Object.class.equals(collectionType) && !IBaseResource.class.isAssignableFrom(collectionType)) { throw new ConfigurationException("Method " + theMethod.getDeclaringClass().getSimpleName() + "#" + theMethod.getName() + " returns an invalid collection generic type: " + collectionType); } 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 ae16bc188f0..c5a0a7644ef 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 @@ -71,8 +71,10 @@ import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; * Server FHIR Provider which serves the conformance statement for a RESTful server implementation * *

- * Note: This class is safe to extend, but it is important to note that the same instance of {@link Conformance} is always returned unless {@link #setCache(boolean)} is called with a value of - * false. This means that if you are adding anything to the returned conformance instance on each call you should call setCache(false) in your provider constructor. + * Note: This class is safe to extend, but it is important to note that the same instance of {@link Conformance} is + * always returned unless {@link #setCache(boolean)} is called with a value of false. This means that if + * you are adding anything to the returned conformance instance on each call you should call + * setCache(false) in your provider constructor. *

*/ public class ServerConformanceProvider implements IServerConformanceProvider { @@ -87,8 +89,9 @@ public class ServerConformanceProvider implements IServerConformanceProvider 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 (MRN or other card number)", param.getDescription()); - found=true; - } - } - assertTrue(found); - Conformance conformance = sc.getServerConformance(createHttpServletRequest()); - - String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); - ourLog.info(conf); - - assertThat(conf, containsString("")); - assertThat(conf, containsString("")); - + 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; } - @Test - public void testOperationDocumentation() throws Exception { - - RestfulServer rs = new RestfulServer(ourCtx); - rs.setProviders(new SearchProvider()); - - ServerConformanceProvider sc = new ServerConformanceProvider(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 (MRN or other card number)", param.getDescription()); - found=true; - } - } - assertTrue(found); - Conformance conformance = sc.getServerConformance(createHttpServletRequest()); - - String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); - ourLog.info(conf); - - assertThat(conf, containsString("")); - assertThat(conf, containsString("")); - + private ServletConfig createServletConfig() { + ServletConfig sc = mock(ServletConfig.class); + when(sc.getServletContext()).thenReturn(null); + return sc; } @Test @@ -126,32 +80,33 @@ public class ServerConformanceProviderDstu2Test { rs.setServerConformanceProvider(sc); rs.init(createServletConfig()); - + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); ourLog.info(conf); - + } - + @Test - public void testValidateGeneratedStatement() throws Exception { + public void testInstanceHistorySupported() throws Exception { RestfulServer rs = new RestfulServer(ourCtx); - rs.setProviders(new MultiOptionalProvider()); + rs.setProviders(new InstanceHistoryProvider()); ServerConformanceProvider sc = new ServerConformanceProvider(rs); rs.setServerConformanceProvider(sc); rs.init(createServletConfig()); - - Conformance conformance = sc.getServerConformance(createHttpServletRequest()); - assertTrue(ourCtx.newValidator().validateWithResult(conformance).isSuccessful()); + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); + String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); + ourLog.info(conf); + + conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance); + assertThat(conf, containsString("")); } - - @Test public void testMultiOptionalDocumentation() throws Exception { @@ -162,8 +117,8 @@ public class ServerConformanceProviderDstu2Test { rs.setServerConformanceProvider(sc); rs.init(createServletConfig()); - - boolean found=false; + + boolean found = false; Collection resourceBindings = rs.getResourceBindings(); for (ResourceBinding resourceBinding : resourceBindings) { if (resourceBinding.getResourceName().equals("Patient")) { @@ -171,7 +126,7 @@ public class ServerConformanceProviderDstu2Test { SearchMethodBinding binding = (SearchMethodBinding) methodBindings.get(0); SearchParameter param = (SearchParameter) binding.getParameters().iterator().next(); assertEquals("The patient's identifier", param.getDescription()); - found=true; + found = true; } } @@ -185,6 +140,69 @@ public class ServerConformanceProviderDstu2Test { assertThat(conf, containsString("")); } + @Test + public void testOperationDocumentation() throws Exception { + + RestfulServer rs = new RestfulServer(ourCtx); + rs.setProviders(new SearchProvider()); + + ServerConformanceProvider sc = new ServerConformanceProvider(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 (MRN or other card number)", param.getDescription()); + found = true; + } + } + assertTrue(found); + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); + + String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); + ourLog.info(conf); + + assertThat(conf, containsString("")); + assertThat(conf, containsString("")); + + } + + @Test + public void testProviderWithRequiredAndOptional() throws Exception { + + RestfulServer rs = new RestfulServer(ourCtx); + rs.setProviders(new ProviderWithRequiredAndOptional()); + + ServerConformanceProvider sc = new ServerConformanceProvider(rs); + rs.setServerConformanceProvider(sc); + + rs.init(createServletConfig()); + + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); + String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); + ourLog.info(conf); + + Rest rest = conformance.getRestFirstRep(); + RestResource res = rest.getResourceFirstRep(); + assertEquals("DiagnosticReport", res.getType()); + + assertEquals(DiagnosticReport.SP_SUBJECT, res.getSearchParam().get(0).getName()); + assertEquals("identifier", res.getSearchParam().get(0).getChain().get(0).getValue()); + + assertEquals(DiagnosticReport.SP_NAME, res.getSearchParam().get(2).getName()); + + assertEquals(DiagnosticReport.SP_DATE, res.getSearchParam().get(1).getName()); + + assertEquals(1, res.getSearchInclude().size()); + assertEquals("DiagnosticReport.result", res.getSearchIncludeFirstRep().getValue()); + } + @Test public void testReadAndVReadSupported() throws Exception { @@ -195,7 +213,7 @@ public class ServerConformanceProviderDstu2Test { rs.setServerConformanceProvider(sc); rs.init(createServletConfig()); - + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); ourLog.info(conf); @@ -215,7 +233,7 @@ public class ServerConformanceProviderDstu2Test { rs.setServerConformanceProvider(sc); rs.init(createServletConfig()); - + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); ourLog.info(conf); @@ -224,87 +242,148 @@ public class ServerConformanceProviderDstu2Test { assertThat(conf, not(containsString(""))); assertThat(conf, containsString("")); } - @Test - public void testProviderWithRequiredAndOptional() throws Exception { + + @Test + public void testConditionalOperations() throws Exception { RestfulServer rs = new RestfulServer(ourCtx); - rs.setProviders(new ProviderWithRequiredAndOptional()); + rs.setProviders(new ConditionalProvider()); ServerConformanceProvider sc = new ServerConformanceProvider(rs); rs.setServerConformanceProvider(sc); rs.init(createServletConfig()); - + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); ourLog.info(conf); - Rest rest = conformance.getRestFirstRep(); - RestResource res = rest.getResourceFirstRep(); - assertEquals("DiagnosticReport", res.getType()); + RestResource res = conformance.getRestFirstRep().getResourceFirstRep(); + assertEquals("Patient", res.getType()); - assertEquals(DiagnosticReport.SP_SUBJECT , res.getSearchParam().get(0).getName()); - assertEquals("identifier", res.getSearchParam().get(0).getChain().get(0).getValue()); - - assertEquals(DiagnosticReport.SP_NAME, res.getSearchParam().get(2).getName()); - - assertEquals(DiagnosticReport.SP_DATE, res.getSearchParam().get(1).getName()); - - assertEquals(1,res.getSearchInclude().size()); - assertEquals("DiagnosticReport.result", res.getSearchIncludeFirstRep().getValue()); + assertTrue(res.getConditionalCreate()); + assertTrue(res.getConditionalDelete()); + assertTrue(res.getConditionalUpdate()); } - - - /** - * Created by dsotnikov on 2/25/2014. - */ - public static class SearchProvider { + @Test + public void testNonConditionalOperations() throws Exception { - @Search(type = Patient.class) - public Patient findPatient( - @Description(shortDefinition = "The patient's identifier (MRN or other card number)") - @RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) { - return null; - } + RestfulServer rs = new RestfulServer(ourCtx); + rs.setProviders(new NonConditionalProvider()); + ServerConformanceProvider sc = new ServerConformanceProvider(rs); + rs.setServerConformanceProvider(sc); + + rs.init(createServletConfig()); + + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); + String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); + ourLog.info(conf); + + RestResource res = conformance.getRestFirstRep().getResourceFirstRep(); + assertEquals("Patient", res.getType()); + + assertNull(res.getConditionalCreate()); + assertNull(res.getConditionalDelete()); + assertNull(res.getConditionalUpdate()); } - - /** - * Created by dsotnikov on 2/25/2014. - */ - public static class VreadProvider { - @Read(version=true) - public Patient readPatient( - @IdParam IdDt theId) { - return null; - } + @Test + public void testSearchParameterDocumentation() throws Exception { - @Search(type = Patient.class) - public Patient findPatient( - @Description(shortDefinition = "The patient's identifier (MRN or other card number)") - @RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) { - return null; + RestfulServer rs = new RestfulServer(ourCtx); + rs.setProviders(new SearchProvider()); + + ServerConformanceProvider sc = new ServerConformanceProvider(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 (MRN or other card number)", param.getDescription()); + found = true; + } } + assertTrue(found); + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); + + String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); + ourLog.info(conf); + + assertThat(conf, containsString("")); + assertThat(conf, containsString("")); } - /** - * Created by dsotnikov on 2/25/2014. - */ - public static class ReadProvider { + @Test + public void testSystemHistorySupported() throws Exception { - @Read(version=false) - public Patient readPatient( - @IdParam IdDt theId) { - return null; + RestfulServer rs = new RestfulServer(ourCtx); + rs.setProviders(new SystemHistoryProvider()); + + ServerConformanceProvider sc = new ServerConformanceProvider(rs); + rs.setServerConformanceProvider(sc); + + rs.init(createServletConfig()); + + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); + String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); + ourLog.info(conf); + + conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance); + assertThat(conf, containsString("")); + } + + @Test + public void testTypeHistorySupported() throws Exception { + + RestfulServer rs = new RestfulServer(ourCtx); + rs.setProviders(new TypeHistoryProvider()); + + ServerConformanceProvider sc = new ServerConformanceProvider(rs); + rs.setServerConformanceProvider(sc); + + rs.init(createServletConfig()); + + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); + String conf = ourCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(conformance); + ourLog.info(conf); + + conf = ourCtx.newXmlParser().setPrettyPrint(false).encodeResourceToString(conformance); + assertThat(conf, containsString("")); + } + + @Test + public void testValidateGeneratedStatement() throws Exception { + + RestfulServer rs = new RestfulServer(ourCtx); + rs.setProviders(new MultiOptionalProvider()); + + ServerConformanceProvider sc = new ServerConformanceProvider(rs); + rs.setServerConformanceProvider(sc); + + rs.init(createServletConfig()); + + Conformance conformance = sc.getServerConformance(createHttpServletRequest()); + + assertTrue(ourCtx.newValidator().validateWithResult(conformance).isSuccessful()); + } + + public static class InstanceHistoryProvider implements IResourceProvider { + @Override + public Class getResourceType() { + return Patient.class; } - @Search(type = Patient.class) - public Patient findPatient( - @Description(shortDefinition = "The patient's identifier (MRN or other card number)") - @RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) { + @History + public List history(@IdParam IdDt theId) { return null; } @@ -316,40 +395,16 @@ public class ServerConformanceProviderDstu2Test { public static class MultiOptionalProvider { @Search(type = Patient.class) - public Patient findPatient( - @Description(shortDefinition = "The patient's identifier") - @OptionalParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier, - @Description(shortDefinition = "The patient's name") - @OptionalParam(name=Patient.SP_NAME) StringDt theName) { + public Patient findPatient(@Description(shortDefinition = "The patient's identifier") @OptionalParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier, @Description(shortDefinition = "The patient's name") @OptionalParam(name = Patient.SP_NAME) StringDt theName) { return null; } } - - - public static class ProviderWithRequiredAndOptional { - - @Description(shortDefinition="This is a search for stuff!") - @Search - public List findDiagnosticReportsByPatient ( - @RequiredParam(name=DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) IdentifierDt thePatientId, - @OptionalParam(name=DiagnosticReport.SP_NAME) TokenOrListParam theNames, - @OptionalParam(name=DiagnosticReport.SP_DATE) DateRangeParam theDateRange, - @IncludeParam(allow= {"DiagnosticReport.result"}) Set theIncludes - ) throws Exception { - return null; - } - } - public static class ProviderWithExtendedOperationReturningBundle implements IResourceProvider { - - @Operation(name="everything", idempotent=true) - public ca.uhn.fhir.rest.server.IBundleProvider everything( - javax.servlet.http.HttpServletRequest theServletRequest, - @IdParam ca.uhn.fhir.model.primitive.IdDt theId, - @OperationParam(name="start") DateDt theStart, - @OperationParam(name="end") DateDt theEnd) { + + @Operation(name = "everything", idempotent = true) + public ca.uhn.fhir.rest.server.IBundleProvider everything(javax.servlet.http.HttpServletRequest theServletRequest, @IdParam ca.uhn.fhir.model.primitive.IdDt theId, @OperationParam(name = "start") DateDt theStart, @OperationParam(name = "end") DateDt theEnd) { return null; } @@ -360,19 +415,132 @@ public class ServerConformanceProviderDstu2Test { } - 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; + public static class ProviderWithRequiredAndOptional { + + @Description(shortDefinition = "This is a search for stuff!") + @Search + public List findDiagnosticReportsByPatient(@RequiredParam(name = DiagnosticReport.SP_SUBJECT + '.' + Patient.SP_IDENTIFIER) IdentifierDt thePatientId, @OptionalParam(name = DiagnosticReport.SP_NAME) TokenOrListParam theNames, + @OptionalParam(name = DiagnosticReport.SP_DATE) DateRangeParam theDateRange, @IncludeParam(allow = { "DiagnosticReport.result" }) Set theIncludes) throws Exception { + return null; + } + } - private ServletConfig createServletConfig() { - ServletConfig sc = mock(ServletConfig.class); - when (sc.getServletContext()).thenReturn(null); - return sc; + /** + * Created by dsotnikov on 2/25/2014. + */ + 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) IdentifierDt theIdentifier) { + return null; + } + + @Read(version = false) + public Patient readPatient(@IdParam IdDt theId) { + return null; + } + + } + + /** + * Created by dsotnikov on 2/25/2014. + */ + public static class SearchProvider { + + @Search(type = Patient.class) + public Patient findPatient(@Description(shortDefinition = "The patient's identifier (MRN or other card number)") @RequiredParam(name = Patient.SP_IDENTIFIER) IdentifierDt theIdentifier) { + return null; + } + + } + + public static class SystemHistoryProvider { + + @History + public List history() { + return null; + } + + } + + public static class TypeHistoryProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @History + public List history() { + return null; + } + + } + + public static class ConditionalProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Create + public MethodOutcome create(@ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) { + return null; + } + + @Update + public MethodOutcome update(@IdParam IdDt theId, @ResourceParam Patient thePatient, @ConditionalUrlParam String theConditionalUrl) { + return null; + } + + @Delete + public MethodOutcome delete(@IdParam IdDt theId, @ConditionalUrlParam String theConditionalUrl) { + return null; + } + + } + + public static class NonConditionalProvider implements IResourceProvider { + + @Override + public Class getResourceType() { + return Patient.class; + } + + @Create + public MethodOutcome create(@ResourceParam Patient thePatient) { + return null; + } + + @Update + public MethodOutcome update(@IdParam IdDt theId, @ResourceParam Patient thePatient) { + return null; + } + + @Delete + public MethodOutcome delete(@IdParam IdDt theId) { + 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) IdentifierDt theIdentifier) { + return null; + } + + @Read(version = true) + public Patient readPatient(@IdParam IdDt theId) { + return null; + } + } } diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 51dab472a25..e42ca35f058 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -92,7 +92,7 @@ and Bundle.entry.base is empty, it will now be automatically set by the parser. - + Add fluent client method for validate operation, and support the new DSTU2 style extended operation for $validate if the client is in DSTU2 mode. Thanks to Eric from the FHIR Skype Implementers chat for @@ -105,7 +105,7 @@ 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! - + Questionnaire.title now gets correctly indexed in JPA server (it has no path, so it is a special case) @@ -133,6 +133,10 @@ JPA server supports _count parameter in transaction containing search URL (nested search) + + DSTU2 servers now indicate support for conditional create/update/delete in their + conformance statement. +