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) + } } } }