INTSAMPLES-63 - Polish Tcp-Client-Server Sample

For reference see: https://jira.springsource.org/browse/INTSAMPLES-63

* Update README.md
* Code clean-up (E.g. convert spaces to tabs)
* Make sample executable via Maven
This commit is contained in:
Gunnar Hillert
2012-12-08 15:24:27 -05:00
parent 863f5eecf4
commit 563e0878c5
9 changed files with 351 additions and 184 deletions

View File

@@ -19,36 +19,37 @@ import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* @author: ceposta
* @author Christian Posta
* @author Gunnar Hillert
*/
public class CustomOrder {
private int number;
private String sender;
private String message;
private int number;
private String sender;
private String message;
public CustomOrder(int number, String sender) {
this.number = number;
this.sender = sender;
}
public CustomOrder(int number, String sender) {
this.number = number;
this.sender = sender;
}
public int getNumber() {
return number;
}
public int getNumber() {
return number;
}
public String getSender() {
return sender;
}
public String getSender() {
return sender;
}
public String getMessage() {
return message;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}

View File

@@ -42,104 +42,103 @@ import org.springframework.core.serializer.Serializer;
* content is. After that message content is parsed from the stream, the stream is assumed
* to not have anything after it. In your code you could have delimiters to mark the end
* of the stream, or could agree with the client that a valid stream is only n characters,
* etc. Eitherway, since its custom, the client and server must have some predefined
* etc. Either way, since its custom, the client and server must have some predefined
* assumptions in place for the communication to take place.
*
*
* @author: ceposta
* @author Christian Posta
* @author Gunnar Hillert
*/
public class CustomSerializerDeserializer implements Serializer<CustomOrder>, Deserializer<CustomOrder>{
protected final Log logger = LogFactory.getLog(this.getClass());
protected final Log logger = LogFactory.getLog(this.getClass());
private static final int ORDER_NUMBER_LENGTH = 3;
private static final int SENDER_NAME_LENGTH = 10;
private static final int MESSAGE_LENGTH_LENGTH = 6;
private static final int ORDER_NUMBER_LENGTH = 3;
private static final int SENDER_NAME_LENGTH = 10;
private static final int MESSAGE_LENGTH_LENGTH = 6;
/**
* Convert a CustomOrder object into a byte-stream
*
* @param object
* @param outputStream
* @throws IOException
*/
public void serialize(CustomOrder object, OutputStream outputStream) throws IOException {
byte[] number = Integer.toString(object.getNumber()).getBytes();
outputStream.write(number);
/**
* Convert a CustomOrder object into a byte-stream
*
* @param object
* @param outputStream
* @throws IOException
*/
public void serialize(CustomOrder object, OutputStream outputStream) throws IOException {
byte[] number = Integer.toString(object.getNumber()).getBytes();
outputStream.write(number);
byte[] senderName = object.getSender().getBytes();
outputStream.write(senderName);
byte[] senderName = object.getSender().getBytes();
outputStream.write(senderName);
String lenghtPadded = pad(6, object.getMessage().length());
byte[] length = lenghtPadded.getBytes();
outputStream.write(length);
String lenghtPadded = pad(6, object.getMessage().length());
byte[] length = lenghtPadded.getBytes();
outputStream.write(length);
outputStream.write(object.getMessage().getBytes());
outputStream.flush();
}
outputStream.write(object.getMessage().getBytes());
outputStream.flush();
}
private String pad(int desiredLength, int length) {
return StringUtils.leftPad(Integer.toString(length), desiredLength, '0');
}
private String pad(int desiredLength, int length) {
return StringUtils.leftPad(Integer.toString(length), desiredLength, '0');
}
/**
* Convert a raw byte stream into a CustomOrder
*
* @param inputStream
* @return
* @throws IOException
*/
public CustomOrder deserialize(InputStream inputStream) throws IOException {
int orderNumber = parseOrderNumber(inputStream);
String senderName = parseSenderName(inputStream);
/**
* Convert a raw byte stream into a CustomOrder
*
* @param inputStream
* @return
* @throws IOException
*/
public CustomOrder deserialize(InputStream inputStream) throws IOException {
int orderNumber = parseOrderNumber(inputStream);
String senderName = parseSenderName(inputStream);
CustomOrder order = new CustomOrder(orderNumber, senderName);
String message = parseMessage(inputStream);
order.setMessage(message);
return order;
}
CustomOrder order = new CustomOrder(orderNumber, senderName);
String message = parseMessage(inputStream);
order.setMessage(message);
return order;
}
private String parseMessage(InputStream inputStream) throws IOException {
String lengthString = parseString(inputStream, MESSAGE_LENGTH_LENGTH);
int lengthOfMessage = Integer.valueOf(lengthString);
private String parseMessage(InputStream inputStream) throws IOException {
String lengthString = parseString(inputStream, MESSAGE_LENGTH_LENGTH);
int lengthOfMessage = Integer.valueOf(lengthString);
String message = parseString(inputStream, lengthOfMessage);
return message;
}
String message = parseString(inputStream, lengthOfMessage);
return message;
}
private String parseString(InputStream inputStream, int length) throws IOException {
StringBuilder builder = new StringBuilder();
private String parseString(InputStream inputStream, int length) throws IOException {
StringBuilder builder = new StringBuilder();
int c;
for (int i = 0; i < length; ++i) {
c = inputStream.read();
checkClosure(c);
builder.append((char)c);
}
int c;
for (int i = 0; i < length; ++i) {
c = inputStream.read();
checkClosure(c);
builder.append((char)c);
}
return builder.toString();
}
return builder.toString();
}
private String parseSenderName(InputStream inputStream) throws IOException {
return parseString(inputStream, SENDER_NAME_LENGTH);
}
private String parseSenderName(InputStream inputStream) throws IOException {
return parseString(inputStream, SENDER_NAME_LENGTH);
}
private int parseOrderNumber(InputStream inputStream) throws IOException {
String value = parseString(inputStream, ORDER_NUMBER_LENGTH);
return Integer.valueOf(value.toString());
}
private int parseOrderNumber(InputStream inputStream) throws IOException {
String value = parseString(inputStream, ORDER_NUMBER_LENGTH);
return Integer.valueOf(value.toString());
}
/**
* Check whether the byte passed in is the "closed socket" byte
* Note, I put this in here just as an example, but you could just extend the
* {@link org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer} class
* which has this method
*
* @param bite
* @throws IOException
*/
/**
* Check whether the byte passed in is the "closed socket" byte
* Note, I put this in here just as an example, but you could just extend the
* {@link org.springframework.integration.ip.tcp.serializer.AbstractByteArraySerializer} class
* which has this method
*
* @param bite
* @throws IOException
*/
protected void checkClosure(int bite) throws IOException {
if (bite < 0) {
logger.debug("Socket closed during message assembly");

View File

@@ -0,0 +1,131 @@
/*
* Copyright 2002-2012 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
*
* http://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.tcpclientserver;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import org.springframework.context.support.GenericXmlApplicationContext;
import org.springframework.core.env.MapPropertySource;
import org.springframework.integration.ip.tcp.connection.AbstractServerConnectionFactory;
import org.springframework.integration.ip.util.TestingUtilities;
import org.springframework.integration.test.util.SocketUtils;
/**
* Demonstrates the use of a gateway as an entry point into the integration flow.
* The message generated by the gateway is sent over tcp by the outbound gateway
* to the inbound gateway. In turn the inbound gateway sends the message to an
* echo service and the echoed response comes back over tcp and is returned to
* the test case for verification.
*
* The test uses explicit transformers to convert the byte array payloads to
* Strings.
*
* Several other samples are provided as JUnit test-cases:
*
* <ul>
* <li>TcpClientServerDemoWithConversionServiceTest</li>
* <li>TcpServerConnectionDeserializeTest</li>
* <li>TcpServerCustomSerializerTest</li>
* </ul>
*
* @author Gunnar Hillert
*
*/
public final class Main {
/**
* Prevent instantiation.
*/
private Main() {}
/**
* Load the Spring Integration Application Context
*
* @param args - command line arguments
*/
public static void main(final String... args) {
final Scanner scanner = new Scanner(System.in);
System.out.println("\n========================================================="
+ "\n "
+ "\n Welcome to the Spring Integration "
+ "\n TCP-Client-Server Sample! "
+ "\n "
+ "\n For more information please visit: "
+ "\n http://www.springintegration.org/ "
+ "\n "
+ "\n=========================================================" );
final GenericXmlApplicationContext context = Main.setupContext();
final SimpleGateway gateway = context.getBean(SimpleGateway.class);
final AbstractServerConnectionFactory crLfServer = context.getBean(AbstractServerConnectionFactory.class);
System.out.print("Waiting for server to accept connections...");
TestingUtilities.waitListening(crLfServer, 10000L);
System.out.println("running.\n\n");
System.out.println("Please enter some text and press <enter>: ");
System.out.println("\tNote:");
System.out.println("\t- Entering FAIL will create an exception");
System.out.println("\t- Entering q will quit the application");
System.out.print("\n");
System.out.println("\t--> Please also check out the other samples, " +
"that are provided as JUnit tests.");
System.out.println("\t--> You can also connect to the server on port '" + crLfServer.getPort() + "' using Telnet.\n\n");
while (true) {
final String input = scanner.nextLine();
if("q".equals(input.trim())) {
break;
}
else {
final String result = gateway.send(input);
System.out.println(result);
}
}
System.out.println("Exiting application...bye.");
System.exit(0);
}
public static GenericXmlApplicationContext setupContext() {
final GenericXmlApplicationContext context = new GenericXmlApplicationContext();
System.out.print("Detect open server socket...");
int availableServerSocket = SocketUtils.findAvailableServerSocket(5678);
final Map<String, Object> sockets = new HashMap<String, Object>();
sockets.put("availableServerSocket", availableServerSocket);
final MapPropertySource propertySource = new MapPropertySource("sockets", sockets);
context.getEnvironment().getPropertySources().addLast(propertySource);
System.out.println("using port " + context.getEnvironment().getProperty("availableServerSocket"));
context.load("classpath:META-INF/spring/integration/tcpClientServerDemo-context.xml");
context.registerShutdownHook();
context.refresh();
return context;
}
}

View File

@@ -1,59 +0,0 @@
/*
* Copyright 2002-2012 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
*
* http://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.tcpclientserver;
import java.util.HashMap;
import java.util.Map;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.core.env.MapPropertySource;
/**
* The configured inbound gateway uses CRLF delimited messages which means
* it works fine as a very simple Telnet server - connect using
* telnet localhost 11111 - each time you hit enter you should see your input
* echoed back, preceded by 'echo:'.
*
* Alternatively, you can also customize the port by providing an additional system
* property at startup e.g. <i>-DavailableServerSocket=7777</i>
*
* @author Gary Russell
* @author Gunnar Hillert
*
*/
public class TelnetServer {
public static void main(String[] args) throws Exception {
ConfigurableApplicationContext context = new ClassPathXmlApplicationContext(new String[]{"/META-INF/spring/integration/tcpClientServerDemo-context.xml"}, false);
final Map<String, Object> sockets = new HashMap<String, Object>();
sockets.put("availableServerSocket", 11111);
final MapPropertySource propertySource = new MapPropertySource("sockets", sockets);
context.getEnvironment().getPropertySources().addLast(propertySource);
context.refresh();
System.out.println("Use telnet and connect to port: " + context.getEnvironment().getProperty("availableServerSocket"));
System.out.println("Press Enter/Return in the console to exit");
System.in.read();
System.out.println("exiting application...bye.\n\n");
System.exit(0);
}
}

View File

@@ -44,7 +44,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* assert that the payload, once it reaches a component (in this case, the message listener
* we create and attach to the incomingServerChannel), does not have any of the Stx/Etx bytes.
*
* @author: ceposta
* @author Christian Posta
* @author Gunnar Hillert
*/
@RunWith(SpringJUnit4ClassRunner.class)

View File

@@ -50,7 +50,7 @@ import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
* the Java socket API on the front end (client) and the Spring Integration TCP inbound
* gateway with the custom serializer/deserializers.
*
* @author ceposta
* @author Christian Posta
* @author Gunnar Hillert
*
*/

View File

@@ -26,9 +26,7 @@ import org.springframework.test.context.MergedContextConfiguration;
import org.springframework.test.context.support.GenericXmlContextLoader;
/**
*
* @author Gunnar Hillert
*
*/
public class CustomTestContextLoader extends GenericXmlContextLoader {
@@ -44,7 +42,7 @@ public class CustomTestContextLoader extends GenericXmlContextLoader {
sockets.put("availableServerSocket", availableServerSocket);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Available Server Socket 1: " + availableServerSocket);
LOGGER.info("Available server socket: " + availableServerSocket);
}
final MapPropertySource propertySource = new MapPropertySource("sockets", sockets);