Skip to content

Commit 9ac2e50

Browse files
authored
#134 Fix message origin of heartbeat message (#146)
* ✨ #134 Fix message origin of heartbeat message - Require MessageOrigin to construct J8583MessageFactory - IdleEventHandler sends message with default MessageOrigin * 💎 dependency definitions
1 parent ac8abfa commit 9ac2e50

9 files changed

Lines changed: 83 additions & 45 deletions

File tree

README.md

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ Now you may use ISO8583 client or server in your code.
6363
The minimal client workflow includes:
6464

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

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

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

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

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

126-
1. First you need to create a `MessageFactory`
129+
1. First you need to create a `MessageFactory`. You MUST specify a role of your server for
130+
originated messages, e.g. `ACQUIRER`, `ISSUER` or `OTHER`.
127131
2. Then you create a [`Iso8583Server`][Iso8583Server] providing `MessageFactory` and port to bind to
128132
3. Add one or more custom [`IsoMessageListener`][IsoMessageListener]s to handle `IsoMessage`s.
129133
4. Configure the server. You may omit this step if you're fine with default configuration.

build.gradle.kts

Lines changed: 15 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,23 +18,27 @@ repositories {
1818
}
1919

2020
dependencies {
21+
val slf4jVersion = "1.7.35"
22+
val junitJupiterVersion = "5.8.2"
23+
2124
implementation(platform("org.jetbrains.kotlin:kotlin-bom"))
22-
implementation(kotlin("stdlib-jdk8"))
23-
implementation("net.sf.j8583:j8583:1.17.0")
24-
implementation("io.netty:netty-handler:4.1.73.Final")
25-
implementation("org.slf4j:slf4j-api:1.7.35")
26-
implementation("com.google.code.findbugs:jsr305:3.0.2")
27-
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.2")
28-
testImplementation("org.junit.jupiter:junit-jupiter-params:5.8.2")
25+
api(kotlin("stdlib-jdk8"))
26+
api("net.sf.j8583:j8583:1.17.0")
27+
api("io.netty:netty-handler:4.1.73.Final")
28+
api("org.slf4j:slf4j-api:$slf4jVersion")
29+
api("com.google.code.findbugs:jsr305:3.0.2")
30+
testImplementation(kotlin("test-junit5"))
31+
testImplementation("org.junit.jupiter:junit-jupiter-params:$junitJupiterVersion")
2932
testImplementation("org.mockito:mockito-junit-jupiter:4.3.1")
3033
testImplementation("org.apache.commons:commons-lang3:3.12.0")
3134
testImplementation("org.assertj:assertj-core:3.22.0")
32-
testImplementation("org.springframework:spring-context:5.3.15")
33-
testImplementation("org.springframework:spring-test:5.3.15")
34-
testImplementation("org.slf4j:slf4j-simple:1.7.35")
35+
testImplementation(platform("org.springframework:spring-framework-bom:5.3.15"))
36+
testImplementation("org.springframework:spring-context")
37+
testImplementation("org.springframework:spring-test")
38+
testImplementation("org.slf4j:slf4j-simple:$slf4jVersion")
3539
testImplementation("net.jcip:jcip-annotations:1.0")
3640
testImplementation("org.awaitility:awaitility:4.1.1")
37-
testImplementation(kotlin("test-junit5"))
41+
testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:$junitJupiterVersion")
3842
}
3943

4044
group = "com.github.kpavlov.jreactive8583"

src/main/kotlin/com/github/kpavlov/jreactive8583/iso/J8583MessageFactory.kt

Lines changed: 18 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,20 @@ import java.io.UnsupportedEncodingException
77
import java.text.ParseException
88
import javax.annotation.Nonnull
99

10+
/**
11+
* @param role Role of the communicating party.
12+
* @see MessageOrigin
13+
*/
1014
public open class J8583MessageFactory<T : IsoMessage> @JvmOverloads constructor(
1115
private val messageFactory: com.solab.iso8583.MessageFactory<T> = defaultMessageFactory(),
12-
private val isoVersion: ISO8583Version = ISO8583Version.V1987
16+
private val isoVersion: ISO8583Version = ISO8583Version.V1987,
17+
private val role: MessageOrigin
1318
) : MessageFactory<T> {
1419

15-
public constructor(isoVersion: ISO8583Version) : this(defaultMessageFactory(), isoVersion)
20+
public constructor(
21+
isoVersion: ISO8583Version,
22+
role: MessageOrigin
23+
) : this(defaultMessageFactory(), isoVersion, role)
1624

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

37+
override fun newMessage(
38+
@Nonnull messageClass: MessageClass,
39+
@Nonnull messageFunction: MessageFunction
40+
): T {
41+
return newMessage(mtiValue(isoVersion, messageClass, messageFunction, this.role))
42+
}
43+
2944
override fun createResponse(requestMessage: T): T {
3045
return messageFactory.createResponse(requestMessage)
3146
}
@@ -49,5 +64,6 @@ public open class J8583MessageFactory<T : IsoMessage> @JvmOverloads constructor(
4964
}
5065
}
5166

