Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

#143 Fix message origin of heartbeat message #146

Merged
merged 3 commits into from
Feb 8, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 10 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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>
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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
)
}
}
Original file line number Diff line number Diff line change
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);
}
}
Original file line number Diff line number Diff line change
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
Original file line number Diff line number Diff line change
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