Skip to content
Closed
Show file tree
Hide file tree
Changes from 7 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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ nb-configuration.xml
*.bak
# ignore vim swap files
*.swp
.sdkmanrc
16 changes: 8 additions & 8 deletions customising-quickfixj.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ The specification for a FIX integration is called a "Rules of Engagement". The R

The message, component and field implementations can be provided by a specialised build, along with the corresponding QuickFIX/J dictionary for the custom Rules of Engagement.

The standard distribution of ```quickfixj-core``` can be used with custom artefacts. You need only build artefacts for versions of the Protocol that you use. These can be maintained independently from the QuickFIX/J project, while depending on the QuickFIX/J for the core functionality and tools.
The standard distribution of ```quickfixj-core``` can be used with custom artifacts. You need only build artifacts for versions of the Protocol that you use. These can be maintained independently from the QuickFIX/J project, while depending on the QuickFIX/J for the core functionality and tools.

To build custom artefacts it's helpful to understand how QuickFIX/J builds the Field, Component and Message classes from the QuickFIX/J dictionaries and from [FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/).
To build custom artifacts it's helpful to understand how QuickFIX/J builds the Field, Component and Message classes from the QuickFIX/J dictionaries and from [FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/).

The QuickFIX/J reference implementations for FIX versions FIX4.0 to FIX5.0sp2 and for FIXT1.1 are generated from the QuickFIX dictionaries for the specific version. The dictionaries are located in the ```src/main/resources``` directory of the respective modules of the ```quickfixj-messages``` module.
Maintaining the FIX4.0 to FIX5.0sp2 builds intentionally provides consistency with the prior QuickFIX/J 2 release in order to ease migration to QuickFIX/J 3.
Expand All @@ -18,7 +18,7 @@ The most recent standard is defined as [FIX Latest](https://www.fixtrading.org/o
An implementation or customisation of the FIX Standars derived from the FIX Orchestra repository is known as an "_orchestration_".
The standard FIX Orchestra repository requires some modification to work well with QuickFIX/J.
This is done by the ```quickfixj-orchestration``` module.
The ```quickfixj-orchestration``` module publishes a modified Orchestra artefact which can then be the basis of a custom FIX Latest build using QuickFIX/J .
The ```quickfixj-orchestration``` module publishes a modified Orchestra artifact which can then be the basis of a custom FIX Latest build using QuickFIX/J .

The complete reference FIX Latest specification results in a very large distribution.
To use FIX Latest, customisation of the [FIX Orchestra](https://www.fixtrading.org/standards/fix-orchestra/) repository is advisable.
Expand All @@ -30,7 +30,7 @@ Please see [QuickFIX/J Orchestration](./quickfixj-orchestration/readme.md) for d

This behaviour is controlled by the ```${generator.decimal}``` build property. It is "false" by default to avoid surprising side effects of incompatible data types.

To enable the use of ```BigDecimal``` in code generation, set the ```${generator.decimal}``` property to "true" in [quickfixj-messages](./quickfixj-messages/readme.md) and build the message artefacts.
To enable the use of ```BigDecimal``` in code generation, set the ```${generator.decimal}``` property to "true" in [quickfixj-messages](./quickfixj-messages/readme.md) and build the message artifacts.

```
<properties>
Expand All @@ -56,13 +56,13 @@ Runtime incompatibilities can be resolved by:
* Amending the QuickFIX Dictionary to coerce the code generation and/or validation
* Changing the ordering of code generation and/or overwrite behaviour of code generation
* Omitting incompatible versions from your customised build
* Building artefacts independently for the conflicting versions and ensuring they are not used them in the same runtime
* Building artifacts independently for the conflicting versions and ensuring they are not used them in the same runtime

See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the build and recommendation for **how to implement custom builds.**

### **Customising the FIX Protocol for specialised Rules of Engagement**

A Rules of Engagement can include customisation Messages, Components and Fields, including User Defined elements.
A Rules of Engagement can include customised Messages, Components and Fields, including User Defined elements.

It is not necessary to maintain a fork of the entire QuickFIX/J project to provide customised QuickFIX Dictionaries and to
generate type-safe libraries that are interoperable with QuickFIX/J.
Expand All @@ -82,11 +82,11 @@ See [QuickFIX/J Messages](./quickfixj-messages/readme.md) for details of the bui

From QuickFIX/J 3.0.0 the code generation for ```quickfix.Field``` prefers the FIX Orchestra Standard. This results in incompatible changes to the names of constants.

For example : ```SettlType.REGULAR_FX_SPOT_SETTLEMENT``` becomes ```SettlType.REGULAR```.
For example : ```SettlmntTyp.REGULAR_FX_SPOT_SETTLEMENT``` becomes ```SettlmntTyp.REGULAR```.

The required code changes may be trivial in most cases, but changes are elective.
The following describes how to use ```quickfixj-core``` from QuickFIX/J 3 without needing to implement code changes:
* build the required Message artefacts without the FIX Latest code generation. The Fields will then be generated only from legacy FIX Protocol Versions as they were prior to QuickFIX/J 3.0.0 - **or**
* build the required Message artifacts without the FIX Latest code generation. The Fields will then be generated only from legacy FIX Protocol Versions as they were prior to QuickFIX/J 3.0.0 - **or**
* if you want to use Messages, Components and/or Fields from FIX Latest while preferring legacy constants,
manipulate the order of code generation and/or the over-write behaviour of code behaviour to prefer earlier versions of FIX.
For example, generate FIX Latest first and overwrite the generated Field classes by subsequently running code generation for an earlier version.
Expand Down
240 changes: 119 additions & 121 deletions quickfixj-core/src/main/java/quickfix/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -2186,151 +2186,149 @@ private void nextLogon(Message logon) throws FieldNotFound, RejectLogon, Incorre
}
}
}
state.lockSenderMsgSeqNum();
try {
// QFJ-926 - reset session before accepting Logon
resetIfSessionNotCurrent(sessionID, SystemTime.currentTimeMillis());

// QFJ-926 - reset session before accepting Logon
resetIfSessionNotCurrent(sessionID, SystemTime.currentTimeMillis());

if (refreshOnLogon) {
refreshState();
}

if (logon.isSetField(ResetSeqNumFlag.FIELD)) {
state.setResetReceived(logon.getBoolean(ResetSeqNumFlag.FIELD));
} else if (state.isResetSent() && logon.getHeader().getInt(MsgSeqNum.FIELD) == 1) { // QFJ-383
getLog().onEvent(
"Inferring ResetSeqNumFlag as sequence number is 1 in response to reset request");
state.setResetReceived(true);
}
if (refreshOnLogon) {
refreshState();
}

if (!verify(logon, false, validateSequenceNumbers)) {
return;
}
if (logon.isSetField(ResetSeqNumFlag.FIELD)) {
state.setResetReceived(logon.getBoolean(ResetSeqNumFlag.FIELD));
} else if (state.isResetSent() && logon.getHeader().getInt(MsgSeqNum.FIELD) == 1) { // QFJ-383
getLog().onEvent(
"Inferring ResetSeqNumFlag as sequence number is 1 in response to reset request");
state.setResetReceived(true);
}

if (state.isResetReceived()) {
getLog().onEvent("Logon contains ResetSeqNumFlag=Y, resetting sequence numbers to 1");
if (!state.isResetSent()) {
resetState();
if (!verify(logon, false, validateSequenceNumbers)) {
return;
}
}

if (state.isLogonSendNeeded() && !state.isResetReceived()) {
disconnect("Received logon response before sending request", true);
return;
}
if (state.isResetReceived()) {
getLog().onEvent("Logon contains ResetSeqNumFlag=Y, resetting sequence numbers to 1");
if (!state.isResetSent()) {
resetState();
}
}

if (!state.isInitiator() && resetOnLogon) {
resetState();
}
if (state.isLogonSendNeeded() && !state.isResetReceived()) {
disconnect("Received logon response before sending request", true);
return;
}

if (!state.isInitiator() && resetOnLogon) {
resetState();
}

// reset logout messages
state.setLogoutReceived(false);
state.setLogoutSent(false);
state.setLogonReceived(true);

// remember the expected sender sequence number of any logon response for future use
final int nextSenderMsgNumAtLogonReceived = state.getMessageStore().getNextSenderMsgSeqNum();
final int sequence = logon.getHeader().getInt(MsgSeqNum.FIELD);
// reset logout messages
state.setLogoutReceived(false);
state.setLogoutSent(false);
state.setLogonReceived(true);

/*
* We test here that it's not too high (which would result in a resend) and that we are not
* resetting on logon 34=1
*/
final boolean isLogonInNormalSequence = !(isTargetTooHigh(sequence) && !resetOnLogon);
// if we have a tag 789 sent to us...
if (logon.isSetField(NextExpectedMsgSeqNum.FIELD) && enableNextExpectedMsgSeqNum) {
// remember the expected sender sequence number of any logon response for future use
final int nextSenderMsgNumAtLogonReceived = state.getMessageStore().getNextSenderMsgSeqNum();
final int sequence = logon.getHeader().getInt(MsgSeqNum.FIELD);

final int targetWantsNextSeqNumToBe = logon.getInt(NextExpectedMsgSeqNum.FIELD);
state.lockSenderMsgSeqNum();
final int actualNextNum;
try {
actualNextNum = state.getNextSenderMsgSeqNum();
} finally {
state.unlockSenderMsgSeqNum();
}
// Is the 789 we received too high ??
if (targetWantsNextSeqNumToBe > actualNextNum) {
// barf! we can't resend what we never sent! something unrecoverable has happened.
final String err = "Tag " + NextExpectedMsgSeqNum.FIELD
+ " (NextExpectedMsgSeqNum) is higher than expected. Expected "
+ actualNextNum + ", Received " + targetWantsNextSeqNumToBe;
generateLogout(err);
disconnect(err, true);
return;
}
}
getLog().onEvent("Received logon");
if (!state.isInitiator()) {
/*
* If we got one too high they need messages resent use the first message they missed (as we gap fill with that).
* If we reset on logon, the current value will be 1 and we always send 2 (we haven't inc'd for current message yet +1)
* If happy path (we haven't inc'd for current message yet so its current +1)
* We test here that it's not too high (which would result in a resend) and that we are not
* resetting on logon 34=1
*/
int nextExpectedTargetNum = state.getMessageStore().getNextTargetMsgSeqNum();
// we increment for the logon later (after Logon response sent) in this method if and only if in sequence
if (isLogonInNormalSequence) {
// logon was fine take account of it in 789
nextExpectedTargetNum++;
final boolean isLogonInNormalSequence = !(isTargetTooHigh(sequence) && !resetOnLogon);
// if we have a tag 789 sent to us...
if (logon.isSetField(NextExpectedMsgSeqNum.FIELD) && enableNextExpectedMsgSeqNum) {

final int targetWantsNextSeqNumToBe = logon.getInt(NextExpectedMsgSeqNum.FIELD);
final int actualNextNum = state.getNextSenderMsgSeqNum();
// Is the 789 we received too high ??
if (targetWantsNextSeqNumToBe > actualNextNum) {
// barf! we can't resend what we never sent! something unrecoverable has happened.
final String err = "Tag " + NextExpectedMsgSeqNum.FIELD
+ " (NextExpectedMsgSeqNum) is higher than expected. Expected "
+ actualNextNum + ", Received " + targetWantsNextSeqNumToBe;
generateLogout(err);
disconnect(err, true);
return;
}
}
getLog().onEvent("Received logon");
if (!state.isInitiator()) {
/*
* If we got one too high they need messages resent use the first message they missed (as we gap fill with that).
* If we reset on logon, the current value will be 1 and we always send 2 (we haven't inc'd for current message yet +1)
* If happy path (we haven't inc'd for current message yet so its current +1)
*/
int nextExpectedTargetNum = state.getMessageStore().getNextTargetMsgSeqNum();
// we increment for the logon later (after Logon response sent) in this method if and only if in sequence
if (isLogonInNormalSequence) {
// logon was fine take account of it in 789
nextExpectedTargetNum++;
}
generateLogon(logon, nextExpectedTargetNum);
}
generateLogon(logon, nextExpectedTargetNum);
}

// Check for proper sequence reset response
if (state.isResetSent() && !state.isResetReceived()) {
disconnect("Expected Logon response to have reset sequence numbers in response to ResetSeqNumFlag", true);
return;
}
// Check for proper sequence reset response
if (state.isResetSent() && !state.isResetReceived()) {
disconnect("Expected Logon response to have reset sequence numbers in response to ResetSeqNumFlag", true);
return;
}

state.setResetSent(false);
state.setResetReceived(false);
state.setResetSent(false);
state.setResetReceived(false);

// Looking at the sequence number of the incoming Logon, is it too high indicating possible missed messages ? ..
if (!isLogonInNormalSequence) {
// if 789 was sent then we effectively have already sent a resend request
if (state.isExpectedLogonNextSeqNumSent()) {
// Mark state as if we have already sent a resend request from the logon's 789 (we sent) to infinity.
// This will supress the resend request in doTargetTooHigh ...
state.setResetRangeFromLastExpectedLogonNextSeqNumLogon();
getLog().onEvent("Required resend will be suppressed as we are setting tag 789");
}
if (validateSequenceNumbers) {
doTargetTooHigh(logon);
// Looking at the sequence number of the incoming Logon, is it too high indicating possible missed messages ? ..
if (!isLogonInNormalSequence) {
// if 789 was sent then we effectively have already sent a resend request
if (state.isExpectedLogonNextSeqNumSent()) {
// Mark state as if we have already sent a resend request from the logon's 789 (we sent) to infinity.
// This will supress the resend request in doTargetTooHigh ...
state.setResetRangeFromLastExpectedLogonNextSeqNumLogon();
getLog().onEvent("Required resend will be suppressed as we are setting tag 789");
}
if (validateSequenceNumbers) {
doTargetTooHigh(logon);
}
} else {
state.incrNextTargetMsgSeqNum();
nextQueued();
}
} else {
state.incrNextTargetMsgSeqNum();
nextQueued();
}

// Do we have a 789
if (logon.isSetField(NextExpectedMsgSeqNum.FIELD) && enableNextExpectedMsgSeqNum) {
final int targetWantsNextSeqNumToBe = logon.getInt(NextExpectedMsgSeqNum.FIELD);
// Do we have a 789
if (logon.isSetField(NextExpectedMsgSeqNum.FIELD) && enableNextExpectedMsgSeqNum) {
final int targetWantsNextSeqNumToBe = logon.getInt(NextExpectedMsgSeqNum.FIELD);

// is the 789 lower (we checked for higher previously) than our next message after receiving the logon
if (targetWantsNextSeqNumToBe != nextSenderMsgNumAtLogonReceived) {
int endSeqNo = nextSenderMsgNumAtLogonReceived;
// is the 789 lower (we checked for higher previously) than our next message after receiving the logon
if (targetWantsNextSeqNumToBe != nextSenderMsgNumAtLogonReceived) {
int endSeqNo = nextSenderMsgNumAtLogonReceived;

// Just do a gap fill when messages aren't persisted
if (!persistMessages) {
endSeqNo += 1;
final int next = state.getNextSenderMsgSeqNum();
if (endSeqNo > next) {
endSeqNo = next;
// Just do a gap fill when messages aren't persisted
if (!persistMessages) {
endSeqNo += 1;
final int next = state.getNextSenderMsgSeqNum();
if (endSeqNo > next) {
endSeqNo = next;
}
getLog().onEvent(
"Received implicit ResendRequest via Logon FROM: "
+ targetWantsNextSeqNumToBe + " TO: " + nextSenderMsgNumAtLogonReceived
+ " will be reset");
generateSequenceReset(logon, targetWantsNextSeqNumToBe, // 34=
endSeqNo); // (NewSeqNo 36=)
} else {
// resend missed messages
getLog().onEvent(
"Received implicit ResendRequest via Logon FROM: "
+ targetWantsNextSeqNumToBe + " TO: " + nextSenderMsgNumAtLogonReceived
+ " will be resent");
resendMessages(logon, targetWantsNextSeqNumToBe, endSeqNo);
}
getLog().onEvent(
"Received implicit ResendRequest via Logon FROM: "
+ targetWantsNextSeqNumToBe + " TO: " + nextSenderMsgNumAtLogonReceived
+ " will be reset");
generateSequenceReset(logon, targetWantsNextSeqNumToBe, // 34=
endSeqNo); // (NewSeqNo 36=)
} else {
// resend missed messages
getLog().onEvent(
"Received implicit ResendRequest via Logon FROM: "
+ targetWantsNextSeqNumToBe + " TO: " + nextSenderMsgNumAtLogonReceived
+ " will be resent");
resendMessages(logon, targetWantsNextSeqNumToBe, endSeqNo);
}
}
} finally {
state.unlockSenderMsgSeqNum();
}
if (isLoggedOn()) {
try {
Expand Down
Loading
Loading