67+
@Suppress("UNCHECKED_CAST")
5268
private fun <T : IsoMessage> defaultMessageFactory() =
5369
ConfigParser.createDefault() as com.solab.iso8583.MessageFactory<T>

src/main/kotlin/com/github/kpavlov/jreactive8583/iso/MessageClass.kt

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ package com.github.kpavlov.jreactive8583.iso
1010
@Suppress("unused")
1111
public enum class MessageClass(internal val value: Int) {
1212
/**
13-
* x1xx Authorization message
13+
* `x1xx` - Authorization message
1414
*
1515
*
1616
* Determine if funds are available, get an approval but do not post
@@ -20,7 +20,7 @@ public enum class MessageClass(internal val value: Int) {
2020
AUTHORIZATION(0x0100),
2121

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

3131
/**
32-
* x3xx File actions message
32+
* `x3xx` - File actions message
3333
*
3434
*
3535
* Used for hot-card, TMS and other exchanges
3636
*/
3737
FILE_ACTIONS(0x0300),
3838

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

4848
/**
49-
* x5xx Reconciliation message
49+
* `x5xx` - Reconciliation message
50+
*
5051
* Transmits settlement information message.
5152
*/
5253
RECONCILIATION(0x0500),
5354

5455
/**
55-
* x6xx Administrative message
56-
*
56+
* `x6xx` - Administrative message
5757
*
5858
* Transmits administrative advice. Often used for failure messages
5959
* (e.g., message reject or failure to apply).
6060
*/
6161
ADMINISTRATIVE(0x0600),
6262

6363
/**
64-
* x7xx Fee collection messages
64+
* `x7xx` - Fee collection messages
6565
*/
6666
FEE_COLLECTION(0x0700),
6767

src/main/kotlin/com/github/kpavlov/jreactive8583/iso/MessageFactory.kt

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import java.io.UnsupportedEncodingException
66
import java.text.ParseException
77

88
public interface MessageFactory<T> {
9+
910
public fun newMessage(type: Int): T
1011

1112
public fun newMessage(
@@ -14,6 +15,14 @@ public interface MessageFactory<T> {
1415
messageOrigin: MessageOrigin
1516
): T
1617

18+
/**
19+
* Creates a new message with a default message origin (i.e. role)
20+
*/
21+
public fun newMessage(
22+
messageClass: MessageClass,
23+
messageFunction: MessageFunction
24+
): T
25+
1726
public fun createResponse(requestMessage: T): T
1827
public fun createResponse(request: T, copyAllFields: Boolean): T
1928

src/main/kotlin/com/github/kpavlov/jreactive8583/netty/pipeline/IdleEventHandler.kt

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,32 +3,33 @@ package com.github.kpavlov.jreactive8583.netty.pipeline
33
import com.github.kpavlov.jreactive8583.iso.MessageClass
44
import com.github.kpavlov.jreactive8583.iso.MessageFactory
55
import com.github.kpavlov.jreactive8583.iso.MessageFunction
6-
import com.github.kpavlov.jreactive8583.iso.MessageOrigin
7-
import com.solab.iso8583.IsoMessage
86
import io.netty.channel.ChannelHandlerContext
97
import io.netty.channel.ChannelInboundHandlerAdapter
108
import io.netty.handler.timeout.IdleState
119
import io.netty.handler.timeout.IdleStateEvent
1210

13-
internal class IdleEventHandler(
14-
private val isoMessageFactory: MessageFactory<IsoMessage>
11+
/**
12+
* IdleEventHandler sends heartbeats (administrative messages) when channel becomes idle,
13+
* i.e. `IdleStateEvent` is received.
14+
*/
15+
internal class IdleEventHandler<T>(
16+
private val isoMessageFactory: MessageFactory<T>
1517
) : ChannelInboundHandlerAdapter() {
1618

1719
override fun userEventTriggered(ctx: ChannelHandlerContext, evt: Any) {
18-
if (evt is IdleStateEvent) {
19-
if (evt.state() == IdleState.READER_IDLE || evt.state() == IdleState.ALL_IDLE) {
20-
val echoMessage = createEchoMessage()
21-
ctx.write(echoMessage)
22-
ctx.flush()
23-
}
20+
if (evt is IdleStateEvent &&
21+
(evt.state() == IdleState.READER_IDLE || evt.state() == IdleState.ALL_IDLE)
22+
) {
23+
val heartbeatMessage = createHeartbeatMessage()
24+
ctx.write(heartbeatMessage)
25+
ctx.flush()
2426
}
2527
}
2628

27-
private fun createEchoMessage(): IsoMessage {
29+
private fun createHeartbeatMessage(): T {
2830
return isoMessageFactory.newMessage(
2931
MessageClass.NETWORK_MANAGEMENT,
30-
MessageFunction.REQUEST,
31-
MessageOrigin.ACQUIRER
32+
MessageFunction.REQUEST
3233
)
3334
}
3435
}

src/test/java/com/github/kpavlov/jreactive8583/example/client/Iso8583ClientConfig.java

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
import com.github.kpavlov.jreactive8583.iso.ISO8583Version;
66
import com.github.kpavlov.jreactive8583.iso.J8583MessageFactory;
77
import com.github.kpavlov.jreactive8583.iso.MessageFactory;
8+
import com.github.kpavlov.jreactive8583.iso.MessageOrigin;
89
import com.solab.iso8583.IsoMessage;
910
import com.solab.iso8583.impl.SimpleTraceGenerator;
1011
import com.solab.iso8583.parse.ConfigParser;
@@ -52,8 +53,9 @@ private MessageFactory<IsoMessage> clientMessageFactory() throws IOException {
5253
messageFactory.setCharacterEncoding(StandardCharsets.US_ASCII.name());
5354
messageFactory.setUseBinaryMessages(false);
5455
messageFactory.setAssignDate(true);
55-
messageFactory.setTraceNumberGenerator(new SimpleTraceGenerator((int) (System
56-
.currentTimeMillis() % 1000000)));
57-
return new J8583MessageFactory<>(messageFactory, ISO8583Version.V1987);
56+
messageFactory.setTraceNumberGenerator(
57+
new SimpleTraceGenerator((int) (System.currentTimeMillis() % 1000000))
58+
);
59+
return new J8583MessageFactory<>(messageFactory, ISO8583Version.V1987, MessageOrigin.OTHER);
5860
}
5961
}

src/test/java/com/github/kpavlov/jreactive8583/example/server/Iso8583ServerConfig.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.github.kpavlov.jreactive8583.iso.ISO8583Version;
44
import com.github.kpavlov.jreactive8583.iso.J8583MessageFactory;
55
import com.github.kpavlov.jreactive8583.iso.MessageFactory;
6+
import com.github.kpavlov.jreactive8583.iso.MessageOrigin;
67
import com.github.kpavlov.jreactive8583.server.Iso8583Server;
78
import com.github.kpavlov.jreactive8583.server.ServerConfiguration;
89
import com.solab.iso8583.IsoMessage;
@@ -40,7 +41,7 @@ private MessageFactory<IsoMessage> serverMessageFactory() throws IOException {
4041
messageFactory.setCharacterEncoding(StandardCharsets.US_ASCII.name());
4142
messageFactory.setUseBinaryMessages(false);
4243
messageFactory.setAssignDate(true);
43-
return new J8583MessageFactory<>(messageFactory, ISO8583Version.V1987);
44+
return new J8583MessageFactory<>(messageFactory, ISO8583Version.V1987, MessageOrigin.ACQUIRER);
4445
}
4546

4647

src/test/java/com/github/kpavlov/jreactive8583/netty/pipeline/ParseExceptionHandlerTest.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import com.github.kpavlov.jreactive8583.iso.ISO8583Version;
44
import com.github.kpavlov.jreactive8583.iso.J8583MessageFactory;
55
import com.github.kpavlov.jreactive8583.iso.MessageFactory;
6+
import com.github.kpavlov.jreactive8583.iso.MessageOrigin;
67
import com.solab.iso8583.IsoMessage;
78
import com.solab.iso8583.IsoType;
89
import com.solab.iso8583.IsoValue;
@@ -34,7 +35,7 @@ public class ParseExceptionHandlerTest {
3435

3536
@BeforeAll
3637
public static void beforeClass() {
37-
messageFactory = new J8583MessageFactory<>(ISO8583Version.V1993);
38+
messageFactory = new J8583MessageFactory<>(ISO8583Version.V1993, MessageOrigin.ACQUIRER);
3839
}
3940

4041
@BeforeEach

0 commit comments

Comments
 (0)