diff --git a/README.md b/README.md
index 4b7d2444..7465d317 100644
--- a/README.md
+++ b/README.md
@@ -70,6 +70,7 @@ This is a good place to get started. The samples here are technically motivated
* **tcp-amqp** - Demonstrates basic functionality of bridging the **Spring Integration TCP Adapters** with **Spring Integration AMQP Adapters**
* **tcp-broadcast** - Demonstrates broadcasting a message to multiple connected TCP clients.
* **tcp-client-server** - Demonstrates socket communication using **TcpOutboundGateway**, **TcpInboundGateway** and also uses a **Gateway** and a **Service Activator**
+* **tcp-with-headers** - Demonstrates sending headers along with the payload over TCP using JSON.
* **testing-examples** - A series of test cases that show techniques to **test** Spring Integration applications.
* **twitter** - Illustrates Twitter support using the **Twitter Inbound Channel Adapter**, **Twitter Inbound Search Channel Adapter**, **Twitter Outbound Channel Adapter**
* **ws-inbound-gateway** - Example showing basic functionality of the **Web Service Gateway**
diff --git a/basic/tcp-with-headers/.gitignore b/basic/tcp-with-headers/.gitignore
new file mode 100644
index 00000000..57a6aeaa
--- /dev/null
+++ b/basic/tcp-with-headers/.gitignore
@@ -0,0 +1,24 @@
+target/
+.mvn
+mvn*
+
+### STS ###
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+
+### NetBeans ###
+nbproject/private/
+build/
+nbbuild/
+dist/
+nbdist/
+.nb-gradle/
diff --git a/basic/tcp-with-headers/README.adoc b/basic/tcp-with-headers/README.adoc
new file mode 100644
index 00000000..59d97805
--- /dev/null
+++ b/basic/tcp-with-headers/README.adoc
@@ -0,0 +1,32 @@
+= TCP With Headers
+
+There is no standard way to convey message headers over raw TCP; they need to be encoded into the payload on the sending side and decoded on the receiving side.
+
+This example demonstrates how to use standard framework components to encode the payload and certain headers using JSON.
+It takes console input and, if the input starts with a lower case, uppercases it and vice versa.
+Whether to upper case or lower case the input is conveyed in a header 'type'.
+
+Run from your favorite IDE, or from the command line `./gradlew :dynamic-tcp-client:run`.
+
+Here is an example run...
+
+```
+ . ____ _ __ _ _
+ /\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
+( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
+ \\/ ___)| |_)| | | | | || (_| | ) ) ) )
+ ' |____| .__|_| |_|_| |_\__, | / / / /
+ =========|_|==============|___/=/_/_/_/
+ :: Spring Boot :: (v2.1.3.RELEASE)
+
+Enter some text; if it starts with a lower case character,
+it will be uppercased by the server; otherwise it will be lowercased;
+enter 'quit' to end
+this should be uppercased
+10:54:39.259 [pool-1-thread-2] INFO exampleLogger - Received type header:upper
+THIS SHOULD BE UPPERCASED
+This should be lowercased
+10:54:49.266 [pool-1-thread-2] INFO exampleLogger - Received type header:lower
+this should be lowercased
+quit
+```
diff --git a/basic/tcp-with-headers/pom.xml b/basic/tcp-with-headers/pom.xml
new file mode 100644
index 00000000..65cf6382
--- /dev/null
+++ b/basic/tcp-with-headers/pom.xml
@@ -0,0 +1,206 @@
+
+
+ 4.0.0
+
+ org.springframework.boot
+ spring-boot-starter-parent
+ 2.2.0.BUILD-SNAPSHOT
+
+ org.springframework.integration.samples
+ tcp-with-headers
+ 5.2.0.BUILD-SNAPSHOT
+ TCP Send/Receive with headers
+ TCP Send/Receive with headers
+ https://projects.spring.io/spring-integration
+
+ SpringIO
+ https://spring.io
+
+
+
+ The Apache Software License, Version 2.0
+ https://www.apache.org/licenses/LICENSE-2.0.txt
+ repo
+
+
+
+
+ garyrussell
+ Gary Russell
+ grussell@pivotal.io
+
+ project lead
+
+
+
+ markfisher
+ Mark Fisher
+ mfisher@pivotal.io
+
+ project founder and lead emeritus
+
+
+
+ ghillert
+ Gunnar Hillert
+ ghillert@pivotal.io
+
+
+ abilan
+ Artem Bilan
+ abilan@pivotal.io
+
+
+
+ scm:git:scm:git:git://github.com/spring-projects/spring-integration-samples.git
+ scm:git:scm:git:ssh://git@github.com:spring-projects/spring-integration-samples.git
+ https://github.com/spring-projects/spring-integration-samples
+
+
+
+ org.springframework.boot
+ spring-boot-starter-integration
+ compile
+
+
+ jackson-module-kotlin
+ com.fasterxml.jackson.module
+
+
+
+
+ org.springframework.integration
+ spring-integration-ip
+ compile
+
+
+ jackson-module-kotlin
+ com.fasterxml.jackson.module
+
+
+
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ 2.9.8
+ compile
+
+
+ jackson-module-kotlin
+ com.fasterxml.jackson.module
+
+
+
+
+ junit
+ junit
+ 4.12
+ test
+
+
+ jackson-module-kotlin
+ com.fasterxml.jackson.module
+
+
+ *
+ org.hamcrest
+
+
+
+
+ org.hamcrest
+ hamcrest-all
+ 1.3
+ test
+
+
+ jackson-module-kotlin
+ com.fasterxml.jackson.module
+
+
+
+
+ org.mockito
+ mockito-core
+ 2.24.0
+ test
+
+
+ jackson-module-kotlin
+ com.fasterxml.jackson.module
+
+
+ *
+ org.hamcrest
+
+
+
+
+ org.springframework
+ spring-test
+ test
+
+
+ jackson-module-kotlin
+ com.fasterxml.jackson.module
+
+
+
+
+ org.springframework.boot
+ spring-boot-starter-test
+ test
+
+
+ jackson-module-kotlin
+ com.fasterxml.jackson.module
+
+
+
+
+
+
+ repo.spring.io.milestone
+ Spring Framework Maven Milestone Repository
+ https://repo.spring.io/libs-milestone
+
+
+ repo.spring.io.snapshot
+ Spring Framework Maven Snapshot Repository
+ https://repo.spring.io/libs-snapshot
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-maven-plugin
+
+
+
+
+
+
+ org.springframework.boot
+ spring-boot-dependencies
+ 2.2.0.BUILD-SNAPSHOT
+ import
+ pom
+
+
+ org.springframework
+ spring-framework-bom
+ 5.2.0.BUILD-SNAPSHOT
+ import
+ pom
+
+
+ org.springframework.integration
+ spring-integration-bom
+ 5.2.0.BUILD-SNAPSHOT
+ import
+ pom
+
+
+
+
diff --git a/basic/tcp-with-headers/src/main/java/org/springframework/integration/samples/tcpheaders/TcpWithHeadersApplication.java b/basic/tcp-with-headers/src/main/java/org/springframework/integration/samples/tcpheaders/TcpWithHeadersApplication.java
new file mode 100644
index 00000000..7f79ab49
--- /dev/null
+++ b/basic/tcp-with-headers/src/main/java/org/springframework/integration/samples/tcpheaders/TcpWithHeadersApplication.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2019 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.samples.tcpheaders;
+
+import java.util.Scanner;
+
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.boot.ApplicationRunner;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.SpringBootApplication;
+import org.springframework.context.ConfigurableApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.DependsOn;
+import org.springframework.integration.dsl.IntegrationFlow;
+import org.springframework.integration.dsl.IntegrationFlows;
+import org.springframework.integration.handler.LoggingHandler.Level;
+import org.springframework.integration.ip.dsl.Tcp;
+import org.springframework.integration.ip.tcp.connection.MessageConvertingTcpMessageMapper;
+import org.springframework.integration.ip.tcp.serializer.MapJsonSerializer;
+import org.springframework.integration.support.converter.MapMessageConverter;
+import org.springframework.messaging.handler.annotation.Header;
+import org.springframework.util.StringUtils;
+
+@SpringBootApplication
+public class TcpWithHeadersApplication {
+
+ public static void main(String[] args) {
+ SpringApplication.run(TcpWithHeadersApplication.class, args);
+ }
+
+ // Client side
+
+ public interface TcpExchanger {
+
+ public String exchange(String data, @Header("type") String type);
+
+ }
+
+ @Bean
+ public IntegrationFlow client(@Value("${tcp.port:1234}") int port) {
+ return IntegrationFlows.from(TcpExchanger.class)
+ .handle(Tcp.outboundGateway(Tcp.netClient("localhost", port)
+ .deserializer(jsonMapping())
+ .serializer(jsonMapping())
+ .mapper(mapper())))
+ .get();
+ }
+
+ // Server side
+
+ @Bean
+ public IntegrationFlow server(@Value("${tcp.port:1234}") int port) {
+ return IntegrationFlows.from(Tcp.inboundGateway(Tcp.netServer(port)
+ .deserializer(jsonMapping())
+ .serializer(jsonMapping())
+ .mapper(mapper())))
+ .log(Level.INFO, "exampleLogger", "'Received type header:' + headers['type']")
+ .route("headers['type']", r -> r
+ .subFlowMapping("upper",
+ subFlow -> subFlow.transform(String.class, p -> p.toUpperCase()))
+ .subFlowMapping("lower",
+ subFlow -> subFlow.transform(String.class, p -> p.toLowerCase())))
+ .get();
+ }
+
+ // Common
+
+ @Bean
+ public MessageConvertingTcpMessageMapper mapper() {
+ MapMessageConverter converter = new MapMessageConverter();
+ converter.setHeaderNames("type");
+ return new MessageConvertingTcpMessageMapper(converter);
+ }
+
+ @Bean
+ public MapJsonSerializer jsonMapping() {
+ return new MapJsonSerializer();
+ }
+
+ // Console
+
+ @Bean
+ @DependsOn("client")
+ public ApplicationRunner runner(TcpExchanger exchanger,
+ ConfigurableApplicationContext context) {
+
+ return args -> {
+ System.out.println("Enter some text; if it starts with a lower case character,\n"
+ + "it will be uppercased by the server; otherwise it will be lowercased;\n"
+ + "enter 'quit' to end");
+ Scanner scanner = new Scanner(System.in);
+ String request;
+ if (scanner.hasNextLine()) {
+ request = scanner.nextLine();
+ while (!"quit".equals(request.toLowerCase())) {
+ if (StringUtils.hasText(request)) {
+ String result = exchanger.exchange(request,
+ Character.isLowerCase(request.charAt(0)) ? "upper" : "lower");
+ System.out.println(result);
+ }
+ if (scanner.hasNextLine()) {
+ request = scanner.nextLine();
+ }
+ else {
+ request = "quit";
+ }
+ }
+ }
+ scanner.close();
+ context.close();
+ };
+ }
+
+}
diff --git a/basic/tcp-with-headers/src/main/resources/application.properties b/basic/tcp-with-headers/src/main/resources/application.properties
new file mode 100644
index 00000000..e69de29b
diff --git a/basic/tcp-with-headers/src/main/resources/logback.xml b/basic/tcp-with-headers/src/main/resources/logback.xml
new file mode 100644
index 00000000..31377ed5
--- /dev/null
+++ b/basic/tcp-with-headers/src/main/resources/logback.xml
@@ -0,0 +1,19 @@
+
+
+
+
+
+ %d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/build.gradle b/build.gradle
index d8bce444..bfc89560 100644
--- a/build.gradle
+++ b/build.gradle
@@ -1445,6 +1445,31 @@ project('dynamic-tcp-client') {
}
}
+project('tcp-with-headers') {
+ description = 'TCP Send/Receive with headers'
+
+ apply plugin: 'org.springframework.boot'
+
+ dependencies {
+ compile 'org.springframework.boot:spring-boot-starter-integration'
+ compile "org.springframework.integration:spring-integration-ip"
+ compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion"
+
+ testCompile 'org.springframework.boot:spring-boot-starter-test'
+ }
+
+ bootRun {
+ main = 'org.springframework.integration.samples.tcpheaders.TcpWithHeadersApplication'
+ standardInput = System.in
+ }
+
+ task run(type: JavaExec) {
+ main 'org.springframework.integration.samples.tcpheaders.TcpWithHeadersApplication'
+ classpath = sourceSets.main.runtimeClasspath
+ standardInput = System.in
+ }
+}
+
sonarqube {
properties {
diff --git a/settings.gradle b/settings.gradle
index 12f6d75f..0c439f88 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -4,12 +4,14 @@ rootProject.name = 'spring-integration-samples'
def projectDir = new File(rootDir, it)
include ":${it}"
projectDir.eachDir { dir ->
- include ":${dir.name}"
- project(":${dir.name}").projectDir = new File(projectDir.absolutePath, dir.name)
- if ('cafe' == dir.name) {
- dir.eachDir { cafe ->
- include ":${cafe.name}"
- project(":${cafe.name}").projectDir = new File(dir.absolutePath, cafe.name)
+ if (!dir.name.startsWith('.')) {
+ include ":${dir.name}"
+ project(":${dir.name}").projectDir = new File(projectDir.absolutePath, dir.name)
+ if ('cafe' == dir.name) {
+ dir.eachDir { cafe ->
+ include ":${cafe.name}"
+ project(":${cafe.name}").projectDir = new File(dir.absolutePath, cafe.name)
+ }
}
}
}