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

Enhancement: First field in repeating group as delimiter #674

Open
wants to merge 21 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
d6c15fc
Add property to set repeating group delimiter from first tag in group
andreydp Sep 6, 2023
e60e3db
Add property to set repeating group delimiter from first tag in group…
andreydp Sep 12, 2023
a2b7ed3
Add property to set repeating group delimiter from first tag in group…
andreydp Sep 14, 2023
f6f24d2
Add property to set repeating group delimiter from first tag in group.
andreydp Sep 14, 2023
5029134
Adjust javadocs. Fix tests.
andreydp Sep 18, 2023
bafae3b
minor clarification in javadoc
chrjohn Sep 20, 2023
53912e5
Merge remote-tracking branch 'upstream/master' into first_field_in_gr…
Nov 29, 2024
e368c17
Solve merge conflicts. Merge master.
andreydp Nov 30, 2024
7a32f28
Solve merge conflicts. Merge master. Fix tests
andreydp Nov 30, 2024
582e2d2
MR review changes, Copy constructor should retain setting
andreydp Dec 4, 2024
23de161
Update documentation
andreydp Dec 17, 2024
2d21835
Post a warning in case setting is enabled but validate unordered grou…
andreydp Jan 10, 2025
294d0db
moved `FirstFieldInGroupIsDelimiter`
chrjohn Jan 16, 2025
82c6924
Merge branch 'master' into first_field_in_group_as_delimiter
chrjohn Jan 16, 2025
61680df
moved `FirstFieldInGroupIsDelimiter`
chrjohn Jan 16, 2025
7c22602
corrected variable names
chrjohn Jan 22, 2025
89d232b
minor addition to javadoc
chrjohn Jan 22, 2025
33c5267
simplified `createValidationSettings()`
chrjohn Jan 22, 2025
ed61057
corrected typo
chrjohn Jan 22, 2025
3072284
typo time, again
chrjohn Jan 22, 2025
ca165d7
Merge branch 'master' into first_field_in_group_as_delimiter
chrjohn Jan 22, 2025
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
6 changes: 4 additions & 2 deletions quickfixj-base/src/main/java/quickfix/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ private void parseGroup(String msgType, StringField field, DataDictionary dd, Da
throw MessageUtils.newInvalidMessageException("Repeating group count requires an Integer but found '" + field.getValue() + "' in " + messageData, this);
}
parent.setField(groupCountTag, field);
final int firstField = rg.getDelimiterField();
int firstField = dds.isFirstFieldInGroupIsDelimiter() ? -1 : rg.getDelimiterField();
Group group = null;
boolean inGroupParse = true;
while (inGroupParse) {
Expand All @@ -743,7 +743,9 @@ private void parseGroup(String msgType, StringField field, DataDictionary dd, Da
break;
}
int tag = field.getTag();
if (tag == firstField) {
boolean shouldCreateNewGroup = tag == firstField || (dds.isFirstFieldInGroupIsDelimiter() && firstField == -1);
if (shouldCreateNewGroup) {
firstField = tag;
addGroupRefToParent(group, parent);
group = new Group(groupCountTag, firstField, groupDataDictionary.getOrderedFields());
group.setField(field);
Expand Down
17 changes: 17 additions & 0 deletions quickfixj-base/src/main/java/quickfix/ValidationSettings.java
chrjohn marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ public class ValidationSettings {
boolean checkUserDefinedFields = true;
boolean checkUnorderedGroupFields = true;
boolean allowUnknownMessageFields = false;
boolean firstFieldInGroupIsDelimiter = false;

public ValidationSettings() {}

Expand All @@ -34,6 +35,7 @@ public ValidationSettings(ValidationSettings validationSettings) {
this.checkUserDefinedFields = validationSettings.checkUserDefinedFields;
this.checkUnorderedGroupFields = validationSettings.checkUnorderedGroupFields;
this.allowUnknownMessageFields = validationSettings.allowUnknownMessageFields;
this.firstFieldInGroupIsDelimiter = validationSettings.firstFieldInGroupIsDelimiter;
}

/**
Expand Down Expand Up @@ -65,6 +67,10 @@ public boolean isAllowUnknownMessageFields() {
return allowUnknownMessageFields;
}

public boolean isFirstFieldInGroupIsDelimiter() {
return firstFieldInGroupIsDelimiter;
}

/**
* Controls whether group fields are in the same order
*
Expand Down Expand Up @@ -95,4 +101,15 @@ public void setCheckUserDefinedFields(boolean flag) {
public void setAllowUnknownMessageFields(boolean allowUnknownFields) {
allowUnknownMessageFields = allowUnknownFields;
}

/**
* Controls whether any field which is
* first in the group would be used as delimiter
*
* @param flag true = use first field from message, false = follow data dictionary
* Must be used with disabled {@link #setCheckUnorderedGroupFields(boolean)}
*/
public void setFirstFieldInGroupIsDelimiter(boolean flag) {
firstFieldInGroupIsDelimiter = flag;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ public void copyConstructor_retains_settings() {
validationSettings.setCheckFieldsOutOfOrder(false);
validationSettings.setCheckUnorderedGroupFields(false);
validationSettings.setCheckUserDefinedFields(false);
validationSettings.setFirstFieldInGroupIsDelimiter(true);

ValidationSettings validationSettingsCopy = new ValidationSettings(validationSettings);

Expand All @@ -22,5 +23,6 @@ public void copyConstructor_retains_settings() {
assertEquals(validationSettingsCopy.isCheckFieldsOutOfOrder(), validationSettings.isCheckFieldsOutOfOrder());
assertEquals(validationSettingsCopy.isCheckUnorderedGroupFields(), validationSettings.isCheckUnorderedGroupFields());
assertEquals(validationSettingsCopy.isCheckUserDefinedFields(), validationSettings.isCheckUserDefinedFields());
assertEquals(validationSettingsCopy.isFirstFieldInGroupIsDelimiter(), validationSettings.isFirstFieldInGroupIsDelimiter());
}
}
chrjohn marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -464,6 +464,14 @@ <H3>QuickFIX Settings</H3>
<TD> Y<br>N</TD>
<TD> Y </TD>
</TR>
<TR ALIGN="left" VALIGN="middle">
<TD><I>FirstFieldInGroupIsDelimiter</I></TD>
<TD>Session validation setting for enabling whether first found field in repeating group will be used as
delimiter. Values are "Y" or "N". Default is "N".</TD>
<TD>Y<br>
N</TD>
<TD>N</TD>
</TR>

<TR ALIGN="center" VALIGN="middle">
<TD COLSPAN="4" class="subsection"><A NAME="Initiator">Initiator</A></TD>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -314,6 +314,11 @@ private ValidationSettings createValidationSettings(SessionID sessionID, Session
Session.SETTING_ALLOW_UNKNOWN_MSG_FIELDS));
}

if (settings.isSetting(sessionID, Session.SETTING_FIRST_FIELD_IN_GROUP_IS_DELIMITER)) {
validationSettings.setFirstFieldInGroupIsDelimiter(settings.getBool(sessionID,
Session.SETTING_FIRST_FIELD_IN_GROUP_IS_DELIMITER));
}

return validationSettings;
}

Expand Down
6 changes: 6 additions & 0 deletions quickfixj-core/src/main/java/quickfix/Session.java
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,12 @@ public class Session implements Closeable {
*/
public static final String SETTING_RESEND_REQUEST_CHUNK_SIZE = "ResendRequestChunkSize";

/**
* Session validation setting for enabling whether first found field in repeating group will be used as
* delimiter. Values are "Y" or "N". Default is "N".
*/
public static final String SETTING_FIRST_FIELD_IN_GROUP_IS_DELIMITER = "FirstFieldInGroupIsDelimiter";

public static final String SETTING_MAX_SCHEDULED_WRITE_REQUESTS = "MaxScheduledWriteRequests";

public static final String SETTING_VALIDATE_CHECKSUM = "ValidateChecksum";
Expand Down
64 changes: 64 additions & 0 deletions quickfixj-core/src/test/java/quickfix/MessageTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

import static org.junit.Assert.assertThrows;
import static quickfix.DataDictionaryTest.getDictionary;

import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
Expand Down Expand Up @@ -120,6 +123,7 @@
import quickfix.fix44.ExecutionReport;
import quickfix.fix44.IndicationOfInterest;
import quickfix.fix44.Logon;
import quickfix.fix44.NewOrderMultileg;
import quickfix.fix44.Logon.NoMsgTypes;
import quickfix.fix44.NewOrderCross;
import quickfix.fix44.NewOrderSingle.NoPartyIDs;
Expand Down Expand Up @@ -1445,6 +1449,66 @@ public void testValidateFieldsOutOfOrderPreFIXT11() throws Exception {
assertEquals(tcrOrdered.toString(), tcrUnOrdered.toString());
}

@Test
public void testFirstFieldInGroupIsDelimiter() throws Exception {

final DataDictionary dataDictionary = new DataDictionary(getDictionary());
ValidationSettings validationSettings = new ValidationSettings();

String fixMsg = "8=FIX.4.4\u00019=688\u000135=AB\u000149=AAA\u000156=BBB\u000134=21133\u000150=ABCABC" +
"\u000152=20230905-13:24:37.022\u000155=AAPL\u00011=ACC1\u000111=123456abcedf\u000121=1\u000138=5\u000154=1\u000140=2\u000144=-0.8" +
"\u000159=0\u000160=20230905-13:24:36.984\u0001100=ALGO\u0001167=MLEG\u0001555=3\u0001602=111\u0001600=AAA" +
"\u0001602=222\u0001654=231\u0001600=BBB\u0001602=333\u0001654=332\u0001600=CCC\u000158=TEXT\u000110=168\u0001";

String byDictFixMsg = "8=FIX.4.4\u00019=688\u000135=AB\u000149=AAA\u000156=BBB\u000134=21133\u000150=ABCABC" +
"\u000152=20230905-13:24:37.022\u000155=AAPL\u00011=ACC1\u000111=123456abcedf\u000121=1\u000138=5\u000154=1\u000140=2\u000144=-0.8" +
"\u000159=0\u000160=20230905-13:24:36.984\u0001100=ALGO\u0001167=MLEG\u0001555=3\u0001600=AAA\u0001602=111" +
"\u0001600=BBB\u0001602=222\u0001654=231\u0001600=CCC\u0001602=333\u0001654=332\u000158=TEXT\u000110=168\u0001";

validationSettings.setFirstFieldInGroupIsDelimiter(true);
validationSettings.setCheckUnorderedGroupFields(false);
final NewOrderMultileg noml1 = new NewOrderMultileg();
noml1.fromString(fixMsg, dataDictionary, validationSettings, true);
dataDictionary.validate(noml1, validationSettings);
assertTrue(noml1.hasGroup(555));
assertEquals(3, noml1.getGroupCount(555));
//when firstFieldInGroupIsDelimiter = true and setCheckUnorderedGroupFields = false - valid
//delimiter should be first tag in group
assertEquals(602, noml1.getGroup(1, 555).delim());

validationSettings.setFirstFieldInGroupIsDelimiter(false);
validationSettings.setCheckUnorderedGroupFields(false);
final NewOrderMultileg noml2 = new NewOrderMultileg();
noml2.fromString(fixMsg, dataDictionary, validationSettings, true);
//when firstFieldInGroupIsDelimiter = false and setCheckUnorderedGroupFields = false - exception is thrown
assertThrows(FieldException.class, () -> dataDictionary.validate(noml2, validationSettings));

validationSettings.setFirstFieldInGroupIsDelimiter(false);
validationSettings.setCheckUnorderedGroupFields(true);
final NewOrderMultileg noml3 = new NewOrderMultileg();
noml3.fromString(fixMsg, dataDictionary, validationSettings, true);
//when firstFieldInGroupIsDelimiter = false and setCheckUnorderedGroupFields = true - exception is thrown
assertThrows(FieldException.class, () -> dataDictionary.validate(noml3, validationSettings));

validationSettings.setFirstFieldInGroupIsDelimiter(true);
validationSettings.setCheckUnorderedGroupFields(true);
final NewOrderMultileg noml4 = new NewOrderMultileg();
noml4.fromString(fixMsg, dataDictionary, validationSettings, true);
//when firstFieldInGroupIsDelimiter = true and setCheckUnorderedGroupFields = true - exception is thrown, since order of tags is incorrect.
assertThrows(FieldException.class, () -> dataDictionary.validate(noml4, validationSettings));

validationSettings.setFirstFieldInGroupIsDelimiter(true);
validationSettings.setCheckUnorderedGroupFields(true);
final NewOrderMultileg noml5 = new NewOrderMultileg();
noml5.fromString(byDictFixMsg, dataDictionary, validationSettings, true);
//when firstFieldInGroupIsDelimiter = true and setCheckUnorderedGroupFields = true, message aligns with dictionary - do NOT fail
dataDictionary.validate(noml5, validationSettings);
assertTrue(noml5.hasGroup(555));
assertEquals(3, noml5.getGroupCount(555));
//delimiter should be dictionary first tag = 600
assertEquals(600, noml5.getGroup(1, 555).delim());
}

private void assertHeaderField(Message message, String expectedValue, int field)
throws FieldNotFound {
assertEquals(expectedValue, message.getHeader().getString(field));
Expand Down
Loading