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:
committed by
Gunnar Hillert
parent
9eaea90170
commit
375cc41b1a
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
}
|
||||
@@ -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
|
||||
*
|
||||
*/
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user