Interceptor cleanup
This commit is contained in:
parent
29c3cee287
commit
b0cbd52ae9
|
@ -58,7 +58,9 @@ public class VersionUtil {
|
||||||
try (InputStream is = VersionUtil.class.getResourceAsStream("/ca/uhn/fhir/hapi-fhir-base-build.properties")) {
|
try (InputStream is = VersionUtil.class.getResourceAsStream("/ca/uhn/fhir/hapi-fhir-base-build.properties")) {
|
||||||
|
|
||||||
Properties p = new Properties();
|
Properties p = new Properties();
|
||||||
p.load(is);
|
if (is != null) {
|
||||||
|
p.load(is);
|
||||||
|
}
|
||||||
|
|
||||||
ourVersion = p.getProperty("hapifhir.version");
|
ourVersion = p.getProperty("hapifhir.version");
|
||||||
ourVersion = defaultIfBlank(ourVersion, "(unknown)");
|
ourVersion = defaultIfBlank(ourVersion, "(unknown)");
|
||||||
|
|
|
@ -42,9 +42,9 @@ import javax.annotation.Nonnull;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -57,10 +57,12 @@ import javax.annotation.Nonnull;
|
||||||
@Configuration
|
@Configuration
|
||||||
@EnableScheduling
|
@EnableScheduling
|
||||||
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
|
@EnableJpaRepositories(basePackages = "ca.uhn.fhir.jpa.dao.data")
|
||||||
@ComponentScan(basePackages = "ca.uhn.fhir.jpa", excludeFilters={
|
@ComponentScan(basePackages = "ca.uhn.fhir.jpa", excludeFilters = {
|
||||||
@ComponentScan.Filter(type=FilterType.ASSIGNABLE_TYPE, value=BaseConfig.class),
|
@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.*")})
|
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*\\.test\\..*"),
|
||||||
|
@ComponentScan.Filter(type = FilterType.REGEX, pattern = ".*Test.*"),
|
||||||
|
@ComponentScan.Filter(type = FilterType.REGEX, pattern = "ca.uhn.fhir.jpa.subscription.module.standalone.*")})
|
||||||
|
|
||||||
public abstract class BaseConfig implements SchedulingConfigurer {
|
public abstract class BaseConfig implements SchedulingConfigurer {
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,10 @@
|
||||||
package ca.uhn.fhir.jpa.model.subscription.interceptor.executor;
|
package ca.uhn.fhir.jpa.interceptor.test;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.Pointcut;
|
import ca.uhn.fhir.jpa.model.interceptor.executor.InterceptorRegistry;
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.SubscriptionHook;
|
import ca.uhn.fhir.jpa.model.interceptor.api.HookParams;
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.SubscriptionInterceptor;
|
import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor;
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.Hook;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
@ -21,24 +23,25 @@ import static org.hamcrest.Matchers.contains;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
|
|
||||||
@RunWith(SpringJUnit4ClassRunner.class)
|
@RunWith(SpringJUnit4ClassRunner.class)
|
||||||
@ContextConfiguration(classes = {SubscriptionInterceptorRegistryTest.MyCtxConfig.class})
|
@ContextConfiguration(classes = {InterceptorRegistryTest.InterceptorRegistryTestCtxConfig.class})
|
||||||
public class SubscriptionInterceptorRegistryTest {
|
public class InterceptorRegistryTest {
|
||||||
|
|
||||||
private static boolean ourNext_beforeRestHookDelivery_Return2;
|
private static boolean ourNext_beforeRestHookDelivery_Return2;
|
||||||
private static boolean ourNext_beforeRestHookDelivery_Return1;
|
private static boolean ourNext_beforeRestHookDelivery_Return1;
|
||||||
private static List<String> ourInvocations = new ArrayList<>();
|
private static List<String> ourInvocations = new ArrayList<>();
|
||||||
private static CanonicalSubscription ourLastCanonicalSubscription;
|
private static CanonicalSubscription ourLastCanonicalSubscription;
|
||||||
private static ResourceDeliveryMessage ourLastResourceDeliveryMessage;
|
private static ResourceDeliveryMessage ourLastResourceDeliveryMessage0;
|
||||||
|
private static ResourceDeliveryMessage ourLastResourceDeliveryMessage1;
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
private SubscriptionInterceptorRegistry mySubscriptionInterceptorRegistry;
|
private InterceptorRegistry myInterceptorRegistry;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testGlobalInterceptorsAreFound() {
|
public void testGlobalInterceptorsAreFound() {
|
||||||
List<Object> globalInterceptors = mySubscriptionInterceptorRegistry.getGlobalInterceptors();
|
List<Object> globalInterceptors = myInterceptorRegistry.getGlobalInterceptorsForUnitTest();
|
||||||
assertEquals(2, globalInterceptors.size());
|
assertEquals(2, globalInterceptors.size());
|
||||||
assertTrue(globalInterceptors.get(0).getClass().toString(), globalInterceptors.get(0) instanceof MyInterceptorOne);
|
assertTrue(globalInterceptors.get(0).getClass().toString(), globalInterceptors.get(0) instanceof MyTestInterceptorOne);
|
||||||
assertTrue(globalInterceptors.get(1).getClass().toString(), globalInterceptors.get(1) instanceof MyInterceptorTwo);
|
assertTrue(globalInterceptors.get(1).getClass().toString(), globalInterceptors.get(1) instanceof MyTestInterceptorTwo);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -46,11 +49,12 @@ public class SubscriptionInterceptorRegistryTest {
|
||||||
ResourceDeliveryMessage msg = new ResourceDeliveryMessage();
|
ResourceDeliveryMessage msg = new ResourceDeliveryMessage();
|
||||||
CanonicalSubscription subs = new CanonicalSubscription();
|
CanonicalSubscription subs = new CanonicalSubscription();
|
||||||
HookParams params = new HookParams(msg, subs);
|
HookParams params = new HookParams(msg, subs);
|
||||||
boolean outcome = mySubscriptionInterceptorRegistry.callHooks(Pointcut.BEFORE_REST_HOOK_DELIVERY, params);
|
boolean outcome = myInterceptorRegistry.callHooks(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY, params);
|
||||||
assertTrue(outcome);
|
assertTrue(outcome);
|
||||||
|
|
||||||
assertThat(ourInvocations, contains("MyInterceptorOne.beforeRestHookDelivery", "MyInterceptorTwo.beforeRestHookDelivery"));
|
assertThat(ourInvocations, contains("MyTestInterceptorOne.beforeRestHookDelivery", "MyTestInterceptorTwo.beforeRestHookDelivery"));
|
||||||
assertSame(msg, ourLastResourceDeliveryMessage);
|
assertSame(msg, ourLastResourceDeliveryMessage0);
|
||||||
|
assertNull(ourLastResourceDeliveryMessage1);
|
||||||
assertSame(subs, ourLastCanonicalSubscription);
|
assertSame(subs, ourLastCanonicalSubscription);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -61,10 +65,10 @@ public class SubscriptionInterceptorRegistryTest {
|
||||||
ResourceDeliveryMessage msg = new ResourceDeliveryMessage();
|
ResourceDeliveryMessage msg = new ResourceDeliveryMessage();
|
||||||
CanonicalSubscription subs = new CanonicalSubscription();
|
CanonicalSubscription subs = new CanonicalSubscription();
|
||||||
HookParams params = new HookParams(msg, subs);
|
HookParams params = new HookParams(msg, subs);
|
||||||
boolean outcome = mySubscriptionInterceptorRegistry.callHooks(Pointcut.BEFORE_REST_HOOK_DELIVERY, params);
|
boolean outcome = myInterceptorRegistry.callHooks(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY, params);
|
||||||
assertFalse(outcome);
|
assertFalse(outcome);
|
||||||
|
|
||||||
assertThat(ourInvocations, contains("MyInterceptorOne.beforeRestHookDelivery"));
|
assertThat(ourInvocations, contains("MyTestInterceptorOne.beforeRestHookDelivery"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -73,10 +77,10 @@ public class SubscriptionInterceptorRegistryTest {
|
||||||
CanonicalSubscription subs = new CanonicalSubscription();
|
CanonicalSubscription subs = new CanonicalSubscription();
|
||||||
HookParams params = new HookParams(msg, subs);
|
HookParams params = new HookParams(msg, subs);
|
||||||
try {
|
try {
|
||||||
mySubscriptionInterceptorRegistry.callHooks(Pointcut.BEFORE_REST_HOOK_DELIVERY, params);
|
myInterceptorRegistry.callHooks(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY, params);
|
||||||
fail();
|
fail();
|
||||||
} catch (AssertionError e) {
|
} catch (AssertionError e) {
|
||||||
// good
|
assertEquals("Wrong hook parameters, wanted [CanonicalSubscription, ResourceDeliveryMessage] and found [CanonicalSubscription, Integer]", e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -86,17 +90,22 @@ public class SubscriptionInterceptorRegistryTest {
|
||||||
ourNext_beforeRestHookDelivery_Return1 = true;
|
ourNext_beforeRestHookDelivery_Return1 = true;
|
||||||
ourNext_beforeRestHookDelivery_Return2 = true;
|
ourNext_beforeRestHookDelivery_Return2 = true;
|
||||||
ourLastCanonicalSubscription = null;
|
ourLastCanonicalSubscription = null;
|
||||||
ourLastResourceDeliveryMessage = null;
|
ourLastResourceDeliveryMessage0 = null;
|
||||||
|
ourLastResourceDeliveryMessage1 = null;
|
||||||
ourInvocations.clear();
|
ourInvocations.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
@ComponentScan(basePackages = "ca.uhn.fhir.jpa.model")
|
@ComponentScan(basePackages = "ca.uhn.fhir.jpa.model")
|
||||||
public static class MyCtxConfig {
|
static class InterceptorRegistryTestCtxConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Note: Orders are deliberately reversed to make sure we get the orders right
|
||||||
|
* using the @Order annotation
|
||||||
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public SubscriptionInterceptorRegistry subscriptionInterceptorRegistry() {
|
public MyTestInterceptorTwo interceptor1() {
|
||||||
return new SubscriptionInterceptorRegistry();
|
return new MyTestInterceptorTwo();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -104,41 +113,37 @@ public class SubscriptionInterceptorRegistryTest {
|
||||||
* using the @Order annotation
|
* using the @Order annotation
|
||||||
*/
|
*/
|
||||||
@Bean
|
@Bean
|
||||||
public MyInterceptorTwo interceptor1() {
|
public MyTestInterceptorOne interceptor2() {
|
||||||
return new MyInterceptorTwo();
|
return new MyTestInterceptorOne();
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Note: Orders are deliberately reversed to make sure we get the orders right
|
|
||||||
* using the @Order annotation
|
|
||||||
*/
|
|
||||||
@Bean
|
|
||||||
public MyInterceptorOne interceptor2() {
|
|
||||||
return new MyInterceptorOne();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscriptionInterceptor
|
@Interceptor
|
||||||
@Order(100)
|
@Order(100)
|
||||||
public static class MyInterceptorOne {
|
public static class MyTestInterceptorOne {
|
||||||
|
|
||||||
@SubscriptionHook(Pointcut.BEFORE_REST_HOOK_DELIVERY)
|
public MyTestInterceptorOne() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Hook(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY)
|
||||||
public boolean beforeRestHookDelivery(CanonicalSubscription theCanonicalSubscription) {
|
public boolean beforeRestHookDelivery(CanonicalSubscription theCanonicalSubscription) {
|
||||||
ourLastCanonicalSubscription = theCanonicalSubscription;
|
ourLastCanonicalSubscription = theCanonicalSubscription;
|
||||||
ourInvocations.add("MyInterceptorOne.beforeRestHookDelivery");
|
ourInvocations.add("MyTestInterceptorOne.beforeRestHookDelivery");
|
||||||
return ourNext_beforeRestHookDelivery_Return1;
|
return ourNext_beforeRestHookDelivery_Return1;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscriptionInterceptor
|
@Interceptor
|
||||||
@Order(200)
|
@Order(200)
|
||||||
public static class MyInterceptorTwo {
|
public static class MyTestInterceptorTwo {
|
||||||
@SubscriptionHook(Pointcut.BEFORE_REST_HOOK_DELIVERY)
|
@Hook(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY)
|
||||||
public boolean beforeRestHookDelivery(ResourceDeliveryMessage theResourceDeliveryMessage) {
|
public boolean beforeRestHookDelivery(ResourceDeliveryMessage theResourceDeliveryMessage0, ResourceDeliveryMessage theResourceDeliveryMessage1) {
|
||||||
ourLastResourceDeliveryMessage = theResourceDeliveryMessage;
|
ourLastResourceDeliveryMessage0 = theResourceDeliveryMessage0;
|
||||||
ourInvocations.add("MyInterceptorTwo.beforeRestHookDelivery");
|
ourLastResourceDeliveryMessage1 = theResourceDeliveryMessage1;
|
||||||
|
ourInvocations.add("MyTestInterceptorTwo.beforeRestHookDelivery");
|
||||||
return ourNext_beforeRestHookDelivery_Return2;
|
return ourNext_beforeRestHookDelivery_Return2;
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -2,11 +2,11 @@ package ca.uhn.fhir.jpa.subscription.resthook;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
import ca.uhn.fhir.jpa.config.StoppableSubscriptionDeliveringRestHookSubscriber;
|
import ca.uhn.fhir.jpa.config.StoppableSubscriptionDeliveringRestHookSubscriber;
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.Hook;
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.Interceptor;
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR4Test;
|
import ca.uhn.fhir.jpa.subscription.BaseSubscriptionsR4Test;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.Pointcut;
|
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.SubscriptionHook;
|
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.SubscriptionInterceptor;
|
|
||||||
import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage;
|
import ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.MethodOutcome;
|
import ca.uhn.fhir.rest.api.MethodOutcome;
|
||||||
|
@ -28,7 +28,7 @@ import static org.junit.Assert.*;
|
||||||
/**
|
/**
|
||||||
* Test the rest-hook subscriptions
|
* Test the rest-hook subscriptions
|
||||||
*/
|
*/
|
||||||
@ContextConfiguration(classes = {RestHookWithInterceptorR4Test.MyCtxConfig.class})
|
@ContextConfiguration(classes = {RestHookWithInterceptorR4Test.MyTestCtxConfig.class})
|
||||||
public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
|
public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
|
||||||
|
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(RestHookWithInterceptorR4Test.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(RestHookWithInterceptorR4Test.class);
|
||||||
|
@ -118,11 +118,11 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Configuration
|
@Configuration
|
||||||
public static class MyCtxConfig {
|
static class MyTestCtxConfig {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public MyInterceptor interceptor() {
|
public MyTestInterceptor interceptor() {
|
||||||
return new MyInterceptor();
|
return new MyTestInterceptor();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -130,17 +130,17 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
|
||||||
/**
|
/**
|
||||||
* Interceptor class
|
* Interceptor class
|
||||||
*/
|
*/
|
||||||
@SubscriptionInterceptor
|
@Interceptor
|
||||||
public static class MyInterceptor {
|
public static class MyTestInterceptor {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
public MyInterceptor() {
|
public MyTestInterceptor() {
|
||||||
ourLog.info("Creating interceptor");
|
ourLog.info("Creating interceptor");
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscriptionHook(Pointcut.BEFORE_REST_HOOK_DELIVERY)
|
@Hook(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY)
|
||||||
public boolean beforeRestHookDelivery(ResourceDeliveryMessage theDeliveryMessage, CanonicalSubscription theSubscription) {
|
public boolean beforeRestHookDelivery(ResourceDeliveryMessage theDeliveryMessage, CanonicalSubscription theSubscription) {
|
||||||
if (ourNextModifyResourceId) {
|
if (ourNextModifyResourceId) {
|
||||||
theDeliveryMessage.getPayload(ourCtx).setId(new IdType("Observation/A"));
|
theDeliveryMessage.getPayload(ourCtx).setId(new IdType("Observation/A"));
|
||||||
|
@ -153,7 +153,7 @@ public class RestHookWithInterceptorR4Test extends BaseSubscriptionsR4Test {
|
||||||
return ourNextBeforeRestHookDeliveryReturn;
|
return ourNextBeforeRestHookDeliveryReturn;
|
||||||
}
|
}
|
||||||
|
|
||||||
@SubscriptionHook(Pointcut.AFTER_REST_HOOK_DELIVERY)
|
@Hook(Pointcut.SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY)
|
||||||
public boolean afterRestHookDelivery(ResourceDeliveryMessage theDeliveryMessage, CanonicalSubscription theSubscription) {
|
public boolean afterRestHookDelivery(ResourceDeliveryMessage theDeliveryMessage, CanonicalSubscription theSubscription) {
|
||||||
ourHitAfterRestHookDelivery = true;
|
ourHitAfterRestHookDelivery = true;
|
||||||
return ourNextAfterRestHookDeliveryReturn;
|
return ourNextAfterRestHookDeliveryReturn;
|
||||||
|
|
|
@ -97,6 +97,11 @@
|
||||||
<artifactId>jscience</artifactId>
|
<artifactId>jscience</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.commons</groupId>
|
||||||
|
<artifactId>commons-collections4</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Java -->
|
<!-- Java -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>javax.annotation</groupId>
|
<groupId>javax.annotation</groupId>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.model.subscription.interceptor.api;
|
package ca.uhn.fhir.jpa.model.interceptor.api;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
|
@ -7,18 +7,18 @@ import java.lang.annotation.Target;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This annotation should be placed on
|
* This annotation should be placed on
|
||||||
* {@link SubscriptionInterceptor Subscription Interceptor}
|
* {@link Interceptor Subscription Interceptor}
|
||||||
* bean methods.
|
* bean methods.
|
||||||
* <p>
|
* <p>
|
||||||
* Methods with this annotation are invoked immediately before a REST HOOK
|
* Methods with this annotation are invoked immediately before a REST HOOK
|
||||||
* subscription delivery
|
* subscription delivery
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @see SubscriptionInterceptor
|
* @see Interceptor
|
||||||
*/
|
*/
|
||||||
@Target(ElementType.METHOD)
|
@Target(ElementType.METHOD)
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
public @interface SubscriptionHook {
|
public @interface Hook {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Provides the specific point where this method should be invoked
|
* Provides the specific point where this method should be invoked
|
|
@ -0,0 +1,56 @@
|
||||||
|
package ca.uhn.fhir.jpa.model.interceptor.api;
|
||||||
|
|
||||||
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
|
import com.google.common.collect.ListMultimap;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
public class HookParams {
|
||||||
|
|
||||||
|
private ListMultimap<Class<?>, Object> myParams = ArrayListMultimap.create();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public HookParams() {
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public HookParams(Object... theParams) {
|
||||||
|
for (Object next : theParams) {
|
||||||
|
add(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private <T> void add(T theNext) {
|
||||||
|
Class<T> nextClass = (Class<T>) theNext.getClass();
|
||||||
|
add(nextClass, theNext);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <T> HookParams add(Class<T> theType, T theParam) {
|
||||||
|
myParams.put(theType, theParam);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
public <T> T get(Class<T> theParamType, int theIndex) {
|
||||||
|
List<T> objects = (List<T>) myParams.get(theParamType);
|
||||||
|
T retVal = null;
|
||||||
|
if (objects.size() > theIndex) {
|
||||||
|
retVal = objects.get(theIndex);
|
||||||
|
}
|
||||||
|
return retVal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Multivalued parameters will be returned twice in this list
|
||||||
|
*/
|
||||||
|
public List<String> getTypesAsSimpleName() {
|
||||||
|
return myParams.values().stream().map(t -> t.getClass().getSimpleName()).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,16 @@
|
||||||
|
package ca.uhn.fhir.jpa.model.interceptor.api;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is currently only here for unit tests!
|
||||||
|
*
|
||||||
|
* DO NOT USE IN NON-TEST CODE. Maybe this will change in the future?
|
||||||
|
*/
|
||||||
|
@FunctionalInterface
|
||||||
|
@VisibleForTesting
|
||||||
|
public interface IAnonymousLambdaHook {
|
||||||
|
|
||||||
|
void invoke(HookParams theArgs);
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
package ca.uhn.fhir.jpa.model.interceptor.api;
|
||||||
|
|
||||||
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
|
|
||||||
|
public interface IInterceptorRegistry {
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void registerAnonymousHookForUnitTest(Pointcut thePointcut, IAnonymousLambdaHook theHook);
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
void clearAnonymousHookForUnitTest();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke the interceptor methods
|
||||||
|
*/
|
||||||
|
boolean callHooks(Pointcut thePointcut, HookParams theParams);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoke the interceptor methods
|
||||||
|
*/
|
||||||
|
boolean callHooks(Pointcut thePointcut, Object... theParams);
|
||||||
|
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
package ca.uhn.fhir.jpa.model.subscription.interceptor.api;
|
package ca.uhn.fhir.jpa.model.interceptor.api;
|
||||||
|
|
||||||
import java.lang.annotation.ElementType;
|
import java.lang.annotation.ElementType;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
|
@ -10,5 +10,5 @@ import java.lang.annotation.Target;
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.RUNTIME)
|
@Retention(RetentionPolicy.RUNTIME)
|
||||||
@Target(ElementType.TYPE)
|
@Target(ElementType.TYPE)
|
||||||
public @interface SubscriptionInterceptor {
|
public @interface Interceptor {
|
||||||
}
|
}
|
|
@ -0,0 +1,63 @@
|
||||||
|
package ca.uhn.fhir.jpa.model.interceptor.api;
|
||||||
|
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Value for {@link Hook#value()}
|
||||||
|
*/
|
||||||
|
public enum Pointcut {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked immediately after the delivery of a REST HOOK subscription.
|
||||||
|
* <p>
|
||||||
|
* When this hook is called, all processing is complete so this hook should not
|
||||||
|
* make any changes to the parameters.
|
||||||
|
* </p>
|
||||||
|
* Hooks may accept the following parameters:
|
||||||
|
* <ul>
|
||||||
|
* <li>ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription</li>
|
||||||
|
* <li>ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY("CanonicalSubscription", "ResourceDeliveryMessage"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked immediately before the delivery of a REST HOOK subscription.
|
||||||
|
* <p>
|
||||||
|
* Hooks may make changes to the delivery payload, or make changes to the
|
||||||
|
* canonical subscription such as adding headers, modifying the channel
|
||||||
|
* endpoint, etc.
|
||||||
|
* </p>
|
||||||
|
* Hooks may accept the following parameters:
|
||||||
|
* <ul>
|
||||||
|
* <li>ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription</li>
|
||||||
|
* <li>ca.uhn.fhir.jpa.subscription.module.subscriber.ResourceDeliveryMessage</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY("CanonicalSubscription", "ResourceDeliveryMessage"),
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invoked whenever a persisted resource (a resource that has just been stored in the
|
||||||
|
* database via a create/update/patch/etc.) has been checked for whether any subscriptions
|
||||||
|
* were triggered as a result of the operation
|
||||||
|
* Hooks may accept the following parameters:
|
||||||
|
* <ul>
|
||||||
|
* <li>ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage</li>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED("ResourceModifiedMessage")
|
||||||
|
|
||||||
|
;
|
||||||
|
|
||||||
|
private final List<String> myParameterTypes;
|
||||||
|
|
||||||
|
Pointcut(String... theParameterTypes) {
|
||||||
|
myParameterTypes = Collections.unmodifiableList(Arrays.asList(theParameterTypes));
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getParameterTypes() {
|
||||||
|
return myParameterTypes;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,10 +1,11 @@
|
||||||
package ca.uhn.fhir.jpa.model.subscription.interceptor.executor;
|
package ca.uhn.fhir.jpa.model.interceptor.executor;
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.Pointcut;
|
import ca.uhn.fhir.jpa.model.interceptor.api.*;
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.SubscriptionHook;
|
import com.google.common.annotations.VisibleForTesting;
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.SubscriptionInterceptor;
|
|
||||||
import com.google.common.collect.ArrayListMultimap;
|
import com.google.common.collect.ArrayListMultimap;
|
||||||
import com.google.common.collect.ListMultimap;
|
import com.google.common.collect.ListMultimap;
|
||||||
|
import com.google.common.collect.Multimaps;
|
||||||
|
import org.apache.commons.collections4.ListUtils;
|
||||||
import org.apache.commons.lang3.Validate;
|
import org.apache.commons.lang3.Validate;
|
||||||
import org.slf4j.Logger;
|
import org.slf4j.Logger;
|
||||||
import org.slf4j.LoggerFactory;
|
import org.slf4j.LoggerFactory;
|
||||||
|
@ -18,27 +19,50 @@ import org.springframework.stereotype.Component;
|
||||||
import javax.annotation.Nonnull;
|
import javax.annotation.Nonnull;
|
||||||
import javax.annotation.PostConstruct;
|
import javax.annotation.PostConstruct;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.util.ArrayList;
|
import java.util.*;
|
||||||
import java.util.Collection;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
import java.util.IdentityHashMap;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@Component
|
@Component
|
||||||
public class SubscriptionInterceptorRegistry implements ISubscriptionInterceptorRegistry, ApplicationContextAware {
|
public class InterceptorRegistry implements IInterceptorRegistry, ApplicationContextAware {
|
||||||
private static final Logger ourLog = LoggerFactory.getLogger(SubscriptionInterceptorRegistry.class);
|
private static final Logger ourLog = LoggerFactory.getLogger(InterceptorRegistry.class);
|
||||||
private ApplicationContext myAppCtx;
|
private ApplicationContext myAppCtx;
|
||||||
private List<Object> myGlobalInterceptors = new ArrayList<>();
|
private List<Object> myGlobalInterceptors = new ArrayList<>();
|
||||||
private ListMultimap<Pointcut, Invoker> myInvokers = ArrayListMultimap.create();
|
private ListMultimap<Pointcut, BaseInvoker> myInvokers = ArrayListMultimap.create();
|
||||||
|
private ListMultimap<Pointcut, BaseInvoker> myAnonymousInvokers = Multimaps.synchronizedListMultimap(ArrayListMultimap.create());
|
||||||
|
|
||||||
List<Object> getGlobalInterceptors() {
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
|
public InterceptorRegistry() {
|
||||||
|
super();
|
||||||
|
}
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
|
public List<Object> getGlobalInterceptorsForUnitTest() {
|
||||||
return myGlobalInterceptors;
|
return myGlobalInterceptors;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@VisibleForTesting
|
||||||
|
public void registerAnonymousHookForUnitTest(Pointcut thePointcut, IAnonymousLambdaHook theHook) {
|
||||||
|
Validate.notNull(thePointcut);
|
||||||
|
Validate.notNull(theHook);
|
||||||
|
|
||||||
|
myAnonymousInvokers.put(thePointcut, new AnonymousLambdaInvoker(theHook));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@VisibleForTesting
|
||||||
|
public void clearAnonymousHookForUnitTest() {
|
||||||
|
myAnonymousInvokers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void start() {
|
public void start() {
|
||||||
|
|
||||||
// Grab the global interceptors
|
// Grab the global interceptors
|
||||||
String[] globalInterceptorNames = myAppCtx.getBeanNamesForAnnotation(SubscriptionInterceptor.class);
|
String[] globalInterceptorNames = myAppCtx.getBeanNamesForAnnotation(Interceptor.class);
|
||||||
for (String nextName : globalInterceptorNames) {
|
for (String nextName : globalInterceptorNames) {
|
||||||
Object nextGlobalInterceptor = myAppCtx.getBean(nextName);
|
Object nextGlobalInterceptor = myAppCtx.getBean(nextName);
|
||||||
myGlobalInterceptors.add(nextGlobalInterceptor);
|
myGlobalInterceptors.add(nextGlobalInterceptor);
|
||||||
|
@ -50,9 +74,9 @@ public class SubscriptionInterceptorRegistry implements ISubscriptionInterceptor
|
||||||
// Pull out the hook methods
|
// Pull out the hook methods
|
||||||
for (Object nextInterceptor : myGlobalInterceptors) {
|
for (Object nextInterceptor : myGlobalInterceptors) {
|
||||||
for (Method nextMethod : nextInterceptor.getClass().getDeclaredMethods()) {
|
for (Method nextMethod : nextInterceptor.getClass().getDeclaredMethods()) {
|
||||||
SubscriptionHook hook = AnnotationUtils.findAnnotation(nextMethod, SubscriptionHook.class);
|
Hook hook = AnnotationUtils.findAnnotation(nextMethod, Hook.class);
|
||||||
if (hook != null) {
|
if (hook != null) {
|
||||||
Invoker invoker = new Invoker(nextInterceptor, nextMethod);
|
HookInvoker invoker = new HookInvoker(hook, nextInterceptor, nextMethod);
|
||||||
for (Pointcut nextPointcut : hook.value()) {
|
for (Pointcut nextPointcut : hook.value()) {
|
||||||
myInvokers.put(nextPointcut, invoker);
|
myInvokers.put(nextPointcut, invoker);
|
||||||
}
|
}
|
||||||
|
@ -84,12 +108,17 @@ public class SubscriptionInterceptorRegistry implements ISubscriptionInterceptor
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean callHooks(Pointcut thePointcut, HookParams theParams) {
|
public boolean callHooks(Pointcut thePointcut, HookParams theParams) {
|
||||||
|
assert haveAppropriateParams(thePointcut, theParams);
|
||||||
|
|
||||||
|
// Anonymous hooks first
|
||||||
|
List<BaseInvoker> invokers = ListUtils.union(
|
||||||
|
myAnonymousInvokers.get(thePointcut),
|
||||||
|
myInvokers.get(thePointcut));
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Call each hook in order
|
* Call each hook in order
|
||||||
*/
|
*/
|
||||||
List<Invoker> invokers = myInvokers.get(thePointcut);
|
for (BaseInvoker nextInvoker : invokers) {
|
||||||
for (Invoker nextInvoker : invokers) {
|
|
||||||
boolean shouldContinue = nextInvoker.invoke(theParams);
|
boolean shouldContinue = nextInvoker.invoke(theParams);
|
||||||
if (!shouldContinue) {
|
if (!shouldContinue) {
|
||||||
return false;
|
return false;
|
||||||
|
@ -99,22 +128,55 @@ public class SubscriptionInterceptorRegistry implements ISubscriptionInterceptor
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Only call this when assertions are enabled, it's expensive
|
||||||
|
*/
|
||||||
|
private boolean haveAppropriateParams(Pointcut thePointcut, HookParams theParams) {
|
||||||
|
List<String> givenTypes = theParams.getTypesAsSimpleName();
|
||||||
|
List<String> wantedTypes = new ArrayList<>(thePointcut.getParameterTypes());
|
||||||
|
givenTypes.sort(Comparator.naturalOrder());
|
||||||
|
wantedTypes.sort(Comparator.naturalOrder());
|
||||||
|
if (!givenTypes.equals(wantedTypes)) {
|
||||||
|
throw new AssertionError("Wrong hook parameters, wanted " + wantedTypes + " and found " + givenTypes);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean callHooks(Pointcut thePointcut, Object... theParams) {
|
public boolean callHooks(Pointcut thePointcut, Object... theParams) {
|
||||||
return callHooks(thePointcut, new HookParams(theParams));
|
return callHooks(thePointcut, new HookParams(theParams));
|
||||||
}
|
}
|
||||||
|
|
||||||
private class Invoker {
|
private abstract class BaseInvoker {
|
||||||
|
abstract boolean invoke(HookParams theParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AnonymousLambdaInvoker extends BaseInvoker {
|
||||||
|
private final IAnonymousLambdaHook myHook;
|
||||||
|
|
||||||
|
public AnonymousLambdaInvoker(IAnonymousLambdaHook theHook) {
|
||||||
|
myHook = theHook;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
boolean invoke(HookParams theParams) {
|
||||||
|
myHook.invoke(theParams);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HookInvoker extends BaseInvoker {
|
||||||
|
|
||||||
private final Object myInterceptor;
|
private final Object myInterceptor;
|
||||||
private final boolean myReturnsBoolean;
|
private final boolean myReturnsBoolean;
|
||||||
private final Method myMethod;
|
private final Method myMethod;
|
||||||
private final Class<?>[] myParameterTypes;
|
private final Class<?>[] myParameterTypes;
|
||||||
|
private final int[] myParameterIndexes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructor
|
* Constructor
|
||||||
*/
|
*/
|
||||||
private Invoker(@Nonnull Object theInterceptor, @Nonnull Method theHookMethod) {
|
private HookInvoker(Hook theHook, @Nonnull Object theInterceptor, @Nonnull Method theHookMethod) {
|
||||||
myInterceptor = theInterceptor;
|
myInterceptor = theInterceptor;
|
||||||
myParameterTypes = theHookMethod.getParameterTypes();
|
myParameterTypes = theHookMethod.getParameterTypes();
|
||||||
myMethod = theHookMethod;
|
myMethod = theHookMethod;
|
||||||
|
@ -126,13 +188,26 @@ public class SubscriptionInterceptorRegistry implements ISubscriptionInterceptor
|
||||||
Validate.isTrue(Void.class.equals(returnType), "Method does not return boolean or void: %s", theHookMethod);
|
Validate.isTrue(Void.class.equals(returnType), "Method does not return boolean or void: %s", theHookMethod);
|
||||||
myReturnsBoolean = false;
|
myReturnsBoolean = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
myParameterIndexes = new int[myParameterTypes.length];
|
||||||
|
Map<Class<?>, AtomicInteger> typeToCount = new HashMap<>();
|
||||||
|
for (int i = 0; i < myParameterTypes.length; i++) {
|
||||||
|
AtomicInteger counter = typeToCount.computeIfAbsent(myParameterTypes[i], t -> new AtomicInteger(0));
|
||||||
|
myParameterIndexes[i] = counter.getAndIncrement();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return Returns true/false if the hook method returns a boolean, returns true otherwise
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
boolean invoke(HookParams theParams) {
|
boolean invoke(HookParams theParams) {
|
||||||
|
|
||||||
Object[] args = new Object[myParameterTypes.length];
|
Object[] args = new Object[myParameterTypes.length];
|
||||||
for (int i = 0; i < myParameterTypes.length; i++) {
|
for (int i = 0; i < myParameterTypes.length; i++) {
|
||||||
Class<?> nextParamType = myParameterTypes[i];
|
Class<?> nextParamType = myParameterTypes[i];
|
||||||
Object nextParamValue = theParams.get(nextParamType);
|
int nextParamIndex = myParameterIndexes[i];
|
||||||
|
Object nextParamValue = theParams.get(nextParamType, nextParamIndex);
|
||||||
args[i] = nextParamValue;
|
args[i] = nextParamValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -153,7 +228,4 @@ public class SubscriptionInterceptorRegistry implements ISubscriptionInterceptor
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static <T> boolean equals(Collection<T> theLhs, Collection<T> theRhs) {
|
|
||||||
return theLhs.size() == theRhs.size() && theLhs.containsAll(theRhs) && theRhs.containsAll(theLhs);
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -1,28 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.model.subscription.interceptor.api;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Value for {@link SubscriptionHook#value()}
|
|
||||||
*/
|
|
||||||
public enum Pointcut {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked immediately after the delivery of a REST HOOK subscription.
|
|
||||||
* <p>
|
|
||||||
* When this hook is called, all processing is complete so this hook should not
|
|
||||||
* make any changes to the parameters.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
AFTER_REST_HOOK_DELIVERY,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoked immediately before the delivery of a REST HOOK subscription.
|
|
||||||
* <p>
|
|
||||||
* Hooks may make changes to the delivery payload, or make changes to the
|
|
||||||
* canonical subscription such as adding headers, modifying the channel
|
|
||||||
* endpoint, etc.
|
|
||||||
* </p>
|
|
||||||
*/
|
|
||||||
BEFORE_REST_HOOK_DELIVERY;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -1,48 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.model.subscription.interceptor.executor;
|
|
||||||
|
|
||||||
import org.apache.commons.lang3.Validate;
|
|
||||||
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
public class HookParams {
|
|
||||||
|
|
||||||
private Map<Class<?>, Object> myParams = new HashMap<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public HookParams() {
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructor
|
|
||||||
*/
|
|
||||||
public HookParams(Object... theParams) {
|
|
||||||
for (Object next : theParams) {
|
|
||||||
add(next);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
private <T> void add(T theNext) {
|
|
||||||
Class<T> nextClass = (Class<T>) theNext.getClass();
|
|
||||||
add(nextClass, theNext);
|
|
||||||
}
|
|
||||||
|
|
||||||
public <T> HookParams add(Class<T> theType, T theParam) {
|
|
||||||
Validate.isTrue(myParams.containsKey(theType) == false, "Already have param of type %s", theType);
|
|
||||||
myParams.put(theType, theParam);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
public <T> T get(Class<T> theParamType) {
|
|
||||||
return (T) myParams.get(theParamType);
|
|
||||||
}
|
|
||||||
|
|
||||||
Set<Class<?>> getTypes() {
|
|
||||||
return myParams.keySet();
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,17 +0,0 @@
|
||||||
package ca.uhn.fhir.jpa.model.subscription.interceptor.executor;
|
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.Pointcut;
|
|
||||||
|
|
||||||
public interface ISubscriptionInterceptorRegistry {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke the interceptor methods
|
|
||||||
*/
|
|
||||||
boolean callHooks(Pointcut thePointcut, HookParams theParams);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Invoke the interceptor methods
|
|
||||||
*/
|
|
||||||
boolean callHooks(Pointcut thePointcut, Object... theParams);
|
|
||||||
|
|
||||||
}
|
|
|
@ -9,9 +9,9 @@ package ca.uhn.fhir.jpa.subscription.module;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -52,11 +52,15 @@ public class ResourceModifiedMessage implements IResourceMessage {
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private transient IBaseResource myPayloadDecoded;
|
private transient IBaseResource myPayloadDecoded;
|
||||||
|
|
||||||
// For JSON
|
/**
|
||||||
|
* Constructor
|
||||||
|
*/
|
||||||
public ResourceModifiedMessage() {
|
public ResourceModifiedMessage() {
|
||||||
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
public ResourceModifiedMessage(FhirContext theFhirContext, IBaseResource theResource, OperationTypeEnum theOperationType) {
|
public ResourceModifiedMessage(FhirContext theFhirContext, IBaseResource theResource, OperationTypeEnum theOperationType) {
|
||||||
|
this();
|
||||||
setId(theResource.getIdElement());
|
setId(theResource.getIdElement());
|
||||||
setOperationType(theOperationType);
|
setOperationType(theOperationType);
|
||||||
if (theOperationType != OperationTypeEnum.DELETE) {
|
if (theOperationType != OperationTypeEnum.DELETE) {
|
||||||
|
|
|
@ -21,8 +21,8 @@ package ca.uhn.fhir.jpa.subscription.module.subscriber;
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
import ca.uhn.fhir.jpa.subscription.module.CanonicalSubscription;
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.api.Pointcut;
|
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.model.subscription.interceptor.executor.ISubscriptionInterceptorRegistry;
|
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
|
||||||
import ca.uhn.fhir.rest.api.EncodingEnum;
|
import ca.uhn.fhir.rest.api.EncodingEnum;
|
||||||
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
import ca.uhn.fhir.rest.api.RequestTypeEnum;
|
||||||
import ca.uhn.fhir.rest.client.api.*;
|
import ca.uhn.fhir.rest.client.api.*;
|
||||||
|
@ -52,7 +52,7 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
|
||||||
IResourceRetriever myResourceRetriever;
|
IResourceRetriever myResourceRetriever;
|
||||||
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class);
|
private Logger ourLog = LoggerFactory.getLogger(SubscriptionDeliveringRestHookSubscriber.class);
|
||||||
@Autowired
|
@Autowired
|
||||||
private ISubscriptionInterceptorRegistry mySubscriptionInterceptorRegistry;
|
private IInterceptorRegistry myInterceptorRegistry;
|
||||||
|
|
||||||
protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) {
|
protected void deliverPayload(ResourceDeliveryMessage theMsg, CanonicalSubscription theSubscription, EncodingEnum thePayloadType, IGenericClient theClient) {
|
||||||
IBaseResource payloadResource = getAndMassagePayload(theMsg, theSubscription);
|
IBaseResource payloadResource = getAndMassagePayload(theMsg, theSubscription);
|
||||||
|
@ -132,8 +132,8 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
|
||||||
public void handleMessage(ResourceDeliveryMessage theMessage) throws MessagingException {
|
public void handleMessage(ResourceDeliveryMessage theMessage) throws MessagingException {
|
||||||
CanonicalSubscription subscription = theMessage.getSubscription();
|
CanonicalSubscription subscription = theMessage.getSubscription();
|
||||||
|
|
||||||
// Interceptor call: BEFORE_REST_HOOK_DELIVERY
|
// Interceptor call: SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY
|
||||||
if (!mySubscriptionInterceptorRegistry.callHooks(Pointcut.BEFORE_REST_HOOK_DELIVERY, theMessage, subscription)) {
|
if (!myInterceptorRegistry.callHooks(Pointcut.SUBSCRIPTION_BEFORE_REST_HOOK_DELIVERY, theMessage, subscription)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -168,8 +168,8 @@ public class SubscriptionDeliveringRestHookSubscriber extends BaseSubscriptionDe
|
||||||
|
|
||||||
deliverPayload(theMessage, subscription, payloadType, client);
|
deliverPayload(theMessage, subscription, payloadType, client);
|
||||||
|
|
||||||
// Interceptor call: AFTER_REST_HOOK_DELIVERY
|
// Interceptor call: SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY
|
||||||
if (!mySubscriptionInterceptorRegistry.callHooks(Pointcut.AFTER_REST_HOOK_DELIVERY, theMessage, subscription)) {
|
if (!myInterceptorRegistry.callHooks(Pointcut.SUBSCRIPTION_AFTER_REST_HOOK_DELIVERY, theMessage, subscription)) {
|
||||||
//noinspection UnnecessaryReturnStatement
|
//noinspection UnnecessaryReturnStatement
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
package ca.uhn.fhir.jpa.subscription.module.subscriber;
|
package ca.uhn.fhir.jpa.subscription.module.subscriber;
|
||||||
|
|
||||||
import ca.uhn.fhir.context.FhirContext;
|
import ca.uhn.fhir.context.FhirContext;
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
import ca.uhn.fhir.jpa.subscription.module.ResourceModifiedMessage;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.cache.ActiveSubscription;
|
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.cache.SubscriptionRegistry;
|
||||||
|
@ -31,9 +33,9 @@ import static org.apache.commons.lang3.StringUtils.isNotBlank;
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
* You may obtain a copy of the License at
|
* You may obtain a copy of the License at
|
||||||
*
|
*
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
*
|
*
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
@ -52,6 +54,8 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
|
||||||
private FhirContext myFhirContext;
|
private FhirContext myFhirContext;
|
||||||
@Autowired
|
@Autowired
|
||||||
private SubscriptionRegistry mySubscriptionRegistry;
|
private SubscriptionRegistry mySubscriptionRegistry;
|
||||||
|
@Autowired
|
||||||
|
private IInterceptorRegistry myInterceptorRegistry;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message<?> theMessage) throws MessagingException {
|
public void handleMessage(Message<?> theMessage) throws MessagingException {
|
||||||
|
@ -64,9 +68,18 @@ public class SubscriptionMatchingSubscriber implements MessageHandler {
|
||||||
|
|
||||||
ResourceModifiedMessage msg = ((ResourceModifiedJsonMessage) theMessage).getPayload();
|
ResourceModifiedMessage msg = ((ResourceModifiedJsonMessage) theMessage).getPayload();
|
||||||
matchActiveSubscriptionsAndDeliver(msg);
|
matchActiveSubscriptionsAndDeliver(msg);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void matchActiveSubscriptionsAndDeliver(ResourceModifiedMessage theMsg) {
|
public void matchActiveSubscriptionsAndDeliver(ResourceModifiedMessage theMsg) {
|
||||||
|
try {
|
||||||
|
doMatchActiveSubscriptionsAndDeliver(theMsg);
|
||||||
|
} finally {
|
||||||
|
myInterceptorRegistry.callHooks(Pointcut.SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED, theMsg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void doMatchActiveSubscriptionsAndDeliver(ResourceModifiedMessage theMsg) {
|
||||||
switch (theMsg.getOperationType()) {
|
switch (theMsg.getOperationType()) {
|
||||||
case CREATE:
|
case CREATE:
|
||||||
case UPDATE:
|
case UPDATE:
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
package ca.uhn.fhir.jpa.subscription.module;
|
package ca.uhn.fhir.jpa.subscription.module;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
|
||||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamRegistry;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
|
import ca.uhn.fhir.jpa.subscription.module.cache.SubscriptionLoader;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSearchParamProvider;
|
import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSearchParamProvider;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSubscriptionProvider;
|
import ca.uhn.fhir.jpa.subscription.module.config.MockFhirClientSubscriptionProvider;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
|
import org.junit.After;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
|
||||||
|
@ -24,6 +26,15 @@ public abstract class BaseSubscriptionTest {
|
||||||
@Autowired
|
@Autowired
|
||||||
SubscriptionLoader mySubscriptionLoader;
|
SubscriptionLoader mySubscriptionLoader;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
protected
|
||||||
|
IInterceptorRegistry myInterceptorRegistry;
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void afterClearAnonymousLambdas() {
|
||||||
|
myInterceptorRegistry.clearAnonymousHookForUnitTest();
|
||||||
|
}
|
||||||
|
|
||||||
public void initSearchParamRegistry(IBundleProvider theBundleProvider) {
|
public void initSearchParamRegistry(IBundleProvider theBundleProvider) {
|
||||||
myMockFhirClientSearchParamProvider.setBundleProvider(theBundleProvider);
|
myMockFhirClientSearchParamProvider.setBundleProvider(theBundleProvider);
|
||||||
mySearchParamRegistry.forceRefresh();
|
mySearchParamRegistry.forceRefresh();
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package ca.uhn.fhir.jpa.subscription.module.config;
|
package ca.uhn.fhir.jpa.subscription.module.config;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.IInterceptorRegistry;
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.executor.InterceptorRegistry;
|
||||||
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
import ca.uhn.fhir.jpa.searchparam.registry.ISearchParamProvider;
|
||||||
import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
|
import ca.uhn.fhir.jpa.subscription.module.cache.ISubscriptionProvider;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
|
@ -18,5 +20,13 @@ public class TestSubscriptionDstu3Config extends SubscriptionDstu3Config {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
@Primary
|
@Primary
|
||||||
public ISubscriptionProvider subsriptionProvider() { return new MockFhirClientSubscriptionProvider();}
|
public ISubscriptionProvider subsriptionProvider() {
|
||||||
|
return new MockFhirClientSubscriptionProvider();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public IInterceptorRegistry interceptorRegistry() {
|
||||||
|
return new InterceptorRegistry();
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package ca.uhn.fhir.jpa.subscription.module.standalone;
|
package ca.uhn.fhir.jpa.subscription.module.standalone;
|
||||||
|
|
||||||
|
import ca.uhn.fhir.jpa.model.interceptor.api.Pointcut;
|
||||||
import ca.uhn.fhir.rest.api.Constants;
|
import ca.uhn.fhir.rest.api.Constants;
|
||||||
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
import ca.uhn.fhir.rest.api.server.IBundleProvider;
|
||||||
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
import ca.uhn.fhir.rest.server.SimpleBundleProvider;
|
||||||
|
@ -8,12 +9,19 @@ import org.junit.Test;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
|
import static org.hamcrest.Matchers.lessThan;
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
|
import static org.junit.Assert.assertThat;
|
||||||
|
|
||||||
public class SubscriptionLoaderFhirClientTest extends BaseBlockingQueueSubscribableChannelDstu3Test {
|
public class SubscriptionLoaderFhirClientTest extends BaseBlockingQueueSubscribableChannelDstu3Test {
|
||||||
@Test
|
@Test
|
||||||
public void testSubscriptionLoaderFhirClient() throws Exception {
|
public void testSubscriptionLoaderFhirClient() throws InterruptedException {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED, t-> latch.countDown());
|
||||||
|
|
||||||
String payload = "application/fhir+json";
|
String payload = "application/fhir+json";
|
||||||
|
|
||||||
String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml";
|
String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml";
|
||||||
|
@ -27,6 +35,7 @@ public class SubscriptionLoaderFhirClientTest extends BaseBlockingQueueSubscriba
|
||||||
initSubscriptionLoader(bundle);
|
initSubscriptionLoader(bundle);
|
||||||
|
|
||||||
sendObservation(myCode, "SNOMED-CT");
|
sendObservation(myCode, "SNOMED-CT");
|
||||||
|
latch.await(10, TimeUnit.SECONDS);
|
||||||
|
|
||||||
waitForSize(0, ourCreatedObservations);
|
waitForSize(0, ourCreatedObservations);
|
||||||
waitForSize(1, ourUpdatedObservations);
|
waitForSize(1, ourUpdatedObservations);
|
||||||
|
@ -34,7 +43,10 @@ public class SubscriptionLoaderFhirClientTest extends BaseBlockingQueueSubscriba
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testSubscriptionLoaderFhirClientSubscriptionNotActive() throws Exception {
|
public void testSubscriptionLoaderFhirClientSubscriptionNotActive() throws InterruptedException {
|
||||||
|
CountDownLatch latch = new CountDownLatch(1);
|
||||||
|
myInterceptorRegistry.registerAnonymousHookForUnitTest(Pointcut.SUBSCRIPTION_AFTER_PERSISTED_RESOURCE_CHECKED, t-> latch.countDown());
|
||||||
|
|
||||||
String payload = "application/fhir+json";
|
String payload = "application/fhir+json";
|
||||||
|
|
||||||
String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml";
|
String criteria1 = "Observation?code=SNOMED-CT|" + myCode + "&_format=xml";
|
||||||
|
@ -48,6 +60,7 @@ public class SubscriptionLoaderFhirClientTest extends BaseBlockingQueueSubscriba
|
||||||
initSubscriptionLoader(bundle);
|
initSubscriptionLoader(bundle);
|
||||||
|
|
||||||
sendObservation(myCode, "SNOMED-CT");
|
sendObservation(myCode, "SNOMED-CT");
|
||||||
|
latch.await(10, TimeUnit.SECONDS);
|
||||||
|
|
||||||
waitForSize(0, ourCreatedObservations);
|
waitForSize(0, ourCreatedObservations);
|
||||||
waitForSize(0, ourUpdatedObservations);
|
waitForSize(0, ourUpdatedObservations);
|
||||||
|
|
Loading…
Reference in New Issue