diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java index e80b6e39d24..0cec64a6961 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/BaseSubscriptionInterceptor.java @@ -48,6 +48,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.messaging.MessageHandler; import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.support.ExecutorSubscribableChannel; @@ -92,8 +93,10 @@ public abstract class BaseSubscriptionInterceptor exten @Autowired(required = false) @Qualifier("myEventDefinitionDaoR4") private IFhirResourceDao myEventDefinitionDaoR4; - @Autowired + @Autowired() private PlatformTransactionManager myTxManager; + @Autowired + private AsyncTaskExecutor myAsyncTaskExecutor; /** * Constructor @@ -364,6 +367,11 @@ public abstract class BaseSubscriptionInterceptor exten }); } + @VisibleForTesting + public void setAsyncTaskExecutorForUnitTest(AsyncTaskExecutor theAsyncTaskExecutor) { + myAsyncTaskExecutor = theAsyncTaskExecutor; + } + public void setFhirContext(FhirContext theCtx) { myCtx = theCtx; } @@ -455,7 +463,7 @@ public abstract class BaseSubscriptionInterceptor exten } if (mySubscriptionActivatingSubscriber == null) { - mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager); + mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager, myAsyncTaskExecutor); } registerSubscriptionCheckingSubscriber(); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java index 66fb005fe0a..52623c4c511 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/subscription/SubscriptionActivatingSubscriber.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription; * 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. @@ -25,41 +25,51 @@ import ca.uhn.fhir.jpa.dao.IFhirResourceDao; import ca.uhn.fhir.rest.api.RestOperationTypeEnum; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.SubscriptionUtil; -import org.apache.commons.lang3.time.DateUtils; +import com.google.common.annotations.VisibleForTesting; +import org.apache.commons.lang3.Validate; import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.r4.model.Subscription; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.messaging.MessagingException; import org.springframework.scheduling.TaskScheduler; import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.TransactionStatus; -import org.springframework.transaction.support.*; +import org.springframework.transaction.support.TransactionCallbackWithoutResult; +import org.springframework.transaction.support.TransactionSynchronizationAdapter; +import org.springframework.transaction.support.TransactionSynchronizationManager; +import org.springframework.transaction.support.TransactionTemplate; import java.util.Date; -import java.util.concurrent.Callable; -import java.util.concurrent.ExecutorService; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; @SuppressWarnings("unchecked") public class SubscriptionActivatingSubscriber { + private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest; private final IFhirResourceDao mySubscriptionDao; private final BaseSubscriptionInterceptor mySubscriptionInterceptor; private final PlatformTransactionManager myTransactionManager; + private final AsyncTaskExecutor myTaskExecutor; private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingSubscriber.class); private FhirContext myCtx; private Subscription.SubscriptionChannelType myChannelType; + /** * Constructor */ - public SubscriptionActivatingSubscriber(IFhirResourceDao theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, PlatformTransactionManager theTransactionManager) { + public SubscriptionActivatingSubscriber(IFhirResourceDao theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, PlatformTransactionManager theTransactionManager, AsyncTaskExecutor theTaskExecutor) { mySubscriptionDao = theSubscriptionDao; mySubscriptionInterceptor = theSubscriptionInterceptor; myChannelType = theChannelType; myCtx = theSubscriptionDao.getContext(); myTransactionManager = theTransactionManager; + myTaskExecutor = theTaskExecutor; + Validate.notNull(theTaskExecutor); } public void activateAndRegisterSubscriptionIfRequired(final IBaseResource theSubscription) { @@ -75,14 +85,40 @@ public class SubscriptionActivatingSubscriber { final String activeStatus = Subscription.SubscriptionStatus.ACTIVE.toCode(); if (requestedStatus.equals(statusString)) { if (TransactionSynchronizationManager.isSynchronizationActive()) { + /* + * If we're in a transaction, we don't want to try and change the status from + * requested to active within the same transaction because it's too late by + * the time we get here to make modifications to the payload. + * + * So, we register a synchronization, meaning that when the transaction is + * finished, we'll schedule a task to do this in a separate worker thread + * to avoid any possibility of conflict. + */ TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() { @Override public void afterCommit() { - activateSubscription(status, activeStatus, theSubscription, requestedStatus); + Future activationFuture = myTaskExecutor.submit(new Runnable() { + @Override + public void run() { + activateSubscription(activeStatus, theSubscription, requestedStatus); + } + }); + + /* + * If we're running in a unit test, it's nice to be predictable in + * terms of order... In the real world it's a recipe for deadlocks + */ + if (ourWaitForSubscriptionActivationSynchronouslyForUnitTest) { + try { + activationFuture.get(5, TimeUnit.SECONDS); + } catch (Exception e) { + ourLog.error("Failed to activate subscription", e); + } + } } }); } else { - activateSubscription(status, activeStatus, theSubscription, requestedStatus); + activateSubscription(activeStatus, theSubscription, requestedStatus); } } else if (activeStatus.equals(statusString)) { if (!mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement())) { @@ -97,21 +133,22 @@ public class SubscriptionActivatingSubscriber { } } - private void activateSubscription(IPrimitiveType theStatus, String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) { - theStatus.setValueAsString(theActiveStatus); - ourLog.info("Activating and registering subscription {} from status {} to {}", theSubscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus); + private void activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) { + IBaseResource subscription = mySubscriptionDao.read(theSubscription.getIdElement()); + + ourLog.info("Activating and registering subscription {} from status {} to {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus); try { - mySubscriptionDao.update(theSubscription); + SubscriptionUtil.setStatus(myCtx, subscription, theActiveStatus); + mySubscriptionDao.update(subscription); + mySubscriptionInterceptor.registerSubscription(subscription.getIdElement(), subscription); } catch (final UnprocessableEntityException e) { - ourLog.info("Changing status of {} to ERROR", theSubscription.getIdElement()); - IBaseResource subscription = mySubscriptionDao.read(theSubscription.getIdElement()); + ourLog.info("Changing status of {} to ERROR", subscription.getIdElement()); SubscriptionUtil.setStatus(myCtx, subscription, "error"); SubscriptionUtil.setReason(myCtx, subscription, e.getMessage()); mySubscriptionDao.update(subscription); } } - public void handleMessage(RestOperationTypeEnum theOperationType, IIdType theId, final IBaseResource theSubscription) throws MessagingException { switch (theOperationType) { @@ -137,4 +174,9 @@ public class SubscriptionActivatingSubscriber { } + @VisibleForTesting + public static void setWaitForSubscriptionActivationSynchronouslyForUnitTest(boolean theWaitForSubscriptionActivationSynchronouslyForUnitTest) { + ourWaitForSubscriptionActivationSynchronouslyForUnitTest = theWaitForSubscriptionActivationSynchronouslyForUnitTest; + } + } diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java index 0328b890c95..dc3e746cefb 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu2.java @@ -74,7 +74,7 @@ public class SubscriptionsRequireManualActivationInterceptorDstu2 extends Server if (newStatus == null) { String actualCode = subscription.getStatusElement().getValueAsString(); - throw new UnprocessableEntityException("Can not " + theOperation.getCode() + " resource: Subscription.status must be populated" + ((isNotBlank(actualCode)) ? " (invalid value " + actualCode + ")" : "")); + throw new UnprocessableEntityException("Can not " + theOperation.getCode() + " resource: Subscription.status must be populated on this server" + ((isNotBlank(actualCode)) ? " (invalid value " + actualCode + ")" : "")); } if (theOldResourceOrNull != null) { @@ -108,7 +108,7 @@ public class SubscriptionsRequireManualActivationInterceptorDstu2 extends Server } if (theSubscription.getStatus() == null) { - throw new UnprocessableEntityException("Can not " + theOperation.getCode().toLowerCase() + " resource: Subscription.status must be populated"); + throw new UnprocessableEntityException("Can not " + theOperation.getCode().toLowerCase() + " resource: Subscription.status must be populated on this server"); } throw new UnprocessableEntityException("Subscription.status must be '" + SubscriptionStatusEnum.OFF.getCode() + "' or '" + SubscriptionStatusEnum.REQUESTED.getCode() + "' on a newly created subscription"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java index 3bdcc708385..d8263c72626 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorDstu3.java @@ -74,7 +74,7 @@ public class SubscriptionsRequireManualActivationInterceptorDstu3 extends Server if (newStatus == null) { String actualCode = subscription.getStatusElement().getValueAsString(); - throw new UnprocessableEntityException("Can not " + theOperation.getCode() + " resource: Subscription.status must be populated" + ((isNotBlank(actualCode)) ? " (invalid value " + actualCode + ")" : "")); + throw new UnprocessableEntityException("Can not " + theOperation.getCode() + " resource: Subscription.status must be populated on this server" + ((isNotBlank(actualCode)) ? " (invalid value " + actualCode + ")" : "")); } if (theOldResourceOrNull != null) { @@ -108,7 +108,7 @@ public class SubscriptionsRequireManualActivationInterceptorDstu3 extends Server } if (theSubscription.getStatus() == null) { - throw new UnprocessableEntityException("Can not " + theOperation.getCode().toLowerCase() + " resource: Subscription.status must be populated"); + throw new UnprocessableEntityException("Can not " + theOperation.getCode().toLowerCase() + " resource: Subscription.status must be populated on this server"); } throw new UnprocessableEntityException("Subscription.status must be '" + SubscriptionStatus.OFF.toCode() + "' or '" + SubscriptionStatus.REQUESTED.toCode() + "' on a newly created subscription"); diff --git a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java index e147c121934..fdccf19f0e4 100644 --- a/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java +++ b/hapi-fhir-jpaserver-base/src/main/java/ca/uhn/fhir/jpa/util/SubscriptionsRequireManualActivationInterceptorR4.java @@ -74,7 +74,7 @@ public class SubscriptionsRequireManualActivationInterceptorR4 extends ServerOpe if (newStatus == null) { String actualCode = subscription.getStatusElement().getValueAsString(); - throw new UnprocessableEntityException("Can not " + theOperation.getCode() + " resource: Subscription.status must be populated" + ((isNotBlank(actualCode)) ? " (invalid value " + actualCode + ")" : "")); + throw new UnprocessableEntityException("Can not " + theOperation.getCode() + " resource: Subscription.status must be populated on this server" + ((isNotBlank(actualCode)) ? " (invalid value " + actualCode + ")" : "")); } if (theOldResourceOrNull != null) { @@ -108,7 +108,7 @@ public class SubscriptionsRequireManualActivationInterceptorR4 extends ServerOpe } if (theSubscription.getStatus() == null) { - throw new UnprocessableEntityException("Can not " + theOperation.getCode().toLowerCase() + " resource: Subscription.status must be populated"); + throw new UnprocessableEntityException("Can not " + theOperation.getCode().toLowerCase() + " resource: Subscription.status must be populated on this server"); } throw new UnprocessableEntityException("Subscription.status must be '" + SubscriptionStatus.OFF.toCode() + "' or '" + SubscriptionStatus.REQUESTED.toCode() + "' on a newly created subscription"); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java index e12ba913717..01de5a78efa 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/config/TestDstu3Config.java @@ -47,7 +47,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 { @Override - public Connection getConnection() throws SQLException { + public Connection getConnection() { ConnectionWrapper retVal; try { retVal = new ConnectionWrapper(super.getConnection()); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java index e24e16d68e8..93dd9d83c19 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/BaseJpaDstu3Test.java @@ -1,21 +1,35 @@ package ca.uhn.fhir.jpa.dao.dstu3; -import static org.junit.Assert.fail; -import static org.mockito.Mockito.mock; - -import java.io.IOException; -import java.io.InputStream; -import java.util.Map; - -import javax.persistence.EntityManager; - +import ca.uhn.fhir.context.FhirContext; +import ca.uhn.fhir.jpa.config.TestDstu3Config; +import ca.uhn.fhir.jpa.dao.*; +import ca.uhn.fhir.jpa.dao.data.*; +import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; +import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; +import ca.uhn.fhir.jpa.entity.ResourceTable; +import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; +import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider; +import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc; +import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc; +import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; +import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; +import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; +import ca.uhn.fhir.parser.IParser; +import ca.uhn.fhir.parser.StrictErrorHandler; +import ca.uhn.fhir.rest.api.Constants; +import ca.uhn.fhir.rest.api.EncodingEnum; +import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; +import ca.uhn.fhir.util.TestUtil; +import ca.uhn.fhir.util.UrlUtil; import org.apache.commons.io.IOUtils; import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.Search; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.instance.model.api.IBaseResource; -import org.junit.*; +import org.junit.After; +import org.junit.AfterClass; +import org.junit.Before; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; @@ -27,29 +41,16 @@ import org.springframework.transaction.TransactionDefinition; import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.support.TransactionTemplate; -import ca.uhn.fhir.context.FhirContext; -import ca.uhn.fhir.jpa.config.TestDstu3Config; -import ca.uhn.fhir.jpa.dao.*; -import ca.uhn.fhir.jpa.dao.data.*; -import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest; -import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; -import ca.uhn.fhir.jpa.entity.ResourceTable; -import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3; -import ca.uhn.fhir.jpa.search.*; -import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc; -import ca.uhn.fhir.jpa.term.IHapiTerminologySvc; -import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3; -import ca.uhn.fhir.parser.IParser; -import ca.uhn.fhir.parser.StrictErrorHandler; -import ca.uhn.fhir.rest.api.Constants; -import ca.uhn.fhir.rest.api.EncodingEnum; -import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor; -import ca.uhn.fhir.rest.server.method.MethodUtil; -import ca.uhn.fhir.util.TestUtil; -import ca.uhn.fhir.util.UrlUtil; +import javax.persistence.EntityManager; +import java.io.IOException; +import java.io.InputStream; +import java.util.Map; + +import static org.junit.Assert.*; +import static org.mockito.Mockito.mock; @RunWith(SpringJUnit4ClassRunner.class) -@ContextConfiguration(classes= {TestDstu3Config.class}) +@ContextConfiguration(classes = {TestDstu3Config.class}) public abstract class BaseJpaDstu3Test extends BaseJpaTest { private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3; @@ -254,6 +255,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest { @Before @Transactional() public void beforePurgeDatabase() { + final EntityManager entityManager = this.myEntityManager; purgeDatabase(entityManager, myTxManager, mySearchParamPresenceSvc, mySearchCoordinatorSvc, mySearchParamRegsitry); } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InvalidSubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InvalidSubscriptionTest.java index ec3bd9dc58a..b958190e5f6 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InvalidSubscriptionTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/dstu3/FhirResourceDaoDstu3InvalidSubscriptionTest.java @@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.dstu3; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.DaoConfig; +import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingSubscriber; import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; @@ -9,6 +10,7 @@ import org.hl7.fhir.dstu3.model.Subscription; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.TransactionStatus; @@ -26,10 +28,16 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes @After public void afterResetDao() { + SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); } + @Before + public void before() { + SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); + } + @Test public void testCreateInvalidSubscriptionOkButCanNotActivate() { Subscription s = new Subscription(); @@ -45,7 +53,7 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes mySubscriptionDao.update(s); fail(); } catch (UnprocessableEntityException e) { - assertEquals("Subscription.criteria must be in the form \"{Resource Type}?[params]", e.getMessage()); + assertEquals("Subscription.criteria must be in the form \"{Resource Type}?[params]\"", e.getMessage()); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java index 5446252d716..9065285af1d 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4InvalidSubscriptionTest.java @@ -2,6 +2,7 @@ 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.subscription.SubscriptionActivatingSubscriber; import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.util.TestUtil; @@ -9,6 +10,7 @@ import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.r4.model.Subscription; import org.junit.After; import org.junit.AfterClass; +import org.junit.Before; import org.junit.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.TransactionStatus; @@ -26,10 +28,16 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { @After public void afterResetDao() { + SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); BaseHapiFhirDao.setValidationDisabledForUnitTest(false); } + @Before + public void before() { + SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); + } + @Test public void testCreateInvalidSubscriptionOkButCanNotActivate() { Subscription s = new Subscription(); @@ -45,7 +53,7 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test { mySubscriptionDao.update(s); fail(); } catch (UnprocessableEntityException e) { - assertEquals("Subscription.criteria must be in the form \"{Resource Type}?[params]", e.getMessage()); + assertEquals("Subscription.criteria must be in the form \"{Resource Type}?[params]\"", e.getMessage()); } } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java index 83f0b019701..4d056b65aa3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/dao/r4/FhirResourceDaoR4SearchPageExpiryTest.java @@ -1,5 +1,6 @@ package ca.uhn.fhir.jpa.dao.r4; +import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.dao.SearchParameterMap; import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.entity.Search; @@ -45,11 +46,13 @@ public class FhirResourceDaoR4SearchPageExpiryTest extends BaseJpaR4Test { public void before() { StaleSearchDeletingSvcImpl staleSearchDeletingSvc = AopTestUtils.getTargetObject(myStaleSearchDeletingSvc); staleSearchDeletingSvc.setCutoffSlackForUnitTest(0); + myDaoConfig.setCountSearchResultsUpTo(new DaoConfig().getCountSearchResultsUpTo()); } @Before public void beforeDisableResultReuse() { myDaoConfig.setReuseCachedSearchResultsForMillis(null); + myDaoConfig.setCountSearchResultsUpTo(10000); } @Test diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java index cb8e90910be..9ce8d0a1ef5 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/EmailSubscriptionDstu2Test.java @@ -22,6 +22,7 @@ import org.junit.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.core.task.AsyncTaskExecutor; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; @@ -42,6 +43,8 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test { @Autowired private List> myResourceDaos; + @Autowired + private AsyncTaskExecutor myAsyncTaskExecutor; @After public void after() throws Exception { @@ -70,6 +73,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test { mySubscriber.setResourceDaos(myResourceDaos); mySubscriber.setFhirContext(myFhirCtx); mySubscriber.setTxManager(ourTxManager); + mySubscriber.setAsyncTaskExecutorForUnitTest(myAsyncTaskExecutor); mySubscriber.start(); ourRestServer.registerInterceptor(mySubscriber); diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java index fd812b96435..4035b3a9ed3 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/email/EmailSubscriptionDstu3Test.java @@ -1,35 +1,21 @@ - package ca.uhn.fhir.jpa.subscription.email; -import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.dao.DaoConfig; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.jpa.util.JpaConstants; -import ca.uhn.fhir.rest.annotation.Create; -import ca.uhn.fhir.rest.annotation.ResourceParam; -import ca.uhn.fhir.rest.annotation.Update; -import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.MethodOutcome; -import ca.uhn.fhir.rest.server.IResourceProvider; -import ca.uhn.fhir.rest.server.RestfulServer; -import ca.uhn.fhir.util.PortUtil; import com.google.common.collect.Lists; import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.ServerSetup; -import org.eclipse.jetty.server.Server; -import org.eclipse.jetty.servlet.ServletContextHandler; -import org.eclipse.jetty.servlet.ServletHolder; import org.hl7.fhir.dstu3.model.*; -import org.hl7.fhir.instance.model.api.IBaseResource; import org.hl7.fhir.instance.model.api.IIdType; import org.junit.*; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; -import javax.servlet.http.HttpServletRequest; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -53,7 +39,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { public void afterUnregisterEmailListener() { ourLog.info("**** Starting @After *****"); - for (IIdType next : mySubscriptionIds){ + for (IIdType next : mySubscriptionIds) { ourClient.delete().resourceById(next).execute(); } mySubscriptionIds.clear(); @@ -71,7 +57,8 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { @Before public void beforeRegisterEmailListener() throws FolderException { - ourTestSmtp.purgeEmailFromAllMailboxes();; + ourTestSmtp.purgeEmailFromAllMailboxes(); + ; ourRestServer.registerInterceptor(ourEmailSubscriptionInterceptor); JavaMailEmailSender emailSender = new JavaMailEmailSender(); @@ -83,24 +70,6 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { ourEmailSubscriptionInterceptor.setDefaultFromAddress("123@hapifhir.io"); } - @AfterClass - public static void afterClass() { - ourTestSmtp.stop(); - } - - @BeforeClass - public static void beforeClass() { - ourListenerPort = RandomServerPortProvider.findFreePort(); - ServerSetup smtp = new ServerSetup(ourListenerPort, null, ServerSetup.PROTOCOL_SMTP); - smtp.setServerStartupTimeout(2000); - smtp.setReadTimeout(2000); - smtp.setConnectionTimeout(2000); - ourTestSmtp = new GreenMail(smtp); - ourTestSmtp.start(); - } - - - private Subscription createSubscription(String theCriteria, String thePayload) throws InterruptedException { Subscription subscription = new Subscription(); subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); @@ -161,15 +130,14 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { List received = Arrays.asList(ourTestSmtp.getReceivedMessages()); assertEquals(1, received.get(0).getFrom().length); - assertEquals("123@hapifhir.io", ((InternetAddress)received.get(0).getFrom()[0]).getAddress()); + assertEquals("123@hapifhir.io", ((InternetAddress) received.get(0).getFrom()[0]).getAddress()); assertEquals(1, received.get(0).getAllRecipients().length); - assertEquals("foo@example.com", ((InternetAddress)received.get(0).getAllRecipients()[0]).getAddress()); + assertEquals("foo@example.com", ((InternetAddress) received.get(0).getAllRecipients()[0]).getAddress()); assertEquals("text/plain; charset=us-ascii", received.get(0).getContentType()); assertEquals("This is the body", received.get(0).getContent().toString().trim()); assertEquals(mySubscriptionIds.get(0).toUnqualifiedVersionless().getValue(), received.get(0).getHeader("X-FHIR-Subscription")[0]); } - @Test public void testEmailSubscriptionWithCustom() throws Exception { String payload = "This is the body"; @@ -180,6 +148,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId()); Assert.assertNotNull(subscriptionTemp); + subscriptionTemp.getChannel().addExtension() .setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM) .setValue(new StringType("mailto:myfrom@from.com")); @@ -208,9 +177,9 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { List received = Arrays.asList(ourTestSmtp.getReceivedMessages()); assertEquals(1, received.size()); assertEquals(1, received.get(0).getFrom().length); - assertEquals("myfrom@from.com", ((InternetAddress)received.get(0).getFrom()[0]).getAddress()); + assertEquals("myfrom@from.com", ((InternetAddress) received.get(0).getFrom()[0]).getAddress()); assertEquals(1, received.get(0).getAllRecipients().length); - assertEquals("foo@example.com", ((InternetAddress)received.get(0).getAllRecipients()[0]).getAddress()); + assertEquals("foo@example.com", ((InternetAddress) received.get(0).getAllRecipients()[0]).getAddress()); assertEquals("text/plain; charset=us-ascii", received.get(0).getContentType()); assertEquals("This is a subject", received.get(0).getSubject().toString().trim()); assertEquals("This is the body", received.get(0).getContent().toString().trim()); @@ -235,14 +204,11 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { .setValue(new StringType("This is a subject")); subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless()); + IIdType id = ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute().getId(); + ourLog.info("Subscription ID is: {}", id.getValue()); - ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscriptionTemp)); - - - ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute(); waitForQueueToDrain(); - sendObservation(code, "SNOMED-CT"); waitForQueueToDrain(); @@ -256,17 +222,39 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test { List received = Arrays.asList(ourTestSmtp.getReceivedMessages()); assertEquals(1, received.size()); assertEquals(1, received.get(0).getFrom().length); - assertEquals("myfrom@from.com", ((InternetAddress)received.get(0).getFrom()[0]).getAddress()); + assertEquals("myfrom@from.com", ((InternetAddress) received.get(0).getFrom()[0]).getAddress()); assertEquals(1, received.get(0).getAllRecipients().length); - assertEquals("foo@example.com", ((InternetAddress)received.get(0).getAllRecipients()[0]).getAddress()); + assertEquals("foo@example.com", ((InternetAddress) received.get(0).getAllRecipients()[0]).getAddress()); assertEquals("text/plain; charset=us-ascii", received.get(0).getContentType()); assertEquals("This is a subject", received.get(0).getSubject().toString().trim()); assertEquals("This is the body", received.get(0).getContent().toString().trim()); assertEquals(mySubscriptionIds.get(0).toUnqualifiedVersionless().getValue(), received.get(0).getHeader("X-FHIR-Subscription")[0]); + + ourLog.info("Subscription: {}", myFhirCtx.newXmlParser().setPrettyPrint(true).encodeResourceToString(ourClient.history().onInstance(id).andReturnBundle(Bundle.class).execute())); + + Subscription subscription = ourClient.read().resource(Subscription.class).withId(id.toUnqualifiedVersionless()).execute(); + assertEquals(Subscription.SubscriptionStatus.ACTIVE, subscription.getStatus()); + assertEquals("3", subscription.getIdElement().getVersionIdPart()); } private void waitForQueueToDrain() throws InterruptedException { RestHookTestDstu2Test.waitForQueueToDrain(ourEmailSubscriptionInterceptor); } + @AfterClass + public static void afterClass() { + ourTestSmtp.stop(); + } + + @BeforeClass + public static void beforeClass() { + ourListenerPort = RandomServerPortProvider.findFreePort(); + ServerSetup smtp = new ServerSetup(ourListenerPort, null, ServerSetup.PROTOCOL_SMTP); + smtp.setServerStartupTimeout(2000); + smtp.setReadTimeout(2000); + smtp.setConnectionTimeout(2000); + ourTestSmtp = new GreenMail(smtp); + ourTestSmtp.start(); + } + } diff --git a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java index 9dea424bd06..a59744903d7 100644 --- a/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java +++ b/hapi-fhir-jpaserver-base/src/test/java/ca/uhn/fhir/jpa/subscription/r4/RestHookActivatesPreExistingSubscriptionsR4Test.java @@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.subscription.r4; import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; +import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingSubscriber; import ca.uhn.fhir.rest.annotation.ResourceParam; import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.api.Constants; @@ -38,11 +39,22 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc private static List ourContentTypes = new ArrayList<>(); private static List ourHeaders = new ArrayList<>(); + @After + public void afterResetSubscriptionActivatingInterceptor() { + SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false); + } + @After public void afterUnregisterRestHookListener() { ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor()); } + @Before + public void beforeSetSubscriptionActivatingInterceptor() { + SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true); + } + + private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException { Subscription subscription = new Subscription(); subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); diff --git a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java index 3ff59ca9263..e91b9270f5f 100644 --- a/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java +++ b/hapi-fhir-spring-boot/hapi-fhir-spring-boot-autoconfigure/src/main/java/ca/uhn/fhir/spring/boot/autoconfigure/FhirAutoConfiguration.java @@ -9,9 +9,9 @@ package ca.uhn.fhir.spring.boot.autoconfigure; * 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. @@ -21,11 +21,6 @@ package ca.uhn.fhir.spring.boot.autoconfigure; */ -import java.util.List; - -import javax.servlet.ServletException; -import javax.sql.DataSource; - import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; @@ -47,16 +42,10 @@ import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor; import ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor; import okhttp3.OkHttpClient; import org.apache.http.client.HttpClient; - import org.springframework.beans.factory.ObjectProvider; import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.EnableAutoConfiguration; -import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; -import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingClass; -import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; -import org.springframework.boot.autoconfigure.condition.ResourceCondition; +import org.springframework.boot.autoconfigure.condition.*; import org.springframework.boot.autoconfigure.domain.EntityScan; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; @@ -67,242 +56,269 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Conditional; import org.springframework.context.annotation.Configuration; import org.springframework.core.annotation.AnnotationAwareOrderComparator; +import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.data.jpa.repository.config.EnableJpaRepositories; +import org.springframework.scheduling.TaskScheduler; +import org.springframework.scheduling.concurrent.ConcurrentTaskScheduler; +import org.springframework.scheduling.concurrent.ScheduledExecutorFactoryBean; import org.springframework.util.CollectionUtils; +import javax.servlet.ServletException; +import javax.sql.DataSource; +import java.util.List; + /** * {@link EnableAutoConfiguration Auto-configuration} for HAPI FHIR. * * @author Mathieu Ouellet */ @Configuration -@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) +@AutoConfigureAfter({DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class}) @EnableConfigurationProperties(FhirProperties.class) public class FhirAutoConfiguration { - private final FhirProperties properties; + private final FhirProperties properties; - public FhirAutoConfiguration(FhirProperties properties) { - this.properties = properties; - } + public FhirAutoConfiguration(FhirProperties properties) { + this.properties = properties; + } - @Bean - @ConditionalOnMissingBean - public FhirContext fhirContext() { - FhirContext fhirContext = new FhirContext(properties.getVersion()); - return fhirContext; - } + @Bean + @ConditionalOnMissingBean + public FhirContext fhirContext() { + FhirContext fhirContext = new FhirContext(properties.getVersion()); + return fhirContext; + } - @Configuration - @ConditionalOnClass(AbstractJaxRsProvider.class) - @EnableConfigurationProperties(FhirProperties.class) - @ConfigurationProperties("hapi.fhir.rest") - @SuppressWarnings("serial") - static class FhirRestfulServerConfiguration extends RestfulServer { - private final FhirProperties properties; + @Configuration + @ConditionalOnClass(AbstractJaxRsProvider.class) + @EnableConfigurationProperties(FhirProperties.class) + @ConfigurationProperties("hapi.fhir.rest") + @SuppressWarnings("serial") + static class FhirRestfulServerConfiguration extends RestfulServer { - private final FhirContext fhirContext; + private final FhirProperties properties; - private final List resourceProviders; + private final FhirContext fhirContext; - private final IPagingProvider pagingProvider; + private final List resourceProviders; - private final List interceptors; + private final IPagingProvider pagingProvider; - private final List customizers; + private final List interceptors; - public FhirRestfulServerConfiguration( - FhirProperties properties, - FhirContext fhirContext, - ObjectProvider> resourceProviders, - ObjectProvider pagingProvider, - ObjectProvider> interceptors, - ObjectProvider> customizers) { - this.properties = properties; - this.fhirContext = fhirContext; - this.resourceProviders = resourceProviders.getIfAvailable(); - this.pagingProvider = pagingProvider.getIfAvailable(); - this.interceptors = interceptors.getIfAvailable(); - this.customizers = customizers.getIfAvailable(); - } + private final List customizers; - @Bean - public ServletRegistrationBean fhirServerRegistrationBean() { - ServletRegistrationBean registration = new ServletRegistrationBean(this, this.properties.getServer().getPath()); - registration.setLoadOnStartup(1); - return registration; - } + public FhirRestfulServerConfiguration( + FhirProperties properties, + FhirContext fhirContext, + ObjectProvider> resourceProviders, + ObjectProvider pagingProvider, + ObjectProvider> interceptors, + ObjectProvider> customizers) { + this.properties = properties; + this.fhirContext = fhirContext; + this.resourceProviders = resourceProviders.getIfAvailable(); + this.pagingProvider = pagingProvider.getIfAvailable(); + this.interceptors = interceptors.getIfAvailable(); + this.customizers = customizers.getIfAvailable(); + } - @Override - protected void initialize() throws ServletException { - super.initialize(); + private void customize() { + if (this.customizers != null) { + AnnotationAwareOrderComparator.sort(this.customizers); + for (FhirRestfulServerCustomizer customizer : this.customizers) { + customizer.customize(this); + } + } + } - setFhirContext(this.fhirContext); - setResourceProviders(this.resourceProviders); - setPagingProvider(this.pagingProvider); - setInterceptors(this.interceptors); + @Bean + public ServletRegistrationBean fhirServerRegistrationBean() { + ServletRegistrationBean registration = new ServletRegistrationBean(this, this.properties.getServer().getPath()); + registration.setLoadOnStartup(1); + return registration; + } - setServerAddressStrategy(new HardcodedServerAddressStrategy(this.properties.getServer().getPath())); + @Override + protected void initialize() throws ServletException { + super.initialize(); - customize(); - } + setFhirContext(this.fhirContext); + setResourceProviders(this.resourceProviders); + setPagingProvider(this.pagingProvider); + setInterceptors(this.interceptors); - private void customize() { - if (this.customizers != null) { - AnnotationAwareOrderComparator.sort(this.customizers); - for (FhirRestfulServerCustomizer customizer : this.customizers) { - customizer.customize(this); - } - } - } - } + setServerAddressStrategy(new HardcodedServerAddressStrategy(this.properties.getServer().getPath())); - @Configuration - @ConditionalOnClass(BaseJpaProvider.class) - @ConditionalOnBean(DataSource.class) - @EnableConfigurationProperties(FhirProperties.class) - static class FhirJpaServerConfiguration { + customize(); + } + } - @Configuration - @EntityScan("ca.uhn.fhir.jpa.entity") - @EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") - static class FhirJpaDaoConfiguration { + @Configuration + @ConditionalOnClass(BaseJpaProvider.class) + @ConditionalOnBean(DataSource.class) + @EnableConfigurationProperties(FhirProperties.class) + static class FhirJpaServerConfiguration { - @Bean - @ConditionalOnMissingBean - @ConfigurationProperties("hapi.fhir.jpa") - public DaoConfig fhirDaoConfig() { - DaoConfig fhirDaoConfig = new DaoConfig(); - return fhirDaoConfig; - } - } + @Bean() + @ConditionalOnMissingBean + public ScheduledExecutorFactoryBean scheduledExecutorService() { + ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean(); + b.setPoolSize(5); + return b; + } - @Configuration - @ConditionalOnBean({ DaoConfig.class, RestfulServer.class }) - @SuppressWarnings("rawtypes") - static class RestfulServerCustomizer implements FhirRestfulServerCustomizer { + @Bean + @ConditionalOnMissingBean + public AsyncTaskExecutor taskScheduler() { + ConcurrentTaskScheduler retVal = new ConcurrentTaskScheduler(); + retVal.setConcurrentExecutor(scheduledExecutorService().getObject()); + retVal.setScheduledExecutor(scheduledExecutorService().getObject()); + return retVal; + } - private final BaseJpaSystemProvider systemProviders; + @Configuration + @EntityScan("ca.uhn.fhir.jpa.entity") + @EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") + static class FhirJpaDaoConfiguration { - public RestfulServerCustomizer(ObjectProvider systemProviders) { - this.systemProviders = systemProviders.getIfAvailable(); - } + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.jpa") + public DaoConfig fhirDaoConfig() { + DaoConfig fhirDaoConfig = new DaoConfig(); + return fhirDaoConfig; + } - @Override - public void customize(RestfulServer server) { - server.setPlainProviders(systemProviders); - } - } + } - @Configuration - @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") - @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU3") - static class Dstu3 extends BaseJavaConfigDstu3 { - } + @Configuration + @ConditionalOnBean({DaoConfig.class, RestfulServer.class}) + @SuppressWarnings("rawtypes") + static class RestfulServerCustomizer implements FhirRestfulServerCustomizer { - @Configuration - @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") - @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU2") - static class Dstu2 extends BaseJavaConfigDstu2 { - } - } + private final BaseJpaSystemProvider systemProviders; - @Configuration - @Conditional(FhirValidationConfiguration.SchemaAvailableCondition.class) - @ConditionalOnProperty(name = "hapi.fhir.validation.enabled", matchIfMissing = true) - static class FhirValidationConfiguration { + public RestfulServerCustomizer(ObjectProvider systemProviders) { + this.systemProviders = systemProviders.getIfAvailable(); + } - @Bean - @ConditionalOnMissingBean - public RequestValidatingInterceptor requestValidatingInterceptor() { - return new RequestValidatingInterceptor(); - } + @Override + public void customize(RestfulServer server) { + server.setPlainProviders(systemProviders); + } + } - @Bean - @ConditionalOnMissingBean - @ConditionalOnProperty(name = "hapi.fhir.validation.request-only", havingValue = "false") - public ResponseValidatingInterceptor responseValidatingInterceptor() { - return new ResponseValidatingInterceptor(); - } + @Configuration + @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") + @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU3") + static class Dstu3 extends BaseJavaConfigDstu3 { + } - static class SchemaAvailableCondition extends ResourceCondition { + @Configuration + @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") + @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU2") + static class Dstu2 extends BaseJavaConfigDstu2 { + } + } - SchemaAvailableCondition() { - super("ValidationSchema", - "hapi.fhir.validation", - "schema-location", - "classpath:/org/hl7/fhir/instance/model/schema", - "classpath:/org/hl7/fhir/dstu2016may/model/schema", - "classpath:/org/hl7/fhir/dstu3/model/schema"); - } - } - } + @Configuration + @Conditional(FhirValidationConfiguration.SchemaAvailableCondition.class) + @ConditionalOnProperty(name = "hapi.fhir.validation.enabled", matchIfMissing = true) + static class FhirValidationConfiguration { - @Configuration - @ConditionalOnProperty("hapi.fhir.server.url") - @EnableConfigurationProperties(FhirProperties.class) - static class FhirRestfulClientConfiguration { + @Bean + @ConditionalOnMissingBean + public RequestValidatingInterceptor requestValidatingInterceptor() { + return new RequestValidatingInterceptor(); + } - private final FhirProperties properties; + @Bean + @ConditionalOnMissingBean + @ConditionalOnProperty(name = "hapi.fhir.validation.request-only", havingValue = "false") + public ResponseValidatingInterceptor responseValidatingInterceptor() { + return new ResponseValidatingInterceptor(); + } - private final List clientInterceptors; + static class SchemaAvailableCondition extends ResourceCondition { - public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider> clientInterceptors) { - this.properties = properties; - this.clientInterceptors = clientInterceptors.getIfAvailable(); - } + SchemaAvailableCondition() { + super("ValidationSchema", + "hapi.fhir.validation", + "schema-location", + "classpath:/org/hl7/fhir/instance/model/schema", + "classpath:/org/hl7/fhir/dstu2016may/model/schema", + "classpath:/org/hl7/fhir/dstu3/model/schema"); + } + } + } - @Bean - @ConditionalOnBean(IRestfulClientFactory.class) - public IGenericClient fhirClient(final IRestfulClientFactory clientFactory) { - IGenericClient fhirClient = clientFactory.newGenericClient(this.properties.getServer().getUrl()); - if (!CollectionUtils.isEmpty(this.clientInterceptors)) { - for (IClientInterceptor interceptor : this.clientInterceptors) { - fhirClient.registerInterceptor(interceptor); - } - } - return fhirClient; - } + @Configuration + @ConditionalOnProperty("hapi.fhir.server.url") + @EnableConfigurationProperties(FhirProperties.class) + static class FhirRestfulClientConfiguration { - @Configuration - @ConditionalOnClass(HttpClient.class) - @ConditionalOnMissingClass("okhttp3.OkHttpClient") - static class Apache { + private final FhirProperties properties; - private final FhirContext context; + private final List clientInterceptors; - public Apache(FhirContext context) { - this.context = context; - } + public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider> clientInterceptors) { + this.properties = properties; + this.clientInterceptors = clientInterceptors.getIfAvailable(); + } - @Bean - @ConditionalOnMissingBean - @ConfigurationProperties("hapi.fhir.rest.client.apache") - public IRestfulClientFactory fhirRestfulClientFactory() { - ApacheRestfulClientFactory restfulClientFactory = new ApacheRestfulClientFactory(this.context); - return restfulClientFactory; - } - } + @Bean + @ConditionalOnBean(IRestfulClientFactory.class) + public IGenericClient fhirClient(final IRestfulClientFactory clientFactory) { + IGenericClient fhirClient = clientFactory.newGenericClient(this.properties.getServer().getUrl()); + if (!CollectionUtils.isEmpty(this.clientInterceptors)) { + for (IClientInterceptor interceptor : this.clientInterceptors) { + fhirClient.registerInterceptor(interceptor); + } + } + return fhirClient; + } - @Configuration - @ConditionalOnClass(OkHttpClient.class) - static class OkHttp { + @Configuration + @ConditionalOnClass(HttpClient.class) + @ConditionalOnMissingClass("okhttp3.OkHttpClient") + static class Apache { - private final FhirContext context; + private final FhirContext context; - public OkHttp(FhirContext context) { - this.context = context; - } + public Apache(FhirContext context) { + this.context = context; + } - @Bean - @ConditionalOnMissingBean - @ConfigurationProperties("hapi.fhir.rest.client.okhttp") - public IRestfulClientFactory fhirRestfulClientFactory() { - OkHttpRestfulClientFactory restfulClientFactory = new OkHttpRestfulClientFactory(this.context); - return restfulClientFactory; - } - } - } + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.rest.client.apache") + public IRestfulClientFactory fhirRestfulClientFactory() { + ApacheRestfulClientFactory restfulClientFactory = new ApacheRestfulClientFactory(this.context); + return restfulClientFactory; + } + } + + @Configuration + @ConditionalOnClass(OkHttpClient.class) + static class OkHttp { + + private final FhirContext context; + + public OkHttp(FhirContext context) { + this.context = context; + } + + @Bean + @ConditionalOnMissingBean + @ConfigurationProperties("hapi.fhir.rest.client.okhttp") + public IRestfulClientFactory fhirRestfulClientFactory() { + OkHttpRestfulClientFactory restfulClientFactory = new OkHttpRestfulClientFactory(this.context); + return restfulClientFactory; + } + } + } }