From 87e11d21f89f657eb0f2942ebdebb88959c404c7 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Thu, 1 Jul 2021 14:49:22 -0400 Subject: [PATCH] Fix CORS registration for IntGraphController We fail with `NoClassDefFoundError` when we use `@EnableIntegrationGraphController` in WebFlux env without Spring MVC. Another issue that we don't register CORS for WebFlux * Extract top-level package protected classes for MVC and WebFlux to register CORS in the appropriate environment according classpath * Adjust `HttpIntegrationConfigurationInitializer` and `WebFluxIntegrationConfigurationInitializer` for native compatibility * Add `@Indexed` for `@MessagingGateway` for indexer support in Spring Boot and Spring Native **Cherry-pick to `5.4.x` & `5.3.x`** # Conflicts: # spring-integration-http/src/main/java/org/springframework/integration/http/config/IntegrationGraphControllerRegistrar.java # Conflicts: # spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpIntegrationConfigurationInitializer.java # spring-integration-webflux/src/main/java/org/springframework/integration/webflux/config/WebFluxIntegrationConfigurationInitializer.java --- build.gradle | 1 + .../annotation/MessagingGateway.java | 5 +- ...tpIntegrationConfigurationInitializer.java | 15 +-- .../IntegrationGraphControllerRegistrar.java | 91 ++++++++++++------- ...WebFluxIntegrationGraphCorsConfigurer.java | 47 ++++++++++ .../WebMvcIntegrationGraphCorsConfigurer.java | 48 ++++++++++ ...uxIntegrationConfigurationInitializer.java | 16 +++- 7 files changed, 176 insertions(+), 47 deletions(-) create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/config/WebFluxIntegrationGraphCorsConfigurer.java create mode 100644 spring-integration-http/src/main/java/org/springframework/integration/http/config/WebMvcIntegrationGraphCorsConfigurer.java diff --git a/build.gradle b/build.gradle index bfc1ddf139..c9dd05c16a 100644 --- a/build.gradle +++ b/build.gradle @@ -509,6 +509,7 @@ project('spring-integration-http') { api 'org.springframework:spring-webmvc' providedImplementation "javax.servlet:javax.servlet-api:$servletApiVersion" optionalApi "com.rometools:rome:$romeToolsVersion" + optionalApi 'org.springframework:spring-webflux' testImplementation project(':spring-integration-security') testImplementation "org.hamcrest:hamcrest-core:$hamcrestVersion" diff --git a/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessagingGateway.java b/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessagingGateway.java index 28f3b1c5a3..4b12031053 100644 --- a/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessagingGateway.java +++ b/spring-integration-core/src/main/java/org/springframework/integration/annotation/MessagingGateway.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2021 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. @@ -22,6 +22,8 @@ import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; +import org.springframework.stereotype.Indexed; + /** * A stereotype annotation to provide an Integration Messaging Gateway Proxy * ({@code }) as an abstraction over the messaging API. The target @@ -44,6 +46,7 @@ import java.lang.annotation.Target; @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @Documented +@Indexed public @interface MessagingGateway { /** diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpIntegrationConfigurationInitializer.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpIntegrationConfigurationInitializer.java index 0c60d9c516..79476ea639 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpIntegrationConfigurationInitializer.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/HttpIntegrationConfigurationInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2019 the original author or authors. + * Copyright 2014-2021 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. @@ -25,7 +25,6 @@ import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.integration.config.IntegrationConfigurationInitializer; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.http.inbound.IntegrationRequestMappingHandlerMapping; /** @@ -55,8 +54,7 @@ public class HttpIntegrationConfigurationInitializer implements IntegrationConfi * which could also be overridden by the user by simply registering * a {@link IntegrationRequestMappingHandlerMapping} {@code } with 'id' * {@link HttpContextUtils#HANDLER_MAPPING_BEAN_NAME}. - *

- * In addition, checks if the {@code javax.servlet.Servlet} class is present on the classpath. + *

