INTSAMPLES-22: Create sample for inbound tcp-connection-factory using deserializer and serializer

* Added new tests
* Added comments
* Created the custom serializer/deserializer. Fixing some small issues with it
* Changed the sending to use java socket api
* Working, just trying to figure out junit differences
* Found an error in the input message (contained \r\n) fixed now
* added .idea and activemq-data folders to ignore
* Cleaned up unused code, added documentation to the README.md
* Added more comments
* Added Copyright notices
This commit is contained in:
Christian Posta
2012-02-03 23:48:20 -07:00
committed by Gunnar Hillert
parent 9eaea90170
commit 375cc41b1a
15 changed files with 554 additions and 12 deletions

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2011 the original author or authors.
* 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.

View File

@@ -0,0 +1,54 @@
/*
* 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 org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.commons.lang.builder.ToStringStyle;
/**
* @author: ceposta
*/
public class CustomOrder {
private int number;
private String sender;
private String message;
public CustomOrder(int number, String sender) {
this.number = number;
this.sender = sender;
}
public int getNumber() {
return number;
}
public String getSender() {
return sender;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
@Override
public String toString() {
return ToStringBuilder.reflectionToString(this, ToStringStyle.MULTI_LINE_STYLE);
}
}

View File

@@ -0,0 +1,149 @@
/*
* 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.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.core.serializer.Deserializer;
import org.springframework.core.serializer.Serializer;
/**
* This class is used to demonstrate how you can create a custom serializer/deserializer
* to convert a TCP stream into custom objects which your domain-specific code can use.
*
* Since this is custom, it will have to have its own predefined assumptions for dealing
* with the stream. In other words, there will have to be some indication within the
* contents of the stream where the beginning/end is and how to extract the contents
* into something meaningful (like an object). An example would be a fixed-file formatted
* stream with the length encoded in some well known part of the stream (for example, the
* first 8 bytes of the stream?).
*
* This custom serializer/deserializer assumes the first 3 bytes of the stream will be
* considered an Order Number, the next 10 bytes will be the Sender's Name, the next 6 bytes
* represents an left-zero-padded integer that specifies how long the rest of the message
* 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
* assumptions in place for the communication to take place.
*
*
* @author: ceposta
*/
public class CustomSerializerDeserializer implements Serializer<CustomOrder>, Deserializer<CustomOrder>{
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;
/**
* 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);
String lenghtPadded = pad(6, object.getMessage().length());
byte[] length = lenghtPadded.getBytes();
outputStream.write(length);
outputStream.write(object.getMessage().getBytes());
outputStream.flush();
}
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);
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);
String message = parseString(inputStream, lengthOfMessage);
return message;
}
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);
}
return builder.toString();
}
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());
}
/**
* 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");
throw new IOException("Socket closed during message assembly");
}
}
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -18,7 +18,7 @@ package org.springframework.integration.samples.tcpclientserver;
/**
* Simple service that receives data in a byte array,
* converts it to a String and appends it to 'echo:'.
*
*
* @author Gary Russell
*
*/

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -22,5 +22,5 @@ package org.springframework.integration.samples.tcpclientserver;
public interface SimpleGateway {
public String send(String text);
}

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2010 the original author or authors.
* 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.
@@ -22,7 +22,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext;
* 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:'.
*
*
* @author Gary Russell
*
*/

View File

@@ -0,0 +1,66 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-ip="http://www.springframework.org/schema/integration/ip"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip-2.0.xsd">
<!-- Client side -->
<int:gateway id="gw"
service-interface="org.springframework.integration.samples.tcpclientserver.SimpleGateway"
default-request-channel="input"/>
<!-- Create a connection for the client gateway that uses the same Stx-Etx deserializer to turn the stream
into the appropriate content (it looks for the Stx byte, extracts anything between it and the Etx byte). We
don't specify the serializer (although we could) because the unit test explicitly shows how the content
to be sent is wrapped by the Stx and Etx bytes. -->
<int-ip:tcp-connection-factory id="client"
type="client"
host="localhost"
port="22222"
single-use="true"
so-timeout="10000"
deserializer="connectionSerializeDeserialize"
/>
<int:channel id="input" />
<int-ip:tcp-outbound-gateway id="outGateway"
request-channel="input"
reply-channel="clientBytes2StringChannel"
connection-factory="client"
request-timeout="10000"
reply-timeout="10000"
/>
<int:channel id="clientBytes2StringChannel"/>
<int:transformer id="clientBytes2String"
input-channel="clientBytes2StringChannel"
expression="new String(payload)"/>
<!-- Server side -->
<!-- When creating the socket factory on the server side, we specify both the serializer and deserializer
which deals with both accepting a stream formatted with the Stx-Etx bytes as well as sending a stream
formatted with the Stx-Etx bytes. -->
<int-ip:tcp-connection-factory id="serverConnectionFactory"
type="server"
port="22222"
serializer="connectionSerializeDeserialize"
deserializer="connectionSerializeDeserialize"/>
<bean id="connectionSerializeDeserialize" class="org.springframework.integration.ip.tcp.serializer.ByteArrayStxEtxSerializer"/>
<int-ip:tcp-inbound-gateway id="gatewayCrLf"
connection-factory="serverConnectionFactory"
request-channel="incomingServerChannel"
error-channel="errorChannel"/>
<!-- We leave a message listener off of this channel on purpose because we hook
one up before the test actually runs (see the unit test associated with this
context file) -->
<int:channel id="incomingServerChannel" />
</beans>

View File

@@ -0,0 +1,34 @@
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:int="http://www.springframework.org/schema/integration"
xmlns:int-ip="http://www.springframework.org/schema/integration/ip"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/integration http://www.springframework.org/schema/integration/spring-integration.xsd http://www.springframework.org/schema/integration/ip http://www.springframework.org/schema/integration/ip/spring-integration-ip-2.0.xsd">
<!-- Server side -->
<!-- When creating the socket factory on the server side, we specify both the serializer and deserializer
which deals with both accepting a stream formatted with the Stx-Etx bytes as well as sending a stream
formatted with the Stx-Etx bytes. -->
<int-ip:tcp-connection-factory id="serverConnectionFactory"
type="server"
port="11111"
single-use="true"
so-linger="10000"
serializer="connectionSerializeDeserialize"
deserializer="connectionSerializeDeserialize"/>
<bean id="connectionSerializeDeserialize" class="org.springframework.integration.samples.tcpclientserver.CustomSerializerDeserializer"/>
<int-ip:tcp-inbound-gateway id="gatewayCrLf"
connection-factory="serverConnectionFactory"
request-channel="incomingServerChannel"
error-channel="errorChannel"/>
<!-- We leave a message listener off of this channel on purpose because we hook
one up before the test actually runs (see the unit test associated with this
context file) -->
<int:channel id="incomingServerChannel" />
</beans>

View File

@@ -15,6 +15,10 @@
<level value="warn" />
</logger>
<logger name="org.springframework.integration">
<level value="warn" />
</logger>
<logger name="org.springframework.integration.samples">
<level value="debug" />
</logger>
@@ -24,5 +28,5 @@
<priority value="warn" />
<appender-ref ref="console" />
</root>
</log4j:configuration>