Subscription module support (#1147)
* Reorganizing packages and dependencies to support standalone subscription running within a CDR container where all hapi modules are on the classpath. Moved Subscription registry out of interceptor and introduced SubscriptionLoader * Created ActiveSubscription and moved cache bits into it * Moved ExecutorQueue stuff out into its own class * Add test and supporting code to validate SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION behaviour * Added SubscriptionCheckingSubscriber * Moved a few beans to @ComponentScan * Replaced use of beanFactory with concrete factory classes * Switched test to use subscribablechannel * Added SubscriptionLoaderFhirClientTest * Confirm that our SubscriptionProviderFhirClient works with a live fhir client * Register interceptors with DaoConfig instead of RestServer. Also, Rename @VisibleForTesting methods with ForUnitTest * Fix triggering service so it uses new subscriptionmatcherinterceptor * Renamed "Database" classes to "Dao" * processing -> matching naming change
This commit is contained in:
parent
dc1f48ffed
commit
84a34eb3c9
|
@ -0,0 +1,11 @@
|
|||
# Unsupported
|
||||
|
||||
Most of the projects in this module are no longer supported.
|
||||
|
||||
The test in hapi-fhir-jpaserver-cds-example is @Ignored until Chris Schuler is able to make a change to the pom
|
||||
this module depends on.
|
||||
|
||||
## Supported JPA Example:
|
||||
|
||||
The supported HAPI-FHIR JPA example is available in the [hapi-fhir-jpaserver-starter](https://github.com/hapifhir/hapi-fhir-jpaserver-starter)
|
||||
project within the [hapifhir](https://github.com/hapifhir) GitHub Organization.
|
|
@ -10,11 +10,7 @@ import org.eclipse.jetty.server.Server;
|
|||
import org.eclipse.jetty.webapp.WebAppContext;
|
||||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.*;
|
||||
|
||||
import java.io.*;
|
||||
import java.net.HttpURLConnection;
|
||||
|
@ -26,7 +22,7 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
import java.util.Scanner;
|
||||
|
||||
// FIXME KHS
|
||||
// TODO Remove @Ignore once Chris Schuler has fixed the external jar this project depends on
|
||||
@Ignore
|
||||
public class CdsExampleTests {
|
||||
private static IGenericClient ourClient;
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
|
@ -20,13 +10,20 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
|||
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemo extends RestfulServer {
|
||||
|
||||
|
@ -129,12 +126,10 @@ public class JpaServerDemo extends RestfulServer {
|
|||
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
|
||||
|
||||
/*
|
||||
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
|
||||
* Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
|
||||
*/
|
||||
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
|
||||
for (IServerInterceptor interceptor : interceptorBeans) {
|
||||
this.registerInterceptor(interceptor);
|
||||
}
|
||||
SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
|
||||
subscriptionInterceptorLoader.registerInterceptors();
|
||||
|
||||
/*
|
||||
* If you are hosting this server at a specific DNS name, the server will try to
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
|
@ -20,13 +10,20 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
|||
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemoDstu2 extends RestfulServer {
|
||||
|
||||
|
@ -129,12 +126,10 @@ public class JpaServerDemoDstu2 extends RestfulServer {
|
|||
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
|
||||
|
||||
/*
|
||||
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
|
||||
* Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
|
||||
*/
|
||||
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
|
||||
for (IServerInterceptor interceptor : interceptorBeans) {
|
||||
this.registerInterceptor(interceptor);
|
||||
}
|
||||
SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
|
||||
subscriptionInterceptorLoader.registerInterceptors();
|
||||
|
||||
/*
|
||||
* If you are hosting this server at a specific DNS name, the server will try to
|
||||
|
|
|
@ -1,14 +1,5 @@
|
|||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirSystemDao;
|
||||
|
@ -16,12 +7,18 @@ import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
|||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemo extends RestfulServer {
|
||||
|
||||
|
@ -96,12 +93,10 @@ public class JpaServerDemo extends RestfulServer {
|
|||
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
|
||||
|
||||
/*
|
||||
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
|
||||
* Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
|
||||
*/
|
||||
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
|
||||
for (IServerInterceptor interceptor : interceptorBeans) {
|
||||
this.registerInterceptor(interceptor);
|
||||
}
|
||||
SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
|
||||
subscriptionInterceptorLoader.registerInterceptors();
|
||||
|
||||
/*
|
||||
* If you are hosting this server at a specific DNS name, the server will try to
|
||||
|
|
|
@ -12,6 +12,7 @@ import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
|||
import ca.uhn.fhir.jpa.provider.r4.JpaConformanceProviderR4;
|
||||
import ca.uhn.fhir.jpa.provider.r4.JpaSystemProviderR4;
|
||||
import ca.uhn.fhir.jpa.provider.r4.TerminologyUploaderProviderR4;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
|
@ -21,13 +22,11 @@ import ca.uhn.fhir.rest.server.FifoMemoryPagingProvider;
|
|||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import ca.uhn.fhir.rest.server.interceptor.CorsInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemo extends RestfulServer {
|
||||
|
@ -143,19 +142,14 @@ public class JpaServerDemo extends RestfulServer {
|
|||
CorsInterceptor corsInterceptor = new CorsInterceptor();
|
||||
registerInterceptor(corsInterceptor);
|
||||
|
||||
/*
|
||||
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
|
||||
*/
|
||||
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
|
||||
for (IServerInterceptor interceptor : interceptorBeans) {
|
||||
this.registerInterceptor(interceptor);
|
||||
}
|
||||
|
||||
DaoConfig daoConfig = myAppCtx.getBean(DaoConfig.class);
|
||||
daoConfig.setAllowExternalReferences(ContextHolder.isAllowExternalRefs());
|
||||
daoConfig.setEnforceReferentialIntegrityOnDelete(!ContextHolder.isDisableReferentialIntegrity());
|
||||
daoConfig.setEnforceReferentialIntegrityOnWrite(!ContextHolder.isDisableReferentialIntegrity());
|
||||
daoConfig.setReuseCachedSearchResultsForMillis(ContextHolder.getReuseCachedSearchResultsForMillis());
|
||||
|
||||
SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
|
||||
subscriptionInterceptorLoader.registerInterceptors();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,18 +2,18 @@ package ca.uhn.fhir.jpa.config;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.i18n.HapiLocalizer;
|
||||
import ca.uhn.fhir.jpa.dao.DatabaseSearchParamProvider;
|
||||
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
|
||||
import ca.uhn.fhir.jpa.search.StaleSearchDeletingSvcImpl;
|
||||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||
import ca.uhn.fhir.jpa.search.reindex.ResourceReindexingSvcImpl;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.config.BaseSubscriptionConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.dbmatcher.CompositeInMemoryDaoSubscriptionMatcher;
|
||||
import ca.uhn.fhir.jpa.subscription.dbmatcher.DaoSubscriptionMatcher;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.BlockingQueueSubscriptionChannelFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
||||
import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
@ -59,7 +59,8 @@ import javax.annotation.Nonnull;
|
|||
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
|
||||
@ComponentScan(basePackages = "ca.uhn.fhir.jpa", excludeFilters={
|
||||
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=BaseConfig.class),
|
||||
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=WebSocketConfigurer.class)})
|
||||
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=WebSocketConfigurer.class),
|
||||
@ComponentScan.Filter(type=FilterType.REGEX, pattern="ca.uhn.fhir.jpa.subscription.module.standalone.*")})
|
||||
|
||||
public abstract class BaseConfig implements SchedulingConfigurer {
|
||||
|
||||
|
@ -132,34 +133,29 @@ public abstract class BaseConfig implements SchedulingConfigurer {
|
|||
}
|
||||
|
||||
@Bean
|
||||
protected ISearchParamProvider searchParamProvider() {
|
||||
return new DatabaseSearchParamProvider();
|
||||
public InMemorySubscriptionMatcher inMemorySubscriptionMatcher() {
|
||||
return new InMemorySubscriptionMatcher();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public DaoSubscriptionMatcher daoSubscriptionMatcher() {
|
||||
return new DaoSubscriptionMatcher();
|
||||
}
|
||||
|
||||
/**
|
||||
* Note: If you're going to use this, you need to provide a bean
|
||||
* of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender}
|
||||
* in your own Spring config
|
||||
* Create a @Primary @Bean if you need a different implementation
|
||||
*/
|
||||
@Bean
|
||||
@Lazy
|
||||
public SubscriptionEmailInterceptor subscriptionEmailInterceptor() {
|
||||
return new SubscriptionEmailInterceptor();
|
||||
public ISubscriptionChannelFactory blockingQueueSubscriptionDeliveryChannelFactory() {
|
||||
return new BlockingQueueSubscriptionChannelFactory();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public SubscriptionRestHookInterceptor subscriptionRestHookInterceptor() {
|
||||
return new SubscriptionRestHookInterceptor();
|
||||
@Primary
|
||||
public ISubscriptionMatcher subscriptionMatcherCompositeInMemoryDatabase() {
|
||||
return new CompositeInMemoryDaoSubscriptionMatcher(daoSubscriptionMatcher(), inMemorySubscriptionMatcher());
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Lazy
|
||||
public SubscriptionWebsocketInterceptor subscriptionWebsocketInterceptor() {
|
||||
return new SubscriptionWebsocketInterceptor();
|
||||
}
|
||||
|
||||
|
||||
public static void configureEntityManagerFactory(LocalContainerEntityManagerFactoryBean theFactory, FhirContext theCtx) {
|
||||
theFactory.setJpaDialect(hibernateJpaDialect(theCtx.getLocalizer()));
|
||||
theFactory.setPackagesToScan("ca.uhn.fhir.jpa.model.entity", "ca.uhn.fhir.jpa.entity");
|
||||
|
|
|
@ -116,7 +116,7 @@ public class BaseDstu2Config extends BaseConfig {
|
|||
|
||||
@Bean
|
||||
public ISearchParamRegistry searchParamRegistry() {
|
||||
return new SearchParamRegistryDstu2(searchParamProvider());
|
||||
return new SearchParamRegistryDstu2();
|
||||
}
|
||||
|
||||
@Bean(name = "mySystemDaoDstu2", autowire = Autowire.BY_NAME)
|
||||
|
|
|
@ -20,9 +20,7 @@ package ca.uhn.fhir.jpa.config;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketHandler;
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionWebsocketHandler;
|
||||
import org.springframework.beans.factory.annotation.Autowire;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
|
|
@ -123,7 +123,7 @@ public class BaseDstu3Config extends BaseConfig {
|
|||
|
||||
@Bean
|
||||
public ISearchParamRegistry searchParamRegistry() {
|
||||
return new SearchParamRegistryDstu3(searchParamProvider());
|
||||
return new SearchParamRegistryDstu3();
|
||||
}
|
||||
|
||||
@Bean(name = "mySystemDaoDstu3", autowire = Autowire.BY_NAME)
|
||||
|
|
|
@ -138,7 +138,7 @@ public class BaseR4Config extends BaseConfig {
|
|||
|
||||
@Bean
|
||||
public ISearchParamRegistry searchParamRegistry() {
|
||||
return new SearchParamRegistryR4(searchParamProvider());
|
||||
return new SearchParamRegistryR4();
|
||||
}
|
||||
|
||||
@Bean(name = "mySystemDaoR4", autowire = Autowire.BY_NAME)
|
||||
|
|
|
@ -2,16 +2,16 @@ package ca.uhn.fhir.jpa.dao;
|
|||
|
||||
import ca.uhn.fhir.context.*;
|
||||
import ca.uhn.fhir.jpa.dao.data.*;
|
||||
import ca.uhn.fhir.jpa.dao.index.*;
|
||||
import ca.uhn.fhir.jpa.model.entity.*;
|
||||
import ca.uhn.fhir.jpa.dao.index.DaoSearchParamSynchronizer;
|
||||
import ca.uhn.fhir.jpa.dao.index.IdHelperService;
|
||||
import ca.uhn.fhir.jpa.dao.index.SearchParamWithInlineReferencesExtractor;
|
||||
import ca.uhn.fhir.jpa.entity.*;
|
||||
import ca.uhn.fhir.jpa.model.entity.*;
|
||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||
import ca.uhn.fhir.jpa.searchparam.ResourceMetaParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ISearchParamExtractor;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.LogicalReferenceHelper;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.ResourceIndexedSearchParams;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorService;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
|
@ -59,7 +59,6 @@ import org.hibernate.internal.SessionImpl;
|
|||
import org.hl7.fhir.instance.model.api.*;
|
||||
import org.hl7.fhir.r4.model.Bundle.HTTPVerb;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
|
@ -78,8 +77,6 @@ import javax.persistence.criteria.Predicate;
|
|||
import javax.persistence.criteria.Root;
|
||||
import javax.xml.stream.events.Characters;
|
||||
import javax.xml.stream.events.XMLEvent;
|
||||
import java.io.CharArrayWriter;
|
||||
import java.text.Normalizer;
|
||||
import java.util.*;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -178,13 +175,13 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
//@Autowired
|
||||
//private ISearchResultDao mySearchResultDao;
|
||||
@Autowired
|
||||
private BeanFactory beanFactory;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private SearchParamWithInlineReferencesExtractor mySearchParamWithInlineReferencesExtractor;
|
||||
@Autowired
|
||||
private DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer;
|
||||
private DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
|
||||
@Autowired
|
||||
private SearchBuilderFactory mySearchBuilderFactory;
|
||||
|
||||
private ApplicationContext myApplicationContext;
|
||||
|
||||
|
@ -746,9 +743,9 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
return LogicalReferenceHelper.isLogicalReference(myConfig.getModelConfig(), theId);
|
||||
}
|
||||
|
||||
@Override
|
||||
// TODO KHS inject a searchBuilderFactory into callers of this method and delete this method
|
||||
public SearchBuilder newSearchBuilder() {
|
||||
return beanFactory.getBean(SearchBuilder.class, this);
|
||||
return mySearchBuilderFactory.newSearchBuilder(this);
|
||||
}
|
||||
|
||||
public void notifyInterceptors(RestOperationTypeEnum theOperationType, ActionRequestDetails theRequestDetails) {
|
||||
|
@ -1406,7 +1403,7 @@ public abstract class BaseHapiFhirDao<T extends IBaseResource> implements IDao,
|
|||
* Indexing
|
||||
*/
|
||||
if (thePerformIndexing) {
|
||||
myDatabaseSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, theEntity, existingParams);
|
||||
myDaoSearchParamSynchronizer.synchronizeSearchParamsToDatabase(newParams, theEntity, existingParams);
|
||||
mySearchParamWithInlineReferencesExtractor.storeCompositeStringUniques(newParams, theEntity, existingParams);
|
||||
}
|
||||
|
||||
|
|
|
@ -24,13 +24,11 @@ import ca.uhn.fhir.context.ConfigurationException;
|
|||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.dao.data.IResourceLinkDao;
|
||||
import ca.uhn.fhir.jpa.dao.r4.MatchResourceUrlService;
|
||||
import ca.uhn.fhir.jpa.model.entity.*;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.PersistedJpaBundleProvider;
|
||||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.util.DeleteConflict;
|
||||
import ca.uhn.fhir.jpa.util.ExpungeOptions;
|
||||
|
@ -73,17 +71,19 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
public abstract class BaseHapiFhirResourceDao<T extends IBaseResource> extends BaseHapiFhirDao<T> implements IFhirResourceDao<T> {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseHapiFhirResourceDao.class);
|
||||
|
||||
@Autowired
|
||||
protected PlatformTransactionManager myPlatformTransactionManager;
|
||||
@Autowired(required = false)
|
||||
protected IFulltextSearchSvc mySearchDao;
|
||||
@Autowired
|
||||
protected DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private MatchResourceUrlService myMatchResourceUrlService;
|
||||
|
||||
private String myResourceName;
|
||||
private Class<T> myResourceType;
|
||||
private String mySecondaryPrimaryKeyParamName;
|
||||
@Autowired
|
||||
private MatchResourceUrlService myMatchResourceUrlService;
|
||||
|
||||
@Override
|
||||
public void addTag(IIdType theId, TagTypeEnum theTagType, String theScheme, String theTerm, String theLabel) {
|
||||
|
|
|
@ -4,12 +4,12 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
|||
import ca.uhn.fhir.jpa.model.entity.ResourceEncodingEnum;
|
||||
import ca.uhn.fhir.jpa.search.warm.WarmCacheEntry;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParamConstants;
|
||||
import ca.uhn.fhir.jpa.util.JpaConstants;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.Sets;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.time.DateUtils;
|
||||
import org.hl7.fhir.instance.model.Subscription;
|
||||
import org.hl7.fhir.r4.model.Bundle;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -112,7 +112,7 @@ public class DaoConfig {
|
|||
* update setter javadoc if default changes
|
||||
*/
|
||||
private boolean myIndexContainedResources = true;
|
||||
private List<IServerInterceptor> myInterceptors;
|
||||
private List<IServerInterceptor> myInterceptors = new ArrayList<>();
|
||||
/**
|
||||
* update setter javadoc if default changes
|
||||
*/
|
||||
|
@ -484,14 +484,26 @@ public class DaoConfig {
|
|||
* Returns the interceptors which will be notified of operations.
|
||||
*
|
||||
* @see #setInterceptors(List)
|
||||
* @deprecated Marked as deprecated as of HAPI 3.7.0. Use {@link #registerInterceptor} or {@link #unregisterInterceptor}instead.
|
||||
*/
|
||||
|
||||
@Deprecated
|
||||
public List<IServerInterceptor> getInterceptors() {
|
||||
if (myInterceptors == null) {
|
||||
myInterceptors = new ArrayList<>();
|
||||
}
|
||||
return myInterceptors;
|
||||
}
|
||||
|
||||
public void registerInterceptor(IServerInterceptor theInterceptor) {
|
||||
Validate.notNull(theInterceptor, "Interceptor can not be null");
|
||||
if (!myInterceptors.contains(theInterceptor)) {
|
||||
myInterceptors.add(theInterceptor);
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterInterceptor(IServerInterceptor theInterceptor) {
|
||||
Validate.notNull(theInterceptor, "Interceptor can not be null");
|
||||
myInterceptors.remove(theInterceptor);
|
||||
}
|
||||
|
||||
/**
|
||||
* This may be used to optionally register server interceptors directly against the DAOs.
|
||||
*/
|
||||
|
@ -1462,6 +1474,47 @@ public class DaoConfig {
|
|||
myModelConfig.setDefaultSearchParamsCanBeOverridden(theDefaultSearchParamsCanBeOverridden);
|
||||
}
|
||||
|
||||
/**
|
||||
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
|
||||
* to the server matching these types will be activated.
|
||||
*
|
||||
*/
|
||||
public DaoConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) {
|
||||
myModelConfig.addSupportedSubscriptionType(theSubscriptionChannelType);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
|
||||
* to the server matching these types will be activated.
|
||||
*
|
||||
*/
|
||||
public Set<Subscription.SubscriptionChannelType> getSupportedSubscriptionTypes() {
|
||||
return myModelConfig.getSupportedSubscriptionTypes();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void clearSupportedSubscriptionTypesForUnitTest() {
|
||||
myModelConfig.clearSupportedSubscriptionTypesForUnitTest();
|
||||
}
|
||||
|
||||
/**
|
||||
* If e-mail subscriptions are supported, the From address used when sending e-mails
|
||||
*/
|
||||
|
||||
public String getEmailFromAddress() {
|
||||
return myModelConfig.getEmailFromAddress();
|
||||
}
|
||||
|
||||
/**
|
||||
* If e-mail subscriptions are supported, the From address used when sending e-mails
|
||||
*/
|
||||
|
||||
public void setEmailFromAddress(String theEmailFromAddress) {
|
||||
myModelConfig.setEmailFromAddress(theEmailFromAddress);
|
||||
}
|
||||
|
||||
|
||||
|
||||
public enum IndexEnabledEnum {
|
||||
ENABLED,
|
||||
|
|
|
@ -20,17 +20,19 @@ package ca.uhn.fhir.jpa.dao;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
||||
public class DatabaseSearchParamProvider implements ISearchParamProvider {
|
||||
@Service
|
||||
public class DaoSearchParamProvider implements ISearchParamProvider {
|
||||
@Autowired
|
||||
private PlatformTransactionManager myTxManager;
|
||||
@Autowired
|
|
@ -0,0 +1,10 @@
|
|||
package ca.uhn.fhir.jpa.dao;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Lookup;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public abstract class SearchBuilderFactory {
|
||||
@Lookup
|
||||
public abstract SearchBuilder newSearchBuilder(BaseHapiFhirDao theBaseHapiFhirResourceDao);
|
||||
}
|
|
@ -41,8 +41,8 @@ import javax.persistence.PersistenceContext;
|
|||
import javax.persistence.PersistenceContextType;
|
||||
|
||||
@Service
|
||||
public class DatabaseResourceLinkResolver implements IResourceLinkResolver {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DatabaseResourceLinkResolver.class);
|
||||
public class DaoResourceLinkResolver implements IResourceLinkResolver {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(DaoResourceLinkResolver.class);
|
||||
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
|
@ -35,7 +35,7 @@ import java.util.Collection;
|
|||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class DatabaseSearchParamSynchronizer {
|
||||
public class DaoSearchParamSynchronizer {
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
|
|
@ -77,9 +77,9 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
@Autowired
|
||||
ResourceLinkExtractor myResourceLinkExtractor;
|
||||
@Autowired
|
||||
DatabaseResourceLinkResolver myDatabaseResourceLinkResolver;
|
||||
DaoResourceLinkResolver myDaoResourceLinkResolver;
|
||||
@Autowired
|
||||
DatabaseSearchParamSynchronizer myDatabaseSearchParamSynchronizer;
|
||||
DaoSearchParamSynchronizer myDaoSearchParamSynchronizer;
|
||||
@Autowired
|
||||
private IResourceIndexedCompositeStringUniqueDao myResourceIndexedCompositeStringUniqueDao;
|
||||
|
||||
|
@ -99,7 +99,7 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
|
||||
extractInlineReferences(theResource);
|
||||
|
||||
myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDatabaseResourceLinkResolver);
|
||||
myResourceLinkExtractor.extractResourceLinks(theParams, theEntity, theResource, theUpdateTime, myDaoResourceLinkResolver);
|
||||
|
||||
/*
|
||||
* If the existing resource already has links and those match links we still want, use them instead of removing them and re adding them
|
||||
|
@ -258,12 +258,12 @@ public class SearchParamWithInlineReferencesExtractor {
|
|||
|
||||
// Store composite string uniques
|
||||
if (myDaoConfig.isUniqueIndexesEnabled()) {
|
||||
for (ResourceIndexedCompositeStringUnique next : myDatabaseSearchParamSynchronizer.subtract(existingParams.compositeStringUniques, theParams.compositeStringUniques)) {
|
||||
for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(existingParams.compositeStringUniques, theParams.compositeStringUniques)) {
|
||||
ourLog.debug("Removing unique index: {}", next);
|
||||
myEntityManager.remove(next);
|
||||
theEntity.getParamsCompositeStringUnique().remove(next);
|
||||
}
|
||||
for (ResourceIndexedCompositeStringUnique next : myDatabaseSearchParamSynchronizer.subtract(theParams.compositeStringUniques, existingParams.compositeStringUniques)) {
|
||||
for (ResourceIndexedCompositeStringUnique next : myDaoSearchParamSynchronizer.subtract(theParams.compositeStringUniques, existingParams.compositeStringUniques)) {
|
||||
if (myDaoConfig.isUniqueIndexesCheckedBeforeSave()) {
|
||||
ResourceIndexedCompositeStringUnique existing = myResourceIndexedCompositeStringUniqueDao.findByQueryString(next.getIndexString());
|
||||
if (existing != null) {
|
||||
|
|
|
@ -1,630 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherCompositeInMemoryDatabase;
|
||||
import ca.uhn.fhir.jpa.subscription.matcher.SubscriptionMatcherDatabase;
|
||||
import ca.uhn.fhir.jpa.util.JpaConstants;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import com.google.common.collect.ArrayListMultimap;
|
||||
import com.google.common.collect.Multimap;
|
||||
import com.google.common.collect.Multimaps;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.core.task.AsyncTaskExecutor;
|
||||
import org.springframework.messaging.MessageChannel;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
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 javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
public abstract class BaseSubscriptionInterceptor<S extends IBaseResource> extends ServerOperationInterceptorAdapter {
|
||||
|
||||
static final String SUBSCRIPTION_STATUS = "Subscription.status";
|
||||
static final String SUBSCRIPTION_TYPE = "Subscription.channel.type";
|
||||
private static final Integer MAX_SUBSCRIPTION_RESULTS = 1000;
|
||||
private static boolean ourForcePayloadEncodeAndDecodeForUnitTests;
|
||||
private final Object myInitSubscriptionsLock = new Object();
|
||||
private SubscribableChannel myProcessingChannel;
|
||||
private Map<String, SubscribableChannel> myDeliveryChannel;
|
||||
private ExecutorService myProcessingExecutor;
|
||||
private int myExecutorThreadCount;
|
||||
private SubscriptionActivatingSubscriber mySubscriptionActivatingSubscriber;
|
||||
private MessageHandler mySubscriptionCheckingSubscriber;
|
||||
private ConcurrentHashMap<String, CanonicalSubscription> myIdToSubscription = new ConcurrentHashMap<>();
|
||||
private ConcurrentHashMap<String, SubscribableChannel> mySubscribableChannel = new ConcurrentHashMap<>();
|
||||
private Multimap<String, MessageHandler> myIdToDeliveryHandler = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
|
||||
private Logger ourLog = LoggerFactory.getLogger(BaseSubscriptionInterceptor.class);
|
||||
private ThreadPoolExecutor myDeliveryExecutor;
|
||||
private LinkedBlockingQueue<Runnable> myProcessingExecutorQueue;
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
@Autowired(required = false)
|
||||
@Qualifier("myEventDefinitionDaoR4")
|
||||
private IFhirResourceDao<org.hl7.fhir.r4.model.EventDefinition> myEventDefinitionDaoR4;
|
||||
@Autowired()
|
||||
private PlatformTransactionManager myTxManager;
|
||||
@Autowired
|
||||
@Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
|
||||
private AsyncTaskExecutor myAsyncTaskExecutor;
|
||||
@Autowired
|
||||
private SubscriptionMatcherCompositeInMemoryDatabase mySubscriptionMatcherCompositeInMemoryDatabase;
|
||||
@Autowired
|
||||
private SubscriptionMatcherDatabase mySubscriptionMatcherDatabase;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private BeanFactory beanFactory;
|
||||
@Autowired
|
||||
private MatchUrlService myMatchUrlService;
|
||||
private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1);
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public BaseSubscriptionInterceptor() {
|
||||
super();
|
||||
setExecutorThreadCount(5);
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalize(S theSubscription) {
|
||||
switch (myCtx.getVersion().getVersion()) {
|
||||
case DSTU2:
|
||||
return canonicalizeDstu2(theSubscription);
|
||||
case DSTU3:
|
||||
return canonicalizeDstu3(theSubscription);
|
||||
case R4:
|
||||
return canonicalizeR4(theSubscription);
|
||||
default:
|
||||
throw new ConfigurationException("Subscription not supported for version: " + myCtx.getVersion().getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) {
|
||||
ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
try {
|
||||
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus()));
|
||||
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType()));
|
||||
retVal.setCriteriaString(subscription.getCriteria());
|
||||
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
|
||||
retVal.setHeaders(subscription.getChannel().getHeader());
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
} catch (FHIRException theE) {
|
||||
throw new InternalErrorException(theE);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) {
|
||||
org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
try {
|
||||
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode()));
|
||||
retVal.setChannelType(org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
|
||||
retVal.setCriteriaString(subscription.getCriteria());
|
||||
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
|
||||
retVal.setHeaders(subscription.getChannel().getHeader());
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
|
||||
if (retVal.getChannelType() == Subscription.SubscriptionChannelType.EMAIL) {
|
||||
String from;
|
||||
String subjectTemplate;
|
||||
String bodyTemplate;
|
||||
try {
|
||||
from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
||||
subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getEmailDetails().setFrom(from);
|
||||
retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
|
||||
}
|
||||
|
||||
if (retVal.getChannelType() == Subscription.SubscriptionChannelType.RESTHOOK) {
|
||||
String stripVersionIds;
|
||||
String deliverLatestVersion;
|
||||
try {
|
||||
stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
|
||||
deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds));
|
||||
retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion));
|
||||
}
|
||||
|
||||
} catch (FHIRException theE) {
|
||||
throw new InternalErrorException(theE);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) {
|
||||
org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
retVal.setStatus(subscription.getStatus());
|
||||
retVal.setChannelType(subscription.getChannel().getType());
|
||||
retVal.setCriteriaString(subscription.getCriteria());
|
||||
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
|
||||
retVal.setHeaders(subscription.getChannel().getHeader());
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
|
||||
if (retVal.getChannelType() == Subscription.SubscriptionChannelType.EMAIL) {
|
||||
String from;
|
||||
String subjectTemplate;
|
||||
try {
|
||||
from = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
||||
subjectTemplate = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getEmailDetails().setFrom(from);
|
||||
retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
|
||||
}
|
||||
|
||||
if (retVal.getChannelType() == Subscription.SubscriptionChannelType.RESTHOOK) {
|
||||
String stripVersionIds;
|
||||
String deliverLatestVersion;
|
||||
try {
|
||||
stripVersionIds = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
|
||||
deliverLatestVersion = subscription.getChannel().getExtensionString(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds));
|
||||
retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion));
|
||||
}
|
||||
|
||||
List<org.hl7.fhir.r4.model.Extension> topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
|
||||
if (topicExts.size() > 0) {
|
||||
IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive();
|
||||
if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) {
|
||||
throw new PreconditionFailedException("Topic reference must be an EventDefinition");
|
||||
}
|
||||
|
||||
org.hl7.fhir.r4.model.EventDefinition def = myEventDefinitionDaoR4.read(ref.getReferenceElement());
|
||||
retVal.addTrigger(new CanonicalSubscription.CanonicalEventDefinition(def));
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected SubscribableChannel createDeliveryChannel(CanonicalSubscription theSubscription) {
|
||||
String subscriptionId = theSubscription.getIdElement(myCtx).getIdPart();
|
||||
|
||||
LinkedBlockingQueue<Runnable> executorQueue = new LinkedBlockingQueue<>(1000);
|
||||
BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
|
||||
.namingPattern("subscription-delivery-" + subscriptionId + "-%d")
|
||||
.daemon(false)
|
||||
.priority(Thread.NORM_PRIORITY)
|
||||
.build();
|
||||
RejectedExecutionHandler rejectedExecutionHandler = new RejectedExecutionHandler() {
|
||||
@Override
|
||||
public void rejectedExecution(Runnable theRunnable, ThreadPoolExecutor theExecutor) {
|
||||
ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", executorQueue.size());
|
||||
StopWatch sw = new StopWatch();
|
||||
try {
|
||||
executorQueue.put(theRunnable);
|
||||
} catch (InterruptedException theE) {
|
||||
throw new RejectedExecutionException("Task " + theRunnable.toString() +
|
||||
" rejected from " + theE.toString());
|
||||
}
|
||||
ourLog.info("Slot become available after {}ms", sw.getMillis());
|
||||
}
|
||||
};
|
||||
ThreadPoolExecutor deliveryExecutor = new ThreadPoolExecutor(
|
||||
1,
|
||||
getExecutorThreadCount(),
|
||||
0L,
|
||||
TimeUnit.MILLISECONDS,
|
||||
executorQueue,
|
||||
threadFactory,
|
||||
rejectedExecutionHandler);
|
||||
|
||||
return new ExecutorSubscribableChannel(deliveryExecutor);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an empty handler if the interceptor will manually handle registration and unregistration
|
||||
*/
|
||||
protected abstract Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription);
|
||||
|
||||
public abstract Subscription.SubscriptionChannelType getChannelType();
|
||||
|
||||
protected MessageChannel getDeliveryChannel(CanonicalSubscription theSubscription) {
|
||||
return mySubscribableChannel.get(theSubscription.getIdElement(myCtx).getIdPart());
|
||||
}
|
||||
|
||||
public int getExecutorQueueSizeForUnitTests() {
|
||||
return myProcessingExecutorQueue.size();
|
||||
}
|
||||
|
||||
public int getExecutorThreadCount() {
|
||||
return myExecutorThreadCount;
|
||||
}
|
||||
|
||||
public void setExecutorThreadCount(int theExecutorThreadCount) {
|
||||
Validate.inclusiveBetween(1, Integer.MAX_VALUE, theExecutorThreadCount);
|
||||
myExecutorThreadCount = theExecutorThreadCount;
|
||||
}
|
||||
|
||||
public Map<String, CanonicalSubscription> getIdToSubscription() {
|
||||
return Collections.unmodifiableMap(myIdToSubscription);
|
||||
}
|
||||
|
||||
public SubscribableChannel getProcessingChannel() {
|
||||
return myProcessingChannel;
|
||||
}
|
||||
|
||||
public void setProcessingChannel(SubscribableChannel theProcessingChannel) {
|
||||
myProcessingChannel = theProcessingChannel;
|
||||
}
|
||||
|
||||
public List<CanonicalSubscription> getRegisteredSubscriptions() {
|
||||
return new ArrayList<>(myIdToSubscription.values());
|
||||
}
|
||||
|
||||
public CanonicalSubscription hasSubscription(IIdType theId) {
|
||||
Validate.notNull(theId);
|
||||
Validate.notBlank(theId.getIdPart());
|
||||
return myIdToSubscription.get(theId.getIdPart());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the existing subscriptions from the database
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Scheduled(fixedDelay = 60000)
|
||||
public void initSubscriptions() {
|
||||
if (!myInitSubscriptionsSemaphore.tryAcquire()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
doInitSubscriptions();
|
||||
} finally {
|
||||
myInitSubscriptionsSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
public Integer doInitSubscriptions() {
|
||||
synchronized (myInitSubscriptionsLock) {
|
||||
ourLog.debug("Starting init subscriptions");
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(Subscription.SP_TYPE, new TokenParam(null, getChannelType().toCode()));
|
||||
map.add(Subscription.SP_STATUS, new TokenOrListParam()
|
||||
.addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode()))
|
||||
.addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode())));
|
||||
map.setLoadSynchronousUpTo(MAX_SUBSCRIPTION_RESULTS);
|
||||
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
||||
IBundleProvider subscriptionBundleList = subscriptionDao.search(map, req);
|
||||
if (subscriptionBundleList.size() >= MAX_SUBSCRIPTION_RESULTS) {
|
||||
ourLog.error("Currently over " + MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
|
||||
}
|
||||
|
||||
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
|
||||
|
||||
Set<String> allIds = new HashSet<>();
|
||||
int changesCount = 0;
|
||||
for (IBaseResource resource : resourceList) {
|
||||
String nextId = resource.getIdElement().getIdPart();
|
||||
allIds.add(nextId);
|
||||
boolean changed = mySubscriptionActivatingSubscriber.activateOrRegisterSubscriptionIfRequired(resource);
|
||||
if (changed) {
|
||||
changesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
unregisterAllSubscriptionsNotInCollection(allIds);
|
||||
ourLog.trace("Finished init subscriptions - found {}", resourceList.size());
|
||||
|
||||
return changesCount;
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@PreDestroy
|
||||
public void preDestroy() {
|
||||
getProcessingChannel().unsubscribe(mySubscriptionCheckingSubscriber);
|
||||
unregisterAllSubscriptionsNotInCollection(Collections.emptyList());
|
||||
}
|
||||
|
||||
public void registerHandler(String theSubscriptionId, MessageHandler theHandler) {
|
||||
mySubscribableChannel.get(theSubscriptionId).subscribe(theHandler);
|
||||
myIdToDeliveryHandler.put(theSubscriptionId, theHandler);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public CanonicalSubscription registerSubscription(IIdType theId, S theSubscription) {
|
||||
Validate.notNull(theId);
|
||||
String subscriptionId = theId.getIdPart();
|
||||
Validate.notBlank(subscriptionId);
|
||||
Validate.notNull(theSubscription);
|
||||
|
||||
CanonicalSubscription canonicalized = canonicalize(theSubscription);
|
||||
SubscribableChannel deliveryChannel = createDeliveryChannel(canonicalized);
|
||||
Optional<MessageHandler> deliveryHandler = createDeliveryHandler(canonicalized);
|
||||
|
||||
mySubscribableChannel.put(subscriptionId, deliveryChannel);
|
||||
myIdToSubscription.put(subscriptionId, canonicalized);
|
||||
|
||||
deliveryHandler.ifPresent(handler -> registerHandler(subscriptionId, handler));
|
||||
|
||||
return canonicalized;
|
||||
}
|
||||
|
||||
protected void registerSubscriptionCheckingSubscriber() {
|
||||
if (mySubscriptionCheckingSubscriber == null) {
|
||||
mySubscriptionCheckingSubscriber = beanFactory.getBean(SubscriptionCheckingSubscriber.class, getChannelType(), this, mySubscriptionMatcherCompositeInMemoryDatabase);
|
||||
}
|
||||
getProcessingChannel().subscribe(mySubscriptionCheckingSubscriber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
|
||||
ResourceModifiedMessage msg = new ResourceModifiedMessage();
|
||||
msg.setId(theResource.getIdElement());
|
||||
msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.DELETE);
|
||||
submitResourceModified(msg);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
|
||||
submitResourceModifiedForUpdate(theNewResource);
|
||||
}
|
||||
|
||||
void submitResourceModifiedForUpdate(IBaseResource theNewResource) {
|
||||
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
||||
}
|
||||
|
||||
private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
|
||||
ResourceModifiedMessage msg = new ResourceModifiedMessage();
|
||||
msg.setId(theNewResource.getIdElement());
|
||||
msg.setOperationType(theOperationType);
|
||||
msg.setNewPayload(myCtx, theNewResource);
|
||||
if (ourForcePayloadEncodeAndDecodeForUnitTests) {
|
||||
msg.clearPayloadDecoded();
|
||||
}
|
||||
submitResourceModified(msg);
|
||||
}
|
||||
|
||||
protected void sendToProcessingChannel(final ResourceModifiedMessage theMessage) {
|
||||
ourLog.trace("Registering synchronization to send resource modified message to processing channel");
|
||||
|
||||
/*
|
||||
* We only actually submit this item work working after the
|
||||
*/
|
||||
if (TransactionSynchronizationManager.isSynchronizationActive()) {
|
||||
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
|
||||
@Override
|
||||
public void afterCommit() {
|
||||
ourLog.trace("Sending resource modified message to processing channel");
|
||||
getProcessingChannel().send(new ResourceModifiedJsonMessage(theMessage));
|
||||
}
|
||||
});
|
||||
} else {
|
||||
ourLog.trace("Sending resource modified message to processing channel");
|
||||
getProcessingChannel().send(new ResourceModifiedJsonMessage(theMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setAsyncTaskExecutorForUnitTest(AsyncTaskExecutor theAsyncTaskExecutor) {
|
||||
myAsyncTaskExecutor = theAsyncTaskExecutor;
|
||||
}
|
||||
|
||||
public void setFhirContext(FhirContext theCtx) {
|
||||
myCtx = theCtx;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setTxManager(PlatformTransactionManager theTxManager) {
|
||||
myTxManager = theTxManager;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
if (myCtx.getVersion().getVersion() == FhirVersionEnum.R4) {
|
||||
Validate.notNull(myEventDefinitionDaoR4);
|
||||
}
|
||||
|
||||
if (getProcessingChannel() == null) {
|
||||
myProcessingExecutorQueue = new LinkedBlockingQueue<>(1000);
|
||||
RejectedExecutionHandler rejectedExecutionHandler = (theRunnable, theExecutor) -> {
|
||||
ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", myProcessingExecutorQueue.size());
|
||||
StopWatch sw = new StopWatch();
|
||||
try {
|
||||
myProcessingExecutorQueue.put(theRunnable);
|
||||
} catch (InterruptedException theE) {
|
||||
throw new RejectedExecutionException("Task " + theRunnable.toString() +
|
||||
" rejected from " + theE.toString());
|
||||
}
|
||||
ourLog.info("Slot become available after {}ms", sw.getMillis());
|
||||
};
|
||||
ThreadFactory threadFactory = new BasicThreadFactory.Builder()
|
||||
.namingPattern("subscription-proc-%d")
|
||||
.daemon(false)
|
||||
.priority(Thread.NORM_PRIORITY)
|
||||
.build();
|
||||
myProcessingExecutor = new ThreadPoolExecutor(
|
||||
1,
|
||||
getExecutorThreadCount(),
|
||||
0L,
|
||||
TimeUnit.MILLISECONDS,
|
||||
myProcessingExecutorQueue,
|
||||
threadFactory,
|
||||
rejectedExecutionHandler);
|
||||
setProcessingChannel(new ExecutorSubscribableChannel(myProcessingExecutor));
|
||||
}
|
||||
|
||||
if (mySubscriptionActivatingSubscriber == null) {
|
||||
IFhirResourceDao<?> subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
||||
mySubscriptionActivatingSubscriber = new SubscriptionActivatingSubscriber(subscriptionDao, getChannelType(), this, myTxManager, myAsyncTaskExecutor);
|
||||
}
|
||||
|
||||
registerSubscriptionCheckingSubscriber();
|
||||
|
||||
TransactionTemplate transactionTemplate = new TransactionTemplate(myTxManager);
|
||||
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
initSubscriptions();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an internal API - Use with caution!
|
||||
*/
|
||||
public void submitResourceModified(final ResourceModifiedMessage theMsg) {
|
||||
mySubscriptionActivatingSubscriber.handleMessage(theMsg.getOperationType(), theMsg.getId(myCtx), theMsg.getNewPayload(myCtx));
|
||||
sendToProcessingChannel(theMsg);
|
||||
}
|
||||
|
||||
private void unregisterAllSubscriptionsNotInCollection(Collection<String> theAllIds) {
|
||||
for (String next : new ArrayList<>(myIdToSubscription.keySet())) {
|
||||
if (!theAllIds.contains(next)) {
|
||||
ourLog.info("Unregistering Subscription/{}", next);
|
||||
CanonicalSubscription subscription = myIdToSubscription.get(next);
|
||||
unregisterSubscription(subscription.getIdElement(myCtx));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void unregisterHandler(String theSubscriptionId, MessageHandler theMessageHandler) {
|
||||
SubscribableChannel channel = mySubscribableChannel.get(theSubscriptionId);
|
||||
if (channel != null) {
|
||||
channel.unsubscribe(theMessageHandler);
|
||||
if (channel instanceof DisposableBean) {
|
||||
try {
|
||||
((DisposableBean) channel).destroy();
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failed to destroy channel bean", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mySubscribableChannel.remove(theSubscriptionId);
|
||||
}
|
||||
|
||||
@SuppressWarnings("UnusedReturnValue")
|
||||
public CanonicalSubscription unregisterSubscription(IIdType theId) {
|
||||
Validate.notNull(theId);
|
||||
|
||||
String subscriptionId = theId.getIdPart();
|
||||
Validate.notBlank(subscriptionId);
|
||||
|
||||
for (MessageHandler next : new ArrayList<>(myIdToDeliveryHandler.get(subscriptionId))) {
|
||||
unregisterHandler(subscriptionId, next);
|
||||
}
|
||||
|
||||
mySubscribableChannel.remove(subscriptionId);
|
||||
|
||||
return myIdToSubscription.remove(subscriptionId);
|
||||
}
|
||||
|
||||
public IFhirResourceDao<?> getSubscriptionDao() {
|
||||
return myDaoRegistry.getSubscriptionDao();
|
||||
}
|
||||
|
||||
public IFhirResourceDao getDao(Class type) {
|
||||
return myDaoRegistry.getResourceDao(type);
|
||||
}
|
||||
|
||||
public void setResourceDaos(List<IFhirResourceDao> theResourceDaos) {
|
||||
myDaoRegistry.setResourceDaos(theResourceDaos);
|
||||
}
|
||||
|
||||
public void validateCriteria(final S theResource) {
|
||||
CanonicalSubscription subscription = canonicalize(theResource);
|
||||
String criteria = subscription.getCriteriaString();
|
||||
try {
|
||||
RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myCtx, criteria);
|
||||
myMatchUrlService.translateMatchUrl(criteria, resourceDef);
|
||||
} catch (InvalidRequestException e) {
|
||||
throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setForcePayloadEncodeAndDecodeForUnitTests(boolean theForcePayloadEncodeAndDecodeForUnitTests) {
|
||||
ourForcePayloadEncodeAndDecodeForUnitTests = theForcePayloadEncodeAndDecodeForUnitTests;
|
||||
}
|
||||
}
|
|
@ -1,98 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
||||
public abstract class BaseSubscriptionSubscriber implements MessageHandler {
|
||||
|
||||
private final Subscription.SubscriptionChannelType myChannelType;
|
||||
private final BaseSubscriptionInterceptor mySubscriptionInterceptor;
|
||||
@Autowired
|
||||
DaoRegistry myDaoRegistry;
|
||||
private IFhirResourceDao<?> mySubscriptionDao;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public BaseSubscriptionSubscriber(Subscription.SubscriptionChannelType theChannelType, BaseSubscriptionInterceptor theSubscriptionInterceptor) {
|
||||
myChannelType = theChannelType;
|
||||
mySubscriptionInterceptor = theSubscriptionInterceptor;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused") // Don't delete, used in Smile
|
||||
public void setDaoRegistry(DaoRegistry theDaoRegistry) {
|
||||
myDaoRegistry = theDaoRegistry;
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void setSubscriptionDao() {
|
||||
mySubscriptionDao = myDaoRegistry.getSubscriptionDao();
|
||||
}
|
||||
|
||||
public Subscription.SubscriptionChannelType getChannelType() {
|
||||
return myChannelType;
|
||||
}
|
||||
|
||||
public FhirContext getContext() {
|
||||
return getSubscriptionDao().getContext();
|
||||
}
|
||||
|
||||
public IFhirResourceDao getSubscriptionDao() {
|
||||
return mySubscriptionDao;
|
||||
}
|
||||
|
||||
public BaseSubscriptionInterceptor getSubscriptionInterceptor() {
|
||||
return mySubscriptionInterceptor;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Does this subscription type (e.g. rest hook, websocket, etc) apply to this interceptor?
|
||||
*/
|
||||
protected boolean subscriptionTypeApplies(CanonicalSubscription theSubscription) {
|
||||
Subscription.SubscriptionChannelType channelType = getChannelType();
|
||||
String subscriptionType = theSubscription.getChannelType().toCode();
|
||||
return subscriptionTypeApplies(subscriptionType, channelType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this subscription type (e.g. rest hook, websocket, etc) apply to this interceptor?
|
||||
*/
|
||||
static boolean subscriptionTypeApplies(String theSubscriptionChannelTypeCode, Subscription.SubscriptionChannelType theChannelType) {
|
||||
boolean subscriptionTypeApplies = false;
|
||||
if (theSubscriptionChannelTypeCode != null) {
|
||||
if (theChannelType.toCode().equals(theSubscriptionChannelTypeCode)) {
|
||||
subscriptionTypeApplies = true;
|
||||
}
|
||||
}
|
||||
return subscriptionTypeApplies;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,32 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.IResourceRetriever;
|
||||
import ca.uhn.fhir.rest.server.exceptions.ResourceGoneException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DaoResourceRetriever implements IResourceRetriever {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(ActiveSubscription.class);
|
||||
|
||||
@Autowired
|
||||
FhirContext myFhirContext;
|
||||
@Autowired
|
||||
DaoRegistry myDaoRegistry;
|
||||
|
||||
@Override
|
||||
public IBaseResource getResource(IIdType payloadId) throws ResourceGoneException {
|
||||
RuntimeResourceDefinition resourceDef = myFhirContext.getResourceDefinition(payloadId.getResourceType());
|
||||
IFhirResourceDao dao = myDaoRegistry.getResourceDao(resourceDef.getImplementingClass());
|
||||
return dao.read(payloadId.toVersionless());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,247 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionCannonicalizer;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
||||
import ca.uhn.fhir.util.SubscriptionUtil;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hl7.fhir.instance.model.Subscription;
|
||||
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.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.stereotype.Service;
|
||||
import org.springframework.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
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.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Responsible for transitioning subscription resources from REQUESTED to ACTIVE
|
||||
* Once activated, the subscription is added to the SubscriptionRegistry.
|
||||
*
|
||||
* Also validates criteria. If invalid, rejects the subscription without persisting the subscription.
|
||||
*/
|
||||
@Service
|
||||
public class SubscriptionActivatingInterceptor extends ServerOperationInterceptorAdapter {
|
||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionActivatingInterceptor.class);
|
||||
|
||||
private static boolean ourWaitForSubscriptionActivationSynchronouslyForUnitTest;
|
||||
|
||||
@Autowired
|
||||
private PlatformTransactionManager myTransactionManager;
|
||||
@Autowired
|
||||
@Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
|
||||
private AsyncTaskExecutor myTaskExecutor;
|
||||
@Autowired
|
||||
private SubscriptionRegistry mySubscriptionRegistry;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
@Autowired
|
||||
private SubscriptionCannonicalizer mySubscriptionCannonicalizer;
|
||||
@Autowired
|
||||
private MatchUrlService myMatchUrlService;
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
|
||||
public boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
|
||||
// Grab the value for "Subscription.channel.type" so we can see if this
|
||||
// subscriber applies..
|
||||
String subscriptionChannelTypeCode = myFhirContext
|
||||
.newTerser()
|
||||
.getSingleValueOrNull(theSubscription, SubscriptionMatcherInterceptor.SUBSCRIPTION_TYPE, IPrimitiveType.class)
|
||||
.getValueAsString();
|
||||
|
||||
Subscription.SubscriptionChannelType subscriptionChannelType = Subscription.SubscriptionChannelType.fromCode(subscriptionChannelTypeCode);
|
||||
// Only activate supported subscriptions
|
||||
if (!myDaoConfig.getSupportedSubscriptionTypes().contains(subscriptionChannelType)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final IPrimitiveType<?> status = myFhirContext.newTerser().getSingleValueOrNull(theSubscription, SubscriptionMatcherInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class);
|
||||
String statusString = status.getValueAsString();
|
||||
|
||||
final String requestedStatus = Subscription.SubscriptionStatus.REQUESTED.toCode();
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return activateSubscription(activeStatus, theSubscription, requestedStatus);
|
||||
}
|
||||
} else if (activeStatus.equals(statusString)) {
|
||||
return mySubscriptionRegistry.registerSubscriptionUnlessAlreadyRegistered(theSubscription);
|
||||
} else {
|
||||
// Status isn't "active" or "requested"
|
||||
return mySubscriptionRegistry.unregisterSubscriptionIfRegistered(theSubscription, statusString);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
|
||||
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
||||
IBaseResource subscription = subscriptionDao.read(theSubscription.getIdElement());
|
||||
|
||||
ourLog.info("Activating subscription {} from status {} to {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus);
|
||||
try {
|
||||
SubscriptionUtil.setStatus(myFhirContext, subscription, theActiveStatus);
|
||||
subscription = subscriptionDao.update(subscription).getResource();
|
||||
submitResourceModifiedForUpdate(subscription);
|
||||
return true;
|
||||
} catch (final UnprocessableEntityException e) {
|
||||
ourLog.info("Changing status of {} to ERROR", subscription.getIdElement());
|
||||
SubscriptionUtil.setStatus(myFhirContext, subscription, "error");
|
||||
SubscriptionUtil.setReason(myFhirContext, subscription, e.getMessage());
|
||||
subscriptionDao.update(subscription);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
void submitResourceModifiedForUpdate(IBaseResource theNewResource) {
|
||||
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
|
||||
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
|
||||
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
||||
}
|
||||
|
||||
private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
|
||||
submitResourceModified(new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType));
|
||||
}
|
||||
|
||||
private void submitResourceModified(final ResourceModifiedMessage theMsg) {
|
||||
IIdType id = theMsg.getId(myFhirContext);
|
||||
if (!id.getResourceType().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
|
||||
return;
|
||||
}
|
||||
switch (theMsg.getOperationType()) {
|
||||
case DELETE:
|
||||
mySubscriptionRegistry.unregisterSubscription(id);
|
||||
break;
|
||||
case CREATE:
|
||||
case UPDATE:
|
||||
final IBaseResource subscription = theMsg.getNewPayload(myFhirContext);
|
||||
validateCriteria(subscription);
|
||||
activateAndRegisterSubscriptionIfRequiredInTransaction(subscription);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
public void validateCriteria(final IBaseResource theResource) {
|
||||
CanonicalSubscription subscription = mySubscriptionCannonicalizer.canonicalize(theResource);
|
||||
String criteria = subscription.getCriteriaString();
|
||||
try {
|
||||
RuntimeResourceDefinition resourceDef = CacheWarmingSvcImpl.parseUrlResourceType(myFhirContext, criteria);
|
||||
myMatchUrlService.translateMatchUrl(criteria, resourceDef);
|
||||
} catch (InvalidRequestException e) {
|
||||
throw new UnprocessableEntityException("Invalid subscription criteria submitted: " + criteria + " " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
private void activateAndRegisterSubscriptionIfRequiredInTransaction(IBaseResource theSubscription) {
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
activateOrRegisterSubscriptionIfRequired(theSubscription);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setWaitForSubscriptionActivationSynchronouslyForUnitTest(boolean theWaitForSubscriptionActivationSynchronouslyForUnitTest) {
|
||||
ourWaitForSubscriptionActivationSynchronouslyForUnitTest = theWaitForSubscriptionActivationSynchronouslyForUnitTest;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,220 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.SubscriptionUtil;
|
||||
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.transaction.PlatformTransactionManager;
|
||||
import org.springframework.transaction.TransactionStatus;
|
||||
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.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<? extends IBaseResource> 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 boolean activateOrRegisterSubscriptionIfRequired(final IBaseResource theSubscription) {
|
||||
// Grab the value for "Subscription.channel.type" so we can see if this
|
||||
// subscriber applies..
|
||||
String subscriptionChannelType = myCtx
|
||||
.newTerser()
|
||||
.getSingleValueOrNull(theSubscription, BaseSubscriptionInterceptor.SUBSCRIPTION_TYPE, IPrimitiveType.class)
|
||||
.getValueAsString();
|
||||
boolean subscriptionTypeApplies = BaseSubscriptionSubscriber.subscriptionTypeApplies(subscriptionChannelType, myChannelType);
|
||||
if (subscriptionTypeApplies == false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final IPrimitiveType<?> status = myCtx.newTerser().getSingleValueOrNull(theSubscription, BaseSubscriptionInterceptor.SUBSCRIPTION_STATUS, IPrimitiveType.class);
|
||||
String statusString = status.getValueAsString();
|
||||
|
||||
final String requestedStatus = Subscription.SubscriptionStatus.REQUESTED.toCode();
|
||||
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() {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
return true;
|
||||
} else {
|
||||
return activateSubscription(activeStatus, theSubscription, requestedStatus);
|
||||
}
|
||||
} else if (activeStatus.equals(statusString)) {
|
||||
return registerSubscriptionUnlessAlreadyRegistered(theSubscription);
|
||||
} else {
|
||||
// Status isn't "active" or "requested"
|
||||
return unregisterSubscriptionIfRegistered(theSubscription, statusString);
|
||||
}
|
||||
}
|
||||
|
||||
protected boolean unregisterSubscriptionIfRegistered(IBaseResource theSubscription, String theStatusString) {
|
||||
if (mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement()) != null) {
|
||||
ourLog.info("Removing {} subscription {}", theStatusString, theSubscription.getIdElement().toUnqualified().getValue());
|
||||
mySubscriptionInterceptor.unregisterSubscription(theSubscription.getIdElement());
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean activateSubscription(String theActiveStatus, final IBaseResource theSubscription, String theRequestedStatus) {
|
||||
IBaseResource subscription = mySubscriptionDao.read(theSubscription.getIdElement());
|
||||
|
||||
ourLog.info("Activating subscription {} from status {} to {} for channel {}", subscription.getIdElement().toUnqualified().getValue(), theRequestedStatus, theActiveStatus, myChannelType);
|
||||
try {
|
||||
SubscriptionUtil.setStatus(myCtx, subscription, theActiveStatus);
|
||||
subscription = mySubscriptionDao.update(subscription).getResource();
|
||||
mySubscriptionInterceptor.submitResourceModifiedForUpdate(subscription);
|
||||
return true;
|
||||
} catch (final UnprocessableEntityException e) {
|
||||
ourLog.info("Changing status of {} to ERROR", subscription.getIdElement());
|
||||
SubscriptionUtil.setStatus(myCtx, subscription, "error");
|
||||
SubscriptionUtil.setReason(myCtx, subscription, e.getMessage());
|
||||
mySubscriptionDao.update(subscription);
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("EnumSwitchStatementWhichMissesCases")
|
||||
public void handleMessage(ResourceModifiedMessage.OperationTypeEnum theOperationType, IIdType theId, final IBaseResource theSubscription) throws MessagingException {
|
||||
if (!theId.getResourceType().equals(ResourceTypeEnum.SUBSCRIPTION.getCode())) {
|
||||
return;
|
||||
}
|
||||
switch (theOperationType) {
|
||||
case DELETE:
|
||||
mySubscriptionInterceptor.unregisterSubscription(theId);
|
||||
break;
|
||||
case CREATE:
|
||||
case UPDATE:
|
||||
mySubscriptionInterceptor.validateCriteria(theSubscription);
|
||||
activateAndRegisterSubscriptionIfRequiredInTransaction(theSubscription);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void activateAndRegisterSubscriptionIfRequiredInTransaction(IBaseResource theSubscription) {
|
||||
TransactionTemplate txTemplate = new TransactionTemplate(myTransactionManager);
|
||||
txTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
@Override
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
activateOrRegisterSubscriptionIfRequired(theSubscription);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected synchronized boolean registerSubscriptionUnlessAlreadyRegistered(IBaseResource theSubscription) {
|
||||
CanonicalSubscription existingSubscription = mySubscriptionInterceptor.hasSubscription(theSubscription.getIdElement());
|
||||
CanonicalSubscription newSubscription = mySubscriptionInterceptor.canonicalize(theSubscription);
|
||||
|
||||
if (existingSubscription != null) {
|
||||
if (newSubscription.equals(existingSubscription)) {
|
||||
// No changes
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (existingSubscription != null) {
|
||||
ourLog.info("Updating already-registered active subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
|
||||
mySubscriptionInterceptor.unregisterSubscription(theSubscription.getIdElement());
|
||||
} else {
|
||||
ourLog.info("Registering active subscription {}", theSubscription.getIdElement().toUnqualified().getValue());
|
||||
}
|
||||
mySubscriptionInterceptor.registerSubscription(theSubscription.getIdElement(), theSubscription);
|
||||
return true;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setWaitForSubscriptionActivationSynchronouslyForUnitTest(boolean theWaitForSubscriptionActivationSynchronouslyForUnitTest) {
|
||||
ourWaitForSubscriptionActivationSynchronouslyForUnitTest = theWaitForSubscriptionActivationSynchronouslyForUnitTest;
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,34 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hl7.fhir.instance.model.Subscription;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
@Service
|
||||
public class SubscriptionInterceptorLoader {
|
||||
@Autowired
|
||||
DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
|
||||
@Autowired
|
||||
SubscriptionActivatingInterceptor mySubscriptionActivatingInterceptor;
|
||||
|
||||
public void registerInterceptors() {
|
||||
Set<Subscription.SubscriptionChannelType> supportedSubscriptionTypes = myDaoConfig.getSupportedSubscriptionTypes();
|
||||
|
||||
if (!supportedSubscriptionTypes.isEmpty()) {
|
||||
myDaoConfig.registerInterceptor(mySubscriptionActivatingInterceptor);
|
||||
myDaoConfig.registerInterceptor(mySubscriptionMatcherInterceptor);
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void unregisterInterceptorsForUnitTest() {
|
||||
myDaoConfig.unregisterInterceptor(mySubscriptionActivatingInterceptor);
|
||||
myDaoConfig.unregisterInterceptor(mySubscriptionMatcherInterceptor);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,127 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceModifiedJsonMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionCheckingSubscriber;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import ca.uhn.fhir.rest.server.interceptor.ServerOperationInterceptorAdapter;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.annotation.PreDestroy;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
@Component
|
||||
public class SubscriptionMatcherInterceptor extends ServerOperationInterceptorAdapter {
|
||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherInterceptor.class);
|
||||
|
||||
static final String SUBSCRIPTION_STATUS = "Subscription.status";
|
||||
static final String SUBSCRIPTION_TYPE = "Subscription.channel.type";
|
||||
private static boolean ourForcePayloadEncodeAndDecodeForUnitTests;
|
||||
private SubscribableChannel myProcessingChannel;
|
||||
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
@Autowired
|
||||
private SubscriptionCheckingSubscriber mySubscriptionCheckingSubscriber;
|
||||
@Autowired
|
||||
private ISubscriptionChannelFactory mySubscriptionChannelFactory;
|
||||
|
||||
/**
|
||||
* Constructor
|
||||
*/
|
||||
public SubscriptionMatcherInterceptor() {
|
||||
super();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
if (myProcessingChannel == null) {
|
||||
myProcessingChannel = mySubscriptionChannelFactory.newMatchingChannel("subscription-matching");
|
||||
}
|
||||
myProcessingChannel.subscribe(mySubscriptionCheckingSubscriber);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unused")
|
||||
@PreDestroy
|
||||
public void preDestroy() {
|
||||
myProcessingChannel.unsubscribe(mySubscriptionCheckingSubscriber);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceCreated(RequestDetails theRequest, IBaseResource theResource) {
|
||||
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.CREATE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceDeleted(RequestDetails theRequest, IBaseResource theResource) {
|
||||
submitResourceModified(theResource, ResourceModifiedMessage.OperationTypeEnum.DELETE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void resourceUpdated(RequestDetails theRequest, IBaseResource theOldResource, IBaseResource theNewResource) {
|
||||
submitResourceModified(theNewResource, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
||||
}
|
||||
|
||||
private void submitResourceModified(IBaseResource theNewResource, ResourceModifiedMessage.OperationTypeEnum theOperationType) {
|
||||
ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theNewResource, theOperationType);
|
||||
if (ourForcePayloadEncodeAndDecodeForUnitTests) {
|
||||
msg.clearPayloadDecoded();
|
||||
}
|
||||
submitResourceModified(msg);
|
||||
}
|
||||
|
||||
protected void sendToProcessingChannel(final ResourceModifiedMessage theMessage) {
|
||||
ourLog.trace("Sending resource modified message to processing channel");
|
||||
myProcessingChannel.send(new ResourceModifiedJsonMessage(theMessage));
|
||||
}
|
||||
|
||||
public void setFhirContext(FhirContext theCtx) {
|
||||
myFhirContext = theCtx;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is an internal API - Use with caution!
|
||||
*/
|
||||
public void submitResourceModified(final ResourceModifiedMessage theMsg) {
|
||||
sendToProcessingChannel(theMsg);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public static void setForcePayloadEncodeAndDecodeForUnitTests(boolean theForcePayloadEncodeAndDecodeForUnitTests) {
|
||||
ourForcePayloadEncodeAndDecodeForUnitTests = theForcePayloadEncodeAndDecodeForUnitTests;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public SubscriptionChannel getProcessingChannelForUnitTest() {
|
||||
return (SubscriptionChannel) myProcessingChannel;
|
||||
}
|
||||
}
|
|
@ -22,13 +22,16 @@ package ca.uhn.fhir.jpa.subscription;
|
|||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.search.warm.CacheWarmingSvcImpl;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionChannelFactory;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import ca.uhn.fhir.rest.annotation.IdParam;
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
|
@ -38,6 +41,7 @@ import ca.uhn.fhir.rest.param.UriParam;
|
|||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerOperationInterceptor;
|
||||
import ca.uhn.fhir.util.ParametersUtil;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import ca.uhn.fhir.util.ValidateUtil;
|
||||
|
@ -71,26 +75,31 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
|||
|
||||
@Service
|
||||
public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc, ApplicationContextAware {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class);
|
||||
|
||||
public static final int DEFAULT_MAX_SUBMIT = 10000;
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionTriggeringProvider.class);
|
||||
private final List<SubscriptionTriggeringJobDetails> myActiveJobs = new ArrayList<>();
|
||||
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
private List<BaseSubscriptionInterceptor<?>> mySubscriptionInterceptorList;
|
||||
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||
@Autowired
|
||||
private MatchUrlService myMatchUrlService;
|
||||
@Autowired
|
||||
private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
|
||||
|
||||
private final List<SubscriptionTriggeringJobDetails> myActiveJobs = new ArrayList<>();
|
||||
private int myMaxSubmitPerPass = DEFAULT_MAX_SUBMIT;
|
||||
private ApplicationContext myAppCtx;
|
||||
private ExecutorService myExecutorService;
|
||||
|
||||
@Override
|
||||
public IBaseParameters triggerSubscription(List<UriParam> theResourceIds, List<StringParam> theSearchUrls, @IdParam IIdType theSubscriptionId) {
|
||||
if (mySubscriptionInterceptorList.isEmpty()) {
|
||||
if (myDaoConfig.getSupportedSubscriptionTypes().isEmpty()) {
|
||||
throw new PreconditionFailedException("Subscription processing not active on this server");
|
||||
}
|
||||
|
||||
|
@ -293,18 +302,13 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
|
|||
|
||||
ourLog.info("Submitting resource {} to subscription {}", theResourceToTrigger.getIdElement().toUnqualifiedVersionless().getValue(), theSubscriptionId);
|
||||
|
||||
ResourceModifiedMessage msg = new ResourceModifiedMessage();
|
||||
msg.setId(theResourceToTrigger.getIdElement());
|
||||
msg.setOperationType(ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
||||
ResourceModifiedMessage msg = new ResourceModifiedMessage(myFhirContext, theResourceToTrigger, ResourceModifiedMessage.OperationTypeEnum.UPDATE);
|
||||
msg.setSubscriptionId(new IdType(theSubscriptionId).toUnqualifiedVersionless().getValue());
|
||||
msg.setNewPayload(myFhirContext, theResourceToTrigger);
|
||||
|
||||
return myExecutorService.submit(() -> {
|
||||
for (int i = 0; ; i++) {
|
||||
try {
|
||||
for (BaseSubscriptionInterceptor<?> next : mySubscriptionInterceptorList) {
|
||||
next.submitResourceModified(msg);
|
||||
}
|
||||
mySubscriptionMatcherInterceptor.submitResourceModified(msg);
|
||||
break;
|
||||
} catch (Exception e) {
|
||||
if (i >= 3) {
|
||||
|
@ -347,13 +351,6 @@ public class SubscriptionTriggeringSvcImpl implements ISubscriptionTriggeringSvc
|
|||
@SuppressWarnings("unchecked")
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
mySubscriptionInterceptorList = ObjectUtils.defaultIfNull(mySubscriptionInterceptorList, Collections.emptyList());
|
||||
mySubscriptionInterceptorList = new ArrayList<>();
|
||||
Collection values1 = myAppCtx.getBeansOfType(BaseSubscriptionInterceptor.class).values();
|
||||
Collection<BaseSubscriptionInterceptor<?>> values = (Collection<BaseSubscriptionInterceptor<?>>) values1;
|
||||
mySubscriptionInterceptorList.addAll(values);
|
||||
|
||||
|
||||
LinkedBlockingQueue<Runnable> executorQueue = new LinkedBlockingQueue<>(1000);
|
||||
BasicThreadFactory threadFactory = new BasicThreadFactory.Builder()
|
||||
.namingPattern("SubscriptionTriggering-%d")
|
||||
|
|
|
@ -0,0 +1,35 @@
|
|||
package ca.uhn.fhir.jpa.subscription.dbcache;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionActivatingInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class DaoSubscriptionProvider implements ISubscriptionProvider {
|
||||
@Autowired
|
||||
DaoRegistry myDaoRegistry;
|
||||
@Autowired
|
||||
private SubscriptionActivatingInterceptor mySubscriptionActivatingInterceptor;
|
||||
|
||||
@Override
|
||||
public IBundleProvider search(SearchParameterMap theMap) {
|
||||
IFhirResourceDao subscriptionDao = myDaoRegistry.getSubscriptionDao();
|
||||
RequestDetails req = new ServletSubRequestDetails();
|
||||
req.setSubRequest(true);
|
||||
|
||||
return subscriptionDao.search(theMap, req);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean loadSubscription(IBaseResource theResource) {
|
||||
return mySubscriptionActivatingInterceptor.activateOrRegisterSubscriptionIfRequired(theResource);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||
package ca.uhn.fhir.jpa.subscription.dbmatcher;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
|
@ -21,34 +21,38 @@ package ca.uhn.fhir.jpa.subscription.matcher;
|
|||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
||||
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
|
||||
import ca.uhn.fhir.jpa.subscription.module.matcher.InMemorySubscriptionMatcher;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class SubscriptionMatcherCompositeInMemoryDatabase implements ISubscriptionMatcher {
|
||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherCompositeInMemoryDatabase.class);
|
||||
public class CompositeInMemoryDaoSubscriptionMatcher implements ISubscriptionMatcher {
|
||||
private Logger ourLog = LoggerFactory.getLogger(CompositeInMemoryDaoSubscriptionMatcher.class);
|
||||
|
||||
@Autowired
|
||||
SubscriptionMatcherDatabase mySubscriptionMatcherDatabase;
|
||||
@Autowired
|
||||
SubscriptionMatcherInMemory mySubscriptionMatcherInMemory;
|
||||
private final DaoSubscriptionMatcher myDaoSubscriptionMatcher;
|
||||
private final InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
|
||||
@Autowired
|
||||
DaoConfig myDaoConfig;
|
||||
|
||||
public CompositeInMemoryDaoSubscriptionMatcher(DaoSubscriptionMatcher theDaoSubscriptionMatcher, InMemorySubscriptionMatcher theInMemorySubscriptionMatcher) {
|
||||
myDaoSubscriptionMatcher = theDaoSubscriptionMatcher;
|
||||
myInMemorySubscriptionMatcher = theInMemorySubscriptionMatcher;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionMatchResult match(String criteria, ResourceModifiedMessage msg) {
|
||||
SubscriptionMatchResult result;
|
||||
if (myDaoConfig.isEnableInMemorySubscriptionMatching()) {
|
||||
result = mySubscriptionMatcherInMemory.match(criteria, msg);
|
||||
result = myInMemorySubscriptionMatcher.match(criteria, msg);
|
||||
if (!result.supported()) {
|
||||
ourLog.info("Criteria {} not supported by InMemoryMatcher: {}. Reverting to DatabaseMatcher", criteria, result.getUnsupportedReason());
|
||||
result = mySubscriptionMatcherDatabase.match(criteria, msg);
|
||||
result = myDaoSubscriptionMatcher.match(criteria, msg);
|
||||
}
|
||||
} else {
|
||||
result = mySubscriptionMatcherDatabase.match(criteria, msg);
|
||||
result = myDaoSubscriptionMatcher.match(criteria, msg);
|
||||
}
|
||||
return result;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||
package ca.uhn.fhir.jpa.subscription.dbmatcher;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
|
@ -24,10 +24,12 @@ import ca.uhn.fhir.context.FhirContext;
|
|||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.provider.ServletSubRequestDetails;
|
||||
import ca.uhn.fhir.jpa.searchparam.MatchUrlService;
|
||||
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.module.matcher.ISubscriptionMatcher;
|
||||
import ca.uhn.fhir.jpa.subscription.module.matcher.SubscriptionMatchResult;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.api.server.RequestDetails;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
@ -35,13 +37,9 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
@Lazy
|
||||
public class SubscriptionMatcherDatabase implements ISubscriptionMatcher {
|
||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionMatcherDatabase.class);
|
||||
public class DaoSubscriptionMatcher implements ISubscriptionMatcher {
|
||||
private Logger ourLog = LoggerFactory.getLogger(DaoSubscriptionMatcher.class);
|
||||
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
|
@ -1,89 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.subscription.email;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
/**
|
||||
* Note: If you're going to use this, you need to provide a bean
|
||||
* of type {@link ca.uhn.fhir.jpa.subscription.email.IEmailSender}
|
||||
* in your own Spring config
|
||||
*/
|
||||
public class SubscriptionEmailInterceptor extends BaseSubscriptionInterceptor {
|
||||
|
||||
/**
|
||||
* This is set to autowired=false just so that implementors can supply this
|
||||
* with a mechanism other than autowiring if they want
|
||||
*/
|
||||
@Autowired(required = false)
|
||||
private IEmailSender myEmailSender;
|
||||
@Autowired
|
||||
BeanFactory myBeanFactory;
|
||||
private String myDefaultFromAddress = "noreply@unknown.com";
|
||||
|
||||
@Override
|
||||
protected Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription) {
|
||||
return Optional.of(myBeanFactory.getBean(SubscriptionDeliveringEmailSubscriber.class, getChannelType(), this));
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
|
||||
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.EMAIL;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "from" address to use for any sent emails that to not explicitly specity a from address
|
||||
*/
|
||||
public String getDefaultFromAddress() {
|
||||
return myDefaultFromAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* The "from" address to use for any sent emails that to not explicitly specity a from address
|
||||
*/
|
||||
public void setDefaultFromAddress(String theDefaultFromAddress) {
|
||||
Validate.notBlank(theDefaultFromAddress, "theDefaultFromAddress must not be null or blank");
|
||||
myDefaultFromAddress = theDefaultFromAddress;
|
||||
}
|
||||
|
||||
public IEmailSender getEmailSender() {
|
||||
return myEmailSender;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the email sender (this method does not need to be explicitly called if you
|
||||
* are using autowiring to supply the sender)
|
||||
*/
|
||||
public void setEmailSender(IEmailSender theEmailSender) {
|
||||
myEmailSender = theEmailSender;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,7 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||
|
||||
public interface ISubscriptionMatcher {
|
||||
boolean match(String criteria, ResourceModifiedMessage msg);
|
||||
}
|
|
@ -1,46 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class SubscriptionRestHookInterceptor extends BaseSubscriptionInterceptor {
|
||||
@Autowired
|
||||
BeanFactory myBeanFactory;
|
||||
|
||||
@Override
|
||||
protected Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription) {
|
||||
SubscriptionDeliveringRestHookSubscriber value = myBeanFactory.getBean(SubscriptionDeliveringRestHookSubscriber.class, getChannelType(), this);
|
||||
return Optional.of(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType getChannelType() {
|
||||
return org.hl7.fhir.r4.model.Subscription.SubscriptionChannelType.RESTHOOK;
|
||||
}
|
||||
|
||||
}
|
|
@ -1,27 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||
|
||||
/*
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import org.springframework.web.socket.WebSocketHandler;
|
||||
|
||||
public interface ISubscriptionWebsocketHandler extends WebSocketHandler {
|
||||
|
||||
}
|
|
@ -1,43 +0,0 @@
|
|||
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
* HAPI FHIR JPA Server
|
||||
* %%
|
||||
* Copyright (C) 2014 - 2018 University Health Network
|
||||
* %%
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
* #L%
|
||||
*/
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.CanonicalSubscription;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
public class SubscriptionWebsocketInterceptor extends BaseSubscriptionInterceptor {
|
||||
|
||||
@Override
|
||||
protected Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription) {
|
||||
return Optional.empty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Subscription.SubscriptionChannelType getChannelType() {
|
||||
return Subscription.SubscriptionChannelType.WEBSOCKET;
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -23,51 +23,6 @@ package ca.uhn.fhir.jpa.util;
|
|||
import ca.uhn.fhir.rest.api.Constants;
|
||||
|
||||
public class JpaConstants {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This extension should be of type <code>string</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This extension should be of type <code>string</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template";
|
||||
|
||||
|
||||
/**
|
||||
* This extension URL indicates whether a REST HOOK delivery should
|
||||
* include the version ID when delivering.
|
||||
* <p>
|
||||
* This extension should be of type <code>boolean</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element.
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids";
|
||||
|
||||
/**
|
||||
* This extension URL indicates whether a REST HOOK delivery should
|
||||
* reload the resource and deliver the latest version always. This
|
||||
* could be useful for example if a resource which triggers a
|
||||
* subscription gets updated many times in short succession and there
|
||||
* is no value in delivering the older versions.
|
||||
* <p>
|
||||
* Note that if the resource is now deleted, this may cause
|
||||
* the delivery to be cancelled altogether.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This extension should be of type <code>boolean</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element.
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version";
|
||||
/**
|
||||
* Operation name for the $expunge operation
|
||||
*/
|
||||
|
|
|
@ -0,0 +1,50 @@
|
|||
package ca.uhn.fhir.jpa.config;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessagingException;
|
||||
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
public class StoppableSubscriptionDeliveringRestHookSubscriber extends SubscriptionDeliveringRestHookSubscriber {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(StoppableSubscriptionDeliveringRestHookSubscriber.class);
|
||||
|
||||
private boolean myPauseEveryMessage = false;
|
||||
private CountDownLatch myCountDownLatch;
|
||||
|
||||
@Override
|
||||
public void handleMessage(Message theMessage) throws MessagingException {
|
||||
if (myCountDownLatch != null) {
|
||||
myCountDownLatch.countDown();
|
||||
}
|
||||
if (myPauseEveryMessage) {
|
||||
waitIfPaused();
|
||||
}
|
||||
super.handleMessage(theMessage);
|
||||
}
|
||||
|
||||
private synchronized void waitIfPaused() {
|
||||
try {
|
||||
if (myPauseEveryMessage) {
|
||||
wait();
|
||||
}
|
||||
} catch (InterruptedException theE) {
|
||||
ourLog.error("interrupted", theE);
|
||||
}
|
||||
}
|
||||
|
||||
public void pause() {
|
||||
myPauseEveryMessage = true;
|
||||
}
|
||||
|
||||
public synchronized void unPause() {
|
||||
myPauseEveryMessage = false;
|
||||
notifyAll();
|
||||
}
|
||||
|
||||
public void setCountDownLatch(CountDownLatch theCountDownLatch) {
|
||||
myCountDownLatch = theCountDownLatch;
|
||||
}
|
||||
}
|
|
@ -1,28 +1,23 @@
|
|||
package ca.uhn.fhir.jpa.config;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.search.LuceneSearchMappingFactory;
|
||||
import ca.uhn.fhir.jpa.subscription.email.IEmailSender;
|
||||
import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender;
|
||||
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.hibernate.jpa.HibernatePersistenceProvider;
|
||||
import org.springframework.context.annotation.*;
|
||||
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
@Configuration
|
||||
@Import(TestJPAConfig.class)
|
||||
|
|
|
@ -2,12 +2,12 @@ package ca.uhn.fhir.jpa.config;
|
|||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.context.annotation.Primary;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
|
||||
|
@ -38,4 +38,16 @@ public class TestJPAConfig {
|
|||
public UnregisterScheduledProcessor unregisterScheduledProcessor(Environment theEnv) {
|
||||
return new UnregisterScheduledProcessor(theEnv);
|
||||
}
|
||||
|
||||
@Lazy
|
||||
@Bean
|
||||
public SubscriptionTestUtil subscriptionTestUtil() {
|
||||
return new SubscriptionTestUtil();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@Primary
|
||||
public SubscriptionDeliveringRestHookSubscriber stoppableSubscriptionDeliveringRestHookSubscriber() {
|
||||
return new StoppableSubscriptionDeliveringRestHookSubscriber();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,27 +1,21 @@
|
|||
package ca.uhn.fhir.jpa.config;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.rest.server.interceptor.RequestValidatingInterceptor;
|
||||
import ca.uhn.fhir.validation.ResultSeverityEnum;
|
||||
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
|
||||
import net.ttddyy.dsproxy.listener.logging.SLF4JLogLevel;
|
||||
import net.ttddyy.dsproxy.support.ProxyDataSourceBuilder;
|
||||
import org.apache.commons.dbcp2.BasicDataSource;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.orm.jpa.JpaTransactionManager;
|
||||
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
|
||||
import org.springframework.transaction.annotation.EnableTransactionManagement;
|
||||
|
||||
import javax.persistence.EntityManagerFactory;
|
||||
import javax.sql.DataSource;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import java.util.Properties;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
|
@ -102,8 +96,8 @@ public class TestR4Config extends BaseJavaConfigR4 {
|
|||
|
||||
DataSource dataSource = ProxyDataSourceBuilder
|
||||
.create(retVal)
|
||||
.logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
||||
.logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||
// .logQueryBySlf4j(SLF4JLogLevel.INFO, "SQL")
|
||||
// .logSlowQueryBySlf4j(10, TimeUnit.SECONDS)
|
||||
// .countQuery(new ThreadQueryCountHolder())
|
||||
.countQuery(singleQueryCountHolder())
|
||||
.build();
|
||||
|
|
|
@ -16,6 +16,7 @@ import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
|||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
|
||||
import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
|
@ -181,6 +182,8 @@ public abstract class BaseJpaDstu2Test extends BaseJpaTest {
|
|||
protected IFhirResourceDaoValueSet<ValueSet, CodingDt, CodeableConceptDt> myValueSetDao;
|
||||
@Autowired
|
||||
private ISearchParamRegistry mySearchParamRegistry;
|
||||
@Autowired
|
||||
protected SubscriptionLoader mySubscriptionLoader;
|
||||
|
||||
@Before
|
||||
public void beforeCreateInterceptor() {
|
||||
|
|
|
@ -2,8 +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.jpa.subscription.SubscriptionActivatingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
|
@ -23,19 +22,20 @@ import static org.junit.Assert.*;
|
|||
|
||||
public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Test {
|
||||
|
||||
|
||||
@Autowired
|
||||
private SubscriptionRestHookInterceptor myInterceptor;
|
||||
private DaoConfig myDaoConfig;
|
||||
|
||||
@After
|
||||
public void afterResetDao() {
|
||||
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
|
||||
SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
|
||||
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
|
||||
BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
|
||||
SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -83,8 +83,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
|
|||
});
|
||||
|
||||
myEntityManager.clear();
|
||||
|
||||
myInterceptor.start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,9 +102,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
|
|||
assertNotNull(id.getIdPart());
|
||||
|
||||
BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
|
||||
|
||||
myInterceptor.start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,9 +119,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
|
|||
IIdType id = mySubscriptionDao.create(s).getId().toUnqualifiedVersionless();
|
||||
|
||||
BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
|
||||
|
||||
myInterceptor.start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,9 +137,6 @@ public class FhirResourceDaoDstu3InvalidSubscriptionTest extends BaseJpaDstu3Tes
|
|||
assertNotNull(id.getIdPart());
|
||||
|
||||
BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
|
||||
|
||||
myInterceptor.start();
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
|
|
@ -15,7 +15,7 @@ import ca.uhn.fhir.jpa.search.IStaleSearchDeletingSvc;
|
|||
import ca.uhn.fhir.jpa.search.reindex.IResourceReindexingSvc;
|
||||
import ca.uhn.fhir.jpa.search.warm.ICacheWarmingSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.sp.ISearchParamPresenceSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
|
||||
import ca.uhn.fhir.jpa.term.BaseHapiTerminologySvcImpl;
|
||||
import ca.uhn.fhir.jpa.term.IHapiTerminologySvc;
|
||||
import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
||||
|
@ -223,8 +223,6 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||
@Qualifier("mySearchParameterDaoR4")
|
||||
protected IFhirResourceDao<SearchParameter> mySearchParameterDao;
|
||||
@Autowired
|
||||
protected ISearchParamPresenceSvc mySearchParamPresenceSvc;
|
||||
@Autowired
|
||||
protected ISearchParamRegistry mySearchParamRegsitry;
|
||||
@Autowired
|
||||
protected IStaleSearchDeletingSvc myStaleSearchDeletingSvc;
|
||||
|
@ -273,6 +271,8 @@ public abstract class BaseJpaR4Test extends BaseJpaTest {
|
|||
protected ICacheWarmingSvc myCacheWarmingSvc;
|
||||
@Autowired
|
||||
private JpaValidationSupportChainR4 myJpaValidationSupportChainR4;
|
||||
@Autowired
|
||||
protected SubscriptionRegistry mySubscriptionRegistry;
|
||||
|
||||
@After()
|
||||
public void afterCleanupDao() {
|
||||
|
|
|
@ -2,8 +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.jpa.subscription.SubscriptionActivatingInterceptor;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
@ -12,7 +11,6 @@ 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;
|
||||
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
|
||||
import org.springframework.transaction.support.TransactionTemplate;
|
||||
|
@ -23,19 +21,16 @@ import static org.junit.Assert.*;
|
|||
|
||||
public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
|
||||
|
||||
@Autowired
|
||||
private SubscriptionRestHookInterceptor myInterceptor;
|
||||
|
||||
@After
|
||||
public void afterResetDao() {
|
||||
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
|
||||
SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
|
||||
myDaoConfig.setResourceServerIdStrategy(new DaoConfig().getResourceServerIdStrategy());
|
||||
BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() {
|
||||
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
|
||||
SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -83,8 +78,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
|
|||
});
|
||||
|
||||
myEntityManager.clear();
|
||||
|
||||
myInterceptor.start();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -104,9 +97,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
|
|||
assertNotNull(id.getIdPart());
|
||||
|
||||
BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
|
||||
|
||||
myInterceptor.start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -124,9 +114,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
|
|||
IIdType id = mySubscriptionDao.create(s).getId().toUnqualifiedVersionless();
|
||||
|
||||
BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
|
||||
|
||||
myInterceptor.start();
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -145,9 +132,6 @@ public class FhirResourceDaoR4InvalidSubscriptionTest extends BaseJpaR4Test {
|
|||
assertNotNull(id.getIdPart());
|
||||
|
||||
BaseHapiFhirDao.setValidationDisabledForUnitTest(false);
|
||||
|
||||
myInterceptor.start();
|
||||
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
|
|
@ -7,6 +7,7 @@ import ca.uhn.fhir.jpa.model.entity.*;
|
|||
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.PathAndRef;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorR4;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.r4.hapi.ctx.DefaultProfileValidationSupport;
|
||||
|
@ -89,6 +90,11 @@ public class SearchParamExtractorR4Test {
|
|||
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) {
|
||||
// nothing
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
@ -3,7 +3,6 @@ package ca.uhn.fhir.jpa.provider;
|
|||
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.dao.dstu2.BaseJpaDstu2Test;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Bundle.Entry;
|
||||
|
@ -46,7 +45,6 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
protected static Server ourServer;
|
||||
protected static String ourServerBase;
|
||||
protected static GenericWebApplicationContext ourWebApplicationContext;
|
||||
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
|
||||
protected static DatabaseBackedPagingProvider ourPagingProvider;
|
||||
protected static PlatformTransactionManager ourTxManager;
|
||||
protected static Integer ourConnectionPoolSize;
|
||||
|
@ -101,7 +99,6 @@ public abstract class BaseResourceProviderDstu2Test extends BaseJpaDstu2Test {
|
|||
ourWebApplicationContext.setParent(myAppCtx);
|
||||
ourWebApplicationContext.refresh();
|
||||
|
||||
ourRestHookSubscriptionInterceptor = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
|
||||
ourTxManager = ourWebApplicationContext.getBean(PlatformTransactionManager.class);
|
||||
|
||||
proxyHandler.getServletContext().setAttribute(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, ourWebApplicationContext);
|
||||
|
|
|
@ -7,8 +7,6 @@ import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
|||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryDstu3;
|
||||
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainDstu3;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.parser.StrictErrorHandler;
|
||||
|
@ -60,8 +58,6 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
|||
protected static GenericWebApplicationContext ourWebApplicationContext;
|
||||
protected static SearchParamRegistryDstu3 ourSearchParamRegistry;
|
||||
protected static DatabaseBackedPagingProvider ourPagingProvider;
|
||||
protected static SubscriptionRestHookInterceptor ourRestHookSubscriptionInterceptor;
|
||||
protected static SubscriptionEmailInterceptor ourEmailSubscriptionInterceptor;
|
||||
protected static ISearchDao mySearchEntityDao;
|
||||
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||
private static Server ourServer;
|
||||
|
@ -158,8 +154,6 @@ public abstract class BaseResourceProviderDstu3Test extends BaseJpaDstu3Test {
|
|||
myValidationSupport = wac.getBean(JpaValidationSupportChainDstu3.class);
|
||||
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
||||
mySearchEntityDao = wac.getBean(ISearchDao.class);
|
||||
ourRestHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class);
|
||||
ourEmailSubscriptionInterceptor = wac.getBean(SubscriptionEmailInterceptor.class);
|
||||
ourSearchParamRegistry = wac.getBean(SearchParamRegistryDstu3.class);
|
||||
ourSubscriptionTriggeringProvider = wac.getBean(SubscriptionTriggeringProvider.class);
|
||||
|
||||
|
|
|
@ -3,10 +3,11 @@ package ca.uhn.fhir.jpa.provider.r4;
|
|||
import ca.uhn.fhir.jpa.config.WebsocketDispatcherConfig;
|
||||
import ca.uhn.fhir.jpa.dao.data.ISearchDao;
|
||||
import ca.uhn.fhir.jpa.dao.r4.BaseJpaR4Test;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.search.ISearchCoordinatorSvc;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.SearchParamRegistryR4;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
|
||||
import ca.uhn.fhir.jpa.util.ResourceCountCache;
|
||||
import ca.uhn.fhir.jpa.validation.JpaValidationSupportChainR4;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
|
@ -33,6 +34,7 @@ import org.hl7.fhir.r4.model.Patient;
|
|||
import org.junit.After;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Before;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.web.context.ContextLoader;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
|
||||
|
@ -61,7 +63,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
protected static ISearchDao mySearchEntityDao;
|
||||
protected static ISearchCoordinatorSvc mySearchCoordinatorSvc;
|
||||
protected static GenericWebApplicationContext ourWebApplicationContext;
|
||||
protected static SubscriptionRestHookInterceptor ourReskHookSubscriptionInterceptor;
|
||||
protected static SubscriptionMatcherInterceptor ourSubscriptionMatcherInterceptor;
|
||||
private static Server ourServer;
|
||||
protected IGenericClient ourClient;
|
||||
protected ResourceCountCache ourResourceCountsCache;
|
||||
|
@ -69,6 +71,9 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
private Object ourGraphQLProvider;
|
||||
private boolean ourRestHookSubscriptionInterceptorRequested;
|
||||
|
||||
@Autowired
|
||||
protected SubscriptionLoader mySubscriptionLoader;
|
||||
|
||||
public BaseResourceProviderR4Test() {
|
||||
super();
|
||||
}
|
||||
|
@ -156,7 +161,7 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
mySearchCoordinatorSvc = wac.getBean(ISearchCoordinatorSvc.class);
|
||||
mySearchEntityDao = wac.getBean(ISearchDao.class);
|
||||
ourSearchParamRegistry = wac.getBean(SearchParamRegistryR4.class);
|
||||
ourReskHookSubscriptionInterceptor = wac.getBean(SubscriptionRestHookInterceptor.class);
|
||||
ourSubscriptionMatcherInterceptor = wac.getBean(SubscriptionMatcherInterceptor.class);
|
||||
|
||||
myFhirCtx.getRestfulClientFactory().setSocketTimeout(5000000);
|
||||
|
||||
|
@ -177,19 +182,6 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is lazy created so we only ask for it if its needed
|
||||
*/
|
||||
protected SubscriptionRestHookInterceptor getRestHookSubscriptionInterceptor() {
|
||||
SubscriptionRestHookInterceptor retVal = ourWebApplicationContext.getBean(SubscriptionRestHookInterceptor.class);
|
||||
ourRestHookSubscriptionInterceptorRequested = true;
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected boolean hasRestHookSubscriptionInterceptor() {
|
||||
return ourRestHookSubscriptionInterceptorRequested;
|
||||
}
|
||||
|
||||
protected boolean shouldLogClient() {
|
||||
return true;
|
||||
}
|
||||
|
@ -206,21 +198,20 @@ public abstract class BaseResourceProviderR4Test extends BaseJpaR4Test {
|
|||
return names;
|
||||
}
|
||||
|
||||
protected void waitForRegisteredSubscriptionCount(int theSize) throws Exception {
|
||||
protected void waitForActivatedSubscriptionCount(int theSize) throws Exception {
|
||||
for (int i = 0; ; i++) {
|
||||
if (i == 10) {
|
||||
fail("Failed to init subscriptions");
|
||||
}
|
||||
try {
|
||||
getRestHookSubscriptionInterceptor().doInitSubscriptions();
|
||||
mySubscriptionLoader.initSubscriptions();
|
||||
break;
|
||||
} catch (ResourceVersionConflictException e) {
|
||||
Thread.sleep(250);
|
||||
}
|
||||
}
|
||||
|
||||
SubscriptionRestHookInterceptor interceptor = getRestHookSubscriptionInterceptor();
|
||||
TestUtil.waitForSize(theSize, () -> interceptor.getRegisteredSubscriptions().size());
|
||||
TestUtil.waitForSize(theSize, () -> mySubscriptionRegistry.size());
|
||||
Thread.sleep(500);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,6 @@ import org.apache.http.client.methods.HttpPost;
|
|||
import org.apache.http.entity.ContentType;
|
||||
import org.apache.http.entity.StringEntity;
|
||||
import org.hamcrest.Matchers;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
|
@ -38,7 +37,6 @@ import java.io.IOException;
|
|||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static org.hamcrest.Matchers.in;
|
||||
import static org.hamcrest.Matchers.startsWith;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
|
|
|
@ -1,11 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test;
|
||||
import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
|
@ -26,7 +24,7 @@ import org.hl7.fhir.instance.model.api.IIdType;
|
|||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
|
@ -39,7 +37,6 @@ import java.util.List;
|
|||
public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(BaseSubscriptionsR4Test.class);
|
||||
|
||||
|
||||
private static int ourListenerPort;
|
||||
private static RestfulServer ourListenerRestServer;
|
||||
private static Server ourListenerServer;
|
||||
|
@ -50,9 +47,9 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
|
|||
@Autowired
|
||||
private SingleQueryCountHolder myCountHolder;
|
||||
@Autowired
|
||||
protected DaoConfig myDaoConfig;
|
||||
protected SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
@Autowired
|
||||
private DaoRegistry myDaoRegistry;
|
||||
protected SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
|
||||
|
||||
protected CountingInterceptor myCountingInterceptor;
|
||||
|
||||
|
@ -65,7 +62,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
|
|||
|
||||
@After
|
||||
public void afterUnregisterRestHookListener() {
|
||||
BaseSubscriptionInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(false);
|
||||
SubscriptionMatcherInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(false);
|
||||
|
||||
for (IIdType next : mySubscriptionIds) {
|
||||
IIdType nextId = next.toUnqualifiedVersionless();
|
||||
|
@ -81,12 +78,12 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
|
|||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor());
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor());
|
||||
mySubscriptionTestUtil.registerRestHookInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -101,12 +98,12 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
|
|||
for (IBaseResource next : BundleUtil.toListOfResources(myFhirCtx, allSubscriptions)) {
|
||||
ourClient.delete().resource(next).execute();
|
||||
}
|
||||
waitForRegisteredSubscriptionCount(0);
|
||||
waitForActivatedSubscriptionCount(0);
|
||||
|
||||
ExecutorSubscribableChannel processingChannel = (ExecutorSubscribableChannel) getRestHookSubscriptionInterceptor().getProcessingChannel();
|
||||
processingChannel.setInterceptors(new ArrayList<>());
|
||||
SubscriptionChannel processingChannel = mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest();
|
||||
processingChannel.clearInterceptorsForUnitTest();
|
||||
myCountingInterceptor = new CountingInterceptor();
|
||||
processingChannel.addInterceptor(myCountingInterceptor);
|
||||
processingChannel.addInterceptorForUnitTest(myCountingInterceptor);
|
||||
}
|
||||
|
||||
|
||||
|
@ -135,7 +132,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
|
|||
|
||||
|
||||
protected void waitForQueueToDrain() throws InterruptedException {
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor());
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
}
|
||||
|
||||
@PostConstruct
|
||||
|
@ -156,8 +153,7 @@ public abstract class BaseSubscriptionsR4Test extends BaseResourceProviderR4Test
|
|||
|
||||
MethodOutcome methodOutcome = ourClient.create().resource(observation).execute();
|
||||
|
||||
String observationId = methodOutcome.getId().getIdPart();
|
||||
observation.setId(observationId);
|
||||
observation.setId(methodOutcome.getId());
|
||||
|
||||
return observation;
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageChannel;
|
|
@ -1,10 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.BaseSearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.jpa.subscription.FhirClientSearchParamProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSearchParamProvider;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import org.hl7.fhir.r4.model.Coding;
|
||||
import org.hl7.fhir.r4.model.Enumerations;
|
||||
|
@ -20,18 +18,18 @@ import static org.junit.Assert.assertEquals;
|
|||
|
||||
public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
|
||||
@Autowired
|
||||
BaseSearchParamRegistry mySearchParamRegistry;
|
||||
ISearchParamRegistry mySearchParamRegistry;
|
||||
@Autowired
|
||||
ISearchParamProvider origSearchParamProvider;
|
||||
|
||||
@Before
|
||||
public void useFhirClientSearchParamProvider() {
|
||||
mySearchParamRegistry.setSearchParamProvider(new FhirClientSearchParamProvider(ourClient));
|
||||
mySearchParamRegistry.setSearchParamProviderForUnitTest(new FhirClientSearchParamProvider(ourClient));
|
||||
}
|
||||
|
||||
@After
|
||||
public void revert() {
|
||||
mySearchParamRegistry.setSearchParamProvider(origSearchParamProvider);
|
||||
mySearchParamRegistry.setSearchParamProviderForUnitTest(origSearchParamProvider);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -48,7 +46,7 @@ public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
|
|||
mySearchParameterDao.create(sp);
|
||||
mySearchParamRegsitry.forceRefresh();
|
||||
createSubscription(criteria, "application/json");
|
||||
waitForRegisteredSubscriptionCount(1);
|
||||
waitForActivatedSubscriptionCount(1);
|
||||
|
||||
{
|
||||
Observation observation = new Observation();
|
||||
|
@ -81,8 +79,5 @@ public class FhirClientSearchParamProviderTest extends BaseSubscriptionsR4Test {
|
|||
waitForQueueToDrain();
|
||||
waitForSize(2, ourUpdatedObservations);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -0,0 +1,61 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
|
||||
import ca.uhn.fhir.jpa.subscription.module.standalone.FhirClientSubscriptionProvider;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
import org.hl7.fhir.dstu3.model.Subscription;
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.config.AutowireCapableBeanFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class FhirClientSubscriptionProviderTest extends BaseSubscriptionsR4Test {
|
||||
@Autowired
|
||||
SubscriptionLoader mySubscriptionLoader;
|
||||
@Autowired
|
||||
ISubscriptionProvider origSubscriptionProvider;
|
||||
@Autowired
|
||||
AutowireCapableBeanFactory autowireCapableBeanFactory;
|
||||
|
||||
@Before
|
||||
public void useFhirClientSubscriptionProvider() {
|
||||
FhirClientSubscriptionProvider subscriptionProvider = new FhirClientSubscriptionProvider(ourClient);
|
||||
// This bean is only available in the standalone subscription context, so we have to autowire it manually.
|
||||
autowireCapableBeanFactory.autowireBean(subscriptionProvider);
|
||||
mySubscriptionLoader.setSubscriptionProviderForUnitTest(subscriptionProvider);
|
||||
}
|
||||
|
||||
@After
|
||||
public void revert() {
|
||||
mySubscriptionLoader.setSubscriptionProviderForUnitTest(origSubscriptionProvider);
|
||||
}
|
||||
|
||||
private String myCode = "1000000050";
|
||||
|
||||
@Test
|
||||
public void testSubscriptionLoaderFhirClient() throws Exception {
|
||||
String payload = "application/fhir+json";
|
||||
|
||||
String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml";
|
||||
String criteria2 = "Observation?code=SNOMED-CT|" + myCode + "111&_format=xml";
|
||||
|
||||
|
||||
List<Subscription> subs = new ArrayList<>();
|
||||
createSubscription(criteria1, payload);
|
||||
createSubscription(criteria2, payload);
|
||||
waitForActivatedSubscriptionCount(2);
|
||||
|
||||
sendObservation(myCode, "SNOMED-CT");
|
||||
|
||||
waitForSize(0, ourCreatedObservations);
|
||||
waitForSize(1, ourUpdatedObservations);
|
||||
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
|
||||
}
|
||||
}
|
|
@ -1,10 +1,11 @@
|
|||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.instance.model.api.*;
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.client.api.IGenericClient;
|
||||
import org.hl7.fhir.instance.model.api.IBaseCoding;
|
||||
import org.hl7.fhir.instance.model.api.IBaseMetaType;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
|
||||
public class FhirR4Util {
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscriptionChannel;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionRegistry;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.email.SubscriptionDeliveringEmailSubscriber;
|
||||
import org.hl7.fhir.instance.model.Subscription;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
public class SubscriptionTestUtil {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionTestUtil.class);
|
||||
|
||||
private JavaMailEmailSender myEmailSender;
|
||||
|
||||
@Autowired
|
||||
private DaoConfig myDaoConfig;
|
||||
@Autowired
|
||||
private SubscriptionInterceptorLoader mySubscriptionInterceptorLoader;
|
||||
@Autowired
|
||||
private SubscriptionMatcherInterceptor mySubscriptionMatcherInterceptor;
|
||||
@Autowired
|
||||
private SubscriptionRegistry mySubscriptionRegistry;
|
||||
|
||||
public int getExecutorQueueSize() {
|
||||
LinkedBlockingQueueSubscriptionChannel channel = (LinkedBlockingQueueSubscriptionChannel) mySubscriptionMatcherInterceptor.getProcessingChannelForUnitTest();
|
||||
return channel.getQueueSizeForUnitTest();
|
||||
}
|
||||
|
||||
// TODO KHS replace this and similar functions with CountdownLatch
|
||||
public void waitForQueueToDrain() throws InterruptedException {
|
||||
Thread.sleep(100);
|
||||
ourLog.info("Executor work queue has {} items", getExecutorQueueSize());
|
||||
if (getExecutorQueueSize() > 0) {
|
||||
while (getExecutorQueueSize() > 0) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
ourLog.info("Executor work queue has {} items", getExecutorQueueSize());
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
public void registerEmailInterceptor() {
|
||||
myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.EMAIL);
|
||||
mySubscriptionInterceptorLoader.registerInterceptors();
|
||||
}
|
||||
|
||||
public void registerRestHookInterceptor() {
|
||||
myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.RESTHOOK);
|
||||
mySubscriptionInterceptorLoader.registerInterceptors();
|
||||
}
|
||||
|
||||
public void registerWebSocketInterceptor() {
|
||||
myDaoConfig.addSupportedSubscriptionType(Subscription.SubscriptionChannelType.WEBSOCKET);
|
||||
mySubscriptionInterceptorLoader.registerInterceptors();
|
||||
}
|
||||
|
||||
public void unregisterSubscriptionInterceptor() {
|
||||
myDaoConfig.clearSupportedSubscriptionTypesForUnitTest();
|
||||
mySubscriptionInterceptorLoader.unregisterInterceptorsForUnitTest();
|
||||
}
|
||||
|
||||
public int getExecutorQueueSizeForUnitTests() {
|
||||
return getExecutorQueueSize();
|
||||
}
|
||||
|
||||
public void initEmailSender(int theListenerPort) {
|
||||
myEmailSender = new JavaMailEmailSender();
|
||||
myEmailSender.setSmtpServerHostname("localhost");
|
||||
myEmailSender.setSmtpServerPort(theListenerPort);
|
||||
myEmailSender.start();
|
||||
}
|
||||
|
||||
public void setEmailSender(IIdType theIdElement) {
|
||||
ActiveSubscription activeSubscription = mySubscriptionRegistry.get(theIdElement.getIdPart());
|
||||
SubscriptionDeliveringEmailSubscriber subscriber = (SubscriptionDeliveringEmailSubscriber) activeSubscription.getDeliveryHandlerForUnitTest();
|
||||
subscriber.setEmailSender(myEmailSender);
|
||||
}
|
||||
|
||||
public IEmailSender getEmailSender() {
|
||||
return myEmailSender;
|
||||
}
|
||||
}
|
|
@ -1,10 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.email;
|
||||
|
||||
import ca.uhn.fhir.jpa.config.BaseConfig;
|
||||
import ca.uhn.fhir.jpa.dao.IFhirResourceDao;
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
|
||||
import ca.uhn.fhir.jpa.subscription.email.JavaMailEmailSender;
|
||||
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
|
@ -22,8 +19,6 @@ import org.junit.*;
|
|||
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 javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
@ -42,12 +37,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
private List<IIdType> mySubscriptionIds = new ArrayList<>();
|
||||
|
||||
@Autowired
|
||||
private SubscriptionEmailInterceptor mySubscriber;
|
||||
@Autowired
|
||||
private List<IFhirResourceDao<?>> myResourceDaos;
|
||||
@Autowired
|
||||
@Qualifier(BaseConfig.TASK_EXECUTOR_NAME)
|
||||
private AsyncTaskExecutor myAsyncTaskExecutor;
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
|
@ -58,38 +48,16 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
ourClient.delete().resourceById(next).execute();
|
||||
}
|
||||
mySubscriptionIds.clear();
|
||||
|
||||
ourRestServer.unregisterInterceptor(mySubscriber);
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
JavaMailEmailSender emailSender = new JavaMailEmailSender();
|
||||
emailSender.setSmtpServerHostname("localhost");
|
||||
emailSender.setSmtpServerPort(ourListenerPort);
|
||||
emailSender.start();
|
||||
mySubscriptionTestUtil.initEmailSender(ourListenerPort);
|
||||
|
||||
mySubscriber.setEmailSender(emailSender);
|
||||
mySubscriber.setResourceDaos(myResourceDaos);
|
||||
mySubscriber.setFhirContext(myFhirCtx);
|
||||
mySubscriber.setTxManager(ourTxManager);
|
||||
mySubscriber.setAsyncTaskExecutorForUnitTest(myAsyncTaskExecutor);
|
||||
mySubscriber.start();
|
||||
ourRestServer.registerInterceptor(mySubscriber);
|
||||
|
||||
// ourLog.info("Sending test email to warm up the server");
|
||||
// EmailDetails details = new EmailDetails();
|
||||
// details.setFrom("a@a.com");
|
||||
// details.setTo(Arrays.asList("b@b.com"));
|
||||
// details.setSubjectTemplate("SUBJ");
|
||||
// details.setBodyTemplate("BODY");
|
||||
// emailSender.send(details);
|
||||
// ourLog.info("Done sending test email to warm up the server");
|
||||
// Store store = ourTestSmtp.getManagers().getImapHostManager().getStore();
|
||||
// MailFolder mailbox = store.getMailbox(ImapConstants.USER_NAMESPACE);
|
||||
// mailbox.deleteAllMessages();
|
||||
mySubscriptionTestUtil.registerEmailInterceptor();
|
||||
}
|
||||
|
||||
private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException {
|
||||
|
@ -108,7 +76,7 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
subscription.setId(methodOutcome.getId().getIdPart());
|
||||
mySubscriptionIds.add(methodOutcome.getId());
|
||||
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
|
||||
return subscription;
|
||||
}
|
||||
|
@ -138,8 +106,8 @@ public class EmailSubscriptionDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
|
||||
Subscription subscription1 = createSubscription(criteria1, payload, "to1@example.com,to2@example.com");
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
|
||||
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
mySubscriptionTestUtil.setEmailSender(subscription1.getIdElement());
|
||||
assertEquals(0, Arrays.asList(ourTestSmtp.getReceivedMessages()).size());
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
|
@ -2,9 +2,9 @@ package ca.uhn.fhir.jpa.subscription.email;
|
|||
|
||||
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.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.jpa.util.JpaConstants;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import com.google.common.collect.Lists;
|
||||
import com.icegreen.greenmail.store.FolderException;
|
||||
|
@ -13,6 +13,7 @@ import com.icegreen.greenmail.util.ServerSetup;
|
|||
import org.hl7.fhir.dstu3.model.*;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.mail.internet.InternetAddress;
|
||||
import javax.mail.internet.MimeMessage;
|
||||
|
@ -21,7 +22,7 @@ import java.util.Arrays;
|
|||
import java.util.List;
|
||||
import java.util.concurrent.Callable;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
|
@ -29,6 +30,10 @@ import static org.junit.Assert.*;
|
|||
public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
|
||||
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(EmailSubscriptionDstu3Test.class);
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
private static List<Observation> ourCreatedObservations = Lists.newArrayList();
|
||||
private static int ourListenerPort;
|
||||
private static List<String> ourContentTypes = new ArrayList<>();
|
||||
|
@ -51,23 +56,17 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
ourRestServer.unregisterInterceptor(ourEmailSubscriptionInterceptor);
|
||||
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterEmailListener() throws FolderException {
|
||||
ourTestSmtp.purgeEmailFromAllMailboxes();
|
||||
;
|
||||
ourRestServer.registerInterceptor(ourEmailSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.registerEmailInterceptor();
|
||||
|
||||
JavaMailEmailSender emailSender = new JavaMailEmailSender();
|
||||
emailSender.setSmtpServerHostname("localhost");
|
||||
emailSender.setSmtpServerPort(ourListenerPort);
|
||||
emailSender.start();
|
||||
mySubscriptionTestUtil.initEmailSender(ourListenerPort);
|
||||
|
||||
ourEmailSubscriptionInterceptor.setEmailSender(emailSender);
|
||||
ourEmailSubscriptionInterceptor.setDefaultFromAddress("123@hapifhir.io");
|
||||
myDaoConfig.setEmailFromAddress("123@hapifhir.io");
|
||||
}
|
||||
|
||||
private Subscription createSubscription(String theCriteria, String thePayload) throws InterruptedException {
|
||||
|
@ -115,8 +114,9 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
|
||||
String code = "1000000050";
|
||||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
createSubscription(criteria1, payload);
|
||||
Subscription subscription = createSubscription(criteria1, payload);
|
||||
waitForQueueToDrain();
|
||||
mySubscriptionTestUtil.setEmailSender(subscription.getIdElement());
|
||||
|
||||
sendObservation(code, "SNOMED-CT");
|
||||
waitForQueueToDrain();
|
||||
|
@ -150,19 +150,18 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
Assert.assertNotNull(subscriptionTemp);
|
||||
|
||||
subscriptionTemp.getChannel().addExtension()
|
||||
.setUrl(JpaConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
|
||||
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
|
||||
.setValue(new StringType("mailto:myfrom@from.com"));
|
||||
subscriptionTemp.getChannel().addExtension()
|
||||
.setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
|
||||
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
|
||||
.setValue(new StringType("This is a subject"));
|
||||
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
|
||||
|
||||
|
||||
ourLog.info(myFhirCtx.newJsonParser().setPrettyPrint(true).encodeResourceToString(subscriptionTemp));
|
||||
|
||||
ourClient.update().resource(subscriptionTemp).withId(subscriptionTemp.getIdElement()).execute();
|
||||
waitForQueueToDrain();
|
||||
|
||||
mySubscriptionTestUtil.setEmailSender(subscriptionTemp.getIdElement());
|
||||
|
||||
sendObservation(code, "SNOMED-CT");
|
||||
waitForQueueToDrain();
|
||||
|
@ -197,10 +196,10 @@ 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)
|
||||
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM)
|
||||
.setValue(new StringType("myfrom@from.com"));
|
||||
subscriptionTemp.getChannel().addExtension()
|
||||
.setUrl(JpaConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
|
||||
.setUrl(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE)
|
||||
.setValue(new StringType("This is a subject"));
|
||||
subscriptionTemp.setIdElement(subscriptionTemp.getIdElement().toUnqualifiedVersionless());
|
||||
|
||||
|
@ -208,6 +207,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
ourLog.info("Subscription ID is: {}", id.getValue());
|
||||
|
||||
waitForQueueToDrain();
|
||||
mySubscriptionTestUtil.setEmailSender(subscriptionTemp.getIdElement());
|
||||
|
||||
sendObservation(code, "SNOMED-CT");
|
||||
waitForQueueToDrain();
|
||||
|
@ -238,7 +238,7 @@ public class EmailSubscriptionDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
}
|
||||
|
||||
private void waitForQueueToDrain() throws InterruptedException {
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(ourEmailSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.subscription.email;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.email.EmailDetails;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.email.JavaMailEmailSender;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import com.icegreen.greenmail.util.GreenMail;
|
||||
import com.icegreen.greenmail.util.GreenMailUtil;
|
||||
|
@ -15,7 +17,7 @@ import javax.mail.internet.InternetAddress;
|
|||
import javax.mail.internet.MimeMessage;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
public class JavaMailEmailSenderTest {
|
||||
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
package ca.uhn.fhir.jpa.subscription.matcher;
|
||||
package ca.uhn.fhir.jpa.subscription.module.matcher;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.config.TestR4Config;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamString;
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.jpa.subscription.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||
import ca.uhn.fhir.model.api.TemporalPrecisionEnum;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.*;
|
||||
|
@ -29,18 +29,18 @@ import static org.junit.Assert.*;
|
|||
|
||||
@RunWith(SpringJUnit4ClassRunner.class)
|
||||
@ContextConfiguration(classes = {TestR4Config.class})
|
||||
public class SubscriptionMatcherInMemoryTestR4 {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(SubscriptionMatcherInMemoryTestR4.class);
|
||||
public class InMemorySubscriptionMatcherTestR4 {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(InMemorySubscriptionMatcherTestR4.class);
|
||||
|
||||
@Autowired
|
||||
SubscriptionMatcherInMemory mySubscriptionMatcherInMemory;
|
||||
InMemorySubscriptionMatcher myInMemorySubscriptionMatcher;
|
||||
@Autowired
|
||||
FhirContext myContext;
|
||||
|
||||
private SubscriptionMatchResult match(IBaseResource resource, SearchParameterMap params) {
|
||||
String criteria = params.toNormalizedQueryString(myContext);
|
||||
ourLog.info("Criteria: <{}>", criteria);
|
||||
return mySubscriptionMatcherInMemory.match(criteria, resource);
|
||||
return myInMemorySubscriptionMatcher.match(criteria, resource);
|
||||
}
|
||||
|
||||
private void assertUnsupported(IBaseResource resource, SearchParameterMap params) {
|
||||
|
@ -380,18 +380,16 @@ public class SubscriptionMatcherInMemoryTestR4 {
|
|||
params.add(Patient.SP_FAMILY, new StringParam("testSearchNameParam01Fam"));
|
||||
try {
|
||||
String criteria = params.toNormalizedQueryString(myContext);
|
||||
ResourceModifiedMessage msg = new ResourceModifiedMessage();
|
||||
ResourceModifiedMessage msg = new ResourceModifiedMessage(myContext, patient, ResourceModifiedMessage.OperationTypeEnum.CREATE);
|
||||
msg.setSubscriptionId("Subscription/123");
|
||||
msg.setId(new IdType("Patient/ABC"));
|
||||
msg.setNewPayload(myContext, patient);
|
||||
SubscriptionMatchResult result = mySubscriptionMatcherInMemory.match(criteria, msg);
|
||||
SubscriptionMatchResult result = myInMemorySubscriptionMatcher.match(criteria, msg);
|
||||
fail();
|
||||
} catch (InternalErrorException e){
|
||||
assertEquals("Failure processing resource ID[Patient/ABC] for subscription ID[Subscription/123]: Invalid resource reference found at path[Patient.managingOrganization] - Does not contain resource type - urn:uuid:13720262-b392-465f-913e-54fb198ff954", e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testSearchResourceReferenceOnlyCorrectPath() {
|
||||
Organization org = new Organization();
|
|
@ -1,9 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
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.jpa.subscription.SubscriptionActivatingInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
import ca.uhn.fhir.rest.api.Constants;
|
||||
|
@ -20,6 +20,7 @@ import org.hl7.fhir.r4.model.*;
|
|||
import org.junit.*;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
|
@ -28,7 +29,6 @@ import java.util.Enumeration;
|
|||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourceProviderR4Test {
|
||||
|
||||
|
@ -41,20 +41,23 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
|
|||
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
|
||||
private static List<String> ourHeaders = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@After
|
||||
public void afterResetSubscriptionActivatingInterceptor() {
|
||||
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
|
||||
SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void afterUnregisterRestHookListener() {
|
||||
ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor());
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeSetSubscriptionActivatingInterceptor() {
|
||||
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
|
||||
getRestHookSubscriptionInterceptor().initSubscriptions();
|
||||
SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
|
||||
mySubscriptionLoader.initSubscriptions();
|
||||
}
|
||||
|
||||
|
||||
|
@ -105,11 +108,8 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
|
|||
createSubscription(criteria1, payload, ourListenerServerBase);
|
||||
createSubscription(criteria2, payload, ourListenerServerBase);
|
||||
|
||||
ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor());
|
||||
getRestHookSubscriptionInterceptor().initSubscriptions();
|
||||
|
||||
assertTrue(hasRestHookSubscriptionInterceptor());
|
||||
|
||||
mySubscriptionTestUtil.registerRestHookInterceptor();
|
||||
mySubscriptionLoader.initSubscriptions();
|
||||
|
||||
sendObservation(code, "SNOMED-CT");
|
||||
|
||||
|
@ -120,9 +120,7 @@ public class RestHookActivatesPreExistingSubscriptionsR4Test extends BaseResourc
|
|||
}
|
||||
|
||||
private void waitForQueueToDrain() throws InterruptedException {
|
||||
if (hasRestHookSubscriptionInterceptor()) {
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(getRestHookSubscriptionInterceptor());
|
||||
}
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
}
|
||||
|
||||
public static class ObservationListener implements IResourceProvider {
|
|
@ -1,8 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
|
@ -27,11 +28,13 @@ import org.eclipse.jetty.servlet.ServletHolder;
|
|||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.junit.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
|
@ -47,6 +50,9 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
private static List<String> ourUpdatedObservations = Lists.newArrayList();
|
||||
private List<IIdType> mySubscriptionIds = new ArrayList<IIdType>();
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@After
|
||||
public void afterUnregisterRestHookListener() {
|
||||
ourLog.info("** AFTER **");
|
||||
|
@ -62,12 +68,12 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.registerRestHookInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -279,7 +285,7 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
}
|
||||
|
||||
private void waitForQueueToDrain() throws InterruptedException {
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
}
|
||||
|
||||
@BeforeClass
|
||||
|
@ -309,18 +315,6 @@ public class RestHookTestDstu2Test extends BaseResourceProviderDstu2Test {
|
|||
ourListenerServer.stop();
|
||||
}
|
||||
|
||||
public static void waitForQueueToDrain(BaseSubscriptionInterceptor theSubscriptionInterceptor) throws InterruptedException {
|
||||
Thread.sleep(100);
|
||||
ourLog.info("Executor work queue has {} items", theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests());
|
||||
if (theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests() > 0) {
|
||||
while (theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests() > 0) {
|
||||
Thread.sleep(50);
|
||||
}
|
||||
ourLog.info("Executor work queue has {} items", theSubscriptionInterceptor.getExecutorQueueSizeForUnitTests());
|
||||
}
|
||||
Thread.sleep(100);
|
||||
}
|
||||
|
||||
public static class ObservationListener implements IResourceProvider {
|
||||
|
||||
@Create
|
|
@ -1,9 +1,10 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
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.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
import ca.uhn.fhir.rest.annotation.Update;
|
||||
|
@ -21,6 +22,7 @@ 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 org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
|
@ -45,6 +47,9 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
private static List<String> ourContentTypes = Collections.synchronizedList(new ArrayList<>());
|
||||
private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@After
|
||||
public void afterUnregisterRestHookListener() {
|
||||
ourLog.info("**** Starting @After *****");
|
||||
|
@ -61,13 +66,12 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor);
|
||||
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.registerRestHookInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -346,7 +350,7 @@ public class RestHookTestDstu3Test extends BaseResourceProviderDstu3Test {
|
|||
}
|
||||
|
||||
private void waitForQueueToDrain() throws InterruptedException {
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
}
|
||||
|
||||
@BeforeClass
|
|
@ -1,42 +1,26 @@
|
|||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.dao.DaoRegistry;
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.RestHookTestDstu2Test;
|
||||
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.jpa.config.StoppableSubscriptionDeliveringRestHookSubscriber;
|
||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR4Test;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionMatcherInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
|
||||
import ca.uhn.fhir.rest.api.CacheControlDirective;
|
||||
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.rest.server.exceptions.InvalidRequestException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.UnprocessableEntityException;
|
||||
import ca.uhn.fhir.util.BundleUtil;
|
||||
import ca.uhn.fhir.util.PortUtil;
|
||||
import net.ttddyy.dsproxy.QueryCount;
|
||||
import net.ttddyy.dsproxy.listener.SingleQueryCountHolder;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseBundle;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.*;
|
||||
import org.junit.After;
|
||||
import org.junit.Assert;
|
||||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Enumeration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.Matchers.hasItem;
|
||||
|
@ -46,7 +30,16 @@ import static org.junit.Assert.*;
|
|||
* Test the rest-hook subscriptions
|
||||
*/
|
||||
public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestR4Test.class);
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(RestHookTestR4Test.class);
|
||||
|
||||
@Autowired
|
||||
StoppableSubscriptionDeliveringRestHookSubscriber myStoppableSubscriptionDeliveringRestHookSubscriber;
|
||||
|
||||
@After
|
||||
public void cleanupStoppableSubscriptionDeliveringRestHookSubscriber() {
|
||||
myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(null);
|
||||
myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscriptionApplicationFhirJson() throws Exception {
|
||||
|
@ -58,7 +51,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
|
||||
createSubscription(criteria1, payload);
|
||||
createSubscription(criteria2, payload);
|
||||
waitForRegisteredSubscriptionCount(2);
|
||||
waitForActivatedSubscriptionCount(2);
|
||||
|
||||
sendObservation(code, "SNOMED-CT");
|
||||
|
||||
|
@ -75,10 +68,10 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
String payload = "application/fhir+json";
|
||||
createSubscription(criteria, payload);
|
||||
|
||||
waitForRegisteredSubscriptionCount(1);
|
||||
waitForActivatedSubscriptionCount(1);
|
||||
for (int i = 0; i < 5; i++) {
|
||||
Integer changes = ourReskHookSubscriptionInterceptor.doInitSubscriptions();
|
||||
assertEquals(0, changes.intValue());
|
||||
int changes = this.mySubscriptionLoader.doInitSubscriptionsForUnitTest();
|
||||
assertEquals(0, changes);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -92,7 +85,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
|
||||
createSubscription(criteria1, payload);
|
||||
createSubscription(criteria2, payload);
|
||||
waitForRegisteredSubscriptionCount(2);
|
||||
waitForActivatedSubscriptionCount(2);
|
||||
|
||||
Observation obs = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
|
@ -122,32 +115,137 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
String code = "1000000050";
|
||||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
|
||||
waitForRegisteredSubscriptionCount(0);
|
||||
waitForActivatedSubscriptionCount(0);
|
||||
Subscription subscription1 = createSubscription(criteria1, payload);
|
||||
waitForRegisteredSubscriptionCount(1);
|
||||
waitForActivatedSubscriptionCount(1);
|
||||
|
||||
int modCount = myCountingInterceptor.getSentCount();
|
||||
subscription1
|
||||
.getChannel()
|
||||
.addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true"));
|
||||
subscription1
|
||||
.getChannel()
|
||||
.addExtension(JpaConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true"));
|
||||
ourLog.info("** About to update subscription");
|
||||
ourClient.update().resource(subscription1).execute();
|
||||
waitForSize(modCount + 1, () -> myCountingInterceptor.getSentCount());
|
||||
|
||||
ourLog.info("** About to send observation");
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
// Should see 1 subscription notification
|
||||
waitForQueueToDrain();
|
||||
waitForSize(0, ourCreatedObservations);
|
||||
waitForSize(1, ourUpdatedObservations);
|
||||
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(0));
|
||||
|
||||
assertEquals(observation1.getIdElement().getIdPart(), ourUpdatedObservations.get(0).getIdElement().getIdPart());
|
||||
assertEquals(null, ourUpdatedObservations.get(0).getIdElement().getVersionIdPart());
|
||||
IdType idElement = ourUpdatedObservations.get(0).getIdElement();
|
||||
assertEquals(observation1.getIdElement().getIdPart(), idElement.getIdPart());
|
||||
// VersionId is present
|
||||
assertEquals(observation1.getIdElement().getVersionIdPart(), idElement.getVersionIdPart());
|
||||
|
||||
subscription1
|
||||
.getChannel()
|
||||
.addExtension(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS, new BooleanType("true"));
|
||||
ourLog.info("** About to update subscription");
|
||||
|
||||
int modCount = myCountingInterceptor.getSentCount();
|
||||
ourClient.update().resource(subscription1).execute();
|
||||
waitForSize(modCount + 1, () -> myCountingInterceptor.getSentCount());
|
||||
|
||||
ourLog.info("** About to send observation");
|
||||
Observation observation2 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
waitForQueueToDrain();
|
||||
waitForSize(0, ourCreatedObservations);
|
||||
waitForSize(2, ourUpdatedObservations);
|
||||
assertEquals(Constants.CT_FHIR_JSON_NEW, ourContentTypes.get(1));
|
||||
|
||||
idElement = ourUpdatedObservations.get(1).getIdElement();
|
||||
assertEquals(observation2.getIdElement().getIdPart(), idElement.getIdPart());
|
||||
// Now VersionId is stripped
|
||||
assertEquals(null, idElement.getVersionIdPart());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscriptionDoesntGetLatestVersionByDefault() throws Exception {
|
||||
String payload = "application/json";
|
||||
|
||||
String code = "1000000050";
|
||||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
|
||||
waitForActivatedSubscriptionCount(0);
|
||||
createSubscription(criteria1, payload);
|
||||
waitForActivatedSubscriptionCount(1);
|
||||
|
||||
myStoppableSubscriptionDeliveringRestHookSubscriber.pause();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(countDownLatch);
|
||||
|
||||
ourLog.info("** About to send observation");
|
||||
Observation observation = sendObservation(code, "SNOMED-CT");
|
||||
assertEquals("1", observation.getIdElement().getVersionIdPart());
|
||||
assertNull(observation.getComment());
|
||||
|
||||
observation.setComment("changed");
|
||||
MethodOutcome methodOutcome = ourClient.update().resource(observation).execute();
|
||||
assertEquals("2", methodOutcome.getId().getVersionIdPart());
|
||||
assertEquals("changed", observation.getComment());
|
||||
|
||||
// Wait for our two delivery channel threads to be paused
|
||||
assertTrue(countDownLatch.await(5L, TimeUnit.SECONDS));
|
||||
// Open the floodgates!
|
||||
myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
|
||||
|
||||
|
||||
waitForSize(0, ourCreatedObservations);
|
||||
waitForSize(2, ourUpdatedObservations);
|
||||
|
||||
Observation observation1 = ourUpdatedObservations.get(0);
|
||||
Observation observation2 = ourUpdatedObservations.get(1);
|
||||
|
||||
assertEquals("1", observation1.getIdElement().getVersionIdPart());
|
||||
assertNull(observation1.getComment());
|
||||
assertEquals("2", observation2.getIdElement().getVersionIdPart());
|
||||
assertEquals("changed", observation2.getComment());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRestHookSubscriptionGetsLatestVersionWithFlag() throws Exception {
|
||||
String payload = "application/json";
|
||||
|
||||
String code = "1000000050";
|
||||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
|
||||
waitForActivatedSubscriptionCount(0);
|
||||
|
||||
Subscription subscription = newSubscription(criteria1, payload);
|
||||
subscription
|
||||
.getChannel()
|
||||
.addExtension(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION, new BooleanType("true"));
|
||||
ourClient.create().resource(subscription).execute();
|
||||
|
||||
waitForActivatedSubscriptionCount(1);
|
||||
|
||||
myStoppableSubscriptionDeliveringRestHookSubscriber.pause();
|
||||
final CountDownLatch countDownLatch = new CountDownLatch(1);
|
||||
myStoppableSubscriptionDeliveringRestHookSubscriber.setCountDownLatch(countDownLatch);
|
||||
|
||||
ourLog.info("** About to send observation");
|
||||
Observation observation = sendObservation(code, "SNOMED-CT");
|
||||
assertEquals("1", observation.getIdElement().getVersionIdPart());
|
||||
assertNull(observation.getComment());
|
||||
|
||||
observation.setComment("changed");
|
||||
MethodOutcome methodOutcome = ourClient.update().resource(observation).execute();
|
||||
assertEquals("2", methodOutcome.getId().getVersionIdPart());
|
||||
assertEquals("changed", observation.getComment());
|
||||
|
||||
// Wait for our two delivery channel threads to be paused
|
||||
assertTrue(countDownLatch.await(5L, TimeUnit.SECONDS));
|
||||
// Open the floodgates!
|
||||
myStoppableSubscriptionDeliveringRestHookSubscriber.unPause();
|
||||
|
||||
|
||||
waitForSize(0, ourCreatedObservations);
|
||||
waitForSize(2, ourUpdatedObservations);
|
||||
|
||||
Observation observation1 = ourUpdatedObservations.get(0);
|
||||
Observation observation2 = ourUpdatedObservations.get(1);
|
||||
|
||||
assertEquals("2", observation1.getIdElement().getVersionIdPart());
|
||||
assertEquals("changed", observation1.getComment());
|
||||
assertEquals("2", observation2.getIdElement().getVersionIdPart());
|
||||
assertEquals("changed", observation2.getComment());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -160,7 +258,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
|
||||
Subscription subscription1 = createSubscription(criteria1, payload);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload);
|
||||
waitForRegisteredSubscriptionCount(2);
|
||||
waitForActivatedSubscriptionCount(2);
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
|
@ -240,7 +338,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
|
||||
Subscription subscription1 = createSubscription(criteria1, payload);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload);
|
||||
waitForRegisteredSubscriptionCount(2);
|
||||
waitForActivatedSubscriptionCount(2);
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
|
@ -318,7 +416,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
|
||||
Subscription subscription1 = createSubscription(criteria1, payload);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload);
|
||||
waitForRegisteredSubscriptionCount(2);
|
||||
waitForActivatedSubscriptionCount(2);
|
||||
|
||||
ourLog.info("** About to send obervation");
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||
|
@ -384,7 +482,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
|
||||
@Test
|
||||
public void testSubscriptionTriggerViaSubscription() throws Exception {
|
||||
BaseSubscriptionInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(true);
|
||||
SubscriptionMatcherInterceptor.setForcePayloadEncodeAndDecodeForUnitTests(true);
|
||||
|
||||
String payload = "application/xml";
|
||||
|
||||
|
@ -392,7 +490,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
|
||||
createSubscription(criteria1, payload);
|
||||
waitForRegisteredSubscriptionCount(1);
|
||||
waitForActivatedSubscriptionCount(1);
|
||||
|
||||
ourLog.info("** About to send obervation");
|
||||
|
||||
|
@ -490,7 +588,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
|
||||
Subscription subscription1 = createSubscription(criteria1, payload);
|
||||
Subscription subscription2 = createSubscription(criteria2, payload);
|
||||
waitForRegisteredSubscriptionCount(2);
|
||||
waitForActivatedSubscriptionCount(2);
|
||||
|
||||
Observation observation1 = sendObservation(code, "SNOMED-CT");
|
||||
|
||||
|
@ -526,7 +624,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
|
||||
// Add some headers, and we'll also turn back to requested status for fun
|
||||
Subscription subscription = createSubscription(criteria1, payload);
|
||||
waitForRegisteredSubscriptionCount(1);
|
||||
waitForActivatedSubscriptionCount(1);
|
||||
|
||||
subscription.getChannel().addHeader("X-Foo: FOO");
|
||||
subscription.getChannel().addHeader("X-Bar: BAR");
|
||||
|
@ -553,7 +651,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
String criteria1 = "Observation?code=SNOMED-CT|" + code + "&_format=xml";
|
||||
|
||||
Subscription subscription = createSubscription(criteria1, payload);
|
||||
waitForRegisteredSubscriptionCount(1);
|
||||
waitForActivatedSubscriptionCount(1);
|
||||
|
||||
sendObservation(code, "SNOMED-CT");
|
||||
|
||||
|
@ -644,7 +742,7 @@ public class RestHookTestR4Test extends BaseSubscriptionsR4Test {
|
|||
mySearchParameterDao.create(sp);
|
||||
mySearchParamRegsitry.forceRefresh();
|
||||
createSubscription(criteria, "application/json");
|
||||
waitForRegisteredSubscriptionCount(1);
|
||||
waitForActivatedSubscriptionCount(1);
|
||||
|
||||
{
|
||||
Observation bodySite = new Observation();
|
|
@ -1,8 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.resource.Observation;
|
||||
|
@ -25,6 +26,7 @@ import org.eclipse.jetty.servlet.ServletContextHandler;
|
|||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
@ -42,6 +44,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
|
|||
private static String ourListenerServerBase;
|
||||
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@After
|
||||
public void afterUnregisterRestHookListener() {
|
||||
ourLog.info("** AFTER **");
|
||||
|
@ -51,12 +56,12 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
|
|||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.registerRestHookInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -64,11 +69,11 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu2Test extends B
|
|||
ourCreatedObservations.clear();
|
||||
ourUpdatedObservations.clear();
|
||||
|
||||
ourRestHookSubscriptionInterceptor.initSubscriptions();
|
||||
mySubscriptionLoader.initSubscriptions();
|
||||
}
|
||||
|
||||
private void waitForQueueToDrain() throws InterruptedException {
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
}
|
||||
|
||||
|
|
@ -1,29 +1,30 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.subscription;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
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.SubscriptionActivatingInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
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.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import com.google.common.collect.Lists;
|
||||
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.junit.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
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.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
|
@ -38,6 +39,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test.class);
|
||||
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@Override
|
||||
protected boolean shouldLogClient() {
|
||||
return false;
|
||||
|
@ -51,20 +55,20 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B
|
|||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
myDaoConfig.getInterceptors().remove(ourRestHookSubscriptionInterceptor);
|
||||
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(false);
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
myDaoConfig.getInterceptors().add(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.registerRestHookInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeReset() {
|
||||
ourCreatedObservations.clear();
|
||||
ourUpdatedObservations.clear();
|
||||
SubscriptionActivatingSubscriber.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
|
||||
SubscriptionActivatingInterceptor.setWaitForSubscriptionActivationSynchronouslyForUnitTest(true);
|
||||
}
|
||||
|
||||
private Subscription createSubscription(String criteria, String payload, String endpoint) throws InterruptedException {
|
||||
|
@ -87,7 +91,7 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigDstu3Test extends B
|
|||
}
|
||||
|
||||
private void waitForQueueToDrain() throws InterruptedException {
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
}
|
||||
|
||||
private Observation sendObservation(String code, String system) throws InterruptedException {
|
|
@ -1,28 +1,30 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.junit.*;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.jpa.testutil.RandomServerPortProvider;
|
||||
import ca.uhn.fhir.model.dstu2.valueset.ResourceTypeEnum;
|
||||
import ca.uhn.fhir.model.primitive.IdDt;
|
||||
import ca.uhn.fhir.rest.annotation.*;
|
||||
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.MethodOutcome;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.eclipse.jetty.server.Server;
|
||||
import org.eclipse.jetty.servlet.ServletContextHandler;
|
||||
import org.eclipse.jetty.servlet.ServletHolder;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.*;
|
||||
import org.junit.*;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Test the rest-hook subscriptions
|
||||
|
@ -37,6 +39,9 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
|
|||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(RestHookTestWithInterceptorRegisteredToDaoConfigR4Test.class);
|
||||
private static List<Observation> ourUpdatedObservations = Collections.synchronizedList(Lists.newArrayList());
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@Override
|
||||
protected boolean shouldLogClient() {
|
||||
return false;
|
||||
|
@ -50,12 +55,12 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
|
|||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
myDaoConfig.getInterceptors().remove(getRestHookSubscriptionInterceptor());
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
myDaoConfig.getInterceptors().add(getRestHookSubscriptionInterceptor());
|
||||
mySubscriptionTestUtil.registerRestHookInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
||||
|
@ -84,11 +89,11 @@ public class RestHookTestWithInterceptorRegisteredToDaoConfigR4Test extends Base
|
|||
}
|
||||
|
||||
private void waitForQueueToDrain() throws InterruptedException {
|
||||
ourLog.info("QUEUE HAS {} ITEMS", getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests());
|
||||
while (getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests() > 0) {
|
||||
ourLog.info("QUEUE HAS {} ITEMS", mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests());
|
||||
while (mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests() > 0) {
|
||||
Thread.sleep(250);
|
||||
}
|
||||
ourLog.info("QUEUE HAS {} ITEMS", getRestHookSubscriptionInterceptor().getExecutorQueueSizeForUnitTests());
|
||||
ourLog.info("QUEUE HAS {} ITEMS", mySubscriptionTestUtil.getExecutorQueueSizeForUnitTests());
|
||||
}
|
||||
|
||||
private Observation sendObservation(String code, String system) throws InterruptedException {
|
|
@ -1,7 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.jpa.subscription.FhirR4Util;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import com.google.common.collect.Lists;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
|
@ -11,6 +13,7 @@ import org.junit.Before;
|
|||
import org.junit.Ignore;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
|
@ -44,6 +47,9 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
|
|||
private String mySubscriptionId;
|
||||
private List<IIdType> mySubscriptionIds = Collections.synchronizedList(new ArrayList<>());
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@Override
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
|
@ -64,7 +70,7 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
|
|||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
ourRestServer.unregisterInterceptor(getRestHookSubscriptionInterceptor());
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -76,6 +82,9 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
|
|||
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignored because this feature isn't implemented yet
|
||||
*/
|
||||
@Test
|
||||
@Ignore
|
||||
public void testSubscriptionAddedTrigger() {
|
||||
|
@ -122,7 +131,7 @@ public class RestHookWithEventDefinitionR4Test extends BaseResourceProviderR4Tes
|
|||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
ourRestServer.registerInterceptor(getRestHookSubscriptionInterceptor());
|
||||
mySubscriptionTestUtil.registerRestHookInterceptor();
|
||||
}
|
||||
|
||||
@Before
|
|
@ -1,9 +1,11 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.resthook;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
import ca.uhn.fhir.jpa.provider.SubscriptionTriggeringProvider;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTriggeringSvcImpl;
|
||||
import ca.uhn.fhir.jpa.util.JpaConstants;
|
||||
import ca.uhn.fhir.rest.annotation.Create;
|
||||
import ca.uhn.fhir.rest.annotation.ResourceParam;
|
||||
|
@ -50,6 +52,9 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
|
|||
private static List<String> ourContentTypes = new ArrayList<>();
|
||||
private List<IIdType> mySubscriptionIds = new ArrayList<>();
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@After
|
||||
public void afterUnregisterRestHookListener() {
|
||||
ourLog.info("**** Starting @After *****");
|
||||
|
@ -66,7 +71,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
|
|||
ourLog.info("Done deleting all subscriptions");
|
||||
myDaoConfig.setAllowMultipleDelete(new DaoConfig().isAllowMultipleDelete());
|
||||
|
||||
ourRestServer.unregisterInterceptor(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
|
||||
mySubscriptionTriggeringSvc.cancelAll();
|
||||
mySubscriptionTriggeringSvc.setMaxSubmitPerPass(null);
|
||||
|
@ -79,7 +84,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
|
|||
|
||||
@Before
|
||||
public void beforeRegisterRestHookListener() {
|
||||
ourRestServer.registerInterceptor(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.registerRestHookInterceptor();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -376,7 +381,7 @@ public class SubscriptionTriggeringDstu3Test extends BaseResourceProviderDstu3Te
|
|||
}
|
||||
|
||||
private void waitForQueueToDrain() throws InterruptedException {
|
||||
RestHookTestDstu2Test.waitForQueueToDrain(ourRestHookSubscriptionInterceptor);
|
||||
mySubscriptionTestUtil.waitForQueueToDrain();
|
||||
}
|
||||
|
||||
public static class ObservationListener implements IResourceProvider {
|
|
@ -1,6 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
|
||||
import ca.uhn.fhir.jpa.subscription.FhirDstu2Util;
|
||||
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
|
||||
|
@ -27,7 +29,7 @@ import java.util.concurrent.Future;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
// This is currently disabled as the criteria mechanism was a non-standard experiment
|
||||
@Ignore
|
|
@ -1,6 +1,8 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||
import ca.uhn.fhir.jpa.subscription.FhirDstu3Util;
|
||||
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
|
@ -18,7 +20,7 @@ import java.util.concurrent.Future;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
// This is currently disabled as the criteria mechanism was a non-standard experiment
|
||||
@Ignore
|
|
@ -1,6 +1,7 @@
|
|||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.jpa.subscription.FhirR4Util;
|
||||
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
|
@ -19,7 +20,7 @@ import java.util.concurrent.Future;
|
|||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
|
||||
// This is currently disabled as the criteria mechanism was a non-standard experiment
|
|
@ -1,7 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.BaseResourceProviderDstu2Test;
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.FhirDstu2Util;
|
||||
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodeableConceptDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.CodingDt;
|
||||
import ca.uhn.fhir.model.dstu2.composite.ResourceReferenceDt;
|
||||
|
@ -21,13 +23,14 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
|
@ -52,11 +55,13 @@ public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDs
|
|||
private WebSocketClient myWebSocketClient;
|
||||
private SocketImplementation mySocketImplementation;
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
super.after();
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.unregisterInterceptor(interceptor);
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -69,8 +74,7 @@ public class WebsocketWithSubscriptionIdDstu2Test extends BaseResourceProviderDs
|
|||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.registerInterceptor(interceptor);
|
||||
mySubscriptionTestUtil.registerWebSocketInterceptor();
|
||||
|
||||
/*
|
||||
* Create patient
|
|
@ -1,7 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.BaseResourceProviderDstu3Test;
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.FhirDstu3Util;
|
||||
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
|
@ -12,13 +14,14 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
|
@ -46,12 +49,14 @@ public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDs
|
|||
private WebSocketClient myWebSocketClient;
|
||||
private SocketImplementation mySocketImplementation;
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
super.after();
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.unregisterInterceptor(interceptor);
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -67,8 +72,7 @@ public class WebsocketWithSubscriptionIdDstu3Test extends BaseResourceProviderDs
|
|||
myDaoConfig.setSubscriptionEnabled(true);
|
||||
myDaoConfig.setSubscriptionPollDelay(0L);
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.registerInterceptor(interceptor);
|
||||
mySubscriptionTestUtil.registerWebSocketInterceptor();
|
||||
|
||||
/*
|
||||
* Create patient
|
|
@ -1,8 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.subscription.r4;
|
||||
package ca.uhn.fhir.jpa.subscription.websocket;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.r4.BaseResourceProviderR4Test;
|
||||
import ca.uhn.fhir.jpa.subscription.FhirR4Util;
|
||||
import ca.uhn.fhir.jpa.subscription.SocketImplementation;
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionTestUtil;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||
import org.eclipse.jetty.websocket.api.Session;
|
||||
|
@ -13,13 +14,14 @@ import org.junit.After;
|
|||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.slf4j.Logger;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static org.hamcrest.Matchers.contains;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.junit.Assert.assertThat;
|
||||
|
||||
/**
|
||||
* Adds a FHIR subscription with criteria through the rest interface. Then creates a websocket with the id of the
|
||||
|
@ -47,12 +49,14 @@ public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Tes
|
|||
private WebSocketClient myWebSocketClient;
|
||||
private SocketImplementation mySocketImplementation;
|
||||
|
||||
@Autowired
|
||||
private SubscriptionTestUtil mySubscriptionTestUtil;
|
||||
|
||||
@Override
|
||||
@After
|
||||
public void after() throws Exception {
|
||||
super.after();
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.unregisterInterceptor(interceptor);
|
||||
mySubscriptionTestUtil.unregisterSubscriptionInterceptor();
|
||||
}
|
||||
|
||||
@After
|
||||
|
@ -66,8 +70,7 @@ public class WebsocketWithSubscriptionIdR4Test extends BaseResourceProviderR4Tes
|
|||
public void before() throws Exception {
|
||||
super.before();
|
||||
|
||||
SubscriptionWebsocketInterceptor interceptor = ourWebApplicationContext.getBean(SubscriptionWebsocketInterceptor.class);
|
||||
ourRestServer.registerInterceptor(interceptor);
|
||||
mySubscriptionTestUtil.registerWebSocketInterceptor();
|
||||
|
||||
/*
|
||||
* Create patient
|
|
@ -1,4 +1,18 @@
|
|||
## Running hapi-fhir-jpaserver-example in Tomcat from IntelliJ
|
||||
# Unsupported
|
||||
|
||||
This [hapi-fhir-jpaserver-example](https://github.com/jamesagnew/hapi-fhir/tree/master/hapi-fhir-jpaserver-example) project is no longer supported.
|
||||
|
||||
|
||||
## Supported JPA Example
|
||||
|
||||
The supported HAPI-FHIR JPA example is available in the [hapi-fhir-jpaserver-starter](https://github.com/hapifhir/hapi-fhir-jpaserver-starter)
|
||||
project within the [hapifhir](https://github.com/hapifhir) GitHub Organization.
|
||||
|
||||
## Previous Documentation
|
||||
|
||||
Below is the original documentation for this project. Note that this documentation is no longer being updated.
|
||||
|
||||
#### Running hapi-fhir-jpaserver-example in Tomcat from IntelliJ
|
||||
|
||||
Install Tomcat.
|
||||
|
||||
|
@ -33,7 +47,7 @@ Point your browser (or fiddler, or what have you) to `http://localhost:8080/hapi
|
|||
You should get an empty bundle back.
|
||||
|
||||
|
||||
## Running hapi-fhir-jpaserver-example in a Docker container
|
||||
#### Running hapi-fhir-jpaserver-example in a Docker container
|
||||
|
||||
Execute the `build-docker-image.sh` script to build the docker image.
|
||||
|
||||
|
|
|
@ -1,23 +1,6 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.subscription.email.SubscriptionEmailInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.resthook.SubscriptionRestHookInterceptor;
|
||||
import ca.uhn.fhir.jpa.subscription.websocket.SubscriptionWebsocketInterceptor;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
|
@ -26,12 +9,22 @@ import ca.uhn.fhir.jpa.provider.JpaConformanceProviderDstu2;
|
|||
import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.TerminologyUploaderProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.*;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemo extends RestfulServer {
|
||||
|
||||
|
@ -134,12 +127,10 @@ public class JpaServerDemo extends RestfulServer {
|
|||
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
|
||||
|
||||
/*
|
||||
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
|
||||
* Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
|
||||
*/
|
||||
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
|
||||
for (IServerInterceptor interceptor : interceptorBeans) {
|
||||
this.registerInterceptor(interceptor);
|
||||
}
|
||||
SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
|
||||
subscriptionInterceptorLoader.registerInterceptors();
|
||||
|
||||
/*
|
||||
* If you are hosting this server at a specific DNS name, the server will try to
|
||||
|
|
|
@ -1,16 +1,6 @@
|
|||
|
||||
package ca.uhn.fhir.jpa.demo;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.context.FhirVersionEnum;
|
||||
import ca.uhn.fhir.jpa.dao.DaoConfig;
|
||||
|
@ -20,12 +10,20 @@ import ca.uhn.fhir.jpa.provider.JpaSystemProviderDstu2;
|
|||
import ca.uhn.fhir.jpa.provider.dstu3.JpaConformanceProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.provider.dstu3.JpaSystemProviderDstu3;
|
||||
import ca.uhn.fhir.jpa.search.DatabaseBackedPagingProvider;
|
||||
import ca.uhn.fhir.model.api.IResource;
|
||||
import ca.uhn.fhir.jpa.subscription.SubscriptionInterceptorLoader;
|
||||
import ca.uhn.fhir.model.dstu2.composite.MetaDt;
|
||||
import ca.uhn.fhir.narrative.DefaultThymeleafNarrativeGenerator;
|
||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||
import ca.uhn.fhir.rest.server.*;
|
||||
import ca.uhn.fhir.rest.server.interceptor.IServerInterceptor;
|
||||
import ca.uhn.fhir.rest.server.ETagSupportEnum;
|
||||
import ca.uhn.fhir.rest.server.IResourceProvider;
|
||||
import ca.uhn.fhir.rest.server.RestfulServer;
|
||||
import org.hl7.fhir.dstu3.model.Bundle;
|
||||
import org.hl7.fhir.dstu3.model.Meta;
|
||||
import org.springframework.web.context.ContextLoaderListener;
|
||||
import org.springframework.web.context.WebApplicationContext;
|
||||
|
||||
import javax.servlet.ServletException;
|
||||
import java.util.List;
|
||||
|
||||
public class JpaServerDemoDstu2 extends RestfulServer {
|
||||
|
||||
|
@ -128,12 +126,10 @@ public class JpaServerDemoDstu2 extends RestfulServer {
|
|||
setPagingProvider(myAppCtx.getBean(DatabaseBackedPagingProvider.class));
|
||||
|
||||
/*
|
||||
* Load interceptors for the server from Spring (these are defined in FhirServerConfig.java)
|
||||
* Register interceptors for the server based on DaoConfig.getSupportedSubscriptionTypes()
|
||||
*/
|
||||
Collection<IServerInterceptor> interceptorBeans = myAppCtx.getBeansOfType(IServerInterceptor.class).values();
|
||||
for (IServerInterceptor interceptor : interceptorBeans) {
|
||||
this.registerInterceptor(interceptor);
|
||||
}
|
||||
SubscriptionInterceptorLoader subscriptionInterceptorLoader = myAppCtx.getBean(SubscriptionInterceptorLoader.class);
|
||||
subscriptionInterceptorLoader.registerInterceptors();
|
||||
|
||||
/*
|
||||
* If you are hosting this server at a specific DNS name, the server will try to
|
||||
|
|
|
@ -20,8 +20,10 @@ package ca.uhn.fhir.jpa.model.entity;
|
|||
* #L%
|
||||
*/
|
||||
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.Validate;
|
||||
import org.hl7.fhir.instance.model.Subscription;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
@ -53,6 +55,8 @@ public class ModelConfig {
|
|||
private Set<String> myTreatBaseUrlsAsLocal = new HashSet<>();
|
||||
private Set<String> myTreatReferencesAsLogical = new HashSet<>(DEFAULT_LOGICAL_BASE_URLS);
|
||||
private boolean myDefaultSearchParamsCanBeOverridden = false;
|
||||
private Set<Subscription.SubscriptionChannelType> mySupportedSubscriptionTypes = new HashSet<>();
|
||||
private String myEmailFromAddress = "noreply@unknown.com";
|
||||
|
||||
/**
|
||||
* If set to {@code true} the default search params (i.e. the search parameters that are
|
||||
|
@ -297,6 +301,46 @@ public class ModelConfig {
|
|||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
|
||||
* to the server matching these types will be activated.
|
||||
*
|
||||
*/
|
||||
public ModelConfig addSupportedSubscriptionType(Subscription.SubscriptionChannelType theSubscriptionChannelType) {
|
||||
mySupportedSubscriptionTypes.add(theSubscriptionChannelType);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* This setting indicates which subscription channel types are supported by the server. Any subscriptions submitted
|
||||
* to the server matching these types will be activated.
|
||||
*
|
||||
*/
|
||||
public Set<Subscription.SubscriptionChannelType> getSupportedSubscriptionTypes() {
|
||||
return Collections.unmodifiableSet(mySupportedSubscriptionTypes);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void clearSupportedSubscriptionTypesForUnitTest() {
|
||||
mySupportedSubscriptionTypes.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* If e-mail subscriptions are supported, the From address used when sending e-mails
|
||||
*/
|
||||
|
||||
public String getEmailFromAddress() {
|
||||
return myEmailFromAddress;
|
||||
}
|
||||
|
||||
/**
|
||||
* If e-mail subscriptions are supported, the From address used when sending e-mails
|
||||
*/
|
||||
|
||||
public void setEmailFromAddress(String theEmailFromAddress) {
|
||||
myEmailFromAddress = theEmailFromAddress;
|
||||
}
|
||||
|
||||
private static void validateTreatBaseUrlsAsLocal(String theUrl) {
|
||||
Validate.notBlank(theUrl, "Base URL must not be null or empty");
|
||||
|
||||
|
@ -309,5 +353,4 @@ public class ModelConfig {
|
|||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -38,9 +38,6 @@ import org.hl7.fhir.r4.model.Reference;
|
|||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.persistence.EntityManager;
|
||||
import javax.persistence.PersistenceContext;
|
||||
import javax.persistence.PersistenceContextType;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
@ -61,9 +58,6 @@ public class ResourceLinkExtractor {
|
|||
@Autowired
|
||||
private ISearchParamExtractor mySearchParamExtractor;
|
||||
|
||||
@PersistenceContext(type = PersistenceContextType.TRANSACTION)
|
||||
protected EntityManager myEntityManager;
|
||||
|
||||
public void extractResourceLinks(ResourceIndexedSearchParams theParams, ResourceTable theEntity, IBaseResource theResource, Date theUpdateTime, IResourceLinkResolver theResourceLinkResolver) {
|
||||
String resourceType = theEntity.getResourceType();
|
||||
|
||||
|
|
|
@ -36,7 +36,6 @@ import org.hl7.fhir.instance.model.api.IBaseResource;
|
|||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
|
@ -45,29 +44,20 @@ import java.util.*;
|
|||
import static org.apache.commons.lang3.StringUtils.isBlank;
|
||||
|
||||
public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implements ISearchParamRegistry {
|
||||
@Autowired
|
||||
private ModelConfig myModelConfig;
|
||||
@Autowired
|
||||
private ISearchParamProvider mySearchParamProvider;
|
||||
@Autowired
|
||||
private FhirContext myFhirContext;
|
||||
|
||||
private static final int MAX_MANAGED_PARAM_COUNT = 10000;
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(BaseSearchParamRegistry.class);
|
||||
private Map<String, Map<String, RuntimeSearchParam>> myBuiltInSearchParams;
|
||||
private volatile Map<String, List<JpaRuntimeSearchParam>> myActiveUniqueSearchParams = Collections.emptyMap();
|
||||
private volatile Map<String, Map<Set<String>, List<JpaRuntimeSearchParam>>> myActiveParamNamesToUniqueSearchParams = Collections.emptyMap();
|
||||
@Autowired
|
||||
private FhirContext myCtx;
|
||||
private volatile Map<String, Map<String, RuntimeSearchParam>> myActiveSearchParams;
|
||||
@Autowired
|
||||
private ModelConfig myModelConfig;
|
||||
private volatile long myLastRefresh;
|
||||
private ISearchParamProvider mySearchParamProvider;
|
||||
|
||||
BaseSearchParamRegistry(ISearchParamProvider theSearchParamProvider) {
|
||||
super();
|
||||
mySearchParamProvider = theSearchParamProvider;
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setSearchParamProvider(ISearchParamProvider theSearchParamProvider) {
|
||||
mySearchParamProvider = theSearchParamProvider;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestRefresh() {
|
||||
|
@ -219,10 +209,10 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
|
|||
public void postConstruct() {
|
||||
Map<String, Map<String, RuntimeSearchParam>> resourceNameToSearchParams = new HashMap<>();
|
||||
|
||||
Set<String> resourceNames = myCtx.getResourceNames();
|
||||
Set<String> resourceNames = myFhirContext.getResourceNames();
|
||||
|
||||
for (String resourceName : resourceNames) {
|
||||
RuntimeResourceDefinition nextResDef = myCtx.getResourceDefinition(resourceName);
|
||||
RuntimeResourceDefinition nextResDef = myFhirContext.getResourceDefinition(resourceName);
|
||||
String nextResourceName = nextResDef.getName();
|
||||
HashMap<String, RuntimeSearchParam> nameToParam = new HashMap<>();
|
||||
resourceNameToSearchParams.put(nextResourceName, nameToParam);
|
||||
|
@ -283,7 +273,7 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
|
|||
continue;
|
||||
}
|
||||
|
||||
for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myCtx, nextSp)) {
|
||||
for (String nextBaseName : SearchParameterUtil.getBaseAsStrings(myFhirContext, nextSp)) {
|
||||
if (isBlank(nextBaseName)) {
|
||||
continue;
|
||||
}
|
||||
|
@ -348,5 +338,9 @@ public abstract class BaseSearchParamRegistry<SP extends IBaseResource> implemen
|
|||
return getActiveSearchParams(theResourceDef.getName()).values();
|
||||
}
|
||||
|
||||
|
||||
@VisibleForTesting
|
||||
@Override
|
||||
public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) {
|
||||
mySearchParamProvider = theSearchParamProvider;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -23,6 +23,7 @@ package ca.uhn.fhir.jpa.searchparam.registry;
|
|||
import ca.uhn.fhir.context.RuntimeResourceDefinition;
|
||||
import ca.uhn.fhir.context.RuntimeSearchParam;
|
||||
import ca.uhn.fhir.jpa.searchparam.JpaRuntimeSearchParam;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
@ -59,4 +60,7 @@ public interface ISearchParamRegistry {
|
|||
RuntimeSearchParam getSearchParamByName(RuntimeResourceDefinition theResourceDef, String theParamName);
|
||||
|
||||
Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef);
|
||||
|
||||
@VisibleForTesting
|
||||
void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider);
|
||||
}
|
||||
|
|
|
@ -39,10 +39,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
|
||||
public class SearchParamRegistryDstu2 extends BaseSearchParamRegistry<SearchParameter> {
|
||||
|
||||
public SearchParamRegistryDstu2(ISearchParamProvider theSearchParamProvider) {
|
||||
super(theSearchParamProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
|
||||
String name = theNextSp.getCode();
|
||||
|
|
|
@ -39,10 +39,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
|
||||
public class SearchParamRegistryDstu3 extends BaseSearchParamRegistry<SearchParameter> {
|
||||
|
||||
public SearchParamRegistryDstu3(ISearchParamProvider theSearchParamProvider) {
|
||||
super(theSearchParamProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JpaRuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
|
||||
String name = theNextSp.getCode();
|
||||
|
|
|
@ -41,10 +41,6 @@ import static org.apache.commons.lang3.StringUtils.isBlank;
|
|||
|
||||
public class SearchParamRegistryR4 extends BaseSearchParamRegistry<SearchParameter> {
|
||||
|
||||
public SearchParamRegistryR4(ISearchParamProvider theSearchParamProvider) {
|
||||
super(theSearchParamProvider);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RuntimeSearchParam toRuntimeSp(SearchParameter theNextSp) {
|
||||
String name = theNextSp.getCode();
|
||||
|
@ -128,6 +124,4 @@ public class SearchParamRegistryR4 extends BaseSearchParamRegistry<SearchParamet
|
|||
|
||||
return new JpaRuntimeSearchParam(id, uri, name, description, path, paramType, providesMembershipInCompartments, targets, status, unique, components, theNextSp.getBase());
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import ca.uhn.fhir.jpa.model.entity.ModelConfig;
|
|||
import ca.uhn.fhir.jpa.model.entity.ResourceIndexedSearchParamToken;
|
||||
import ca.uhn.fhir.jpa.model.entity.ResourceTable;
|
||||
import ca.uhn.fhir.jpa.searchparam.extractor.SearchParamExtractorDstu3;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||
import ca.uhn.fhir.util.TestUtil;
|
||||
import org.hl7.fhir.dstu3.hapi.ctx.IValidationSupport;
|
||||
|
@ -86,6 +87,11 @@ public class SearchParamExtractorDstu3Test {
|
|||
public Collection<RuntimeSearchParam> getSearchParamsByResourceType(RuntimeResourceDefinition theResourceDef) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSearchParamProviderForUnitTest(ISearchParamProvider theSearchParamProvider) {
|
||||
// nothing
|
||||
}
|
||||
};
|
||||
|
||||
SearchParamExtractorDstu3 extractor = new SearchParamExtractorDstu3(new ModelConfig(), ourCtx, ourValidationSupport, searchParamRegistry);
|
||||
|
|
|
@ -29,6 +29,47 @@
|
|||
<artifactId>hapi-fhir-validation</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-messaging</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context</artifactId>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<artifactId>xml-apis</artifactId>
|
||||
<groupId>xml-apis</groupId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.thymeleaf</groupId>
|
||||
<artifactId>thymeleaf-spring5</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-websocket</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-context-support</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.mail</groupId>
|
||||
<artifactId>javax.mail-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
|
||||
<!-- test dependencies -->
|
||||
<dependency>
|
||||
|
@ -46,6 +87,16 @@
|
|||
<artifactId>mockito-core</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-server</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.eclipse.jetty</groupId>
|
||||
<artifactId>jetty-servlet</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
<build>
|
||||
<pluginManagement>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.module;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
|
@ -54,7 +54,7 @@ public class CanonicalSubscription implements Serializable {
|
|||
@JsonProperty("headers")
|
||||
private List<String> myHeaders;
|
||||
@JsonProperty("channelType")
|
||||
private Subscription.SubscriptionChannelType myChannelType;
|
||||
private CanonicalSubscriptionChannelType myChannelType;
|
||||
@JsonProperty("status")
|
||||
private Subscription.SubscriptionStatus myStatus;
|
||||
@JsonProperty("triggerDefinition")
|
||||
|
@ -73,11 +73,11 @@ public class CanonicalSubscription implements Serializable {
|
|||
}
|
||||
|
||||
|
||||
public Subscription.SubscriptionChannelType getChannelType() {
|
||||
public CanonicalSubscriptionChannelType getChannelType() {
|
||||
return myChannelType;
|
||||
}
|
||||
|
||||
public void setChannelType(Subscription.SubscriptionChannelType theChannelType) {
|
||||
public void setChannelType(CanonicalSubscriptionChannelType theChannelType) {
|
||||
myChannelType = theChannelType;
|
||||
}
|
||||
|
|
@ -0,0 +1,115 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module;
|
||||
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
|
||||
public enum CanonicalSubscriptionChannelType {
|
||||
/**
|
||||
* The channel is executed by making a post to the URI. If a payload is included, the URL is interpreted as the service base, and an update (PUT) is made.
|
||||
*/
|
||||
RESTHOOK,
|
||||
/**
|
||||
* The channel is executed by sending a packet across a web socket connection maintained by the client. The URL identifies the websocket, and the client binds to this URL.
|
||||
*/
|
||||
WEBSOCKET,
|
||||
/**
|
||||
* The channel is executed by sending an email to the email addressed in the URI (which must be a mailto:).
|
||||
*/
|
||||
EMAIL,
|
||||
/**
|
||||
* The channel is executed by sending an SMS message to the phone number identified in the URL (tel:).
|
||||
*/
|
||||
SMS,
|
||||
/**
|
||||
* The channel is executed by sending a message (e.g. a Bundle with a MessageHeader resource etc.) to the application identified in the URI.
|
||||
*/
|
||||
MESSAGE,
|
||||
/**
|
||||
* added to help the parsers with the generic types
|
||||
*/
|
||||
NULL;
|
||||
|
||||
public static CanonicalSubscriptionChannelType fromCode(String codeString) throws FHIRException {
|
||||
if (codeString == null || "".equals(codeString))
|
||||
return null;
|
||||
if ("rest-hook".equals(codeString))
|
||||
return RESTHOOK;
|
||||
if ("websocket".equals(codeString))
|
||||
return WEBSOCKET;
|
||||
if ("email".equals(codeString))
|
||||
return EMAIL;
|
||||
if ("sms".equals(codeString))
|
||||
return SMS;
|
||||
if ("message".equals(codeString))
|
||||
return MESSAGE;
|
||||
else
|
||||
throw new FHIRException("Unknown SubscriptionChannelType code '" + codeString + "'");
|
||||
}
|
||||
|
||||
public String toCode() {
|
||||
switch (this) {
|
||||
case RESTHOOK:
|
||||
return "rest-hook";
|
||||
case WEBSOCKET:
|
||||
return "websocket";
|
||||
case EMAIL:
|
||||
return "email";
|
||||
case SMS:
|
||||
return "sms";
|
||||
case MESSAGE:
|
||||
return "message";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
public String getSystem() {
|
||||
switch (this) {
|
||||
case RESTHOOK:
|
||||
return "http://hl7.org/fhir/subscription-channel-type";
|
||||
case WEBSOCKET:
|
||||
return "http://hl7.org/fhir/subscription-channel-type";
|
||||
case EMAIL:
|
||||
return "http://hl7.org/fhir/subscription-channel-type";
|
||||
case SMS:
|
||||
return "http://hl7.org/fhir/subscription-channel-type";
|
||||
case MESSAGE:
|
||||
return "http://hl7.org/fhir/subscription-channel-type";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
public String getDefinition() {
|
||||
switch (this) {
|
||||
case RESTHOOK:
|
||||
return "The channel is executed by making a post to the URI. If a payload is included, the URL is interpreted as the service base, and an update (PUT) is made.";
|
||||
case WEBSOCKET:
|
||||
return "The channel is executed by sending a packet across a web socket connection maintained by the client. The URL identifies the websocket, and the client binds to this URL.";
|
||||
case EMAIL:
|
||||
return "The channel is executed by sending an email to the email addressed in the URI (which must be a mailto:).";
|
||||
case SMS:
|
||||
return "The channel is executed by sending an SMS message to the phone number identified in the URL (tel:).";
|
||||
case MESSAGE:
|
||||
return "The channel is executed by sending a message (e.g. a Bundle with a MessageHeader resource etc.) to the application identified in the URI.";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
|
||||
public String getDisplay() {
|
||||
switch (this) {
|
||||
case RESTHOOK:
|
||||
return "Rest Hook";
|
||||
case WEBSOCKET:
|
||||
return "Websocket";
|
||||
case EMAIL:
|
||||
return "Email";
|
||||
case SMS:
|
||||
return "SMS";
|
||||
case MESSAGE:
|
||||
return "Message";
|
||||
default:
|
||||
return "?";
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,12 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
|
||||
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
public class LinkedBlockingQueueSubscriptionChannel extends SubscriptionChannel {
|
||||
|
||||
public LinkedBlockingQueueSubscriptionChannel(String theThreadNamingPattern) {
|
||||
super(new LinkedBlockingQueue<>(SubscriptionConstants.DELIVERY_EXECUTOR_QUEUE_SIZE), theThreadNamingPattern);
|
||||
}
|
||||
}
|
|
@ -1,4 +1,4 @@
|
|||
package ca.uhn.fhir.jpa.subscription;
|
||||
package ca.uhn.fhir.jpa.subscription.module;
|
||||
|
||||
/*-
|
||||
* #%L
|
||||
|
@ -53,6 +53,18 @@ public class ResourceModifiedMessage {
|
|||
@JsonIgnore
|
||||
private transient IBaseResource myPayloadDecoded;
|
||||
|
||||
// For JSON
|
||||
public ResourceModifiedMessage() {
|
||||
}
|
||||
|
||||
public ResourceModifiedMessage(FhirContext theFhirContext, IBaseResource theResource, OperationTypeEnum theOperationType) {
|
||||
setId(theResource.getIdElement());
|
||||
setOperationType(theOperationType);
|
||||
if (theOperationType != OperationTypeEnum.DELETE) {
|
||||
setNewPayload(theFhirContext, theResource);
|
||||
}
|
||||
}
|
||||
|
||||
public String getPayloadId() {
|
||||
return myPayloadId;
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionConstants;
|
||||
import ca.uhn.fhir.util.StopWatch;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.apache.commons.lang3.concurrent.BasicThreadFactory;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.messaging.Message;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
import org.springframework.messaging.support.ChannelInterceptor;
|
||||
import org.springframework.messaging.support.ExecutorSubscribableChannel;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
public class SubscriptionChannel implements SubscribableChannel {
|
||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionChannel.class);
|
||||
|
||||
private final ExecutorSubscribableChannel mySubscribableChannel;
|
||||
private final BlockingQueue<Runnable> myQueue;
|
||||
|
||||
public SubscriptionChannel(BlockingQueue<Runnable> theQueue, String theThreadNamingPattern) {
|
||||
|
||||
ThreadFactory threadFactory = new BasicThreadFactory.Builder()
|
||||
.namingPattern(theThreadNamingPattern)
|
||||
.daemon(false)
|
||||
.priority(Thread.NORM_PRIORITY)
|
||||
.build();
|
||||
RejectedExecutionHandler rejectedExecutionHandler = (theRunnable, theExecutor) -> {
|
||||
ourLog.info("Note: Executor queue is full ({} elements), waiting for a slot to become available!", theQueue.size());
|
||||
StopWatch sw = new StopWatch();
|
||||
try {
|
||||
theQueue.put(theRunnable);
|
||||
} catch (InterruptedException theE) {
|
||||
throw new RejectedExecutionException("Task " + theRunnable.toString() +
|
||||
" rejected from " + theE.toString());
|
||||
}
|
||||
ourLog.info("Slot become available after {}ms", sw.getMillis());
|
||||
};
|
||||
ThreadPoolExecutor executor = new ThreadPoolExecutor(
|
||||
1,
|
||||
SubscriptionConstants.EXECUTOR_THREAD_COUNT,
|
||||
0L,
|
||||
TimeUnit.MILLISECONDS,
|
||||
theQueue,
|
||||
threadFactory,
|
||||
rejectedExecutionHandler);
|
||||
myQueue = theQueue;
|
||||
mySubscribableChannel = new ExecutorSubscribableChannel(executor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean subscribe(MessageHandler handler) {
|
||||
return mySubscribableChannel.subscribe(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean unsubscribe(MessageHandler handler) {
|
||||
return mySubscribableChannel.unsubscribe(handler);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean send(Message<?> message, long timeout) {
|
||||
return mySubscribableChannel.send(message, timeout);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void clearInterceptorsForUnitTest() {
|
||||
mySubscribableChannel.setInterceptors(new ArrayList<>());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void addInterceptorForUnitTest(ChannelInterceptor theInterceptor) {
|
||||
mySubscribableChannel.addInterceptor(theInterceptor);
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int getQueueSizeForUnitTest() {
|
||||
return myQueue.size();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,73 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hl7.fhir.instance.model.api.IIdType;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
public class ActiveSubscription {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(ActiveSubscription.class);
|
||||
|
||||
private final CanonicalSubscription mySubscription;
|
||||
private final SubscribableChannel mySubscribableChannel;
|
||||
private final Collection<MessageHandler> myDeliveryHandlerSet = new HashSet<>();
|
||||
|
||||
public ActiveSubscription(CanonicalSubscription theSubscription, SubscribableChannel theSubscribableChannel) {
|
||||
mySubscription = theSubscription;
|
||||
mySubscribableChannel = theSubscribableChannel;
|
||||
}
|
||||
|
||||
public CanonicalSubscription getSubscription() {
|
||||
return mySubscription;
|
||||
}
|
||||
|
||||
public SubscribableChannel getSubscribableChannel() {
|
||||
return mySubscribableChannel;
|
||||
}
|
||||
|
||||
public void register(MessageHandler theHandler) {
|
||||
mySubscribableChannel.subscribe(theHandler);
|
||||
myDeliveryHandlerSet.add(theHandler);
|
||||
}
|
||||
|
||||
public void unregister(MessageHandler theMessageHandler) {
|
||||
if (mySubscribableChannel != null) {
|
||||
mySubscribableChannel.unsubscribe(theMessageHandler);
|
||||
if (mySubscribableChannel instanceof DisposableBean) {
|
||||
try {
|
||||
((DisposableBean) mySubscribableChannel).destroy();
|
||||
} catch (Exception e) {
|
||||
ourLog.error("Failed to destroy channel bean", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public void unregisterAll() {
|
||||
for (MessageHandler messageHandler : myDeliveryHandlerSet) {
|
||||
unregister(messageHandler);
|
||||
}
|
||||
}
|
||||
|
||||
public IIdType getIdElement(FhirContext theFhirContext) {
|
||||
return mySubscription.getIdElement(theFhirContext);
|
||||
}
|
||||
|
||||
public String getCriteriaString() {
|
||||
return mySubscription.getCriteriaString();
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public MessageHandler getDeliveryHandlerForUnitTest() {
|
||||
return myDeliveryHandlerSet.iterator().next();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||
|
||||
import org.apache.commons.lang3.Validate;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
public class ActiveSubscriptionCache {
|
||||
private static final org.slf4j.Logger ourLog = org.slf4j.LoggerFactory.getLogger(ActiveSubscriptionCache.class);
|
||||
|
||||
private final Map<String, ActiveSubscription> myCache = new ConcurrentHashMap<>();
|
||||
|
||||
public ActiveSubscription get(String theIdPart) {
|
||||
return myCache.get(theIdPart);
|
||||
}
|
||||
|
||||
public Collection<ActiveSubscription> getAll() {
|
||||
return Collections.unmodifiableCollection(myCache.values());
|
||||
}
|
||||
|
||||
public int size() {
|
||||
return myCache.size();
|
||||
}
|
||||
|
||||
public void put(String theSubscriptionId, ActiveSubscription theValue) {
|
||||
myCache.put(theSubscriptionId, theValue);
|
||||
}
|
||||
|
||||
public void remove(String theSubscriptionId) {
|
||||
Validate.notBlank(theSubscriptionId);
|
||||
|
||||
ActiveSubscription activeSubscription = myCache.get(theSubscriptionId);
|
||||
if (activeSubscription == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
activeSubscription.unregisterAll();
|
||||
myCache.remove(theSubscriptionId);
|
||||
}
|
||||
|
||||
public void unregisterAllSubscriptionsNotInCollection(Collection<String> theAllIds) {
|
||||
for (String next : new ArrayList<>(myCache.keySet())) {
|
||||
if (!theAllIds.contains(next)) {
|
||||
ourLog.info("Unregistering Subscription/{}", next);
|
||||
remove(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,22 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.module.SubscriptionChannel;
|
||||
import ca.uhn.fhir.jpa.subscription.module.LinkedBlockingQueueSubscriptionChannel;
|
||||
|
||||
public class BlockingQueueSubscriptionChannelFactory implements ISubscriptionChannelFactory {
|
||||
|
||||
@Override
|
||||
public SubscriptionChannel newDeliveryChannel(String theSubscriptionId, String theChannelType) {
|
||||
String threadName = "subscription-delivery-" +
|
||||
theChannelType +
|
||||
"-" +
|
||||
theSubscriptionId +
|
||||
"-%d";
|
||||
return new LinkedBlockingQueueSubscriptionChannel(threadName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public SubscriptionChannel newMatchingChannel(String theChannelName) {
|
||||
return new LinkedBlockingQueueSubscriptionChannel(theChannelName + "-%d");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,9 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||
|
||||
import org.springframework.messaging.SubscribableChannel;
|
||||
|
||||
public interface ISubscriptionChannelFactory {
|
||||
SubscribableChannel newDeliveryChannel(String theSubscriptionId, String theChannelType);
|
||||
|
||||
SubscribableChannel newMatchingChannel(String theChannelName);
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
|
||||
public interface ISubscriptionProvider {
|
||||
IBundleProvider search(SearchParameterMap theMap);
|
||||
|
||||
boolean loadSubscription(IBaseResource theResource);
|
||||
}
|
|
@ -0,0 +1,149 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||
|
||||
import ca.uhn.fhir.context.ConfigurationException;
|
||||
import ca.uhn.fhir.context.FhirContext;
|
||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
|
||||
import ca.uhn.fhir.rest.server.exceptions.InternalErrorException;
|
||||
import ca.uhn.fhir.rest.server.exceptions.PreconditionFailedException;
|
||||
import org.hl7.fhir.exceptions.FHIRException;
|
||||
import org.hl7.fhir.instance.model.api.IBaseReference;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.Extension;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class SubscriptionCannonicalizer<S extends IBaseResource> {
|
||||
@Autowired
|
||||
FhirContext myFhirContext;
|
||||
|
||||
public CanonicalSubscription canonicalize(S theSubscription) {
|
||||
switch (myFhirContext.getVersion().getVersion()) {
|
||||
case DSTU2:
|
||||
return canonicalizeDstu2(theSubscription);
|
||||
case DSTU3:
|
||||
return canonicalizeDstu3(theSubscription);
|
||||
case R4:
|
||||
return canonicalizeR4(theSubscription);
|
||||
default:
|
||||
throw new ConfigurationException("Subscription not supported for version: " + myFhirContext.getVersion().getVersion());
|
||||
}
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalizeDstu2(IBaseResource theSubscription) {
|
||||
ca.uhn.fhir.model.dstu2.resource.Subscription subscription = (ca.uhn.fhir.model.dstu2.resource.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
try {
|
||||
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus()));
|
||||
retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType()));
|
||||
retVal.setCriteriaString(subscription.getCriteria());
|
||||
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
|
||||
retVal.setHeaders(subscription.getChannel().getHeader());
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
} catch (FHIRException theE) {
|
||||
throw new InternalErrorException(theE);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalizeDstu3(IBaseResource theSubscription) {
|
||||
org.hl7.fhir.dstu3.model.Subscription subscription = (org.hl7.fhir.dstu3.model.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
try {
|
||||
retVal.setStatus(org.hl7.fhir.r4.model.Subscription.SubscriptionStatus.fromCode(subscription.getStatus().toCode()));
|
||||
retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
|
||||
retVal.setCriteriaString(subscription.getCriteria());
|
||||
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
|
||||
retVal.setHeaders(subscription.getChannel().getHeader());
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
|
||||
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
|
||||
String from;
|
||||
String subjectTemplate;
|
||||
String bodyTemplate;
|
||||
try {
|
||||
from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
||||
subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getEmailDetails().setFrom(from);
|
||||
retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
|
||||
}
|
||||
|
||||
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) {
|
||||
String stripVersionIds;
|
||||
String deliverLatestVersion;
|
||||
try {
|
||||
stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
|
||||
deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds));
|
||||
retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion));
|
||||
}
|
||||
|
||||
} catch (FHIRException theE) {
|
||||
throw new InternalErrorException(theE);
|
||||
}
|
||||
return retVal;
|
||||
}
|
||||
|
||||
protected CanonicalSubscription canonicalizeR4(IBaseResource theSubscription) {
|
||||
org.hl7.fhir.r4.model.Subscription subscription = (org.hl7.fhir.r4.model.Subscription) theSubscription;
|
||||
|
||||
CanonicalSubscription retVal = new CanonicalSubscription();
|
||||
retVal.setStatus(subscription.getStatus());
|
||||
retVal.setChannelType(CanonicalSubscriptionChannelType.fromCode(subscription.getChannel().getType().toCode()));
|
||||
retVal.setCriteriaString(subscription.getCriteria());
|
||||
retVal.setEndpointUrl(subscription.getChannel().getEndpoint());
|
||||
retVal.setHeaders(subscription.getChannel().getHeader());
|
||||
retVal.setIdElement(subscription.getIdElement());
|
||||
retVal.setPayloadString(subscription.getChannel().getPayload());
|
||||
|
||||
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
|
||||
String from;
|
||||
String subjectTemplate;
|
||||
try {
|
||||
from = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_EMAIL_FROM);
|
||||
subjectTemplate = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_SUBJECT_TEMPLATE);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getEmailDetails().setFrom(from);
|
||||
retVal.getEmailDetails().setSubjectTemplate(subjectTemplate);
|
||||
}
|
||||
|
||||
if (retVal.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) {
|
||||
String stripVersionIds;
|
||||
String deliverLatestVersion;
|
||||
try {
|
||||
stripVersionIds = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS);
|
||||
deliverLatestVersion = subscription.getChannel().getExtensionString(SubscriptionConstants.EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION);
|
||||
} catch (FHIRException theE) {
|
||||
throw new ConfigurationException("Failed to extract subscription extension(s): " + theE.getMessage(), theE);
|
||||
}
|
||||
retVal.getRestHookDetails().setStripVersionId(Boolean.parseBoolean(stripVersionIds));
|
||||
retVal.getRestHookDetails().setDeliverLatestVersion(Boolean.parseBoolean(deliverLatestVersion));
|
||||
}
|
||||
|
||||
List<Extension> topicExts = subscription.getExtensionsByUrl("http://hl7.org/fhir/subscription/topics");
|
||||
if (topicExts.size() > 0) {
|
||||
IBaseReference ref = (IBaseReference) topicExts.get(0).getValueAsPrimitive();
|
||||
if (!"EventDefinition".equals(ref.getReferenceElement().getResourceType())) {
|
||||
throw new PreconditionFailedException("Topic reference must be an EventDefinition");
|
||||
}
|
||||
}
|
||||
|
||||
return retVal;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,70 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||
|
||||
public class SubscriptionConstants {
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This extension should be of type <code>string</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_EMAIL_FROM = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-from";
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* This extension should be of type <code>string</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_SUBJECT_TEMPLATE = "http://hapifhir.io/fhir/StructureDefinition/subscription-email-subject-template";
|
||||
|
||||
|
||||
/**
|
||||
* This extension URL indicates whether a REST HOOK delivery should
|
||||
* include the version ID when delivering.
|
||||
* <p>
|
||||
* This extension should be of type <code>boolean</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element.
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_RESTHOOK_STRIP_VERSION_IDS = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-strip-version-ids";
|
||||
|
||||
/**
|
||||
* This extension URL indicates whether a REST HOOK delivery should
|
||||
* reload the resource and deliver the latest version always. This
|
||||
* could be useful for example if a resource which triggers a
|
||||
* subscription gets updated many times in short succession and there
|
||||
* is no value in delivering the older versions.
|
||||
* <p>
|
||||
* Note that if the resource is now deleted, this may cause
|
||||
* the delivery to be cancelled altogether.
|
||||
* </p>
|
||||
*
|
||||
* <p>
|
||||
* This extension should be of type <code>boolean</code> and should be
|
||||
* placed on the <code>Subscription.channel</code> element.
|
||||
* </p>
|
||||
*/
|
||||
public static final String EXT_SUBSCRIPTION_RESTHOOK_DELIVER_LATEST_VERSION = "http://hapifhir.io/fhir/StructureDefinition/subscription-resthook-deliver-latest-version";
|
||||
|
||||
/**
|
||||
* The number of threads used in subscription channel processing
|
||||
*/
|
||||
public static final int EXECUTOR_THREAD_COUNT = 5;
|
||||
|
||||
/**
|
||||
* The maximum number of subscriptions that can be active at once
|
||||
*/
|
||||
|
||||
public static final int MAX_SUBSCRIPTION_RESULTS = 1000;
|
||||
|
||||
/**
|
||||
* The size of the queue used for sending resources to the subscription matching processor
|
||||
*/
|
||||
public static final int PROCESSING_EXECUTOR_QUEUE_SIZE = 1000;
|
||||
|
||||
/**
|
||||
* The size of the queue used by each subscription delivery queue
|
||||
*/
|
||||
public static final int DELIVERY_EXECUTOR_QUEUE_SIZE = 1000;
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||
|
||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscriptionChannelType;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.SubscriptionDeliveringRestHookSubscriber;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.email.IEmailSender;
|
||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.email.SubscriptionDeliveringEmailSubscriber;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.springframework.beans.factory.annotation.Lookup;
|
||||
import org.springframework.messaging.MessageHandler;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
@Component
|
||||
public abstract class SubscriptionDeliveryHandlerFactory {
|
||||
private IEmailSender myEmailSender;
|
||||
|
||||
@Lookup
|
||||
protected abstract SubscriptionDeliveringEmailSubscriber getSubscriptionDeliveringEmailSubscriber(IEmailSender myEmailSender);
|
||||
@Lookup
|
||||
protected abstract SubscriptionDeliveringRestHookSubscriber getSubscriptionDeliveringRestHookSubscriber();
|
||||
|
||||
public Optional<MessageHandler> createDeliveryHandler(CanonicalSubscription theSubscription) {
|
||||
if (theSubscription.getChannelType() == CanonicalSubscriptionChannelType.EMAIL) {
|
||||
return Optional.of(getSubscriptionDeliveringEmailSubscriber(myEmailSender));
|
||||
} else if (theSubscription.getChannelType() == CanonicalSubscriptionChannelType.RESTHOOK) {
|
||||
return Optional.of(getSubscriptionDeliveringRestHookSubscriber());
|
||||
} else {
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public void setEmailSender(IEmailSender theEmailSender) {
|
||||
myEmailSender = theEmailSender;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,103 @@
|
|||
package ca.uhn.fhir.jpa.subscription.module.cache;
|
||||
|
||||
import ca.uhn.fhir.jpa.searchparam.SearchParameterMap;
|
||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||
import ca.uhn.fhir.rest.param.TokenOrListParam;
|
||||
import ca.uhn.fhir.rest.param.TokenParam;
|
||||
import com.google.common.annotations.VisibleForTesting;
|
||||
import org.hl7.fhir.instance.model.api.IBaseResource;
|
||||
import org.hl7.fhir.r4.model.Subscription;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Lazy;
|
||||
import org.springframework.scheduling.annotation.Scheduled;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.PostConstruct;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.Semaphore;
|
||||
|
||||
|
||||
@Service
|
||||
@Lazy
|
||||
public class SubscriptionLoader {
|
||||
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionLoader.class);
|
||||
|
||||
@Autowired
|
||||
private ISubscriptionProvider mySubscriptionProvidor;
|
||||
@Autowired
|
||||
private SubscriptionRegistry mySubscriptionRegistry;
|
||||
|
||||
private final Object myInitSubscriptionsLock = new Object();
|
||||
private Semaphore myInitSubscriptionsSemaphore = new Semaphore(1);
|
||||
|
||||
@PostConstruct
|
||||
public void start() {
|
||||
initSubscriptions();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the existing subscriptions from the database
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Scheduled(fixedDelay = 60000)
|
||||
public void initSubscriptions() {
|
||||
if (!myInitSubscriptionsSemaphore.tryAcquire()) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
doInitSubscriptions();
|
||||
} finally {
|
||||
myInitSubscriptionsSemaphore.release();
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public int doInitSubscriptionsForUnitTest() {
|
||||
return doInitSubscriptions();
|
||||
}
|
||||
|
||||
private int doInitSubscriptions() {
|
||||
synchronized (myInitSubscriptionsLock) {
|
||||
ourLog.debug("Starting init subscriptions");
|
||||
SearchParameterMap map = new SearchParameterMap();
|
||||
map.add(Subscription.SP_STATUS, new TokenOrListParam()
|
||||
.addOr(new TokenParam(null, Subscription.SubscriptionStatus.REQUESTED.toCode()))
|
||||
.addOr(new TokenParam(null, Subscription.SubscriptionStatus.ACTIVE.toCode())));
|
||||
map.setLoadSynchronousUpTo(SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS);
|
||||
|
||||
IBundleProvider subscriptionBundleList = mySubscriptionProvidor.search(map);
|
||||
|
||||
if (subscriptionBundleList.size() >= SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS) {
|
||||
ourLog.error("Currently over " + SubscriptionConstants.MAX_SUBSCRIPTION_RESULTS + " subscriptions. Some subscriptions have not been loaded.");
|
||||
}
|
||||
|
||||
List<IBaseResource> resourceList = subscriptionBundleList.getResources(0, subscriptionBundleList.size());
|
||||
|
||||
Set<String> allIds = new HashSet<>();
|
||||
int changesCount = 0;
|
||||
for (IBaseResource resource : resourceList) {
|
||||
String nextId = resource.getIdElement().getIdPart();
|
||||
allIds.add(nextId);
|
||||
boolean changed = mySubscriptionProvidor.loadSubscription(resource);
|
||||
if (changed) {
|
||||
changesCount++;
|
||||
}
|
||||
}
|
||||
|
||||
mySubscriptionRegistry.unregisterAllSubscriptionsNotInCollection(allIds);
|
||||
ourLog.trace("Finished init subscriptions - found {}", resourceList.size());
|
||||
|
||||
return changesCount;
|
||||
}
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
public void setSubscriptionProviderForUnitTest(ISubscriptionProvider theSubscriptionProvider) {
|
||||
mySubscriptionProvidor = theSubscriptionProvider;
|
||||
}
|
||||
}
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue