diff --git a/examples/src/main/java/example/IRestfulClient.java b/examples/src/main/java/example/IRestfulClient.java index b9a8ebbfd18..d9963f27d62 100644 --- a/examples/src/main/java/example/IRestfulClient.java +++ b/examples/src/main/java/example/IRestfulClient.java @@ -33,7 +33,7 @@ public interface IRestfulClient extends IBasicClient { /** * The "@Search" annotation indicates that this method supports the - * search operation. You may have many different method annotated with + * search operation. You may have many different methods annotated with * this annotation, to support many different search criteria. This * example searches by family name. * diff --git a/examples/src/main/java/example/RestfulObservationResourceProvider.java b/examples/src/main/java/example/RestfulObservationResourceProvider.java index 8c2795364b4..773d7640c7c 100644 --- a/examples/src/main/java/example/RestfulObservationResourceProvider.java +++ b/examples/src/main/java/example/RestfulObservationResourceProvider.java @@ -55,7 +55,7 @@ public class RestfulObservationResourceProvider implements IResourceProvider { /** * The "@Search" annotation indicates that this method supports the - * search operation. You may have many different method annotated with + * search operation. You may have many different methods annotated with * this annotation, to support many different search criteria. This * example searches by family name. * diff --git a/examples/src/main/java/example/RestfulPatientResourceProvider.java b/examples/src/main/java/example/RestfulPatientResourceProvider.java index 2bf5c6ba0ac..3fe42a9ab39 100644 --- a/examples/src/main/java/example/RestfulPatientResourceProvider.java +++ b/examples/src/main/java/example/RestfulPatientResourceProvider.java @@ -56,7 +56,7 @@ public class RestfulPatientResourceProvider implements IResourceProvider { /** * The "@Search" annotation indicates that this method supports the - * search operation. You may have many different method annotated with + * search operation. You may have many different methods annotated with * this annotation, to support many different search criteria. This * example searches by family name. * diff --git a/examples/src/main/java/example/ValidatorExamples.java b/examples/src/main/java/example/ValidatorExamples.java index 79c5f428799..7f853a7f13f 100644 --- a/examples/src/main/java/example/ValidatorExamples.java +++ b/examples/src/main/java/example/ValidatorExamples.java @@ -234,6 +234,12 @@ public class ValidatorExamples { return null; } + @Override + public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { + // TODO: implement + return null; + } + @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { // TODO: implement diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ResourceParam.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ResourceParam.java index 3df494f2973..68bbdea0792 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ResourceParam.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/rest/annotation/ResourceParam.java @@ -19,10 +19,14 @@ package ca.uhn.fhir.rest.annotation; * limitations under the License. * #L% */ -import java.lang.annotation.*; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; /** - * Denotes a parameter for a REST method which will contain the resource actually + * Denotes a parameter for a REST method which will contain the resource actually * being created/updated/etc in an operation which contains a resource in the HTTP request. *

* For example, in a {@link Create} operation the method parameter annotated with this @@ -32,7 +36,7 @@ import java.lang.annotation.*; * Parameters with this annotation should typically be of the type of resource being * operated on (see below for an exception when raw data is required). For example, in a * IResourceProvider for Patient resources, the parameter annotated with this - * annotation should be of type Patient. + * annotation should be of type Patient. *

*

* Note that in servers it is also acceptable to have parameters with this annotation @@ -41,8 +45,11 @@ import java.lang.annotation.*; * have multiple parameters with this annotation, so you can have one parameter * which accepts the parsed resource, and another which accepts the raw request. *

+ *

+ * Also note that this parameter may be null if a client does not supply a body. + *

*/ -@Target(value=ElementType.PARAMETER) +@Target(value = ElementType.PARAMETER) @Retention(RetentionPolicy.RUNTIME) public @interface ResourceParam { diff --git a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/PortUtil.java b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/PortUtil.java index 17998d54178..92836d62f0b 100644 --- a/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/PortUtil.java +++ b/hapi-fhir-base/src/main/java/ca/uhn/fhir/util/PortUtil.java @@ -217,28 +217,20 @@ public class PortUtil { } private static boolean isAvailable(int port) { - ServerSocket ss = null; - DatagramSocket ds = null; - try { - ss = new ServerSocket(port); + ourLog.info("Testing a bind on port {}", port); + try (ServerSocket ss = new ServerSocket(port)) { ss.setReuseAddress(true); - ds = new DatagramSocket(port); - ds.setReuseAddress(true); - return true; + try (DatagramSocket ds = new DatagramSocket(port)) { + ds.setReuseAddress(true); + ourLog.info("Successfully bound port {}", port); + return true; + } catch (IOException e) { + ourLog.info("Failed to bind port {}: {}", port, e.toString()); + return false; + } } catch (IOException e) { + ourLog.info("Failed to bind port {}: {}", port, e.toString()); return false; - } finally { - if (ds != null) { - ds.close(); - } - - if (ss != null) { - try { - ss.close(); - } catch (IOException e) { - /* should not be thrown */ - } - } } } diff --git a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties index 4ff252c1a3c..14644b67b34 100644 --- a/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties +++ b/hapi-fhir-base/src/main/resources/ca/uhn/fhir/i18n/hapi-messages.properties @@ -89,6 +89,7 @@ ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.failedToCreateWithClientAssignedIdNo ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidParameterChain=Invalid parameter chain: {0} ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.invalidVersion=Version "{0}" is not valid for resource {1} ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.multipleParamsWithSameNameOneIsMissingTrue=This server does not know how to handle multiple "{0}" parameters where one has a value of :missing=true +ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.missingBody=No body was supplied in request ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.unableToDeleteNotFound=Unable to find resource matching URL "{0}". Deletion failed. ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulCreate=Successfully created resource "{0}" in {1}ms ca.uhn.fhir.jpa.dao.BaseHapiFhirResourceDao.successfulUpdate=Successfully updated resource "{0}" in {1}ms diff --git a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/PortUtilTest.java b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/PortUtilTest.java index 84d2b73f9a7..662789dbce5 100644 --- a/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/PortUtilTest.java +++ b/hapi-fhir-base/src/test/java/ca/uhn/fhir/util/PortUtilTest.java @@ -63,13 +63,23 @@ public class PortUtilTest { for (int j = 0; j < portsPerTaskCount; j++) { int nextFreePort = portUtil.getNextFreePort(); + boolean bound; try (ServerSocket ss = new ServerSocket()) { ss.bind(new InetSocketAddress("localhost", nextFreePort)); + bound = true; } catch (IOException e) { - String msg = "Failure binding new port " + nextFreePort + ": " + e.toString(); - ourLog.error(msg, e); - errors.add(msg); + bound = false; + } + if (!bound) { + try (ServerSocket ss = new ServerSocket()) { + Thread.sleep(1000); + ss.bind(new InetSocketAddress("localhost", nextFreePort)); + } catch (Exception e) { + String msg = "Failure binding new port (second attempt) " + nextFreePort + ": " + e.toString(); + ourLog.error(msg, e); + errors.add(msg); + } } ports.add(nextFreePort); diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java index 6f562364e37..2ae169af895 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportDstu3.java @@ -27,6 +27,7 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -56,6 +57,11 @@ public class LoadingValidationSupportDstu3 implements IValidationSupport { return null; } + @Override + public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { + return null; + } + @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { String resName = myCtx.getResourceDefinition(theClass).getName(); diff --git a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java index 4df8b05ec71..d50537cd0f8 100644 --- a/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java +++ b/hapi-fhir-cli/hapi-fhir-cli-api/src/main/java/ca/uhn/fhir/cli/LoadingValidationSupportR4.java @@ -27,6 +27,7 @@ import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r4.terminologies.ValueSetExpander; @@ -58,6 +59,11 @@ public class LoadingValidationSupportR4 implements org.hl7.fhir.r4.hapi.ctx.IVal return null; } + @Override + public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { + return null; + } + @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { String resName = myCtx.getResourceDefinition(theClass).getName(); diff --git a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu3.java b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu3.java index 44d71df9722..b2aebea2720 100644 --- a/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu3.java +++ b/hapi-fhir-igpacks/src/main/java/ca/uhn/fhir/igpacks/parser/IgPackValidationSupportDstu3.java @@ -67,6 +67,11 @@ public class IgPackValidationSupportDstu3 implements IValidationSupport { return fetchResource(theContext, CodeSystem.class, theSystem); } + @Override + public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { + return fetchResource(theContext, ValueSet.class, theSystem); + } + @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { for (Map.Entry next : myIgResources.entrySet()) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java index b543db2985f..c99ce5df4f2 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/config/BaseConfig.java @@ -73,14 +73,17 @@ public abstract class BaseConfig implements SchedulingConfigurer { @Autowired protected Environment myEnv; - @Autowired - protected DaoRegistry myDaoRegistry; @Override public void configureTasks(@Nonnull ScheduledTaskRegistrar theTaskRegistrar) { theTaskRegistrar.setTaskScheduler(taskScheduler()); } + @Bean("myDaoRegistry") + public DaoRegistry daoRegistry() { + return new DaoRegistry(); + } + @Bean(autowire = Autowire.BY_TYPE) public DatabaseBackedPagingProvider databaseBackedPagingProvider() { return new DatabaseBackedPagingProvider(); @@ -182,7 +185,7 @@ public abstract class BaseConfig implements SchedulingConfigurer { * Subclasses may override */ protected boolean isSupported(String theResourceType) { - return myDaoRegistry.getResourceDao(theResourceType) != null; + return daoRegistry().getResourceDao(theResourceType) != null; } public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java index cc8b139cbe8..58e374f6a83 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/BaseHapiFhirResourceDao.java @@ -142,6 +142,11 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public DaoMethodOutcome create(T theResource, String theIfNoneExist, boolean thePerformIndexing, Date theUpdateTimestamp, RequestDetails theRequestDetails) { + if (theResource == null) { + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody"); + throw new InvalidRequestException(msg); + } + if (isNotBlank(theResource.getIdElement().getIdPart())) { if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3)) { String message = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "failedToCreateWithClientAssignedId", theResource.getIdElement().getIdPart()); @@ -1270,6 +1275,11 @@ public abstract class BaseHapiFhirResourceDao extends B @Override public DaoMethodOutcome update(T theResource, String theMatchUrl, boolean thePerformIndexing, boolean theForceUpdateVersion, RequestDetails theRequestDetails) { + if (theResource == null) { + String msg = getContext().getLocalizer().getMessage(BaseHapiFhirResourceDao.class, "missingBody"); + throw new InvalidRequestException(msg); + } + StopWatch w = new StopWatch(); preProcessResourceForStorage(theResource); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java index 5695b16f313..313075c4d36 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/DaoRegistry.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.dao; * 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. @@ -35,13 +35,20 @@ import org.springframework.stereotype.Component; import java.util.*; import java.util.stream.Collectors; -@Component("myDaoRegistry") public class DaoRegistry implements ApplicationContextAware { private ApplicationContext myAppCtx; @Autowired private FhirContext myContext; + /** + * Constructor + */ + public DaoRegistry() { + super(); + } + + private volatile Map> myResourceNameToResourceDao; private volatile IFhirSystemDao mySystemDao; private Set mySupportedResourceTypes; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/JpaValidationSupportDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/JpaValidationSupportDstu3.java index fe92f4aebee..3d607db71c8 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/JpaValidationSupportDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/dstu3/JpaValidationSupportDstu3.java @@ -89,6 +89,14 @@ public class JpaValidationSupportDstu3 implements IJpaValidationSupportDstu3, Ap return fetchResource(theCtx, CodeSystem.class, theSystem); } + @Override + public ValueSet fetchValueSet(FhirContext theCtx, String theSystem) { + if (isBlank(theSystem)) { + return null; + } + return fetchResource(theCtx, ValueSet.class, theSystem); + } + @SuppressWarnings("unchecked") @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java index 0e5f2319260..e223b4754eb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/index/SearchParamWithInlineReferencesExtractor.java @@ -87,7 +87,7 @@ public class SearchParamWithInlineReferencesExtractor { @PersistenceContext(type = PersistenceContextType.TRANSACTION) protected EntityManager myEntityManager; - public void populateFromResource(ResourceIndexedSearchParams theParams, IDao theCallingDao, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams existingParams) { + public void populateFromResource(ResourceIndexedSearchParams theParams, IDao theCallingDao, Date theUpdateTime, ResourceTable theEntity, IBaseResource theResource, ResourceIndexedSearchParams theExistingParams) { mySearchParamExtractorService.extractFromResource(theParams, theEntity, theResource); Set> activeSearchParams = mySearchParamRegistry.getActiveSearchParams(theEntity.getResourceType()).entrySet(); @@ -104,7 +104,7 @@ public class SearchParamWithInlineReferencesExtractor { /* * If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them */ - for (Iterator existingLinkIter = existingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) { + for (Iterator existingLinkIter = theExistingParams.getResourceLinks().iterator(); existingLinkIter.hasNext(); ) { ResourceLink nextExisting = existingLinkIter.next(); if (theParams.myLinks.remove(nextExisting)) { existingLinkIter.remove(); @@ -263,6 +263,7 @@ public class SearchParamWithInlineReferencesExtractor { myEntityManager.remove(next); theEntity.getParamsCompositeStringUnique().remove(next); } + boolean haveNewParams = false; for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.myCompositeStringUniques, existingParams.myCompositeStringUniques)) { if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) { ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString()); @@ -273,6 +274,12 @@ public class SearchParamWithInlineReferencesExtractor { } ourLog.debug("Persisting unique index: {}", next); myEntityManager.persist(next); + haveNewParams = true; + } + if (theParams.myCompositeStringUniques.size() > 0 || haveNewParams) { + theEntity.setParamsCompositeStringUniquePresent(true); + } else { + theEntity.setParamsCompositeStringUniquePresent(false); } } } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java index 4c8ecf24b49..6a04443b1f1 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/dao/r4/JpaValidationSupportR4.java @@ -86,6 +86,11 @@ public class JpaValidationSupportR4 implements IJpaValidationSupportR4, Applicat return fetchResource(theCtx, CodeSystem.class, theSystem); } + @Override + public ValueSet fetchValueSet(FhirContext theCtx, String theSystem) { + return fetchResource(theCtx, ValueSet.class, theSystem); + } + @SuppressWarnings("unchecked") @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java index 92a5a1dddd8..083e22a3ae3 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcDstu3.java @@ -204,6 +204,12 @@ public class HapiTerminologySvcDstu3 extends BaseHapiTerminologySvcImpl implemen return null; } + @CoverageIgnore + @Override + public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { + return null; + } + @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { return null; diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java index d400a0e715f..719faab06dd 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/term/HapiTerminologySvcR4.java @@ -155,6 +155,12 @@ public class HapiTerminologySvcR4 extends BaseHapiTerminologySvcImpl implements return null; } + @CoverageIgnore + @Override + public ValueSet fetchValueSet(FhirContext theContext, String theSystem) { + return null; + } + @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { return null; diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java index 119c07a00bd..7ed2077130f 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchOptimizedTest.java @@ -887,6 +887,7 @@ public class FhirResourceDaoR4SearchOptimizedTest extends BaseJpaR4Test { * Select the version from HFJ_RES_VER * Select the current token indexes */ + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); assertEquals(3, myCaptureQueriesListener.countSelectQueriesForCurrentThread()); assertEquals(0, myCaptureQueriesListener.countDeleteQueriesForCurrentThread()); assertEquals(1, myCaptureQueriesListener.countInsertQueriesForCurrentThread()); // Add an entry to HFJ_RES_VER diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java index 402973f59ad..6d3a3526a88 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4UniqueSearchParamTest.java @@ -1,9 +1,11 @@ package ca.uhn.fhir.jpa.dao.r4; +import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchBuilder; import ca.uhn.fhir.jpa.model.entity.ModelConfig; import ca.uhn.fhir.jpa.model.entity.ResourceIndexedCompositeStringUnique; +import ca.uhn.fhir.jpa.model.entity.ResourceTable; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.SearchParamConstants; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; @@ -33,7 +35,9 @@ import javax.annotation.Nonnull; import java.util.Collections; import java.util.List; import java.util.UUID; +import java.util.stream.Collectors; +import static ca.uhn.fhir.jpa.dao.BaseHapiFhirDao.INDEX_STATUS_INDEXED; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; @@ -503,7 +507,7 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { assertThat(toUnqualifiedVersionlessIdValues(outcome), containsInAnyOrder(srId)); unformattedSql = myCaptureQueriesListener.getSelectQueriesForCurrentThread().get(0).getSql(true, false); assertThat(unformattedSql, stringContainsInOrder( - "IDX_STRING='ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F"+ practId.getIdPart() +"'", + "IDX_STRING='ServiceRequest?identifier=sys%7C111&patient=Patient%2F" + ptId.getIdPart() + "&performer=Practitioner%2F" + practId.getIdPart() + "'", "HASH_SYS_AND_VALUE in ('6795110643554413877')" )); assertThat(unformattedSql, not(containsString(("RES_DELETED_AT")))); @@ -773,11 +777,23 @@ public class FhirResourceDaoR4UniqueSearchParamTest extends BaseJpaR4Test { // The third pass has a low of (Coverage.lastUpdated + 1ms) assertEquals(0, myResourceReindexingSvc.forceReindexingPass()); - List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); - ourLog.info("** Uniques: {}", uniques); - assertEquals(uniques.toString(), 1, uniques.size()); - assertEquals("Coverage/" + id3.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); - assertEquals("Coverage?beneficiary=Patient%2F" + id2.getIdPart() + "&identifier=urn%3Afoo%3Abar%7C123", uniques.get(0).getIndexString()); + runInTransaction(() -> { + List tables = myResourceTableDao.findAll(); + String resourceIds = tables.stream().map(t -> t.getIdDt().getValue()).collect(Collectors.joining(", ")); + // 1 patient, 1 coverage, 3 search parameters + assertEquals(resourceIds, 5, tables.size()); + for (int i = 0; i < tables.size(); i++) { + assertEquals(INDEX_STATUS_INDEXED, tables.get(i).getIndexStatus().intValue()); + } + }); + + runInTransaction(() -> { + List uniques = myResourceIndexedCompositeStringUniqueDao.findAll(); + ourLog.info("** Uniques: {}", uniques); + assertEquals(uniques.toString(), 1, uniques.size()); + assertEquals("Coverage/" + id3.getIdPart(), uniques.get(0).getResource().getIdDt().toUnqualifiedVersionless().getValue()); + assertEquals("Coverage?beneficiary=Patient%2F" + id2.getIdPart() + "&identifier=urn%3Afoo%3Abar%7C123", uniques.get(0).getIndexString()); + }); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java index 3597e4b0b99..02061893f58 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/SearchParamExtractorR4Test.java @@ -8,7 +8,9 @@ import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef; import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4; import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry; +import ca.uhn.fhir.rest.api.RestSearchParameterTypeEnum; import ca.uhn.fhir.util.TestUtil; +import com.google.common.collect.Sets; import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.model.*; @@ -34,6 +36,7 @@ public class SearchParamExtractorR4Test { @Before public void before() { + mySearchParamRegistry = new ISearchParamRegistry() { @Override public void forceRefresh() { @@ -52,11 +55,12 @@ public class SearchParamExtractorR4Test { @Override public Map getActiveSearchParams(String theResourceName) { - RuntimeResourceDefinition nextResDef = ourCtx.getResourceDefinition(theResourceName); Map sps = new HashMap<>(); + RuntimeResourceDefinition nextResDef = ourCtx.getResourceDefinition(theResourceName); for (RuntimeSearchParam nextSp : nextResDef.getSearchParams()) { sps.put(nextSp.getName(), nextSp); } + return sps; } @@ -130,6 +134,21 @@ public class SearchParamExtractorR4Test { assertEquals("Consent/999", ((Reference) links.get(0).getRef()).getReference()); } + + @Test + public void testExtensionContainingReference() { + String path = "Patient.extension('http://patext').value.as(Reference)"; + + RuntimeSearchParam sp = new RuntimeSearchParam("extpat", "Patient SP", path, RestSearchParameterTypeEnum.REFERENCE, new HashSet<>(), Sets.newHashSet("Patient"), RuntimeSearchParam.RuntimeSearchParamStatusEnum.ACTIVE); + + Patient patient = new Patient(); + patient.addExtension("http://patext", new Reference("Organization/AAA")); + + SearchParamExtractorR4 extractor = new SearchParamExtractorR4(new ModelConfig(), ourCtx, ourValidationSupport, mySearchParamRegistry); + List links = extractor.extractResourceLinks(patient, sp); + assertEquals(1, links.size()); + } + @Test public void testExtractComponentQuantities() { Observation o1 = new Observation(); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java index 1d7b3c63be8..1cdce063232 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/provider/r4/ResourceProviderR4Test.java @@ -267,6 +267,84 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } + @Test + public void testSearchWithDeepChain() throws IOException { + + SearchParameter sp = new SearchParameter(); + sp.addBase("Patient"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.setCode("extpatorg"); + sp.setName("extpatorg"); + sp.setExpression("Patient.extension('http://patext').value.as(Reference)"); + ourClient.create().resource(sp).execute(); + + sp = new SearchParameter(); + sp.addBase("Organization"); + sp.setStatus(Enumerations.PublicationStatus.ACTIVE); + sp.setType(Enumerations.SearchParamType.REFERENCE); + sp.setCode("extorgorg"); + sp.setName("extorgorg"); + sp.setExpression("Organization.extension('http://orgext').value.as(Reference)"); + ourClient.create().resource(sp).execute(); + + mySearchParamRegistry.forceRefresh(); + + Organization grandParent = new Organization(); + grandParent.setName("GRANDPARENT"); + IIdType grandParentId = ourClient.create().resource(grandParent).execute().getId().toUnqualifiedVersionless(); + + Organization parent = new Organization(); + parent.setName("PARENT"); + parent.getPartOf().setReference(grandParentId.getValue()); + parent.addExtension("http://orgext", new Reference().setReference(grandParentId.getValue())); + IIdType parentId = ourClient.create().resource(parent).execute().getId().toUnqualifiedVersionless(); + + Organization org = new Organization(); + org.setName("ORGANIZATION"); + org.getPartOf().setReference(parentId.getValue()); + org.addExtension("http://orgext", new Reference().setReference(parentId.getValue())); + IIdType orgId = ourClient.create().resource(org).execute().getId().toUnqualifiedVersionless(); + + myCaptureQueriesListener.logSelectQueriesForCurrentThread(); + myCaptureQueriesListener.clear(); + + Patient p = new Patient(); + p.getManagingOrganization().setReference(orgId.getValue()); + p.addExtension("http://patext", new Reference().setReference(orgId.getValue())); + String pid = ourClient.create().resource(p).execute().getId().toUnqualified().getValue(); + + List idValues; + + // Regular search param + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?organization=" + orgId.getValue()); + assertThat(idValues, contains(pid)); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?organization.name=ORGANIZATION"); + assertThat(idValues, contains(pid)); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?organization.partof.name=PARENT"); + assertThat(idValues, contains(pid)); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?organization.partof.partof.name=GRANDPARENT"); + assertThat(idValues, contains(pid)); + + // Search param on extension + myCaptureQueriesListener.clear(); + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg=" + orgId.getValue()); + myCaptureQueriesListener.logSelectQueries(); + assertThat(idValues, contains(pid)); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.name=ORGANIZATION"); + assertThat(idValues, contains(pid)); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.name=PARENT"); + assertThat(idValues, contains(pid)); + + idValues = searchAndReturnUnqualifiedIdValues(ourServerBase + "/Patient?extpatorg.extorgorg.extorgorg.name=GRANDPARENT"); + assertThat(idValues, contains(pid)); + + } @Test public void testSearchFetchPageBeyondEnd() { @@ -319,6 +397,38 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } } + @Test + public void testUpdateWithNoBody() throws IOException { + + HttpPut httpPost = new HttpPut(ourServerBase + "/Patient/AAA"); + try (CloseableHttpResponse status = ourHttpClient.execute(httpPost)) { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(status.getStatusLine().toString()); + ourLog.info(responseContent); + + assertEquals(400, status.getStatusLine().getStatusCode()); + assertThat(responseContent, containsString("No body was supplied in request")); + } + + } + + + @Test + public void testCreateWithNoBody() throws IOException { + + HttpPost httpPost = new HttpPost(ourServerBase + "/Patient"); + try (CloseableHttpResponse status = ourHttpClient.execute(httpPost)) { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(status.getStatusLine().toString()); + ourLog.info(responseContent); + + assertEquals(400, status.getStatusLine().getStatusCode()); + assertThat(responseContent, containsString("No body was supplied in request")); + } + + } + + @Before public void beforeDisableResultReuse() { @@ -398,7 +508,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { @Test @Ignore public void test() throws IOException { - HttpGet get = new HttpGet(ourServerBase + "/QuestionnaireResponse?_count=50&status=completed&questionnaire=ARIncenterAbsRecord&_lastUpdated=%3E"+UrlUtil.escapeUrlParam("=2018-01-01")+"&context.organization=O3435"); + HttpGet get = new HttpGet(ourServerBase + "/QuestionnaireResponse?_count=50&status=completed&questionnaire=ARIncenterAbsRecord&_lastUpdated=%3E" + UrlUtil.escapeUrlParam("=2018-01-01") + "&context.organization=O3435"); ourLog.info("*** MAKING QUERY"); ourHttpClient.execute(get); System.exit(0); @@ -549,7 +659,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { Binary fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); assertEquals("1", fromDB.getIdElement().getVersionIdPart()); - arr[ 0 ] = 2; + arr[0] = 2; HttpPut putRequest = new HttpPut(ourServerBase + "/Binary/" + resource.getIdPart()); putRequest.setEntity(new ByteArrayEntity(arr, ContentType.parse("dansk"))); CloseableHttpResponse resp = ourHttpClient.execute(putRequest); @@ -563,7 +673,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { fromDB = ourClient.read().resource(Binary.class).withId(resource.toVersionless()).execute(); assertEquals("2", fromDB.getIdElement().getVersionIdPart()); - arr[ 0 ] = 3; + arr[0] = 3; fromDB.setContent(arr); String encoded = myFhirCtx.newJsonParser().encodeResourceToString(fromDB); putRequest = new HttpPut(ourServerBase + "/Binary/" + resource.getIdPart()); @@ -581,7 +691,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { // Now an update with the wrong ID in the body - arr[ 0 ] = 4; + arr[0] = 4; binary.setId(""); encoded = myFhirCtx.newJsonParser().encodeResourceToString(binary); putRequest = new HttpPut(ourServerBase + "/Binary/" + resource.getIdPart()); @@ -635,12 +745,13 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { ourClient.registerInterceptor(new IClientInterceptor() { @Override public void interceptRequest(IHttpRequest theRequest) { - theRequest.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME); + theRequest.addHeader(Constants.HEADER_PREFER, Constants.HEADER_PREFER_RETURN + "=" + Constants.HEADER_PREFER_RETURN_OPERATION_OUTCOME); } + @Override - public void interceptResponse(IHttpResponse theResponse) { // TODO Auto-generated method stu + public void interceptResponse(IHttpResponse theResponse) { // TODO Auto-generated method stu } - + }); try { // Missing status, which is mandatory @@ -837,7 +948,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { response.close(); } } - + @Test public void testCreateResourceReturnsOperationOutcome() throws IOException { String resource = ""; @@ -1193,7 +1304,7 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { // String response = ""; StringBuilder b = new StringBuilder(); - char[] buf = new char[ 1000 ]; + char[] buf = new char[1000]; while (socketInput.read(buf) != -1) { b.append(buf); } @@ -5194,7 +5305,6 @@ public class ResourceProviderR4Test extends BaseResourceProviderR4Test { } - private String toStr(Date theDate) { return new InstantDt(theDate).getValueAsString(); } diff --git a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java index 8389b781344..3b613beeab7 100644 --- a/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java +++ b/hapi-fhir-jpaserver-searchparam/src/main/java/ca/uhn/fhir/jpa/searchparam/registry/BaseSearchParamRegistry.java @@ -24,6 +24,9 @@ import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.RuntimeResourceDefinition; import ca.uhn.fhir.context.RuntimeSearchParam; import ca.uhn.fhir.jpa.model.entity.ModelConfig; +import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorBroadcaster; +import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut; +import ca.uhn.fhir.jpa.model.search.PerformanceMessage; import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam; import ca.uhn.fhir.jpa.searchparam.SearchParameterMap; import ca.uhn.fhir.jpa.searchparam.retry.Retrier; @@ -41,6 +44,7 @@ import org.springframework.scheduling.annotation.Scheduled; import javax.annotation.PostConstruct; import java.util.*; +import java.util.stream.Collectors; import static org.apache.commons.lang3.StringUtils.isBlank; @@ -63,6 +67,9 @@ public abstract class BaseSearchParamRegistry implemen private volatile Map> myActiveSearchParams; private volatile long myLastRefresh; + @Autowired + private IInterceptorBroadcaster myInterceptorBroadcaster; + @Override public RuntimeSearchParam getActiveSearchParam(String theResourceName, String theParamName) { @@ -121,6 +128,7 @@ public abstract class BaseSearchParamRegistry implemen } private void populateActiveSearchParams(Map> theActiveSearchParams) { + Map> activeUniqueSearchParams = new HashMap<>(); Map, List>> activeParamNamesToUniqueSearchParams = new HashMap<>(); @@ -133,8 +141,13 @@ public abstract class BaseSearchParamRegistry implemen for (Map.Entry> nextResourceNameToEntries : theActiveSearchParams.entrySet()) { List uniqueSearchParams = activeUniqueSearchParams.computeIfAbsent(nextResourceNameToEntries.getKey(), k -> new ArrayList<>()); Collection nextSearchParamsForResourceName = nextResourceNameToEntries.getValue().values(); + + ourLog.trace("Resource {} has {} params", nextResourceNameToEntries.getKey(), nextResourceNameToEntries.getValue().size()); + for (RuntimeSearchParam nextCandidate : nextSearchParamsForResourceName) { + ourLog.trace("Resource {} has parameter {} with ID {}", nextResourceNameToEntries.getKey(), nextCandidate.getName(), nextCandidate.getId()); + if (nextCandidate.getId() != null) { idToRuntimeSearchParam.put(nextCandidate.getId().toUnqualifiedVersionless().getValue(), nextCandidate); } @@ -150,6 +163,8 @@ public abstract class BaseSearchParamRegistry implemen } + ourLog.trace("Have {} search params loaded", idToRuntimeSearchParam.size()); + Set haveSeen = new HashSet<>(); for (JpaRuntimeSearchParam next : jpaSearchParams) { if (!haveSeen.add(next.getId().toUnqualifiedVersionless().getValue())) { @@ -164,7 +179,14 @@ public abstract class BaseSearchParamRegistry implemen next.getCompositeOf().add(componentTarget); paramNames.add(componentTarget.getName()); } else { - ourLog.warn("Search parameter {} refers to unknown component {}", next.getId().toUnqualifiedVersionless().getValue(), nextRef); + String existingParams = idToRuntimeSearchParam + .keySet() + .stream() + .sorted() + .collect(Collectors.joining(", ")); + String message = "Search parameter " + next.getId().toUnqualifiedVersionless().getValue() + " refers to unknown component " + nextRef + ", ignoring this parameter (valid values: " + existingParams + ")"; + ourLog.warn(message); + myInterceptorBroadcaster.callHooks(Pointcut.PERFTRACE_MESSAGE, new PerformanceMessage().setMessage(message)); } } @@ -182,6 +204,8 @@ public abstract class BaseSearchParamRegistry implemen } } + ourLog.trace("Have {} unique search params", activeParamNamesToUniqueSearchParams.size()); + myActiveUniqueSearchParams = activeUniqueSearchParams; myActiveParamNamesToUniqueSearchParams = activeParamNamesToUniqueSearchParams; } @@ -225,12 +249,15 @@ public abstract class BaseSearchParamRegistry implemen IBundleProvider allSearchParamsBp = mySearchParamProvider.search(params); int size = allSearchParamsBp.size(); + ourLog.trace("Loaded {} search params from the DB", size); + // Just in case.. if (size >= MAX_MANAGED_PARAM_COUNT) { ourLog.warn("Unable to support >" + MAX_MANAGED_PARAM_COUNT + " search params!"); size = MAX_MANAGED_PARAM_COUNT; } + int overriddenCount = 0; List allSearchParams = allSearchParamsBp.getResources(0, size); for (IBaseResource nextResource : allSearchParams) { SP nextSp = (SP) nextResource; @@ -252,11 +279,14 @@ public abstract class BaseSearchParamRegistry implemen String name = runtimeSp.getName(); if (myModelConfig.isDefaultSearchParamsCanBeOverridden() || !searchParamMap.containsKey(name)) { searchParamMap.put(name, runtimeSp); + overriddenCount++; } } } + ourLog.trace("Have overridden {} built-in search parameters", overriddenCount); + Map> activeSearchParams = new HashMap<>(); for (Map.Entry> nextEntry : searchParams.entrySet()) { for (RuntimeSearchParam nextSp : nextEntry.getValue().values()) { @@ -323,7 +353,7 @@ public abstract class BaseSearchParamRegistry implemen int refreshCacheWithRetry() { Retrier refreshCacheRetrier = new Retrier(() -> { - synchronized(BaseSearchParamRegistry.this) { + synchronized (BaseSearchParamRegistry.this) { return mySearchParamProvider.refreshCache(this, REFRESH_INTERVAL); } }, MAX_RETRIES); diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/IPointcutLatch.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/IPointcutLatch.java index 3dc269094d8..d0af4d01d25 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/IPointcutLatch.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/IPointcutLatch.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.subscription.module; +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * 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 ca.uhn.fhir.jpa.model.interceptor.api.HookParams; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java index e814f8a1349..b93b88566af 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/cache/ActiveSubscriptionCache.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription.module.cache; * 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. diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/SubscriptionWebsocketHandler.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/SubscriptionWebsocketHandler.java index d9dc2e8a2e7..35951a07479 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/SubscriptionWebsocketHandler.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/SubscriptionWebsocketHandler.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; * 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. diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidator.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidator.java index 2871efe1582..623bf733b89 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidator.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketConnectionValidator.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * 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 ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType; import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry; diff --git a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketValidationResponse.java b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketValidationResponse.java index c16323866b7..137b44421b6 100644 --- a/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketValidationResponse.java +++ b/hapi-fhir-jpaserver-subscription/src/main/java/ca/uhn/fhir/jpa/subscription/module/subscriber/websocket/WebsocketValidationResponse.java @@ -1,5 +1,25 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber.websocket; +/*- + * #%L + * HAPI FHIR Subscription Server + * %% + * 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 ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription; public class WebsocketValidationResponse { diff --git a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBindingWithResourceParam.java b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBindingWithResourceParam.java index e2b089b1248..1b0461c5364 100644 --- a/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBindingWithResourceParam.java +++ b/hapi-fhir-server/src/main/java/ca/uhn/fhir/rest/server/method/BaseOutcomeReturningMethodBindingWithResourceParam.java @@ -25,6 +25,7 @@ import static org.apache.commons.lang3.StringUtils.defaultIfBlank; import java.lang.reflect.Method; import java.lang.reflect.Modifier; +import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; @@ -98,18 +99,20 @@ abstract class BaseOutcomeReturningMethodBindingWithResourceParam extends BaseOu } if (myResourceParameterIndex != -1) { IBaseResource resource = ((IBaseResource) theParams[myResourceParameterIndex]); - String resourceId = resource.getIdElement().getIdPart(); - String urlId = theRequest.getId() != null ? theRequest.getId().getIdPart() : null; - if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) == false) { - resource.setId(theRequest.getId()); - } + if (resource != null) { + String resourceId = resource.getIdElement().getIdPart(); + String urlId = theRequest.getId() != null ? theRequest.getId().getIdPart() : null; + if (getContext().getVersion().getVersion().isOlderThan(FhirVersionEnum.DSTU3) == false) { + resource.setId(theRequest.getId()); + } - String matchUrl = null; - if (myConditionalUrlIndex != -1) { - matchUrl = (String) theParams[myConditionalUrlIndex]; - matchUrl = defaultIfBlank(matchUrl, null); + String matchUrl = null; + if (myConditionalUrlIndex != -1) { + matchUrl = (String) theParams[myConditionalUrlIndex]; + matchUrl = defaultIfBlank(matchUrl, null); + } + validateResourceIdAndUrlIdForNonConditionalOperation(resource, resourceId, urlId, matchUrl); } - validateResourceIdAndUrlIdForNonConditionalOperation(resource, resourceId, urlId, matchUrl); } } diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/DefaultProfileValidationSupport.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/DefaultProfileValidationSupport.java index cefd590c2cc..68723c53338 100644 --- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/DefaultProfileValidationSupport.java +++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/DefaultProfileValidationSupport.java @@ -139,8 +139,9 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return provideStructureDefinitionMap(theContext).get(theUrl); } - ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false); + @Override + public ValueSet fetchValueSet(FhirContext theContext, String uri) { + return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false); } public void flush() { diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/IValidationSupport.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/IValidationSupport.java index a5ab02d2ea3..3af08eb3b0b 100644 --- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/IValidationSupport.java +++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/IValidationSupport.java @@ -4,6 +4,7 @@ import java.util.List; import org.hl7.fhir.dstu2016may.model.CodeSystem; import org.hl7.fhir.dstu2016may.model.CodeSystem.ConceptDefinitionComponent; +import org.hl7.fhir.dstu2016may.model.ValueSet; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; import org.hl7.fhir.dstu2016may.model.StructureDefinition; import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent; @@ -33,14 +34,23 @@ public interface IValidationSupport List fetchAllStructureDefinitions(FhirContext theContext); /** - * Fetch a code system by ID - * - * @param theSystem - * The code system + * Fetch a code system by Uri + * + * @param uri + * Canonical Uri of the code system * @return The valueset (must not be null, but can be an empty ValueSet) */ @Override - CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem); + CodeSystem fetchCodeSystem(FhirContext theContext, String uri); + + /** + * Fetch a valueset by Uri + * + * @param uri + * Canonical Uri of the ValueSet + * @return The valueset (must not be null, but can be an empty ValueSet) + */ + ValueSet fetchValueSet(FhirContext theContext, String uri); /** * Loads a resource needed by the validation (a StructureDefinition, or a diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/PrePopulatedValidationSupport.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/PrePopulatedValidationSupport.java index 8771c1609ef..a1d074e7389 100644 --- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/PrePopulatedValidationSupport.java +++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/PrePopulatedValidationSupport.java @@ -99,8 +99,13 @@ public class PrePopulatedValidationSupport implements IValidationSupport { } @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return myCodeSystems.get(theSystem); + public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { + return myCodeSystems.get(uri); + } + + @Override + public ValueSet fetchValueSet(FhirContext theContext, String uri) { + return myValueSets.get(uri); } @SuppressWarnings("unchecked") diff --git a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/ValidationSupportChain.java b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/ValidationSupportChain.java index 11889dba553..fe3bdc92913 100644 --- a/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/ValidationSupportChain.java +++ b/hapi-fhir-structures-dstu2.1/src/main/java/org/hl7/fhir/dstu2016may/hapi/validation/ValidationSupportChain.java @@ -3,6 +3,7 @@ package org.hl7.fhir.dstu2016may.hapi.validation; import ca.uhn.fhir.context.FhirContext; import org.hl7.fhir.dstu2016may.model.CodeSystem; import org.hl7.fhir.dstu2016may.model.StructureDefinition; +import org.hl7.fhir.dstu2016may.model.ValueSet; import org.hl7.fhir.dstu2016may.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu2016may.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -78,9 +79,9 @@ public class ValidationSupportChain implements IValidationSupport { } @Override - public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { + public CodeSystem fetchCodeSystem(FhirContext theCtx, String uri) { for (IValidationSupport next : myChain) { - CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); + CodeSystem retVal = next.fetchCodeSystem(theCtx, uri); if (retVal != null) { return retVal; } @@ -88,6 +89,18 @@ public class ValidationSupportChain implements IValidationSupport { return null; } + @Override + public ValueSet fetchValueSet(FhirContext theCtx, String uri) { + for (IValidationSupport next : myChain) { + ValueSet retVal = next.fetchValueSet(theCtx, uri); + if (retVal != null) { + return retVal; + } + } + return null; + } + + @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { for (IValidationSupport next : myChain) { diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/DefaultProfileValidationSupport.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/DefaultProfileValidationSupport.java index 446c10f117f..406ecd67218 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/DefaultProfileValidationSupport.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/DefaultProfileValidationSupport.java @@ -155,8 +155,9 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return retVal; } - ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false); + @Override + public ValueSet fetchValueSet(FhirContext theContext, String uri) { + return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false); } public void flush() { diff --git a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/IValidationSupport.java b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/IValidationSupport.java index 222a1bfdd70..0cb23892bfc 100644 --- a/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/IValidationSupport.java +++ b/hapi-fhir-structures-dstu3/src/main/java/org/hl7/fhir/dstu3/hapi/ctx/IValidationSupport.java @@ -5,6 +5,7 @@ import java.util.List; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.dstu3.model.StructureDefinition; +import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -32,15 +33,24 @@ public interface IValidationSupport @Override List fetchAllStructureDefinitions(FhirContext theContext); - /** - * Fetch a code system by ID - * - * @param theSystem - * The code system - * @return The valueset (must not be null, but can be an empty ValueSet) - */ - @Override - CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem); + /** + * Fetch a code system by Uri + * + * @param uri + * Canonical Uri of the code system + * @return The valueset (must not be null, but can be an empty ValueSet) + */ + @Override + CodeSystem fetchCodeSystem(FhirContext theContext, String uri); + + /** + * Fetch a valueset by Uri + * + * @param uri + * Canonical Uri of the ValueSet + * @return The valueset (must not be null, but can be an empty ValueSet) + */ + ValueSet fetchValueSet(FhirContext theContext, String uri); /** * Loads a resource needed by the validation (a StructureDefinition, or a diff --git a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ServerExceptionDstu3Test.java b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ServerExceptionDstu3Test.java index 25ef830c6a9..0383d6cd320 100644 --- a/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ServerExceptionDstu3Test.java +++ b/hapi-fhir-structures-dstu3/src/test/java/ca/uhn/fhir/rest/server/ServerExceptionDstu3Test.java @@ -1,7 +1,10 @@ package ca.uhn.fhir.rest.server; import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.rest.annotation.Create; +import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Search; +import ca.uhn.fhir.rest.api.MethodOutcome; import ca.uhn.fhir.rest.server.exceptions.AuthenticationException; import ca.uhn.fhir.rest.server.exceptions.BaseServerResponseException; import ca.uhn.fhir.rest.server.exceptions.InternalErrorException; @@ -10,14 +13,17 @@ import ca.uhn.fhir.util.PortUtil; import ca.uhn.fhir.util.TestUtil; import com.google.common.base.Charsets; import org.apache.commons.io.IOUtils; +import org.apache.commons.lang3.Validate; import org.apache.http.client.methods.CloseableHttpResponse; import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; import org.apache.http.impl.client.CloseableHttpClient; import org.apache.http.impl.client.HttpClientBuilder; import org.apache.http.impl.conn.PoolingHttpClientConnectionManager; import org.eclipse.jetty.server.Server; import org.eclipse.jetty.servlet.ServletHandler; import org.eclipse.jetty.servlet.ServletHolder; +import org.hl7.fhir.dstu3.model.IdType; import org.hl7.fhir.dstu3.model.OperationOutcome; import org.hl7.fhir.dstu3.model.OperationOutcome.IssueType; import org.hl7.fhir.dstu3.model.Patient; @@ -26,6 +32,7 @@ import org.junit.AfterClass; import org.junit.BeforeClass; import org.junit.Test; +import java.io.IOException; import java.nio.charset.StandardCharsets; import java.util.List; import java.util.concurrent.TimeUnit; @@ -93,6 +100,22 @@ public class ServerExceptionDstu3Test { } + @Test + public void testPostWithNoBody() throws IOException { + + HttpPost httpPost = new HttpPost("http://localhost:" + ourPort + "/Patient"); + try (CloseableHttpResponse status = ourClient.execute(httpPost)) { + String responseContent = IOUtils.toString(status.getEntity().getContent(), StandardCharsets.UTF_8); + ourLog.info(status.getStatusLine().toString()); + ourLog.info(responseContent); + + assertEquals(201, status.getStatusLine().getStatusCode()); + assertThat(status.getFirstHeader("Location").getValue(), containsString("Patient/123")); + } + + } + + @Test public void testAuthorize() throws Exception { @@ -125,6 +148,12 @@ public class ServerExceptionDstu3Test { throw ourException; } + @Create() + public MethodOutcome create(@ResourceParam Patient thePatient) { + Validate.isTrue(thePatient == null); + return new MethodOutcome().setId(new IdType("Patient/123")); + } + } @AfterClass diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java index b013796c26c..f50726b5613 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/DefaultProfileValidationSupport.java @@ -158,8 +158,9 @@ public class DefaultProfileValidationSupport implements IValidationSupport { return provideStructureDefinitionMap(theContext).get(url); } - ValueSet fetchValueSet(FhirContext theContext, String theSystem) { - return (ValueSet) fetchCodeSystemOrValueSet(theContext, theSystem, false); + @Override + public ValueSet fetchValueSet(FhirContext theContext, String uri) { + return (ValueSet) fetchCodeSystemOrValueSet(theContext, uri, false); } public void flush() { diff --git a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.java b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.java index 08fcc8e83e2..d0c31bddec2 100644 --- a/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.java +++ b/hapi-fhir-structures-r4/src/main/java/org/hl7/fhir/r4/hapi/ctx/IValidationSupport.java @@ -6,6 +6,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.CodeSystem.ConceptDefinitionComponent; import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r4.terminologies.ValueSetExpander; import org.hl7.fhir.utilities.validation.ValidationMessage.IssueSeverity; @@ -32,14 +33,23 @@ public interface IValidationSupport List fetchAllStructureDefinitions(FhirContext theContext); /** - * Fetch a code system by ID + * Fetch a code system by Uri * - * @param theSystem - * The code system - * @return The valueset (must not be null, but can be an empty ValueSet) + * @param uri + * Canonical Uri of the code system + * @return The valueset (must not be null, but can be an empty ValueSet) */ @Override - CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem); + CodeSystem fetchCodeSystem(FhirContext theContext, String uri); + + /** + * Fetch a valueset by Uri + * + * @param uri + * Canonical Uri of the ValueSet + * @return The valueset (must not be null, but can be an empty ValueSet) + */ + ValueSet fetchValueSet(FhirContext theContext, String uri); /** * Loads a resource needed by the validation (a StructureDefinition, or a diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/CachingValidationSupport.java index 59c6ea38d42..3d95895bd98 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/CachingValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/CachingValidationSupport.java @@ -41,8 +41,13 @@ public class CachingValidationSupport implements IValidationSupport { } @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return myWrap.fetchCodeSystem(theContext, theSystem); + public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { + return myWrap.fetchCodeSystem(theContext, uri); + } + + @Override + public ValueSet fetchValueSet(FhirContext theContext, String uri) { + return myWrap.fetchValueSet(theContext, uri); } @Override diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java index 0b32d594219..8cd61f35972 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/PrePopulatedValidationSupport.java @@ -146,11 +146,16 @@ public class PrePopulatedValidationSupport implements IValidationSupport { } @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return myCodeSystems.get(theSystem); - } + public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { + return myCodeSystems.get(uri); + } - @SuppressWarnings("unchecked") + @Override + public ValueSet fetchValueSet(FhirContext theContext, String uri) { + return myValueSets.get(uri); + } + + @SuppressWarnings("unchecked") @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { if (theClass.equals(StructureDefinition.class)) { diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java index c4a87b4f8d5..1d7fb84585b 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/dstu3/hapi/validation/ValidationSupportChain.java @@ -5,6 +5,7 @@ import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.CodeSystem; import org.hl7.fhir.dstu3.model.StructureDefinition; import org.hl7.fhir.dstu3.model.UriType; +import org.hl7.fhir.dstu3.model.ValueSet; import org.hl7.fhir.dstu3.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.dstu3.model.ValueSet.ValueSetExpansionComponent; import org.hl7.fhir.instance.model.api.IBaseResource; @@ -101,6 +102,17 @@ public class ValidationSupportChain implements IValidationSupport { return null; } + @Override + public ValueSet fetchValueSet(FhirContext theCtx, String uri) { + for (IValidationSupport next : myChain) { + ValueSet retVal = next.fetchValueSet(theCtx, uri); + if (retVal != null) { + return retVal; + } + } + return null; + } + @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { for (IValidationSupport next : myChain) { diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/CachingValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/CachingValidationSupport.java index 7719c458d55..3015b22abf3 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/CachingValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/CachingValidationSupport.java @@ -42,8 +42,13 @@ public class CachingValidationSupport implements IValidationSupport { } @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return myWrap.fetchCodeSystem(theContext, theSystem); + public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { + return myWrap.fetchCodeSystem(theContext, uri); + } + + @Override + public ValueSet fetchValueSet(FhirContext theContext, String uri) { + return myWrap.fetchValueSet(theContext, uri); } @Override diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/PrePopulatedValidationSupport.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/PrePopulatedValidationSupport.java index 1b85736e4a5..d27c3afc054 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/PrePopulatedValidationSupport.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/PrePopulatedValidationSupport.java @@ -146,12 +146,18 @@ public class PrePopulatedValidationSupport implements IValidationSupport { return new ArrayList(myStructureDefinitions.values()); } - @Override - public CodeSystem fetchCodeSystem(FhirContext theContext, String theSystem) { - return myCodeSystems.get(theSystem); - } + @Override + public CodeSystem fetchCodeSystem(FhirContext theContext, String uri) { + return myCodeSystems.get(uri); + } - @SuppressWarnings("unchecked") + @Override + public ValueSet fetchValueSet(FhirContext theContext, String uri) { + return myValueSets.get(uri); + } + + + @SuppressWarnings("unchecked") @Override public T fetchResource(FhirContext theContext, Class theClass, String theUri) { if (theClass.equals(StructureDefinition.class)) { diff --git a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/ValidationSupportChain.java b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/ValidationSupportChain.java index e3992d4d5d3..be28686504c 100644 --- a/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/ValidationSupportChain.java +++ b/hapi-fhir-validation/src/main/java/org/hl7/fhir/r4/hapi/validation/ValidationSupportChain.java @@ -7,6 +7,7 @@ import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.r4.hapi.ctx.IValidationSupport; import org.hl7.fhir.r4.model.CodeSystem; import org.hl7.fhir.r4.model.StructureDefinition; +import org.hl7.fhir.r4.model.ValueSet; import org.hl7.fhir.r4.model.ValueSet.ConceptSetComponent; import org.hl7.fhir.r4.terminologies.ValueSetExpander; @@ -70,9 +71,20 @@ public class ValidationSupportChain implements IValidationSupport { } @Override - public CodeSystem fetchCodeSystem(FhirContext theCtx, String theSystem) { + public CodeSystem fetchCodeSystem(FhirContext theCtx, String uri) { for (IValidationSupport next : myChain) { - CodeSystem retVal = next.fetchCodeSystem(theCtx, theSystem); + CodeSystem retVal = next.fetchCodeSystem(theCtx, uri); + if (retVal != null) { + return retVal; + } + } + return null; + } + + @Override + public ValueSet fetchValueSet(FhirContext theCtx, String uri) { + for (IValidationSupport next : myChain) { + ValueSet retVal = next.fetchValueSet(theCtx, uri); if (retVal != null) { return retVal; } diff --git a/pom.xml b/pom.xml index 7429fbb8f42..9d3b3042615 100755 --- a/pom.xml +++ b/pom.xml @@ -1464,7 +1464,7 @@ org.apache.maven.plugins maven-failsafe-plugin - 2.21.0 + 3.0.0-M3 org.apache.maven.plugins @@ -1491,7 +1491,7 @@ org.apache.maven.plugins maven-surefire-plugin - 2.21.0 + 3.0.0-M3 true random @@ -2268,7 +2268,6 @@ hapi-fhir-jpaserver-elasticsearch hapi-fhir-jpaserver-migrate restful-server-example - restful-server-example-test hapi-fhir-testpage-overlay hapi-fhir-jpaserver-uhnfhirtest hapi-fhir-client-okhttp diff --git a/restful-server-example-test/.gitignore b/restful-server-example-test/.gitignore deleted file mode 100644 index 2fca895151a..00000000000 --- a/restful-server-example-test/.gitignore +++ /dev/null @@ -1,125 +0,0 @@ -/target/ - -# Created by https://www.gitignore.io - -### Java ### -*.class - -# Mobile Tools for Java (J2ME) -.mtj.tmp/ - -# Package Files # -*.jar -*.war -*.ear - -# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml -hs_err_pid* - - -### Maven ### -target/ -pom.xml.tag -pom.xml.releaseBackup -pom.xml.versionsBackup -pom.xml.next -release.properties -dependency-reduced-pom.xml -buildNumber.properties - - -### Vim ### -[._]*.s[a-w][a-z] -[._]s[a-w][a-z] -*.un~ -Session.vim -.netrwhist -*~ - - -### Intellij ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm - -*.iml - -## Directory-based project format: -.idea/ -# if you remove the above rule, at least ignore the following: - -# User-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries - -# Sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# .idea/uiDesigner.xml - -# Gradle: -# .idea/gradle.xml -# .idea/libraries - -# Mongo Explorer plugin: -# .idea/mongoSettings.xml - -## File-based project format: -*.ipr -*.iws - -## Plugin-specific files: - -# IntelliJ -/out/ - -# mpeltonen/sbt-idea plugin -.idea_modules/ - -# JIRA plugin -atlassian-ide-plugin.xml - -# Crashlytics plugin (for Android Studio and IntelliJ) -com_crashlytics_export_strings.xml -crashlytics.properties -crashlytics-build.properties - - - -### Eclipse ### -*.pydevproject -.metadata -.gradle -bin/ -tmp/ -*.tmp -*.bak -*.swp -*~.nib -local.properties -.loadpath - -# Eclipse Core -.project - -# External tool builders -.externalToolBuilders/ - -# Locally stored "Eclipse launch configurations" -*.launch - -# CDT-specific -.cproject - -# JDT-specific (Eclipse Java Development Tools) - -# PDT-specific -.buildpath - -# sbteclipse plugin -.target - -# TeXlipse plugin -.texlipse - diff --git a/restful-server-example-test/pom.xml b/restful-server-example-test/pom.xml deleted file mode 100644 index 4ce3d078bde..00000000000 --- a/restful-server-example-test/pom.xml +++ /dev/null @@ -1,81 +0,0 @@ - - 4.0.0 - - - ca.uhn.hapi.fhir - hapi-fhir - 3.8.0-SNAPSHOT - ../pom.xml - - - restful-server-example-test - jar - - HAPI FHIR Sample RESTful Server - Tests - - - - ca.uhn.hapi.fhir - hapi-fhir-base - ${project.version} - - - ca.uhn.hapi.fhir - hapi-fhir-structures-dstu2 - ${project.version} - test - - - ch.qos.logback - logback-classic - true - - - - - ca.uhn.hapi.fhir - hapi-fhir-client - ${project.version} - test - - - org.eclipse.jetty - jetty-servlets - test - - - org.eclipse.jetty - jetty-servlet - test - - - org.eclipse.jetty - jetty-server - test - - - org.eclipse.jetty - jetty-util - test - - - org.eclipse.jetty - jetty-webapp - test - - - - - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - - - diff --git a/restful-server-example-test/src/test/java/ca/uhn/example/ExampleTest.java b/restful-server-example-test/src/test/java/ca/uhn/example/ExampleTest.java deleted file mode 100644 index b4e20f5c83a..00000000000 --- a/restful-server-example-test/src/test/java/ca/uhn/example/ExampleTest.java +++ /dev/null @@ -1,97 +0,0 @@ -package ca.uhn.example; - -import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertThat; - -import java.io.File; - -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.webapp.WebAppContext; -import org.hamcrest.core.StringContains; -import org.junit.*; - -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.model.dstu2.resource.Bundle; -import ca.uhn.fhir.model.dstu2.resource.Patient; -import ca.uhn.fhir.rest.client.api.IGenericClient; - -public class ExampleTest { - - private static Integer ourPort; - private static Server ourServer; - private static FhirContext ourCtx; - private static IGenericClient ourClient; - - @AfterClass - public static void afterClass() throws Exception { - if (ourServer != null) { - ourServer.stop(); - } - - System.clearProperty("ca.uhn.fhir.to.TesterConfig_SYSPROP_FORCE_SERVERS"); - - } - - /** - * Tests here have some weird windows inconsistency relating to the path for finding the WAR file. Since this test isn't really important to work multiplatform, we can skip it - */ - public static boolean isWindows() { - return System.getProperty("os.name").startsWith("Windows"); - } - - @Test - public void test01Search() throws Exception { - if (isWindows()) { - return; - } - - Bundle results = ourClient.search().forResource(Patient.class).returnBundle(Bundle.class).execute(); - assertEquals(1, results.getEntry().size()); - } - - @Test - public void test02Read() throws Exception { - if (isWindows()) { - return; - } - - Patient results = ourClient.read(Patient.class, "1"); - assertThat(results.getNameFirstRep().getGivenAsSingleString(), StringContains.containsString("PatientOne")); - } - - @BeforeClass - public static void beforeClass() throws Exception { - if (isWindows()) { - return; - } - - if (ourPort != null) { - return; - } - - ourPort = RandomServerPortProvider.findFreePort(); - ourServer = new Server(ourPort); - - String base = "http://localhost:" + ourPort + "/fhir"; - System.setProperty("ca.uhn.fhir.to.TesterConfig_SYSPROP_FORCE_SERVERS", "example , Restful Server Example , " + base); - - WebAppContext root = new WebAppContext(); - root.setAllowDuplicateFragmentNames(true); - - root.setWar(new File("../restful-server-example/target/restful-server-example.war").toURI().toString()); - root.setContextPath("/"); - root.setAttribute(WebAppContext.BASETEMPDIR, "target/tempextrtact"); - root.setParentLoaderPriority(false); - root.setCopyWebInf(true); - root.setCopyWebDir(true); - - ourServer.setHandler(root); - - ourServer.start(); - - ourCtx = FhirContext.forDstu2(); - ourClient = ourCtx.newRestfulGenericClient(base); - - } - -} diff --git a/restful-server-example-test/src/test/java/ca/uhn/example/RandomServerPortProvider.java b/restful-server-example-test/src/test/java/ca/uhn/example/RandomServerPortProvider.java deleted file mode 100644 index 3afd9e5f95f..00000000000 --- a/restful-server-example-test/src/test/java/ca/uhn/example/RandomServerPortProvider.java +++ /dev/null @@ -1,37 +0,0 @@ -package ca.uhn.example; - - -import java.io.IOException; -import java.net.ServerSocket; -import java.util.ArrayList; -import java.util.List; - -/** - * Finds and provides a free port to use in unit tests - */ -public class RandomServerPortProvider { - - private static List ourPorts = new ArrayList(); - - public static int findFreePort() { - ServerSocket server; - try { - server = new ServerSocket(0); - int port = server.getLocalPort(); - ourPorts.add(port); - server.close(); - Thread.sleep(500); - return port; - } catch (IOException e) { - throw new Error(e); - } catch (InterruptedException e) { - throw new Error(e); - } - } - - public static List list() { - return ourPorts; - } - -} - \ No newline at end of file diff --git a/restful-server-example-test/src/test/resources/logback-test.xml b/restful-server-example-test/src/test/resources/logback-test.xml deleted file mode 100644 index e5cbbb9c22e..00000000000 --- a/restful-server-example-test/src/test/resources/logback-test.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} [%file:%line] - %msg%n - - - - - - - - - - - - - - - - - - - - - diff --git a/restful-server-example/src/main/webapp/WEB-INF/web.xml b/restful-server-example/src/main/webapp/WEB-INF/web.xml index 49bc32a74f6..cfc2fb6f4cb 100644 --- a/restful-server-example/src/main/webapp/WEB-INF/web.xml +++ b/restful-server-example/src/main/webapp/WEB-INF/web.xml @@ -36,4 +36,4 @@ /fhir/* - \ No newline at end of file + diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 51d5b3a6c99..25257aa58e4 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -137,6 +137,15 @@ the resource version is not updated and no new version is created. In this situation, the update time was modified however. It will no longer be updated. + + Performing a PUT or POST against a HAPI FHIR Server with no request body caused an + HTTP 500 to be returned instead of a more appropriate HTTP 400. This has been + corrected. + + + The fetchValueSet method on IValidationSupport implementation was not visible and could + not be overridden. Thanks to Patrick Werner for the pull reuqest! +