From d57db56a396e37e91e431f908d752a716f478f23 Mon Sep 17 00:00:00 2001 From: Artem Bilan Date: Mon, 23 May 2016 20:08:11 -0400 Subject: [PATCH] INT-4035, INT-4008: Docs for Integration Graph JIRA: https://jira.spring.io/browse/INT-4035, https://jira.spring.io/browse/INT-4008 Polishing --- ...raphControllerRegistrarImportSelector.java | 3 +- ...tionGraphControllerParserTests-context.xml | 10 +- .../IntegrationGraphControllerTests.java | 21 +- src/reference/asciidoc/graph.adoc | 191 ++++++++++++++++++ src/reference/asciidoc/http.adoc | 6 + src/reference/asciidoc/system-management.adoc | 2 + src/reference/asciidoc/whats-new.adoc | 5 + 7 files changed, 232 insertions(+), 6 deletions(-) create mode 100644 src/reference/asciidoc/graph.adoc diff --git a/spring-integration-http/src/main/java/org/springframework/integration/http/config/IntegrationGraphControllerRegistrarImportSelector.java b/spring-integration-http/src/main/java/org/springframework/integration/http/config/IntegrationGraphControllerRegistrarImportSelector.java index 2e087c45d8..5d5cc0ca99 100644 --- a/spring-integration-http/src/main/java/org/springframework/integration/http/config/IntegrationGraphControllerRegistrarImportSelector.java +++ b/spring-integration-http/src/main/java/org/springframework/integration/http/config/IntegrationGraphControllerRegistrarImportSelector.java @@ -21,7 +21,6 @@ import org.apache.commons.logging.LogFactory; import org.springframework.context.annotation.ImportSelector; import org.springframework.core.type.AnnotationMetadata; -import org.springframework.integration.http.support.DefaultHttpHeaderMapper; import org.springframework.integration.http.support.HttpContextUtils; /** @@ -30,7 +29,7 @@ import org.springframework.integration.http.support.HttpContextUtils; */ class IntegrationGraphControllerRegistrarImportSelector implements ImportSelector { - private static final Log logger = LogFactory.getLog(DefaultHttpHeaderMapper.class); + private static final Log logger = LogFactory.getLog(IntegrationGraphControllerRegistrarImportSelector.class); @Override public String[] selectImports(AnnotationMetadata importingClassMetadata) { diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerParserTests-context.xml b/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerParserTests-context.xml index 685ee9e609..a969d64fd3 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerParserTests-context.xml +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerParserTests-context.xml @@ -10,8 +10,14 @@ http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc.xsd"> - + - + + + + + diff --git a/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerTests.java b/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerTests.java index 55ef914316..6f5b366bab 100644 --- a/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerTests.java +++ b/spring-integration-http/src/test/java/org/springframework/integration/http/management/IntegrationGraphControllerTests.java @@ -36,10 +36,13 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ConfigurableApplicationContext; import org.springframework.context.annotation.Configuration; import org.springframework.context.support.ClassPathXmlApplicationContext; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpMethod; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.integration.channel.DirectChannel; import org.springframework.integration.config.EnableIntegration; +import org.springframework.integration.config.EnableIntegrationManagement; import org.springframework.integration.http.config.EnableIntegrationGraphController; import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.mock.web.MockHttpServletResponse; @@ -53,7 +56,9 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.servlet.HandlerAdapter; import org.springframework.web.servlet.HandlerExecutionChain; import org.springframework.web.servlet.HandlerMapping; +import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.EnableWebMvc; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; @@ -80,6 +85,7 @@ public class IntegrationGraphControllerTests { @Test public void testIntegrationGraphGet() throws Exception { this.mockMvc.perform(get("/testIntegration") + .header(HttpHeaders.ORIGIN, "http://foo.bar.com") .accept(MediaType.parseMediaType("application/json;charset=UTF-8"))) .andExpect(status().isOk()) .andExpect(content().contentType("application/json;charset=UTF-8")) @@ -99,7 +105,6 @@ public class IntegrationGraphControllerTests { "IntegrationGraphControllerParserTests-context.xml", getClass()); - HandlerMapping handlerMapping = context.getBean(RequestMappingHandlerMapping.class.getName(), HandlerMapping.class); @@ -108,6 +113,7 @@ public class IntegrationGraphControllerTests { MockHttpServletRequest request = new MockHttpServletRequest(); request.setMethod("GET"); request.setRequestURI("/foo"); + request.addHeader(HttpHeaders.ORIGIN, "http://foo.bar.com"); MockHttpServletResponse response = new MockHttpServletResponse(); HandlerExecutionChain executionChain = handlerMapping.getHandler(request); @@ -142,8 +148,19 @@ public class IntegrationGraphControllerTests { @Configuration @EnableWebMvc @EnableIntegration + @EnableIntegrationManagement(statsEnabled = "_org.springframework.integration.errorLogger.handler", + countsEnabled = "!*", + defaultLoggingEnabled = "false") @EnableIntegrationGraphController(path = "/testIntegration") - public static class ContextConfiguration { + public static class ContextConfiguration extends WebMvcConfigurerAdapter { + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/testIntegration/**") + .allowedOrigins("http://foo.bar.com") + .allowedMethods(HttpMethod.GET.name()); + + } } diff --git a/src/reference/asciidoc/graph.adoc b/src/reference/asciidoc/graph.adoc new file mode 100644 index 0000000000..7d3a11d7d1 --- /dev/null +++ b/src/reference/asciidoc/graph.adoc @@ -0,0 +1,191 @@ +[[integration-graph]] +=== Integration Graph + +Starting with _version 4.3_, Spring Integration provides access to an application's runtime object model which can, optionally, include component metrics. +It is exposed as a graph, which may be used to visualize the current state of the integration application. +The `o.s.i.support.management.graph` package contains all the required classes to collect, build and render the runtime state of Spring Integration components as a single tree-like `Graph` object. +The `IntegrationGraphServer` should be declared as a bean to build, retrieve and refresh the `Graph` object. +The resulting `Graph` object can be serialized to any format, although JSON is flexible and convenient to parse and represent on the client side. +A simple Spring Integration application with only the default components would expose a graph as follows: + +[source,json] +---- +{ + "contentDescriptor": { + "providerVersion": "4.3.0.RELEASE", + "providerFormatVersion": 1.0, + "provider": "spring-integration", + "name": "myApplication" + }, + "nodes": [ + { + "nodeId": 1, + "name": "nullChannel", + "stats": null, + "componentType": "channel" + }, + { + "nodeId": 2, + "name": "errorChannel", + "stats": null, + "componentType": "publish-subscribe-channel" + }, + { + "nodeId": 3, + "name": "_org.springframework.integration.errorLogger", + "stats": { + "duration": { + "count": 0, + "min": 0.0, + "max": 0.0, + "mean": 0.0, + "standardDeviation": 0.0, + "countLong": 0 + }, + "errorCount": 0, + "standardDeviationDuration": 0.0, + "countsEnabled": true, + "statsEnabled": true, + "loggingEnabled": false, + "handleCount": 0, + "meanDuration": 0.0, + "maxDuration": 0.0, + "minDuration": 0.0, + "activeCount": 0 + }, + "componentType": "logging-channel-adapter", + "output": null, + "input": "errorChannel" + } + ], + "links": [ + { + "from": 2, + "to": 3 + } + ] +} +---- + +As you can see, the graph consists of three top-level elements. + +The `contentDescriptor` graph element is pretty straightforward and contains general information about the application providing the data. +The `name` can be customized on the `IntegrationGraphServer` bean or via `spring.application.name` application context environment property. +Other properties are provided by the framework and allows you to distinguish a similar model from other sources. + +The `links` graph element represents connections between nodes from the `nodes` graph element and, therefore, between integration components in the source Spring Integration application. +For example _from_ a `MessageChannel` _to_ an `EventDrivenConsumer` with some `MessageHandler`; +or _from_ an `AbstractReplyProducingMessageHandler` _to_ a `MessageChannel`. +The information from this element can be used by a visualizing tool to render connections between nodes from the `nodes` graph element, where the `from` and `to` numbers represent the value from the `nodeId` property of the linked nodes. + +The `nodes` graph element is perhaps the most interesting because its elements contain not only the runtime components with their `componentType` s and `name` s, but can also optionally contain metrics exposed by the component. +To enable the metrics, add an `@EnableIntegrationManagement` to some `@Configuration` class or add an `` element to your XML configuration. +You can control exactly which components in the framework collect statistics. +See <> for complete information. +See the `stats` attribute from the `_org.springframework.integration.errorLogger` component in the JSON example above. +The `nullChannel` and `errorChannel` don't provide statistics information in this case, because the configuration for this example was: + +[source,java] +---- +@Configuration +@EnableIntegration +@EnableIntegrationManagement(statsEnabled = "_org.springframework.integration.errorLogger.handler", + countsEnabled = "!*", + defaultLoggingEnabled = "false") +public class ManagementConfiguration { + + @Bean + public IntegrationGraphServer integrationGraphServer() { + return new IntegrationGraphServer(); + } + +} +---- + +The `nodeId` represents a unique incremental identifier to distinguish one component from another. +It is also used in the `links` element to represent a relationship (connection) of this component to others, if any. +The `input` and `output` attributes are for the `inputChannel` and `outputChannel` properties of the `AbstractEndpoint`, `MessageHandler`, `SourcePollingChannelAdapter` or `MessageProducerSupport`. +See the next paragraph for more information. + +==== Graph Runtime Model + +Spring Integration components have various levels of complexity. +For example, any polled `MessageSource` also has a `SourcePollingChannelAdapter` and a `MessageChannel` to which to send messages from the source data periodically. +Other components might be middleware request-reply components, e.g. `JmsOutboundGateway`, with a consuming `AbstractEndpoint` to subscribe to (or poll) the `requestChannel` (`input`) for messages, and a `replyChannel` (`output`) to produce a reply message to send downstream. +Meanwhile, any `MessageProducerSupport` implementation (e.g. `ApplicationEventListeningMessageProducer`) simply wraps some source protocol listening logic and sends messages to the `outputChannel`. + +Within the graph, Spring Integration components are represented using the `IntegrationNode` class hierarchy, which you can find in the `o.s.i.support.management.graph` package. +For example the `ErrorCapableDiscardingMessageHandlerNode` could be used for the `AggregatingMessageHandler` (because it has a `discardChannel` option) and can produce errors when consuming from a `PollableChannel` using a `PollingConsumer`. +Another sample is `CompositeMessageHandlerNode` - for a `MessageHandlerChain` when subscribed to a `SubscribableChannel`, using an `EventDrivenConsumer`. + +This `IntegrationNode` hierarchy can be used for parsing the graph model on the client side, as well as for the understanding the general Spring Integration runtime behavior. +See also <> for more information. + +=== Integration Graph Controller + +If your application is WEB-based (or built on top of Spring Boot using an embedded web container) and the Spring Integration HTTP module (see <>) is present on the classpath, you can use a `IntegrationGraphController` to expose the `IntegrationGraphServer` functionality as a REST service. +For this purpose, the `@EnableIntegrationGraphController` `@Configuration` class annotation and the `` XML element, are available in the HTTP module. +Together with the `@EnableWebMvc` annotation (or `` for xml definitions), this configuration registers an `IntegrationGraphController` `@RestController` where its `@RequestMapping.path` can be configured on the `@EnableIntegrationGraphController` annotation or `` element. +The default path is `/integration`. + +The `IntegrationGraphController` `@RestController` provides these services: + +- `@GetMapping(name = "getGraph")` - to retrieve the state of the Spring Integration components since the last `IntegrationGraphServer` refresh. +The `o.s.i.support.management.graph.Graph` is returned as a `@ResponseBody` of the REST service; +- `@GetMapping(path = "/refresh", name = "refreshGraph")` - to refresh the current `Graph` for the actual runtime state and return it as a REST response. +It is not necessaery to refresh the graph for metrics, they are provided in real-time when the graph is retrieved. +Refresh can be called if the application context has been modified since the graph was last retrieved and the graph is completely rebuilt. + +Any Security and Cross Origin restrictions for the `IntegrationGraphController` can be achieved with the standard configuration options and components provided by Spring Security and Spring MVC projects. +The simple example of that may be: + +[source,xml] +---- + + + + + + + + + + + + +---- + +and the Java & Annotation Configuration variant is: + +[source,java] +---- +@Configuration +@EnableWebMvc +@EnableWebSecurity +@EnableIntegration +@EnableIntegrationGraphController(path = "/testIntegration") +public class IntegrationConfiguration extends WebSecurityConfigurerAdapter + implements WebMvcConfigurer { + + @Override + protected void configure(HttpSecurity http) throws Exception { + http + .authorizeRequests() + .antMatchers("/testIntegration/**").hasRole("ADMIN") + // ... + .formLogin(); + } + + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/testIntegration/**") + .allowedOrigins("http://localhost:9090") + .allowedMethods(HttpMethod.GET.name()); + } + + //... + +} +---- diff --git a/src/reference/asciidoc/http.adoc b/src/reference/asciidoc/http.adoc index 74b13fcf13..b08499024d 100644 --- a/src/reference/asciidoc/http.adoc +++ b/src/reference/asciidoc/http.adoc @@ -791,6 +791,12 @@ If further customization is required you can also configure a `DefaultHttpHeader Of course, you can even implement the HeaderMapper strategy interface directly and provide a reference to that if you need to do something other than what the `DefaultHttpHeaderMapper` supports. +[[int-graph-controller]] +=== Integration Graph Controller + +Starting with _version 4.3_, the HTTP module provides an `@EnableIntegrationGraphController` `@Configuration` class annotation and `` XML element to expose the `IntegrationGraphServer` as a REST service. +See <> for more information. + [[http-samples]] === HTTP Samples diff --git a/src/reference/asciidoc/system-management.adoc b/src/reference/asciidoc/system-management.adoc index 23b63efb7d..e8a93d03a8 100644 --- a/src/reference/asciidoc/system-management.adoc +++ b/src/reference/asciidoc/system-management.adoc @@ -15,4 +15,6 @@ include::./meta-data-store.adoc[] include::./control-bus.adoc[] include::./shutdown.adoc[] + +include::./graph.adoc[] // BE SURE TO PRECEDE ALL include:: with a blank line - see https://github.com/asciidoctor/asciidoctor/issues/1297 diff --git a/src/reference/asciidoc/whats-new.adoc b/src/reference/asciidoc/whats-new.adoc index efd765e11f..0c5346d8aa 100644 --- a/src/reference/asciidoc/whats-new.adoc +++ b/src/reference/asciidoc/whats-new.adoc @@ -38,6 +38,11 @@ See <> and <> for more information. A new `StreamTransformer` is provided to transform an `InputStream` payload to either a `byte[]` or `String`. See <> for more information. +==== Integration Graph + +A new `IntegrationGraphServer` together with the `IntegrationGraphController` REST service are provided to expose the runtime model of a Spring Integration application as a graph. +See <> for more information. + [[x4.3-general]] === General Changes