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 11 commits into
base: master
Choose a base branch
from
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
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());
}
}
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". ValidateUnorderedGroupFields should be set to "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