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 6 commits into
base: master
Choose a base branch
from
Open
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
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
23 changes: 22 additions & 1 deletion quickfixj-core/src/main/java/quickfix/DataDictionary.java
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ private static Supplier<DocumentBuilderFactory> createDocumentBuilderFactorySupp
private boolean checkUserDefinedFields = true;
private boolean checkUnorderedGroupFields = true;
private boolean allowUnknownMessageFields = false;
private boolean firstFieldInGroupIsDelimiter = false;
private String beginString;
private String fullVersion;
private String majorVersion;
Expand Down Expand Up @@ -561,8 +562,27 @@ public boolean isAllowUnknownMessageFields() {
return allowUnknownMessageFields;
}

public boolean isFirstFieldInGroupIsDelimiter() {
return firstFieldInGroupIsDelimiter;
}
/**
* Controls whether any field which is
* first in the repeating group would be used as delimiter
*
* @param flag true = use first field from repeating group, false = follow data dictionary
* Must be used with disabled {@link #setCheckUnorderedGroupFields(boolean)}
*/
public void setFirstFieldInGroupIsDelimiter(boolean flag) {
firstFieldInGroupIsDelimiter = flag;
for (Map<Integer, GroupInfo> gm : groups.values()) {
for (GroupInfo gi : gm.values()) {
gi.getDataDictionary().setFirstFieldInGroupIsDelimiter(flag);
}
}
}

/**
* Controls whether group fields are in the same order
* Controls whether repeating group fields are in the same order
*
* @param flag true = checked, false = not checked
*/
Expand Down Expand Up @@ -638,6 +658,7 @@ private void copyFrom(DataDictionary rhs) {
setCheckUserDefinedFields(rhs.checkUserDefinedFields);
setCheckUnorderedGroupFields(rhs.checkUnorderedGroupFields);
setAllowUnknownMessageFields(rhs.allowUnknownMessageFields);
setFirstFieldInGroupIsDelimiter(rhs.firstFieldInGroupIsDelimiter);

calculateOrderedFields();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -309,6 +309,11 @@ private DataDictionary createDataDictionary(SessionID sessionID, SessionSettings
Session.SETTING_ALLOW_UNKNOWN_MSG_FIELDS));
}

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

return dataDictionary;
}

Expand Down
6 changes: 4 additions & 2 deletions quickfixj-core/src/main/java/quickfix/Message.java
Original file line number Diff line number Diff line change
Expand Up @@ -723,7 +723,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 = groupDataDictionary.isFirstFieldInGroupIsDelimiter() ? -1 : rg.getDelimiterField();
Group group = null;
boolean inGroupParse = true;
while (inGroupParse) {
Expand All @@ -733,7 +733,9 @@ private void parseGroup(String msgType, StringField field, DataDictionary dd, Da
break;
}
int tag = field.getTag();
if (tag == firstField) {
boolean shouldCreateNewGroup = tag == firstField || (groupDataDictionary.isFirstFieldInGroupIsDelimiter() && firstField == -1);
if (shouldCreateNewGroup) {
firstField = tag;
addGroupRefToParent(group, parent);
group = new Group(groupCountTag, firstField, groupDataDictionary.getOrderedFields());
group.setField(field);
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 @@ -376,6 +376,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
61 changes: 61 additions & 0 deletions quickfixj-core/src/test/java/quickfix/DataDictionaryTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
import quickfix.field.TargetCompID;
import quickfix.field.TimeInForce;
import quickfix.field.TransactTime;
import quickfix.fix44.NewOrderMultileg;
import quickfix.fix44.NewOrderSingle;
import quickfix.fix44.Quote;
import quickfix.fix44.QuoteRequest;
Expand All @@ -78,6 +79,7 @@
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import static org.junit.Assert.assertThrows;

public class DataDictionaryTest {

Expand Down Expand Up @@ -1493,6 +1495,65 @@ private String getCommonDataDictionaryString(String data) {
return data;
}

@Test
public void testFirstFieldInGroupIsDelimiter() throws Exception {

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

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";

dataDictionary.setFirstFieldInGroupIsDelimiter(true);
dataDictionary.setCheckUnorderedGroupFields(false);
final NewOrderMultileg noml1 = new NewOrderMultileg();
noml1.fromString(fixMsg, dataDictionary, true);
dataDictionary.validate(noml1);
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());

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

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

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

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


//
// Group Validation Tests in RepeatingGroupTest
Expand Down