Fix an issue in subscription interceptor which prevented the server from

starting if an invalid subscription was already in the database
This commit is contained in:
jamesagnew 2018-02-03 19:20:58 -05:00
parent 17b1ff727e
commit 86a0774305
14 changed files with 391 additions and 300 deletions

View File

@ -48,6 +48,7 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.messaging.MessageHandler; import org.springframework.messaging.MessageHandler;
import org.springframework.messaging.SubscribableChannel; import org.springframework.messaging.SubscribableChannel;
import org.springframework.messaging.support.ExecutorSubscribableChannel; import org.springframework.messaging.support.ExecutorSubscribableChannel;
@ -92,8 +93,10 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
@Autowired(required = false) @Autowired(required = false)
@Qualifier("myEventDefinitionDaoR4") @Qualifier("myEventDefinitionDaoR4")
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDaoR4; private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDaoR4;
@Autowired @Autowired()
private PlatformTransactionManager myTxManager; private PlatformTransactionManager myTxManager;
@Autowired
private AsyncTaskExecutor myAsyncTaskExecutor;
/** /**
* Constructor * Constructor
@ -364,6 +367,11 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
}); });
} }
@VisibleForTesting
public void setAsyncTaskExecutorForUnitTest(AsyncTaskExecutor theAsyncTaskExecutor) {
myAsyncTaskExecutor = theAsyncTaskExecutor;
}
public void setFhirContext(FhirContext theCtx) { public void setFhirContext(FhirContext theCtx) {
myCtx = theCtx; myCtx = theCtx;
} }
@ -455,7 +463,7 @@ public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> exten
} }
if (mySubscriptionActivatingSubscriber == null) { if (mySubscriptionActivatingSubscriber == null) {
mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager); mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(getSubscriptionDao(), getChannelType(), this, myTxManager, myAsyncTaskExecutor);
} }
registerSubscriptionCheckingSubscriber(); registerSubscriptionCheckingSubscriber();

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.api.RestOperationTypeEnum;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.SubscriptionUtil; 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.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.hl7.fhir.instance.model.api.IPrimitiveType; import org.hl7.fhir.instance.model.api.IPrimitiveType;
import org.hl7.fhir.r4.model.Subscription; import org.hl7.fhir.r4.model.Subscription;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.messaging.MessagingException; import org.springframework.messaging.MessagingException;
import org.springframework.scheduling.TaskScheduler; import org.springframework.scheduling.TaskScheduler;
import org.springframework.transaction.PlatformTransactionManager; import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionStatus; 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.Date;
import java.util.concurrent.Callable; import java.util.concurrent.Future;
import java.util.concurrent.ExecutorService; import java.util.concurrent.TimeUnit;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public class SubscriptionActivatingSubscriber { public class SubscriptionActivatingSubscriber {
private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest;
private final IFhirResourceDao mySubscriptionDao; private final IFhirResourceDao mySubscriptionDao;
private final BaseSubscriptionInterceptor mySubscriptionInterceptor; private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
private final PlatformTransactionManager myTransactionManager; private final PlatformTransactionManager myTransactionManager;
private final AsyncTaskExecutor myTaskExecutor;
private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingSubscriber.class); private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingSubscriber.class);
private FhirContext myCtx; private FhirContext myCtx;
private Subscription.SubscriptionChannelType myChannelType; private Subscription.SubscriptionChannelType myChannelType;
/** /**
* Constructor * Constructor
*/ */
public SubscriptionActivatingSubscriber(IFhirResourceDao<? extends IBaseResource> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, PlatformTransactionManager theTransactionManager) { public SubscriptionActivatingSubscriber(IFhirResourceDao<? extends IBaseResource> theSubscriptionDao, Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor, PlatformTransactionManager theTransactionManager, AsyncTaskExecutor theTaskExecutor) {
mySubscriptionDao = theSubscriptionDao; mySubscriptionDao = theSubscriptionDao;
mySubscriptionInterceptor = theSubscriptionInterceptor; mySubscriptionInterceptor = theSubscriptionInterceptor;
myChannelType = theChannelType; myChannelType = theChannelType;
myCtx = theSubscriptionDao.getContext(); myCtx = theSubscriptionDao.getContext();
myTransactionManager = theTransactionManager; myTransactionManager = theTransactionManager;
myTaskExecutor = theTaskExecutor;
Validate.notNull(theTaskExecutor);
} }
public void activateAndRegisterSubscriptionIfRequired(final IBaseResource theSubscription) { public void activateAndRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
@ -75,14 +85,40 @@ public class SubscriptionActivatingSubscriber {
final String activeStatus = Subscription.SubscriptionStatus.ACTIVE.toCode(); final String activeStatus = Subscription.SubscriptionStatus.ACTIVE.toCode();
if (requestedStatus.equals(statusString)) { if (requestedStatus.equals(statusString)) {
if (TransactionSynchronizationManager.isSynchronizationActive()) { 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() { TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override @Override
public void afterCommit() { 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 { } else {
activateSubscription(status, activeStatus, theSubscription, requestedStatus); activateSubscription(activeStatus, theSubscription, requestedStatus);
} }
} else if (activeStatus.equals(statusString)) { } else if (activeStatus.equals(statusString)) {
if (!mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement())) { if (!mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement())) {
@ -97,21 +133,22 @@ public class SubscriptionActivatingSubscriber {
} }
} }
private void activateSubscription(IPrimitiveType<?> theStatus, String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) { private void activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
theStatus.setValueAsString(theActiveStatus); IBaseResource subscription = mySubscriptionDao.read(theSubscription.getIdElement());
ourLog.info("Activating and registering subscription {} from status {} to {}", theSubscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus);
ourLog.info("Activating and registering subscription {} from status {} to {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus);
try { try {
mySubscriptionDao.update(theSubscription); SubscriptionUtil.setStatus(myCtx, subscription, theActiveStatus);
mySubscriptionDao.update(subscription);
mySubscriptionInterceptor.registerSubscription(subscription.getIdElement(), subscription);
} catch (final UnprocessableEntityException e) { } catch (final UnprocessableEntityException e) {
ourLog.info("Changing status of {} to ERROR", theSubscription.getIdElement()); ourLog.info("Changing status of {} to ERROR", subscription.getIdElement());
IBaseResource subscription = mySubscriptionDao.read(theSubscription.getIdElement());
SubscriptionUtil.setStatus(myCtx, subscription, "error"); SubscriptionUtil.setStatus(myCtx, subscription, "error");
SubscriptionUtil.setReason(myCtx, subscription, e.getMessage()); SubscriptionUtil.setReason(myCtx, subscription, e.getMessage());
mySubscriptionDao.update(subscription); mySubscriptionDao.update(subscription);
} }
} }
public void handleMessage(RestOperationTypeEnum theOperationType, IIdType theId, final IBaseResource theSubscription) throws MessagingException { public void handleMessage(RestOperationTypeEnum theOperationType, IIdType theId, final IBaseResource theSubscription) throws MessagingException {
switch (theOperationType) { switch (theOperationType) {
@ -137,4 +174,9 @@ public class SubscriptionActivatingSubscriber {
} }
@VisibleForTesting
public static void setWaitForSubscriptionActivationSynchronouslyForUnitTest(boolean theWaitForSubscriptionActivationSynchronouslyForUnitTest) {
ourWaitForSubscriptionActivationSynchronouslyForUnitTest = theWaitForSubscriptionActivationSynchronouslyForUnitTest;
}
} }

View File

@ -74,7 +74,7 @@ public class SubscriptionsRequireManualActivationInterceptorDstu2 extends Server
if (newStatus == null) { if (newStatus == null) {
String actualCode = subscription.getStatusElement().getValueAsString(); 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) { if (theOldResourceOrNull != null) {
@ -108,7 +108,7 @@ public class SubscriptionsRequireManualActivationInterceptorDstu2 extends Server
} }
if (theSubscription.getStatus() == null) { 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"); throw new UnprocessableEntityException("Subscription.status must be '" + SubscriptionStatusEnum.OFF.getCode() + "' or '" + SubscriptionStatusEnum.REQUESTED.getCode() + "' on a newly created subscription");

View File

@ -74,7 +74,7 @@ public class SubscriptionsRequireManualActivationInterceptorDstu3 extends Server
if (newStatus == null) { if (newStatus == null) {
String actualCode = subscription.getStatusElement().getValueAsString(); 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) { if (theOldResourceOrNull != null) {
@ -108,7 +108,7 @@ public class SubscriptionsRequireManualActivationInterceptorDstu3 extends Server
} }
if (theSubscription.getStatus() == null) { 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"); throw new UnprocessableEntityException("Subscription.status must be '" + SubscriptionStatus.OFF.toCode() + "' or '" + SubscriptionStatus.REQUESTED.toCode() + "' on a newly created subscription");

View File

@ -74,7 +74,7 @@ public class SubscriptionsRequireManualActivationInterceptorR4 extends ServerOpe
if (newStatus == null) { if (newStatus == null) {
String actualCode = subscription.getStatusElement().getValueAsString(); 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) { if (theOldResourceOrNull != null) {
@ -108,7 +108,7 @@ public class SubscriptionsRequireManualActivationInterceptorR4 extends ServerOpe
} }
if (theSubscription.getStatus() == null) { 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"); throw new UnprocessableEntityException("Subscription.status must be '" + SubscriptionStatus.OFF.toCode() + "' or '" + SubscriptionStatus.REQUESTED.toCode() + "' on a newly created subscription");

View File

@ -47,7 +47,7 @@ public class TestDstu3Config extends BaseJavaConfigDstu3 {
@Override @Override
public Connection getConnection() throws SQLException { public Connection getConnection() {
ConnectionWrapper retVal; ConnectionWrapper retVal;
try { try {
retVal = new ConnectionWrapper(super.getConnection()); retVal = new ConnectionWrapper(super.getConnection());

View File

@ -1,21 +1,35 @@
package ca.uhn.fhir.jpa.dao.dstu3; package ca.uhn.fhir.jpa.dao.dstu3;
import static org.junit.Assert.fail; import ca.uhn.fhir.context.FhirContext;
import static org.mockito.Mockito.mock; import ca.uhn.fhir.jpa.config.TestDstu3Config;
import ca.uhn.fhir.jpa.dao.*;
import java.io.IOException; import ca.uhn.fhir.jpa.dao.data.*;
import java.io.InputStream; import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import java.util.Map; import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString;
import ca.uhn.fhir.jpa.entity.ResourceTable;
import javax.persistence.EntityManager; 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.apache.commons.io.IOUtils;
import org.hibernate.search.jpa.FullTextEntityManager; import org.hibernate.search.jpa.FullTextEntityManager;
import org.hibernate.search.jpa.Search; import org.hibernate.search.jpa.Search;
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport; import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
import org.hl7.fhir.dstu3.model.*; import org.hl7.fhir.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseResource; 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.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier; 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.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate; import org.springframework.transaction.support.TransactionTemplate;
import ca.uhn.fhir.context.FhirContext; import javax.persistence.EntityManager;
import ca.uhn.fhir.jpa.config.TestDstu3Config; import java.io.IOException;
import ca.uhn.fhir.jpa.dao.*; import java.io.InputStream;
import ca.uhn.fhir.jpa.dao.data.*; import java.util.Map;
import ca.uhn.fhir.jpa.dao.dstu2.FhirResourceDaoDstu2SearchNoFtTest;
import ca.uhn.fhir.jpa.entity.ResourceIndexedSearchParamString; import static org.junit.Assert.*;
import ca.uhn.fhir.jpa.entity.ResourceTable; import static org.mockito.Mockito.mock;
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;
@RunWith(SpringJUnit4ClassRunner.class) @RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes= {TestDstu3Config.class}) @ContextConfiguration(classes = {TestDstu3Config.class})
public abstract class BaseJpaDstu3Test extends BaseJpaTest { public abstract class BaseJpaDstu3Test extends BaseJpaTest {
private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3; private static JpaValidationSupportChainDstu3 ourJpaValidationSupportChainDstu3;
@ -254,6 +255,7 @@ public abstract class BaseJpaDstu3Test extends BaseJpaTest {
@Before @Before
@Transactional() @Transactional()
public void beforePurgeDatabase() { public void beforePurgeDatabase() {
final EntityManager entityManager = this.myEntityManager; final EntityManager entityManager = this.myEntityManager;
purgeDatabase(entityManager, myTxManager, mySearchParamPresenceSvc, mySearchCoordinatorSvc, mySearchParamRegsitry); purgeDatabase(entityManager, myTxManager, mySearchParamPresenceSvc, mySearchCoordinatorSvc, mySearchParamRegsitry);
} }

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.dstu3;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; 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.hl7.fhir.instance.model.api.IIdType;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
@ -26,10 +28,16 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
@After @After
public void afterResetDao() { public void afterResetDao() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
} }
@Before
public void before() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
}
@Test @Test
public void testCreateInvalidSubscriptionOkButCanNotActivate() { public void testCreateInvalidSubscriptionOkButCanNotActivate() {
Subscription s = new Subscription(); Subscription s = new Subscription();
@ -45,7 +53,7 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
mySubscriptionDao.update(s); mySubscriptionDao.update(s);
fail(); fail();
} catch (UnprocessableEntityException e) { } 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());
} }
} }

View File

@ -2,6 +2,7 @@ package ca.uhn.fhir.jpa.dao.r4;
import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao; import ca.uhn.fhir.jpa.dao.BaseHapiFhirDao;
import ca.uhn.fhir.jpa.dao.DaoConfig; 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.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException; import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
import ca.uhn.fhir.util.TestUtil; 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.hl7.fhir.r4.model.Subscription;
import org.junit.After; import org.junit.After;
import org.junit.AfterClass; import org.junit.AfterClass;
import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.TransactionStatus; import org.springframework.transaction.TransactionStatus;
@ -26,10 +28,16 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
@After @After
public void afterResetDao() { public void afterResetDao() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy()); myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
BaseHapiFhirDao.setValidationDisabledForUnitTest(false); BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
} }
@Before
public void before() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
}
@Test @Test
public void testCreateInvalidSubscriptionOkButCanNotActivate() { public void testCreateInvalidSubscriptionOkButCanNotActivate() {
Subscription s = new Subscription(); Subscription s = new Subscription();
@ -45,7 +53,7 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
mySubscriptionDao.update(s); mySubscriptionDao.update(s);
fail(); fail();
} catch (UnprocessableEntityException e) { } 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());
} }
} }

View File

@ -1,5 +1,6 @@
package ca.uhn.fhir.jpa.dao.r4; 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.SearchParameterMap;
import ca.uhn.fhir.jpa.dao.data.ISearchDao; import ca.uhn.fhir.jpa.dao.data.ISearchDao;
import ca.uhn.fhir.jpa.entity.Search; import ca.uhn.fhir.jpa.entity.Search;
@ -45,11 +46,13 @@ public class FhirResourceDaoR4SearchPageExpiryTest extends BaseJpaR4Test {
public void before() { public void before() {
StaleSearchDeletingSvcImpl staleSearchDeletingSvc = AopTestUtils.getTargetObject(myStaleSearchDeletingSvc); StaleSearchDeletingSvcImpl staleSearchDeletingSvc = AopTestUtils.getTargetObject(myStaleSearchDeletingSvc);
staleSearchDeletingSvc.setCutoffSlackForUnitTest(0); staleSearchDeletingSvc.setCutoffSlackForUnitTest(0);
myDaoConfig.setCountSearchResultsUpTo(new DaoConfig().getCountSearchResultsUpTo());
} }
@Before @Before
public void beforeDisableResultReuse() { public void beforeDisableResultReuse() {
myDaoConfig.setReuseCachedSearchResultsForMillis(null); myDaoConfig.setReuseCachedSearchResultsForMillis(null);
myDaoConfig.setCountSearchResultsUpTo(10000);
} }
@Test @Test

View File

@ -22,6 +22,7 @@ import org.junit.*;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.task.AsyncTaskExecutor;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
@ -42,6 +43,8 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
@Autowired @Autowired
private List<IFhirResourceDao<?>> myResourceDaos; private List<IFhirResourceDao<?>> myResourceDaos;
@Autowired
private AsyncTaskExecutor myAsyncTaskExecutor;
@After @After
public void after() throws Exception { public void after() throws Exception {
@ -70,6 +73,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
mySubscriber.setResourceDaos(myResourceDaos); mySubscriber.setResourceDaos(myResourceDaos);
mySubscriber.setFhirContext(myFhirCtx); mySubscriber.setFhirContext(myFhirCtx);
mySubscriber.setTxManager(ourTxManager); mySubscriber.setTxManager(ourTxManager);
mySubscriber.setAsyncTaskExecutorForUnitTest(myAsyncTaskExecutor);
mySubscriber.start(); mySubscriber.start();
ourRestServer.registerInterceptor(mySubscriber); ourRestServer.registerInterceptor(mySubscriber);

View File

@ -1,35 +1,21 @@
package ca.uhn.fhir.jpa.subscription.email; 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.dao.DaoConfig;
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test; import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test;
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider; import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
import ca.uhn.fhir.jpa.util.JpaConstants; 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.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.google.common.collect.Lists;
import com.icegreen.greenmail.store.FolderException; import com.icegreen.greenmail.store.FolderException;
import com.icegreen.greenmail.util.GreenMail; import com.icegreen.greenmail.util.GreenMail;
import com.icegreen.greenmail.util.ServerSetup; 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.dstu3.model.*;
import org.hl7.fhir.instance.model.api.IBaseResource;
import org.hl7.fhir.instance.model.api.IIdType; import org.hl7.fhir.instance.model.api.IIdType;
import org.junit.*; import org.junit.*;
import javax.mail.internet.InternetAddress; import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage; import javax.mail.internet.MimeMessage;
import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
@ -53,7 +39,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
public void afterUnregisterEmailListener() { public void afterUnregisterEmailListener() {
ourLog.info("**** Starting @After *****"); ourLog.info("**** Starting @After *****");
for (IIdType next : mySubscriptionIds){ for (IIdType next : mySubscriptionIds) {
ourClient.delete().resourceById(next).execute(); ourClient.delete().resourceById(next).execute();
} }
mySubscriptionIds.clear(); mySubscriptionIds.clear();
@ -71,7 +57,8 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
@Before @Before
public void beforeRegisterEmailListener() throws FolderException { public void beforeRegisterEmailListener() throws FolderException {
ourTestSmtp.purgeEmailFromAllMailboxes();; ourTestSmtp.purgeEmailFromAllMailboxes();
;
ourRestServer.registerInterceptor(ourEmailSubscriptionInterceptor); ourRestServer.registerInterceptor(ourEmailSubscriptionInterceptor);
JavaMailEmailSender emailSender = new JavaMailEmailSender(); JavaMailEmailSender emailSender = new JavaMailEmailSender();
@ -83,24 +70,6 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
ourEmailSubscriptionInterceptor.setDefaultFromAddress("123@hapifhir.io"); 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 { private Subscription createSubscription(String theCriteria, String thePayload) throws InterruptedException {
Subscription subscription = new Subscription(); Subscription subscription = new Subscription();
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");
@ -161,15 +130,14 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
List<MimeMessage> received = Arrays.asList(ourTestSmtp.getReceivedMessages()); List<MimeMessage> received = Arrays.asList(ourTestSmtp.getReceivedMessages());
assertEquals(1, received.get(0).getFrom().length); 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(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("text/plain; charset=us-ascii", received.get(0).getContentType());
assertEquals("This is the body", received.get(0).getContent().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]); assertEquals(mySubscriptionIds.get(0).toUnqualifiedVersionless().getValue(), received.get(0).getHeader("X-FHIR-Subscription")[0]);
} }
@Test @Test
public void testEmailSubscriptionWithCustom() throws Exception { public void testEmailSubscriptionWithCustom() throws Exception {
String payload = "This is the body"; String payload = "This is the body";
@ -180,6 +148,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId()); Subscription subscriptionTemp = ourClient.read(Subscription.class, sub1.getId());
Assert.assertNotNull(subscriptionTemp); Assert.assertNotNull(subscriptionTemp);
subscriptionTemp.getChannel().addExtension() subscriptionTemp.getChannel().addExtension()
.setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM) .setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
.setValue(new StringType("mailto:myfrom@from.com")); .setValue(new StringType("mailto:myfrom@from.com"));
@ -208,9 +177,9 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
List<MimeMessage> received = Arrays.asList(ourTestSmtp.getReceivedMessages()); List<MimeMessage> received = Arrays.asList(ourTestSmtp.getReceivedMessages());
assertEquals(1, received.size()); assertEquals(1, received.size());
assertEquals(1, received.get(0).getFrom().length); 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(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("text/plain; charset=us-ascii", received.get(0).getContentType());
assertEquals("This is a subject", received.get(0).getSubject().toString().trim()); assertEquals("This is a subject", received.get(0).getSubject().toString().trim());
assertEquals("This is the body", received.get(0).getContent().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")); .setValue(new StringType("This is a subject"));
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless()); 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(); waitForQueueToDrain();
sendObservation(code, "SNOMED-CT"); sendObservation(code, "SNOMED-CT");
waitForQueueToDrain(); waitForQueueToDrain();
@ -256,17 +222,39 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
List<MimeMessage> received = Arrays.asList(ourTestSmtp.getReceivedMessages()); List<MimeMessage> received = Arrays.asList(ourTestSmtp.getReceivedMessages());
assertEquals(1, received.size()); assertEquals(1, received.size());
assertEquals(1, received.get(0).getFrom().length); 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(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("text/plain; charset=us-ascii", received.get(0).getContentType());
assertEquals("This is a subject", received.get(0).getSubject().toString().trim()); assertEquals("This is a subject", received.get(0).getSubject().toString().trim());
assertEquals("This is the body", received.get(0).getContent().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]); 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 { private void waitForQueueToDrain() throws InterruptedException {
RestHookTestDstu2Test.waitForQueueToDrain(ourEmailSubscriptionInterceptor); 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();
}
} }

View File

@ -3,6 +3,7 @@ package ca.uhn.fhir.jpa.subscription.r4;
import ca.uhn.fhir.context.FhirContext; import ca.uhn.fhir.context.FhirContext;
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test; import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test; 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.ResourceParam;
import ca.uhn.fhir.rest.annotation.Update; import ca.uhn.fhir.rest.annotation.Update;
import ca.uhn.fhir.rest.api.Constants; import ca.uhn.fhir.rest.api.Constants;
@ -38,11 +39,22 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
private static List<String> ourContentTypes = new ArrayList<>(); private static List<String> ourContentTypes = new ArrayList<>();
private static List<String> ourHeaders = new ArrayList<>(); private static List<String> ourHeaders = new ArrayList<>();
@After
public void afterResetSubscriptionActivatingInterceptor() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
}
@After @After
public void afterUnregisterRestHookListener() { public void afterUnregisterRestHookListener() {
ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor()); ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor());
} }
@Before
public void beforeSetSubscriptionActivatingInterceptor() {
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
}
private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException { private Subscription createSubscription(String theCriteria, String thePayload, String theEndpoint) throws InterruptedException {
Subscription subscription = new Subscription(); Subscription subscription = new Subscription();
subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)"); subscription.setReason("Monitor new neonatal function (note, age will be determined by the monitor)");

View File

@ -9,9 +9,9 @@ package ca.uhn.fhir.spring.boot.autoconfigure;
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * 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.context.FhirContext;
import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider; import ca.uhn.fhir.jaxrs.server.AbstractJaxRsProvider;
import ca.uhn.fhir.jpa.config.BaseJavaConfigDstu2; 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 ca.uhn.fhir.rest.server.interceptor.ResponseValidatingInterceptor;
import okhttp3.OkHttpClient; import okhttp3.OkHttpClient;
import org.apache.http.client.HttpClient; import org.apache.http.client.HttpClient;
import org.springframework.beans.factory.ObjectProvider; import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter; import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration; import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean; import org.springframework.boot.autoconfigure.condition.*;
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.domain.EntityScan; import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration; import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; 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.Conditional;
import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Configuration;
import org.springframework.core.annotation.AnnotationAwareOrderComparator; import org.springframework.core.annotation.AnnotationAwareOrderComparator;
import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories; 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 org.springframework.util.CollectionUtils;
import javax.servlet.ServletException;
import javax.sql.DataSource;
import java.util.List;
/** /**
* {@link EnableAutoConfiguration Auto-configuration} for HAPI FHIR. * {@link EnableAutoConfiguration Auto-configuration} for HAPI FHIR.
* *
* @author Mathieu Ouellet * @author Mathieu Ouellet
*/ */
@Configuration @Configuration
@AutoConfigureAfter({ DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class }) @AutoConfigureAfter({DataSourceAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
@EnableConfigurationProperties(FhirProperties.class) @EnableConfigurationProperties(FhirProperties.class)
public class FhirAutoConfiguration { public class FhirAutoConfiguration {
private final FhirProperties properties; private final FhirProperties properties;
public FhirAutoConfiguration(FhirProperties properties) { public FhirAutoConfiguration(FhirProperties properties) {
this.properties = properties; this.properties = properties;
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
public FhirContext fhirContext() { public FhirContext fhirContext() {
FhirContext fhirContext = new FhirContext(properties.getVersion()); FhirContext fhirContext = new FhirContext(properties.getVersion());
return fhirContext; 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<IResourceProvider> resourceProviders; private final FhirContext fhirContext;
private final IPagingProvider pagingProvider; private final List<IResourceProvider> resourceProviders;
private final List<IServerInterceptor> interceptors; private final IPagingProvider pagingProvider;
private final List<FhirRestfulServerCustomizer> customizers; private final List<IServerInterceptor> interceptors;
public FhirRestfulServerConfiguration( private final List<FhirRestfulServerCustomizer> customizers;
FhirProperties properties,
FhirContext fhirContext,
ObjectProvider<List<IResourceProvider>> resourceProviders,
ObjectProvider<IPagingProvider> pagingProvider,
ObjectProvider<List<IServerInterceptor>> interceptors,
ObjectProvider<List<FhirRestfulServerCustomizer>> customizers) {
this.properties = properties;
this.fhirContext = fhirContext;
this.resourceProviders = resourceProviders.getIfAvailable();
this.pagingProvider = pagingProvider.getIfAvailable();
this.interceptors = interceptors.getIfAvailable();
this.customizers = customizers.getIfAvailable();
}
@Bean public FhirRestfulServerConfiguration(
public ServletRegistrationBean fhirServerRegistrationBean() { FhirProperties properties,
ServletRegistrationBean registration = new ServletRegistrationBean(this, this.properties.getServer().getPath()); FhirContext fhirContext,
registration.setLoadOnStartup(1); ObjectProvider<List<IResourceProvider>> resourceProviders,
return registration; ObjectProvider<IPagingProvider> pagingProvider,
} ObjectProvider<List<IServerInterceptor>> interceptors,
ObjectProvider<List<FhirRestfulServerCustomizer>> customizers) {
this.properties = properties;
this.fhirContext = fhirContext;
this.resourceProviders = resourceProviders.getIfAvailable();
this.pagingProvider = pagingProvider.getIfAvailable();
this.interceptors = interceptors.getIfAvailable();
this.customizers = customizers.getIfAvailable();
}
@Override private void customize() {
protected void initialize() throws ServletException { if (this.customizers != null) {
super.initialize(); AnnotationAwareOrderComparator.sort(this.customizers);
for (FhirRestfulServerCustomizer customizer : this.customizers) {
customizer.customize(this);
}
}
}
setFhirContext(this.fhirContext); @Bean
setResourceProviders(this.resourceProviders); public ServletRegistrationBean fhirServerRegistrationBean() {
setPagingProvider(this.pagingProvider); ServletRegistrationBean registration = new ServletRegistrationBean(this, this.properties.getServer().getPath());
setInterceptors(this.interceptors); 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() { setServerAddressStrategy(new HardcodedServerAddressStrategy(this.properties.getServer().getPath()));
if (this.customizers != null) {
AnnotationAwareOrderComparator.sort(this.customizers);
for (FhirRestfulServerCustomizer customizer : this.customizers) {
customizer.customize(this);
}
}
}
}
@Configuration customize();
@ConditionalOnClass(BaseJpaProvider.class) }
@ConditionalOnBean(DataSource.class) }
@EnableConfigurationProperties(FhirProperties.class)
static class FhirJpaServerConfiguration {
@Configuration @Configuration
@EntityScan("ca.uhn.fhir.jpa.entity") @ConditionalOnClass(BaseJpaProvider.class)
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data") @ConditionalOnBean(DataSource.class)
static class FhirJpaDaoConfiguration { @EnableConfigurationProperties(FhirProperties.class)
static class FhirJpaServerConfiguration {
@Bean @Bean()
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConfigurationProperties("hapi.fhir.jpa") public ScheduledExecutorFactoryBean scheduledExecutorService() {
public DaoConfig fhirDaoConfig() { ScheduledExecutorFactoryBean b = new ScheduledExecutorFactoryBean();
DaoConfig fhirDaoConfig = new DaoConfig(); b.setPoolSize(5);
return fhirDaoConfig; return b;
} }
}
@Configuration @Bean
@ConditionalOnBean({ DaoConfig.class, RestfulServer.class }) @ConditionalOnMissingBean
@SuppressWarnings("rawtypes") public AsyncTaskExecutor taskScheduler() {
static class RestfulServerCustomizer implements FhirRestfulServerCustomizer { 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<BaseJpaSystemProvider> systemProviders) { @Bean
this.systemProviders = systemProviders.getIfAvailable(); @ConditionalOnMissingBean
} @ConfigurationProperties("hapi.fhir.jpa")
public DaoConfig fhirDaoConfig() {
DaoConfig fhirDaoConfig = new DaoConfig();
return fhirDaoConfig;
}
@Override }
public void customize(RestfulServer server) {
server.setPlainProviders(systemProviders);
}
}
@Configuration @Configuration
@ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig") @ConditionalOnBean({DaoConfig.class, RestfulServer.class})
@ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU3") @SuppressWarnings("rawtypes")
static class Dstu3 extends BaseJavaConfigDstu3 { static class RestfulServerCustomizer implements FhirRestfulServerCustomizer {
}
@Configuration private final BaseJpaSystemProvider systemProviders;
@ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig")
@ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU2")
static class Dstu2 extends BaseJavaConfigDstu2 {
}
}
@Configuration public RestfulServerCustomizer(ObjectProvider<BaseJpaSystemProvider> systemProviders) {
@Conditional(FhirValidationConfiguration.SchemaAvailableCondition.class) this.systemProviders = systemProviders.getIfAvailable();
@ConditionalOnProperty(name = "hapi.fhir.validation.enabled", matchIfMissing = true) }
static class FhirValidationConfiguration {
@Bean @Override
@ConditionalOnMissingBean public void customize(RestfulServer server) {
public RequestValidatingInterceptor requestValidatingInterceptor() { server.setPlainProviders(systemProviders);
return new RequestValidatingInterceptor(); }
} }
@Bean @Configuration
@ConditionalOnMissingBean @ConditionalOnMissingBean(type = "ca.uhn.fhir.jpa.config.BaseConfig")
@ConditionalOnProperty(name = "hapi.fhir.validation.request-only", havingValue = "false") @ConditionalOnProperty(name = "hapi.fhir.version", havingValue = "DSTU3")
public ResponseValidatingInterceptor responseValidatingInterceptor() { static class Dstu3 extends BaseJavaConfigDstu3 {
return new ResponseValidatingInterceptor(); }
}
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() { @Configuration
super("ValidationSchema", @Conditional(FhirValidationConfiguration.SchemaAvailableCondition.class)
"hapi.fhir.validation", @ConditionalOnProperty(name = "hapi.fhir.validation.enabled", matchIfMissing = true)
"schema-location", static class FhirValidationConfiguration {
"classpath:/org/hl7/fhir/instance/model/schema",
"classpath:/org/hl7/fhir/dstu2016may/model/schema",
"classpath:/org/hl7/fhir/dstu3/model/schema");
}
}
}
@Configuration @Bean
@ConditionalOnProperty("hapi.fhir.server.url") @ConditionalOnMissingBean
@EnableConfigurationProperties(FhirProperties.class) public RequestValidatingInterceptor requestValidatingInterceptor() {
static class FhirRestfulClientConfiguration { 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<IClientInterceptor> clientInterceptors; static class SchemaAvailableCondition extends ResourceCondition {
public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider<List<IClientInterceptor>> clientInterceptors) { SchemaAvailableCondition() {
this.properties = properties; super("ValidationSchema",
this.clientInterceptors = clientInterceptors.getIfAvailable(); "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 @Configuration
@ConditionalOnBean(IRestfulClientFactory.class) @ConditionalOnProperty("hapi.fhir.server.url")
public IGenericClient fhirClient(final IRestfulClientFactory clientFactory) { @EnableConfigurationProperties(FhirProperties.class)
IGenericClient fhirClient = clientFactory.newGenericClient(this.properties.getServer().getUrl()); static class FhirRestfulClientConfiguration {
if (!CollectionUtils.isEmpty(this.clientInterceptors)) {
for (IClientInterceptor interceptor : this.clientInterceptors) {
fhirClient.registerInterceptor(interceptor);
}
}
return fhirClient;
}
@Configuration private final FhirProperties properties;
@ConditionalOnClass(HttpClient.class)
@ConditionalOnMissingClass("okhttp3.OkHttpClient")
static class Apache {
private final FhirContext context; private final List<IClientInterceptor> clientInterceptors;
public Apache(FhirContext context) { public FhirRestfulClientConfiguration(FhirProperties properties, ObjectProvider<List<IClientInterceptor>> clientInterceptors) {
this.context = context; this.properties = properties;
} this.clientInterceptors = clientInterceptors.getIfAvailable();
}
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnBean(IRestfulClientFactory.class)
@ConfigurationProperties("hapi.fhir.rest.client.apache") public IGenericClient fhirClient(final IRestfulClientFactory clientFactory) {
public IRestfulClientFactory fhirRestfulClientFactory() { IGenericClient fhirClient = clientFactory.newGenericClient(this.properties.getServer().getUrl());
ApacheRestfulClientFactory restfulClientFactory = new ApacheRestfulClientFactory(this.context); if (!CollectionUtils.isEmpty(this.clientInterceptors)) {
return restfulClientFactory; for (IClientInterceptor interceptor : this.clientInterceptors) {
} fhirClient.registerInterceptor(interceptor);
} }
}
return fhirClient;
}
@Configuration @Configuration
@ConditionalOnClass(OkHttpClient.class) @ConditionalOnClass(HttpClient.class)
static class OkHttp { @ConditionalOnMissingClass("okhttp3.OkHttpClient")
static class Apache {
private final FhirContext context; private final FhirContext context;
public OkHttp(FhirContext context) { public Apache(FhirContext context) {
this.context = context; this.context = context;
} }
@Bean @Bean
@ConditionalOnMissingBean @ConditionalOnMissingBean
@ConfigurationProperties("hapi.fhir.rest.client.okhttp") @ConfigurationProperties("hapi.fhir.rest.client.apache")
public IRestfulClientFactory fhirRestfulClientFactory() { public IRestfulClientFactory fhirRestfulClientFactory() {
OkHttpRestfulClientFactory restfulClientFactory = new OkHttpRestfulClientFactory(this.context); ApacheRestfulClientFactory restfulClientFactory = new ApacheRestfulClientFactory(this.context);
return restfulClientFactory; 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;
}
}
}
} }