/*
* 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. Either way, since its custom, the client and server must have some predefined
* assumptions in place for the communication to take place.
*
*
* @author Christian Posta
* @author Gunnar Hillert
*/
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");
}
}
}