In addition, checks if the {@code javax.servlet.Servlet} class is present on the classpath. * When Spring Integration HTTP is used only as an HTTP client, there is no reason to use and register * the HTTP server components. */ @@ -64,9 +62,14 @@ public class HttpIntegrationConfigurationInitializer implements IntegrationConfi if (HttpContextUtils.WEB_MVC_PRESENT && !registry.containsBeanDefinition(HttpContextUtils.HANDLER_MAPPING_BEAN_NAME)) { BeanDefinitionBuilder requestMappingBuilder = - BeanDefinitionBuilder.genericBeanDefinition(IntegrationRequestMappingHandlerMapping.class); + BeanDefinitionBuilder.genericBeanDefinition(IntegrationRequestMappingHandlerMapping.class, + () -> { + IntegrationRequestMappingHandlerMapping mapping = + new IntegrationRequestMappingHandlerMapping(); + mapping.setOrder(0); + return mapping; + }); requestMappingBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - requestMappingBuilder.addPropertyValue(IntegrationNamespaceUtils.ORDER, 0); registry.registerBeanDefinition(HttpContextUtils.HANDLER_MAPPING_BEAN_NAME, requestMappingBuilder.getBeanDefinition()); } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/IntegrationGraphControllerRegistrar.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/IntegrationGraphControllerRegistrar.java index ed65a023d4..55f860df13 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/IntegrationGraphControllerRegistrar.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/IntegrationGraphControllerRegistrar.java @@ -1,5 +1,5 @@ /* - * Copyright 2016-2019 the original author or authors. + * Copyright 2016-2021 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. @@ -20,7 +20,11 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + import org.springframework.beans.BeansException; +import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.config.BeanDefinition; import org.springframework.beans.factory.config.BeanFactoryPostProcessor; import org.springframework.beans.factory.config.ConfigurableListableBeanFactory; @@ -28,6 +32,7 @@ import org.springframework.beans.factory.support.AbstractBeanDefinition; import org.springframework.beans.factory.support.BeanDefinitionBuilder; import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; +import org.springframework.beans.factory.support.GenericBeanDefinition; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.context.EnvironmentAware; import org.springframework.context.annotation.ImportBeanDefinitionRegistrar; @@ -39,8 +44,6 @@ import org.springframework.core.type.AnnotationMetadata; import org.springframework.integration.context.IntegrationContextUtils; import org.springframework.integration.graph.IntegrationGraphServer; import org.springframework.integration.http.management.IntegrationGraphController; -import org.springframework.web.servlet.config.annotation.CorsRegistry; -import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; /** * Registers the necessary beans for {@link EnableIntegrationGraphController}. @@ -50,10 +53,19 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; * * @since 4.3 */ -class IntegrationGraphControllerRegistrar implements ImportBeanDefinitionRegistrar { +public class IntegrationGraphControllerRegistrar implements ImportBeanDefinitionRegistrar { + + private static final Log LOGGER = LogFactory.getLog(IntegrationGraphControllerRegistrar.class); @Override public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) { + if (!HttpContextUtils.WEB_MVC_PRESENT && !HttpContextUtils.WEB_FLUX_PRESENT) { + LOGGER.warn("The 'IntegrationGraphController' isn't registered with the application context because" + + " there is no 'org.springframework.web.servlet.DispatcherServlet' or" + + " 'org.springframework.web.reactive.DispatcherHandler' in the classpath."); + return; + } + Map annotationAttributes = importingClassMetadata.getAnnotationAttributes(EnableIntegrationGraphController.class.getName()); if (annotationAttributes == null) { @@ -62,40 +74,67 @@ class IntegrationGraphControllerRegistrar implements ImportBeanDefinitionRegistr if (!registry.containsBeanDefinition(IntegrationContextUtils.INTEGRATION_GRAPH_SERVER_BEAN_NAME)) { registry.registerBeanDefinition(IntegrationContextUtils.INTEGRATION_GRAPH_SERVER_BEAN_NAME, - new RootBeanDefinition(IntegrationGraphServer.class)); + new RootBeanDefinition(IntegrationGraphServer.class, IntegrationGraphServer::new)); } + String path = (String) annotationAttributes.get("value"); String[] allowedOrigins = (String[]) annotationAttributes.get("allowedOrigins"); if (allowedOrigins != null && allowedOrigins.length > 0) { - AbstractBeanDefinition controllerCorsConfigurer = - BeanDefinitionBuilder.genericBeanDefinition(IntegrationGraphCorsConfigurer.class) - .addConstructorArgValue(annotationAttributes.get("value")) - .addConstructorArgValue(allowedOrigins) - .getBeanDefinition(); - BeanDefinitionReaderUtils.registerWithGeneratedName(controllerCorsConfigurer, registry); + AbstractBeanDefinition controllerCorsConfigurer = null; + if (HttpContextUtils.WEB_MVC_PRESENT) { + controllerCorsConfigurer = webMvcControllerCorsConfigurerBean(path, allowedOrigins); + } + else if (HttpContextUtils.WEB_FLUX_PRESENT) { + controllerCorsConfigurer = webFluxControllerCorsConfigurerBean(path, allowedOrigins); + } + + if (controllerCorsConfigurer != null) { + BeanDefinitionReaderUtils.registerWithGeneratedName(controllerCorsConfigurer, registry); + } + else { + LOGGER.warn("Nor Spring MVC, neither WebFlux is present to configure CORS origins " + + "for Integration Graph Controller."); + } } if (!registry.containsBeanDefinition(HttpContextUtils.GRAPH_CONTROLLER_BEAN_NAME)) { + Map properties = annotationAttributes; AbstractBeanDefinition controllerPropertiesPopulator = - BeanDefinitionBuilder.genericBeanDefinition(GraphControllerPropertiesPopulator.class) - .addConstructorArgValue(annotationAttributes) + BeanDefinitionBuilder.genericBeanDefinition(GraphControllerPropertiesPopulator.class, + () -> new GraphControllerPropertiesPopulator(properties)) .setRole(BeanDefinition.ROLE_INFRASTRUCTURE) .getBeanDefinition(); BeanDefinitionReaderUtils.registerWithGeneratedName(controllerPropertiesPopulator, registry); BeanDefinition graphController = - BeanDefinitionBuilder.genericBeanDefinition(IntegrationGraphController.class) - .addConstructorArgReference(IntegrationContextUtils.INTEGRATION_GRAPH_SERVER_BEAN_NAME) - .getBeanDefinition(); + new RootBeanDefinition(IntegrationGraphController.class, () -> + new IntegrationGraphController( + ((BeanFactory) registry) + .getBean(IntegrationContextUtils.INTEGRATION_GRAPH_SERVER_BEAN_NAME, + IntegrationGraphServer.class))); registry.registerBeanDefinition(HttpContextUtils.GRAPH_CONTROLLER_BEAN_NAME, graphController); } } + private static AbstractBeanDefinition webMvcControllerCorsConfigurerBean(String path, String[] allowedOrigins) { + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setBeanClass(WebMvcIntegrationGraphCorsConfigurer.class); + beanDefinition.setInstanceSupplier(() -> new WebMvcIntegrationGraphCorsConfigurer(path, allowedOrigins)); + return beanDefinition; + } + + private static AbstractBeanDefinition webFluxControllerCorsConfigurerBean(String path, String[] allowedOrigins) { + GenericBeanDefinition beanDefinition = new GenericBeanDefinition(); + beanDefinition.setBeanClass(WebFluxIntegrationGraphCorsConfigurer.class); + beanDefinition.setInstanceSupplier(() -> new WebFluxIntegrationGraphCorsConfigurer(path, allowedOrigins)); + return beanDefinition; + } + private static final class GraphControllerPropertiesPopulator implements BeanFactoryPostProcessor, EnvironmentAware { - private final Map properties = new HashMap(); + private final Map properties = new HashMap<>(); private GraphControllerPropertiesPopulator(Map annotationAttributes) { Object graphControllerPath = annotationAttributes.get(AnnotationUtils.VALUE); @@ -117,22 +156,4 @@ class IntegrationGraphControllerRegistrar implements ImportBeanDefinitionRegistr } - private static final class IntegrationGraphCorsConfigurer implements WebMvcConfigurer { - - private final String path; - - private final String[] allowedOrigins; - - private IntegrationGraphCorsConfigurer(String path, String[] allowedOrigins) { // NOSONAR - this.path = path; - this.allowedOrigins = allowedOrigins; - } - - @Override - public void addCorsMappings(CorsRegistry registry) { - registry.addMapping(this.path).allowedOrigins(this.allowedOrigins).allowedMethods("GET"); - } - - } - } diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/WebFluxIntegrationGraphCorsConfigurer.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/WebFluxIntegrationGraphCorsConfigurer.java new file mode 100644 index 0000000000..66dd006f2e --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/WebFluxIntegrationGraphCorsConfigurer.java @@ -0,0 +1,47 @@ +/* + * Copyright 2021 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. + * You may obtain a copy of the License at + * + * https://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. + */ + +package org.springframework.integration.http.config; + +import org.springframework.web.reactive.config.CorsRegistry; +import org.springframework.web.reactive.config.WebFluxConfigurer; + +/** + * The {@link WebFluxConfigurer} implementation for CORS mapping on the Integration Graph Controller. + * + * @author Artem Bilan + * + * @since 5.3.9 + * + * @see EnableIntegrationGraphController + */ +final class WebFluxIntegrationGraphCorsConfigurer implements WebFluxConfigurer { + + private final String path; + + private final String[] allowedOrigins; + + WebFluxIntegrationGraphCorsConfigurer(String path, String[] allowedOrigins) { // NOSONAR + this.path = path; + this.allowedOrigins = allowedOrigins; + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping(this.path).allowedOrigins(this.allowedOrigins).allowedMethods("GET"); + } + +} diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/WebMvcIntegrationGraphCorsConfigurer.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/WebMvcIntegrationGraphCorsConfigurer.java new file mode 100644 index 0000000000..611fbca388 --- /dev/null +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/WebMvcIntegrationGraphCorsConfigurer.java @@ -0,0 +1,48 @@ +/* + * Copyright 2021 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. + * You may obtain a copy of the License at + * + * https://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. + */ + +package org.springframework.integration.http.config; + +import org.springframework.web.reactive.config.WebFluxConfigurer; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +/** + * The {@link WebMvcConfigurer} implementation for CORS mapping on the Integration Graph Controller. + * + * @author Artem Bilan + * + * @since 5.3.9 + * + * @see EnableIntegrationGraphController + */ +final class WebMvcIntegrationGraphCorsConfigurer implements WebMvcConfigurer { + + private final String path; + + private final String[] allowedOrigins; + + WebMvcIntegrationGraphCorsConfigurer(String path, String[] allowedOrigins) { // NOSONAR + this.path = path; + this.allowedOrigins = allowedOrigins; + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping(this.path).allowedOrigins(this.allowedOrigins).allowedMethods("GET"); + } + +} diff --git a/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/config/WebFluxIntegrationConfigurationInitializer.java b/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/config/WebFluxIntegrationConfigurationInitializer.java index e8d0e98114..668335b596 100644 --- a/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/config/WebFluxIntegrationConfigurationInitializer.java +++ b/spring-integration-webflux/src/main/java/org/springframework/integration/webflux/config/WebFluxIntegrationConfigurationInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 the original author or authors. + * Copyright 2017-2021 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. @@ -27,7 +27,6 @@ import org.springframework.beans.factory.support.BeanDefinitionReaderUtils; import org.springframework.beans.factory.support.BeanDefinitionRegistry; import org.springframework.beans.factory.support.RootBeanDefinition; import org.springframework.integration.config.IntegrationConfigurationInitializer; -import org.springframework.integration.config.xml.IntegrationNamespaceUtils; import org.springframework.integration.http.config.HttpContextUtils; import org.springframework.integration.webflux.inbound.IntegrationHandlerResultHandler; import org.springframework.integration.webflux.inbound.WebFluxIntegrationRequestMappingHandlerMapping; @@ -69,15 +68,22 @@ public class WebFluxIntegrationConfigurationInitializer implements IntegrationCo private void registerReactiveRequestMappingHandlerMappingIfNecessary(BeanDefinitionRegistry registry) { if (HttpContextUtils.WEB_FLUX_PRESENT && !registry.containsBeanDefinition(WebFluxContextUtils.HANDLER_MAPPING_BEAN_NAME)) { + BeanDefinitionBuilder requestMappingBuilder = - BeanDefinitionBuilder.genericBeanDefinition(WebFluxIntegrationRequestMappingHandlerMapping.class); + BeanDefinitionBuilder.genericBeanDefinition(WebFluxIntegrationRequestMappingHandlerMapping.class, + () -> { + WebFluxIntegrationRequestMappingHandlerMapping mapping = + new WebFluxIntegrationRequestMappingHandlerMapping(); + mapping.setOrder(0); + return mapping; + }); requestMappingBuilder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE); - requestMappingBuilder.addPropertyValue(IntegrationNamespaceUtils.ORDER, 0); registry.registerBeanDefinition(WebFluxContextUtils.HANDLER_MAPPING_BEAN_NAME, requestMappingBuilder.getBeanDefinition()); BeanDefinitionReaderUtils.registerWithGeneratedName( - new RootBeanDefinition(IntegrationHandlerResultHandler.class), registry); + new RootBeanDefinition(IntegrationHandlerResultHandler.class, IntegrationHandlerResultHandler::new), + registry); } }