diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java index fdb2f1575c..6c8adc18c4 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/inbound/IntegrationRequestMappingHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2013-2017 the original author or authors. + * Copyright 2013-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,6 +24,8 @@ import java.util.concurrent.atomic.AtomicBoolean; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.core.annotation.AnnotationUtils; @@ -60,13 +62,18 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl * {@code org.springframework.stereotype.Controller} user-class may have their own * {@link org.springframework.web.bind.annotation.RequestMapping}. * On the other side, all Spring Integration HTTP Inbound Endpoints are configured on - * the basis of the same {@link HttpRequestHandlingEndpointSupport} class and there is no + * the basis of the same {@link BaseHttpInboundEndpoint} class and there is no * single {@link RequestMappingInfo} configuration without * {@link org.springframework.web.method.HandlerMethod} in Spring MVC. * Accordingly {@link IntegrationRequestMappingHandlerMapping} is a * {@link org.springframework.web.servlet.HandlerMapping} * compromise implementation between method-level annotations and component-level * (e.g. Spring Integration XML) configurations. + *

+ * Starting with version 5.1, this class implements {@link DestructionAwareBeanPostProcessor} to + * register HTTP endpoints at runtime for dynamically declared beans, e.g. via + * {@link org.springframework.integration.dsl.context.IntegrationFlowContext}, and unregister + * them during the {@link BaseHttpInboundEndpoint} destruction. * * @author Artem Bilan * @@ -76,13 +83,35 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl * @see RequestMappingHandlerMapping */ public final class IntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping - implements ApplicationListener { + implements ApplicationListener, DestructionAwareBeanPostProcessor { private static final Method HANDLE_REQUEST_METHOD = ReflectionUtils.findMethod(HttpRequestHandler.class, "handleRequest", HttpServletRequest.class, HttpServletResponse.class); private final AtomicBoolean initialized = new AtomicBoolean(); + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (this.initialized.get() && isHandler(bean.getClass())) { + detectHandlerMethods(bean); + } + + return bean; + } + + @Override + @SuppressWarnings("unchecked") + public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { + if (isHandler(bean.getClass())) { + unregisterMapping(getMappingForEndpoint((BaseHttpInboundEndpoint) bean)); + } + } + + @Override + public boolean requiresDestruction(Object bean) { + return isHandler(bean.getClass()); + } + @Override protected boolean isHandler(Class beanType) { return HttpRequestHandlingEndpointSupport.class.isAssignableFrom(beanType); @@ -97,6 +126,7 @@ public final class IntegrationRequestMappingHandlerMapping extends RequestMappin handler = bean; } } + return super.getHandlerExecutionChain(handler, request); } diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java index 6a59700ebd..9d1ba1bd2a 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/dsl/HttpDslTests.java @@ -38,6 +38,7 @@ import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.context.IntegrationFlowContext; import org.springframework.integration.http.outbound.HttpRequestExecutingMessageHandler; import org.springframework.integration.security.channel.ChannelSecurityInterceptor; import org.springframework.integration.security.channel.SecuredChannel; @@ -80,6 +81,9 @@ public class HttpDslTests { @Autowired private HttpRequestExecutingMessageHandler serviceInternalGatewayHandler; + @Autowired + private IntegrationFlowContext integrationFlowContext; + private MockMvc mockMvc; @Before @@ -114,6 +118,37 @@ public class HttpDslTests { .isForbidden()); } + @Test + public void testDynamicHttpEndpoint() throws Exception { + IntegrationFlow flow = + IntegrationFlows.from(Http.inboundGateway("/dynamic") + .requestMapping(r -> r.params("name")) + .payloadExpression("#requestParams.name[0]")) + .transform(String::toLowerCase) + .get(); + + IntegrationFlowContext.IntegrationFlowRegistration flowRegistration = + this.integrationFlowContext.registration(flow).register(); + + this.mockMvc.perform( + get("/dynamic") + .with(httpBasic("admin", "admin")) + .param("name", "BAR")) + .andExpect( + content() + .string("bar")); + + flowRegistration.destroy(); + + this.mockMvc.perform( + get("/dynamic") + .with(httpBasic("admin", "admin")) + .param("name", "BAZ")) + .andExpect( + status() + .isNotFound()); + } + @Configuration @EnableWebSecurity @EnableIntegration diff --git a/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxIntegrationRequestMappingHandlerMapping.java b/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxIntegrationRequestMappingHandlerMapping.java index cacb45fd49..d9540932e0 100644 --- a/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxIntegrationRequestMappingHandlerMapping.java +++ b/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/inbound/WebFluxIntegrationRequestMappingHandlerMapping.java @@ -1,5 +1,5 @@ /* - * Copyright 2017 the original author or authors. + * Copyright 2017-2018 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,8 @@ package org.springframework.integration.webflux.inbound; import java.lang.reflect.Method; import java.util.concurrent.atomic.AtomicBoolean; +import org.springframework.beans.BeansException; +import org.springframework.beans.factory.config.DestructionAwareBeanPostProcessor; import org.springframework.context.ApplicationListener; import org.springframework.context.event.ContextRefreshedEvent; import org.springframework.integration.http.config.HttpContextUtils; @@ -66,6 +68,11 @@ import org.springframework.web.server.WebHandler; * {@link org.springframework.web.reactive.HandlerMapping} * compromise implementation between method-level annotations and component-level * (e.g. Spring Integration XML) configurations. + *

+ * Starting with version 5.1, this class implements {@link DestructionAwareBeanPostProcessor} to + * register HTTP endpoints at runtime for dynamically declared beans, e.g. via + * {@link org.springframework.integration.dsl.context.IntegrationFlowContext}, and unregister + * them during the {@link WebFluxInboundEndpoint} destruction. * * @author Artem Bilan * @@ -75,13 +82,35 @@ import org.springframework.web.server.WebHandler; * @see RequestMappingHandlerMapping */ public class WebFluxIntegrationRequestMappingHandlerMapping extends RequestMappingHandlerMapping - implements ApplicationListener { + implements ApplicationListener, DestructionAwareBeanPostProcessor { private static final Method HANDLER_METHOD = ReflectionUtils.findMethod(WebHandler.class, "handle", ServerWebExchange.class); private final AtomicBoolean initialized = new AtomicBoolean(); + @Override + public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { + if (this.initialized.get() && isHandler(bean.getClass())) { + detectHandlerMethods(bean); + } + + return bean; + } + + @Override + @SuppressWarnings("unchecked") + public void postProcessBeforeDestruction(Object bean, String beanName) throws BeansException { + if (isHandler(bean.getClass())) { + unregisterMapping(getMappingForEndpoint((WebFluxInboundEndpoint) bean)); + } + } + + @Override + public boolean requiresDestruction(Object bean) { + return isHandler(bean.getClass()); + } + @Override protected boolean isHandler(Class beanType) { return WebFluxInboundEndpoint.class.isAssignableFrom(beanType); diff --git a/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/dsl/WebFluxDslTests.java b/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/dsl/WebFluxDslTests.java index df6118bda5..3e8d888356 100644 --- a/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/dsl/WebFluxDslTests.java +++ b/spring-integration-webflux/src/test/java/org/springframework/integration/webflux/dsl/WebFluxDslTests.java @@ -51,6 +51,7 @@ import org.springframework.integration.channel.QueueChannel; import org.springframework.integration.config.EnableIntegration; import org.springframework.integration.dsl.IntegrationFlow; import org.springframework.integration.dsl.IntegrationFlows; +import org.springframework.integration.dsl.context.IntegrationFlowContext; import org.springframework.integration.http.HttpHeaders; import org.springframework.integration.http.dsl.Http; import org.springframework.integration.support.MessageBuilder; @@ -109,6 +110,9 @@ public class WebFluxDslTests { @Autowired private WebApplicationContext wac; + @Autowired + private IntegrationFlowContext integrationFlowContext; + @Autowired @Qualifier("webFluxWithReplyPayloadToFlux.handler") private WebFluxRequestExecutingMessageHandler webFluxWithReplyPayloadToFlux; @@ -246,6 +250,33 @@ public class WebFluxDslTests { .verifyComplete(); } + @Test + public void testDynamicHttpEndpoint() throws Exception { + IntegrationFlow flow = + IntegrationFlows.from(WebFlux.inboundGateway("/dynamic") + .requestMapping(r -> r.params("name")) + .payloadExpression("#requestParams.name[0]")) + .transform(String::toLowerCase) + .get(); + + IntegrationFlowContext.IntegrationFlowRegistration flowRegistration = + this.integrationFlowContext.registration(flow).register(); + + this.webTestClient.get().uri("/dynamic?name=BAR") + .attributes(basicAuthenticationCredentials("guest", "guest")) + .exchange() + .expectBody(String.class) + .isEqualTo("bar"); + + flowRegistration.destroy(); + + this.webTestClient.get().uri("/dynamic?name=BAZ") + .attributes(basicAuthenticationCredentials("guest", "guest")) + .exchange() + .expectStatus() + .isNotFound(); + } + @Configuration @EnableWebFlux @EnableWebSecurity