Creating ISO8583 Server using Spring Integration
Hi, in this post we are going to talk about Creating ISO8583 Server using Spring Integration, i know there is library called jPOS that has complete feature regarding financial transaction, especially ISO8583 messaging. But jPOS isn't free for commercial purposes, you need to buy its licence or make your code open source (i don't think that's possible for commercial purposes).
Some Developers combine jPOS with Spring Application on a single project, that could work, yeah! But personally i don't like it, the difference in design pattern between Pull Configuration used by jPOS and Dependency Injection by Spring, our code will be more readable and clean if we are using one Pattern on our project. Well, that's what I thought!
Instead of creating ISO8583 Server using jPOS and processing it with Spring code, we will Create ISO8583 Server using Spring Integration. More about Spring Integration Here.
Server Configuration:
-
Server Connection Factory with Serializer and DeserializerWe are going to user TCP Nio Server Connection Factory and set the Serializer and Deserializer
-
TCP Inbound GatewayUsed for mapping request messages into message channels
-
Message ChannelWe are going to user DirectChannel, this is default channel implementation by Spring Integration
-
Message TransformerUsed to transform byte message into ISO8583 Format
-
Service ActivatorEndpoint for connecting a service instance to the messaging system
Dependency:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-integration</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.integration</groupId>
<artifactId>spring-integration-ip</artifactId>
</dependency>
Serializer and Deselializer:
Deselializer will get a message from the stream based on specification, below example will get message based on message length indicator (header). The readHeader method will read 4 first bytes from the stream to get the message body, while the Serializer call writeHeader method will put a message length indicator into the stream.
public class AsciiSerializer extends ByteArrayLengthHeaderSerializer {
private static final BigInteger ten = BigInteger.valueOf(10L);
public AsciiSerializer(boolean headerIncluded) {
super(HEADER_SIZE_INT);
setInclusive(headerIncluded);
}
@Override
protected int readHeader(InputStream inputStream) throws IOException {
byte[] lengthPart = new byte[HEADER_SIZE_INT];
int status = read(inputStream, lengthPart, true);
if (status < 0)
throw new SoftEndOfStreamException("Stream closed between payloads");
return Integer.valueOf(new String(lengthPart));
}
@Override
protected void writeHeader(OutputStream outputStream, int len) throws IOException {
int maxLen = ten.pow(HEADER_SIZE_INT).intValue() - 1; // 10^lengthDigits - 1
if (len > maxLen)
throw new IOException("len exceeded (" + len + " > " + maxLen + ")");
else if (len < 0)
throw new IOException("invalid negative length (" + len + ")");
outputStream.write(StringUtils.leftPad(String.valueOf(len), HEADER_SIZE_INT, "0").getBytes());
}
}
After creating the Serializers, the next step is to configure the Server Connection Factory to listen to specific ports. We are using TcpNioServerConnectionFactory TcpNio" refers to a TCP connection factory that utilizes Java's NIO (New I/O) framework for network communication, enabling asynchronous, non-blocking I/O.
@Bean
@Qualifier(value = "billerASCIIServerCF")
public AbstractServerConnectionFactory serverASCII_CF(@Value("${biller.ascii.server.port}") int port) {
TcpNioServerConnectionFactory connectionFactory = new TcpNioServerConnectionFactory(port);
connectionFactory.setSerializer(new AsciiSerializer(false));
connectionFactory.setDeserializer(new AsciiSerializer(false));
return connectionFactory;
}
After creating the Server Connection Factory, we need to configure the Tcp Inbound Gateway. This gateway will put request messages into the Message Channel to be processed by the Transformer.
@Bean
public TcpInboundGateway tcpASCIIInGate(@Qualifier("billerASCIIServerCF") AbstractServerConnectionFactory connectionFactory) {
TcpInboundGateway inGate = new TcpInboundGateway();
inGate.setConnectionFactory(connectionFactory);
inGate.setRequestChannel(fromTcp());
return inGate;
}
Message Endpoint:
Consist of Message Transformer and Service Activator. Message Transformer will transform the Message body in bytes into ISO8583 message format. Service Activator will calling our Service instance to process the messages.
@Slf4j
@MessageEndpoint
public class BillerSimulatorEndpoint {
@Autowired
private MessageFactory<CustomIsoMessage> messageFactory;
@Autowired
private DynamicBillerResponseService dynamicBillerResponseService;
@Transformer(inputChannel="fromTcp", outputChannel="toHandler")
public TransactionContext convert(Message<byte[]> messages) {
String clientIpAddress = (String) messages.getHeaders().get("ip_address");
Integer clientPort = (Integer) messages.getHeaders().get("ip_tcp_remotePort");
String connectionId = ((UUID) messages.getHeaders().get("id")).toString();
Long timestamp = (Long) messages.getHeaders().get("timestamp");
byte[] payLoad = messages.getPayload();
log.debug("fromTcp-toHandler [{}]", connectionId);
TransactionContext ctx = TransactionContext.builder()
.clientIpAddress(clientIpAddress)
.clientPort(clientPort)
.rawReqMsg(payLoad)
.connectionId(connectionId)
.build();
if (payLoad.length != 0) {
try {
CustomIsoMessage reqMsg = messageFactory.parseMessage(payLoad, 0);
log.info("Receive Request [{}]", reqMsg.dumpField(true, connectionId,
clientIpAddress, String.valueOf(clientPort), timestamp));
ctx.setReqMsg(reqMsg);
return ctx;
} catch (Exception e) {
log.error("Error when processing message", e);
}
}
return null;
}
@ServiceActivator(inputChannel="toHandler")
public byte[] handle(@Payload TransactionContext ctx) throws Exception {
CustomIsoMessage respMsg = messageFactory.createResponse(ctx.getReqMsg());
String additionalInfo = ctx.getReqMsg().getObjectValue(48);
String productCode = StringUtils.substring(additionalInfo, 0, 6);
BillerResponseService billerResponseService = dynamicBillerResponseService.getBillerResponseServiceImpl(productCode);
billerResponseService.handle(ctx.getReqMsg(), respMsg);
log.info("Sending Response [{}]", respMsg.dumpField(false, ctx.getConnectionId(),
ctx.getClientIpAddress(), String.valueOf(ctx.getClientPort()), System.currentTimeMillis()));
return respMsg.writeData();
}
}
All you need to do is replace dynamicBillerResponseService with your own service implementation. I'm creating a TransactionContext. This class's main purpose is to hold some useful data that may be required on the next processing.
That's all about Creating ISO8583 Server using Spring Integration. I plan to share the code with its sample running application on the next post, so stay tuned.
Have a good day!
Comments
Post a Comment