Skip to content

Commit

Permalink
#134 Fix message origin of heartbeat message (#146)
Browse files Browse the repository at this point in the history
* ✨ #134 Fix message origin of heartbeat message

- Require MessageOrigin to construct J8583MessageFactory
- IdleEventHandler sends message with default MessageOrigin

* 💎 dependency definitions
  • Loading branch information
kpavlov committed Feb 8, 2022
1 parent ac8abfa commit 9ac2e50
Show file tree
Hide file tree
Showing 9 changed files with 83 additions and 45 deletions.
16 changes: 10 additions & 6 deletions README.md
Expand Up @@ -63,7 +63,7 @@ Now you may use ISO8583 client or server in your code.
The minimal client workflow includes:

~~~java
var messageFactory = new J8583MessageFactory<>();// [1]
var messageFactory = new J8583MessageFactory<>(ISO8583Version.V1987, MessageOrigin.OTHER);// [1]
Iso8583Client<IsoMessage> client = new Iso8583Client<>(messageFactory);// [2]

client.addMessageListener(new IsoMessageListener<IsoMessage>() { // [3]
Expand All @@ -88,12 +88,15 @@ if (client.isConnected()) { // [7]
client.shutdown();// [11]
~~~

1. First you need to create a `MessageFactory`
2. Then you create a [`Iso8583Client`][Iso8583Client] providing `MessageFactory` and, optionally, `SocketAddress`
1. First you need to create a `MessageFactory`. You MUST specify a role of your client for
originated messages, e.g. `ACQUIRER`, `ISSUER` or `OTHER`.
2. Then you create a [`Iso8583Client`][Iso8583Client] providing `MessageFactory` and,
optionally, `SocketAddress`
3. Add one or more custom [`IsoMessageListener`][IsoMessageListener]s to handle `IsoMessage`s.
4. Configure the client. You may omit this step if you're fine with default configuration.
5. Initialize a client. Now it is ready to connect.
6. Establish a connection. By default, if connection will is lost, it reconnects automatically. You may disable this behaviour or change _reconnectInterval_.
6. Establish a connection. By default, if connection will is lost, it reconnects automatically. You
may disable this behaviour or change _reconnectInterval_.
7. Verify that connection is established
8. Send `IsoMessage` asynchronously
9. Send `IsoMessage` synchronously
Expand All @@ -105,7 +108,7 @@ client.shutdown();// [11]
Typical server workflow includes:

~~~java
var messageFactory = new J8583MessageFactory<>(ConfigParser.createDefault(), ISO8583Version.V1987);// [1]
var messageFactory = new J8583MessageFactory<>(ConfigParser.createDefault(), ISO8583Version.V1987, MessageOrigin.ACQUIRER);// [1]
Iso8583Server<IsoMessage> server = new Iso8583Server<>(port, messageFactory);// [2]

server.addMessageListener(new IsoMessageListener<IsoMessage>() { // [3]
Expand All @@ -123,7 +126,8 @@ if (server.isStarted()) { // [7]
server.shutdown();// [8]
~~~

1. First you need to create a `MessageFactory`
1. First you need to create a `MessageFactory`. You MUST specify a role of your server for
originated messages, e.g. `ACQUIRER`, `ISSUER` or `OTHER`.
2. Then you create a [`Iso8583Server`][Iso8583Server] providing `MessageFactory` and port to bind to
3. Add one or more custom [`IsoMessageListener`][IsoMessageListener]s to handle `IsoMessage`s.
4. Configure the server. You may omit this step if you're fine with default configuration.
Expand Down
26 changes: 15 additions & 11 deletions build.gradle.kts
Expand Up @@ -18,23 +18,27 @@ repositories {
}

dependencies {
val slf4jVersion = "1.7.35"
val junitJupiterVersion = "5.8.2"

implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
implementation(kotlin("stdlib-jdk8"))
implementation("net.sf.j8583:j8583:1.17.0")
implementation("io.netty:netty-handler:4.1.73.Final")
implementation("org.slf4j:slf4j-api:1.7.35")
implementation("com.google.code.findbugs:jsr305:3.0.2")
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2")
testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2")
api(kotlin("stdlib-jdk8"))
api("net.sf.j8583:j8583:1.17.0")
api("io.netty:netty-handler:4.1.73.Final")
api("org.slf4j:slf4j-api:$slf4jVersion")
api("com.google.code.findbugs:jsr305:3.0.2")
testImplementation(kotlin("test-junit5"))
testImplementation("org.junit.jupiter:junit-jupiter-params:$junitJupiterVersion")
testImplementation("org.mockito:mockito-junit-jupiter:4.3.1")
testImplementation("org.apache.commons:commons-lang3:3.12.0")
testImplementation("org.assertj:assertj-core:3.22.0")
testImplementation("org.springframework:spring-context:5.3.15")
testImplementation("org.springframework:spring-test:5.3.15")
testImplementation("org.slf4j:slf4j-simple:1.7.35")
testImplementation(platform("org.springframework:spring-framework-bom:5.3.15"))
testImplementation("org.springframework:spring-context")
testImplementation("org.springframework:spring-test")
testImplementation("org.slf4j:slf4j-simple:$slf4jVersion")
testImplementation("net.jcip:jcip-annotations:1.0")
testImplementation("org.awaitility:awaitility:4.1.1")
testImplementation(kotlin("test-junit5"))
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion")
}

group = "com.github.kpavlov.jreactive8583"
Expand Down
Expand Up @@ -7,12 +7,20 @@ import java.io.UnsupportedEncodingException
import java.text.ParseException
import javax.annotation.Nonnull

/**
* @param role Role of the communicating party.
* @see MessageOrigin
*/
public open class J8583MessageFactory<T : IsoMessage> @JvmOverloads constructor(
private val messageFactory: com.solab.iso8583.MessageFactory<T> = defaultMessageFactory(),
private val isoVersion: ISO8583Version = ISO8583Version.V1987
private val isoVersion: ISO8583Version = ISO8583Version.V1987,
private val role: MessageOrigin
) : MessageFactory<T> {

public constructor(isoVersion: ISO8583Version) : this(defaultMessageFactory(), isoVersion)
public constructor(
isoVersion: ISO8583Version,
role: MessageOrigin
) : this(defaultMessageFactory(), isoVersion, role)

override fun newMessage(type: Int): T {
return messageFactory.newMessage(type)
Expand All @@ -26,6 +34,13 @@ public open class J8583MessageFactory<T : IsoMessage> @JvmOverloads constructor(
return newMessage(mtiValue(isoVersion, messageClass, messageFunction, messageOrigin))
}

override fun newMessage(
@Nonnull messageClass: MessageClass,
@Nonnull messageFunction: MessageFunction
): T {
return newMessage(mtiValue(isoVersion, messageClass, messageFunction, this.role))
}

override fun createResponse(requestMessage: T): T {
return messageFactory.createResponse(requestMessage)
}
Expand All @@ -49,5 +64,6 @@ public open class J8583MessageFactory<T : IsoMessage> @JvmOverloads constructor(
}
}

@Suppress("UNCHECKED_CAST")
private fun <T : IsoMessage> defaultMessageFactory() =
ConfigParser.createDefault() as com.solab.iso8583.MessageFactory<T>
Expand Up @@ -10,7 +10,7 @@ package com.github.kpavlov.jreactive8583.iso
@Suppress("unused")
public enum class MessageClass(internal val value: Int) {
/**
* x1xx Authorization message
* `x1xx` - Authorization message
*
*
* Determine if funds are available, get an approval but do not post
Expand All @@ -20,7 +20,7 @@ public enum class MessageClass(internal val value: Int) {
AUTHORIZATION(0x0100),

/**
* x2xx Financial messages
* `x2xx` - Financial messages
*
*
* Determine if funds are available, get an approval and post directly
Expand All @@ -29,15 +29,15 @@ public enum class MessageClass(internal val value: Int) {
FINANCIAL(0x0200),

/**
* x3xx File actions message
* `x3xx` - File actions message
*
*
* Used for hot-card, TMS and other exchanges
*/
FILE_ACTIONS(0x0300),

/**
* x4xx Reversal and chargeback messages
* `x4xx` Reversal and chargeback messages
*
*
* - Reversal (x4x0 or x4x1): Reverses the action of a previous authorization.
Expand All @@ -46,22 +46,22 @@ public enum class MessageClass(internal val value: Int) {
REVERSAL_CHARGEBACK(0x0400),

/**
* x5xx Reconciliation message
* `x5xx` - Reconciliation message
*
* Transmits settlement information message.
*/
RECONCILIATION(0x0500),

/**
* x6xx Administrative message
*
* `x6xx` - Administrative message
*
* Transmits administrative advice. Often used for failure messages
* (e.g., message reject or failure to apply).
*/
ADMINISTRATIVE(0x0600),

/**
* x7xx Fee collection messages
* `x7xx` - Fee collection messages
*/
FEE_COLLECTION(0x0700),

Expand Down
Expand Up @@ -6,6 +6,7 @@ import java.io.UnsupportedEncodingException
import java.text.ParseException

public interface MessageFactory<T> {

public fun newMessage(type: Int): T

public fun newMessage(
Expand All @@ -14,6 +15,14 @@ public interface MessageFactory<T> {
messageOrigin: MessageOrigin
): T

/**
* Creates a new message with a default message origin (i.e. role)
*/
public fun newMessage(
messageClass: MessageClass,
messageFunction: MessageFunction
): T

public fun createResponse(requestMessage: T): T
public fun createResponse(request: T, copyAllFields: Boolean): T

Expand Down
Expand Up @@ -3,32 +3,33 @@ package com.github.kpavlov.jreactive8583.netty.pipeline
import com.github.kpavlov.jreactive8583.iso.MessageClass
import com.github.kpavlov.jreactive8583.iso.MessageFactory
import com.github.kpavlov.jreactive8583.iso.MessageFunction
import com.github.kpavlov.jreactive8583.iso.MessageOrigin
import com.solab.iso8583.IsoMessage
import io.netty.channel.ChannelHandlerContext
import io.netty.channel.ChannelInboundHandlerAdapter
import io.netty.handler.timeout.IdleState
import io.netty.handler.timeout.IdleStateEvent

internal class IdleEventHandler(
private val isoMessageFactory: MessageFactory<IsoMessage>
/**
* IdleEventHandler sends heartbeats (administrative messages) when channel becomes idle,
* i.e. `IdleStateEvent` is received.
*/
internal class IdleEventHandler<T>(
private val isoMessageFactory: MessageFactory<T>
) : ChannelInboundHandlerAdapter() {

override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
if (evt is IdleStateEvent) {
if (evt.state() == IdleState.READER_IDLE || evt.state() == IdleState.ALL_IDLE) {
val echoMessage = createEchoMessage()
ctx.write(echoMessage)
ctx.flush()
}
if (evt is IdleStateEvent &&
(evt.state() == IdleState.READER_IDLE || evt.state() == IdleState.ALL_IDLE)
) {
val heartbeatMessage = createHeartbeatMessage()
ctx.write(heartbeatMessage)
ctx.flush()
}
}

private fun createEchoMessage(): IsoMessage {
private fun createHeartbeatMessage(): T {
return isoMessageFactory.newMessage(
MessageClass.NETWORK_MANAGEMENT,
MessageFunction.REQUEST,
MessageOrigin.ACQUIRER
MessageFunction.REQUEST
)
}
}
Expand Up @@ -5,6 +5,7 @@
import com.github.kpavlov.jreactive8583.iso.ISO8583Version;
import com.github.kpavlov.jreactive8583.iso.J8583MessageFactory;
import com.github.kpavlov.jreactive8583.iso.MessageFactory;
import com.github.kpavlov.jreactive8583.iso.MessageOrigin;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.impl.SimpleTraceGenerator;
import com.solab.iso8583.parse.ConfigParser;
Expand Down Expand Up @@ -52,8 +53,9 @@ private MessageFactory<IsoMessage> clientMessageFactory() throws IOException {
messageFactory.setCharacterEncoding(StandardCharsets.US_ASCII.name());
messageFactory.setUseBinaryMessages(false);
messageFactory.setAssignDate(true);
messageFactory.setTraceNumberGenerator(new SimpleTraceGenerator((int) (System
.currentTimeMillis() % 1000000)));
return new J8583MessageFactory<>(messageFactory, ISO8583Version.V1987);
messageFactory.setTraceNumberGenerator(
new SimpleTraceGenerator((int) (System.currentTimeMillis() % 1000000))
);
return new J8583MessageFactory<>(messageFactory, ISO8583Version.V1987, MessageOrigin.OTHER);
}
}
Expand Up @@ -3,6 +3,7 @@
import com.github.kpavlov.jreactive8583.iso.ISO8583Version;
import com.github.kpavlov.jreactive8583.iso.J8583MessageFactory;
import com.github.kpavlov.jreactive8583.iso.MessageFactory;
import com.github.kpavlov.jreactive8583.iso.MessageOrigin;
import com.github.kpavlov.jreactive8583.server.Iso8583Server;
import com.github.kpavlov.jreactive8583.server.ServerConfiguration;
import com.solab.iso8583.IsoMessage;
Expand Down Expand Up @@ -40,7 +41,7 @@ private MessageFactory<IsoMessage> serverMessageFactory() throws IOException {
messageFactory.setCharacterEncoding(StandardCharsets.US_ASCII.name());
messageFactory.setUseBinaryMessages(false);
messageFactory.setAssignDate(true);
return new J8583MessageFactory<>(messageFactory, ISO8583Version.V1987);
return new J8583MessageFactory<>(messageFactory, ISO8583Version.V1987, MessageOrigin.ACQUIRER);
}


Expand Down
Expand Up @@ -3,6 +3,7 @@
import com.github.kpavlov.jreactive8583.iso.ISO8583Version;
import com.github.kpavlov.jreactive8583.iso.J8583MessageFactory;
import com.github.kpavlov.jreactive8583.iso.MessageFactory;
import com.github.kpavlov.jreactive8583.iso.MessageOrigin;
import com.solab.iso8583.IsoMessage;
import com.solab.iso8583.IsoType;
import com.solab.iso8583.IsoValue;
Expand Down Expand Up @@ -34,7 +35,7 @@ public class ParseExceptionHandlerTest {

@BeforeAll
public static void beforeClass() {
messageFactory = new J8583MessageFactory<>(ISO8583Version.V1993);
messageFactory = new J8583MessageFactory<>(ISO8583Version.V1993, MessageOrigin.ACQUIRER);
}

@BeforeEach
Expand Down

0 comments on commit 9ac2e50

Please sign in to comment.