Skip to content

Commit bbff6a7

Browse files
authored
Merge branch 'master' into tls-13
2 parents 2a75f6f + e92a2a3 commit bbff6a7

File tree

7 files changed

+128
-24
lines changed

7 files changed

+128
-24
lines changed

quickfixj-base/src/main/java/quickfix/Message.java

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -733,7 +733,7 @@ private void parseGroup(String msgType, StringField field, DataDictionary dd, Da
733733
throw MessageUtils.newInvalidMessageException("Repeating group count requires an Integer but found '" + field.getValue() + "' in " + messageData, this);
734734
}
735735
parent.setField(groupCountTag, field);
736-
final int firstField = rg.getDelimiterField();
736+
int firstField = dds.isFirstFieldInGroupIsDelimiter() ? -1 : rg.getDelimiterField();
737737
Group group = null;
738738
boolean inGroupParse = true;
739739
while (inGroupParse) {
@@ -743,7 +743,9 @@ private void parseGroup(String msgType, StringField field, DataDictionary dd, Da
743743
break;
744744
}
745745
int tag = field.getTag();
746-
if (tag == firstField) {
746+
boolean shouldCreateNewGroup = tag == firstField || (dds.isFirstFieldInGroupIsDelimiter() && firstField == -1);
747+
if (shouldCreateNewGroup) {
748+
firstField = tag;
747749
addGroupRefToParent(group, parent);
748750
group = new Group(groupCountTag, firstField, groupDataDictionary.getOrderedFields());
749751
group.setField(field);

quickfixj-base/src/main/java/quickfix/ValidationSettings.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ public class ValidationSettings {
2525
boolean checkUserDefinedFields = true;
2626
boolean checkUnorderedGroupFields = true;
2727
boolean allowUnknownMessageFields = false;
28+
boolean firstFieldInGroupIsDelimiter = false;
2829

2930
public ValidationSettings() {}
3031

@@ -34,6 +35,7 @@ public ValidationSettings(ValidationSettings validationSettings) {
3435
this.checkUserDefinedFields = validationSettings.checkUserDefinedFields;
3536
this.checkUnorderedGroupFields = validationSettings.checkUnorderedGroupFields;
3637
this.allowUnknownMessageFields = validationSettings.allowUnknownMessageFields;
38+
this.firstFieldInGroupIsDelimiter = validationSettings.firstFieldInGroupIsDelimiter;
3739
}
3840

3941
/**
@@ -65,6 +67,10 @@ public boolean isAllowUnknownMessageFields() {
6567
return allowUnknownMessageFields;
6668
}
6769

70+
public boolean isFirstFieldInGroupIsDelimiter() {
71+
return firstFieldInGroupIsDelimiter;
72+
}
73+
6874
/**
6975
* Controls whether group fields are in the same order
7076
*
@@ -95,4 +101,15 @@ public void setCheckUserDefinedFields(boolean flag) {
95101
public void setAllowUnknownMessageFields(boolean allowUnknownFields) {
96102
allowUnknownMessageFields = allowUnknownFields;
97103
}
104+
105+
/**
106+
* Controls whether any field which is
107+
* first in the repeating group would be used as delimiter
108+
*
109+
* @param flag true = use first field from message, false = follow data dictionary
110+
* Must be used with disabled {@link #setCheckUnorderedGroupFields(boolean)}
111+
*/
112+
public void setFirstFieldInGroupIsDelimiter(boolean flag) {
113+
firstFieldInGroupIsDelimiter = flag;
114+
}
98115
}

quickfixj-base/src/test/java/quickfix/ValidationSettingsTest.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ public void copyConstructor_retains_settings() {
1414
validationSettings.setCheckFieldsOutOfOrder(false);
1515
validationSettings.setCheckUnorderedGroupFields(false);
1616
validationSettings.setCheckUserDefinedFields(false);
17+
validationSettings.setFirstFieldInGroupIsDelimiter(true);
1718

1819
ValidationSettings validationSettingsCopy = new ValidationSettings(validationSettings);
1920

@@ -22,5 +23,6 @@ public void copyConstructor_retains_settings() {
2223
assertEquals(validationSettingsCopy.isCheckFieldsOutOfOrder(), validationSettings.isCheckFieldsOutOfOrder());
2324
assertEquals(validationSettingsCopy.isCheckUnorderedGroupFields(), validationSettings.isCheckUnorderedGroupFields());
2425
assertEquals(validationSettingsCopy.isCheckUserDefinedFields(), validationSettings.isCheckUserDefinedFields());
26+
assertEquals(validationSettingsCopy.isFirstFieldInGroupIsDelimiter(), validationSettings.isFirstFieldInGroupIsDelimiter());
2527
}
2628
}

quickfixj-core/src/main/doc/usermanual/usage/configuration.html

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,14 @@ <H3>QuickFIX Settings</H3>
367367
N</TD>
368368
<TD>Y</TD>
369369
</TR>
370+
<TR ALIGN="left" VALIGN="middle">
371+
<TD><I>FirstFieldInGroupIsDelimiter</I></TD>
372+
<TD>Session validation setting for enabling whether first found field in repeating group will be used as
373+
delimiter. Values are "Y" or "N". Default is "N". ValidateUnorderedGroupFields should be set to "N"</TD>
374+
<TD>Y<br>
375+
N</TD>
376+
<TD>N</TD>
377+
</TR>
370378
<TR ALIGN="left" VALIGN="middle">
371379
<TD><I>ValidateIncomingMessage</I></TD>
372380
<TD>Allow to bypass the message validation (against the dictionary). Default is "Y".</TD>

quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java

Lines changed: 27 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
import org.quickfixj.QFJException;
2323
import org.quickfixj.SimpleCache;
24+
import org.slf4j.Logger;
25+
import org.slf4j.LoggerFactory;
2426
import quickfix.field.ApplVerID;
2527
import quickfix.field.DefaultApplVerID;
2628

@@ -37,7 +39,8 @@
3739
* initiators) for creating sessions.
3840
*/
3941
public class DefaultSessionFactory implements SessionFactory {
40-
private static final SimpleCache<String, DataDictionary> dictionaryCache = new SimpleCache<>(path -> {
42+
private static final Logger LOG = LoggerFactory.getLogger(DefaultSessionFactory.class);
43+
private static final SimpleCache<String, DataDictionary> DICTIONARY_CACHE = new SimpleCache<>(path -> {
4144
try {
4245
return new DataDictionary(path);
4346
} catch (ConfigError e) {
@@ -289,34 +292,36 @@ private DataDictionary createDataDictionary(SessionID sessionID, SessionSettings
289292
private ValidationSettings createValidationSettings(SessionID sessionID, SessionSettings settings) throws FieldConvertError, ConfigError {
290293
ValidationSettings validationSettings = new ValidationSettings();
291294

292-
if (settings.isSetting(sessionID, Session.SETTING_VALIDATE_FIELDS_OUT_OF_ORDER)) {
293-
validationSettings.setCheckFieldsOutOfOrder(settings.getBool(sessionID,
294-
Session.SETTING_VALIDATE_FIELDS_OUT_OF_ORDER));
295-
}
295+
validationSettings.setCheckFieldsOutOfOrder(settings.getBoolOrDefault(sessionID,
296+
Session.SETTING_VALIDATE_FIELDS_OUT_OF_ORDER, validationSettings.isCheckFieldsOutOfOrder()));
296297

297-
if (settings.isSetting(sessionID, Session.SETTING_VALIDATE_FIELDS_HAVE_VALUES)) {
298-
validationSettings.setCheckFieldsHaveValues(settings.getBool(sessionID,
299-
Session.SETTING_VALIDATE_FIELDS_HAVE_VALUES));
300-
}
298+
validationSettings.setCheckFieldsHaveValues(settings.getBoolOrDefault(sessionID,
299+
Session.SETTING_VALIDATE_FIELDS_HAVE_VALUES, validationSettings.isCheckFieldsHaveValues()));
301300

302-
if (settings.isSetting(sessionID, Session.SETTING_VALIDATE_UNORDERED_GROUP_FIELDS)) {
303-
validationSettings.setCheckUnorderedGroupFields(settings.getBool(sessionID,
304-
Session.SETTING_VALIDATE_UNORDERED_GROUP_FIELDS));
305-
}
301+
validationSettings.setCheckUnorderedGroupFields(settings.getBoolOrDefault(sessionID,
302+
Session.SETTING_VALIDATE_UNORDERED_GROUP_FIELDS, validationSettings.isCheckUnorderedGroupFields()));
306303

307-
if (settings.isSetting(sessionID, Session.SETTING_VALIDATE_USER_DEFINED_FIELDS)) {
308-
validationSettings.setCheckUserDefinedFields(settings.getBool(sessionID,
309-
Session.SETTING_VALIDATE_USER_DEFINED_FIELDS));
310-
}
304+
validationSettings.setCheckUserDefinedFields(settings.getBoolOrDefault(sessionID,
305+
Session.SETTING_VALIDATE_USER_DEFINED_FIELDS, validationSettings.isCheckUserDefinedFields()));
311306

312-
if (settings.isSetting(sessionID, Session.SETTING_ALLOW_UNKNOWN_MSG_FIELDS)) {
313-
validationSettings.setAllowUnknownMessageFields(settings.getBool(sessionID,
314-
Session.SETTING_ALLOW_UNKNOWN_MSG_FIELDS));
315-
}
307+
validationSettings.setAllowUnknownMessageFields(settings.getBoolOrDefault(sessionID,
308+
Session.SETTING_ALLOW_UNKNOWN_MSG_FIELDS, validationSettings.isAllowUnknownMessageFields()));
309+
310+
validationSettings.setFirstFieldInGroupIsDelimiter(settings.getBoolOrDefault(sessionID,
311+
Session.SETTING_FIRST_FIELD_IN_GROUP_IS_DELIMITER, validationSettings.isFirstFieldInGroupIsDelimiter()));
312+
313+
validateValidationSettings(validationSettings);
316314

317315
return validationSettings;
318316
}
319317

318+
private void validateValidationSettings(ValidationSettings validationSettings) {
319+
if (validationSettings.isFirstFieldInGroupIsDelimiter() && validationSettings.isCheckUnorderedGroupFields()) {
320+
LOG.warn("Setting " + Session.SETTING_FIRST_FIELD_IN_GROUP_IS_DELIMITER
321+
+ " requires " + Session.SETTING_VALIDATE_UNORDERED_GROUP_FIELDS + " to be set to false");
322+
}
323+
}
324+
320325
private void processFixtDataDictionaries(SessionID sessionID, SessionSettings settings,
321326
DefaultDataDictionaryProvider dataDictionaryProvider) throws ConfigError,
322327
FieldConvertError {
@@ -384,7 +389,7 @@ private String toDictionaryPath(String beginString) {
384389

385390
private DataDictionary getDataDictionary(String path) throws ConfigError {
386391
try {
387-
return dictionaryCache.computeIfAbsent(path);
392+
return DICTIONARY_CACHE.computeIfAbsent(path);
388393
} catch (QFJException e) {
389394
final Throwable cause = e.getCause();
390395
if (cause instanceof ConfigError) {

quickfixj-core/src/main/java/quickfix/Session.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,12 @@ public class Session implements Closeable {
208208
*/
209209
public static final String SETTING_VALIDATE_UNORDERED_GROUP_FIELDS = "ValidateUnorderedGroupFields";
210210

211+
/**
212+
* Session validation setting for enabling whether first found field in repeating group will be used as
213+
* delimiter. Values are "Y" or "N". Default is "N".
214+
*/
215+
public static final String SETTING_FIRST_FIELD_IN_GROUP_IS_DELIMITER = "FirstFieldInGroupIsDelimiter";
216+
211217
/**
212218
* Session validation setting for enabling whether field values are
213219
* validated. Empty fields values are not allowed. Values are "Y" or "N".

quickfixj-core/src/test/java/quickfix/MessageTest.java

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import static org.junit.Assert.assertTrue;
2828
import static org.junit.Assert.fail;
2929

30+
import static org.junit.Assert.assertThrows;
31+
import static quickfix.DataDictionaryTest.getDictionary;
32+
3033
import java.math.BigDecimal;
3134
import java.time.LocalDateTime;
3235
import java.time.ZoneOffset;
@@ -120,6 +123,7 @@
120123
import quickfix.fix44.ExecutionReport;
121124
import quickfix.fix44.IndicationOfInterest;
122125
import quickfix.fix44.Logon;
126+
import quickfix.fix44.NewOrderMultileg;
123127
import quickfix.fix44.Logon.NoMsgTypes;
124128
import quickfix.fix44.NewOrderCross;
125129
import quickfix.fix44.NewOrderSingle.NoPartyIDs;
@@ -1445,6 +1449,66 @@ public void testValidateFieldsOutOfOrderPreFIXT11() throws Exception {
14451449
assertEquals(tcrOrdered.toString(), tcrUnOrdered.toString());
14461450
}
14471451

1452+
@Test
1453+
public void testFirstFieldInGroupIsDelimiter() throws Exception {
1454+
1455+
final DataDictionary dataDictionary = new DataDictionary(getDictionary());
1456+
ValidationSettings validationSettings = new ValidationSettings();
1457+
1458+
String fixMsg = "8=FIX.4.4\u00019=688\u000135=AB\u000149=AAA\u000156=BBB\u000134=21133\u000150=ABCABC" +
1459+
"\u000152=20230905-13:24:37.022\u000155=AAPL\u00011=ACC1\u000111=123456abcedf\u000121=1\u000138=5\u000154=1\u000140=2\u000144=-0.8" +
1460+
"\u000159=0\u000160=20230905-13:24:36.984\u0001100=ALGO\u0001167=MLEG\u0001555=3\u0001602=111\u0001600=AAA" +
1461+
"\u0001602=222\u0001654=231\u0001600=BBB\u0001602=333\u0001654=332\u0001600=CCC\u000158=TEXT\u000110=168\u0001";
1462+
1463+
String byDictFixMsg = "8=FIX.4.4\u00019=688\u000135=AB\u000149=AAA\u000156=BBB\u000134=21133\u000150=ABCABC" +
1464+
"\u000152=20230905-13:24:37.022\u000155=AAPL\u00011=ACC1\u000111=123456abcedf\u000121=1\u000138=5\u000154=1\u000140=2\u000144=-0.8" +
1465+
"\u000159=0\u000160=20230905-13:24:36.984\u0001100=ALGO\u0001167=MLEG\u0001555=3\u0001600=AAA\u0001602=111" +
1466+
"\u0001600=BBB\u0001602=222\u0001654=231\u0001600=CCC\u0001602=333\u0001654=332\u000158=TEXT\u000110=168\u0001";
1467+
1468+
validationSettings.setFirstFieldInGroupIsDelimiter(true);
1469+
validationSettings.setCheckUnorderedGroupFields(false);
1470+
final NewOrderMultileg noml1 = new NewOrderMultileg();
1471+
noml1.fromString(fixMsg, dataDictionary, validationSettings, true);
1472+
dataDictionary.validate(noml1, validationSettings);
1473+
assertTrue(noml1.hasGroup(555));
1474+
assertEquals(3, noml1.getGroupCount(555));
1475+
//when firstFieldInGroupIsDelimiter = true and setCheckUnorderedGroupFields = false - valid
1476+
//delimiter should be first tag in group
1477+
assertEquals(602, noml1.getGroup(1, 555).delim());
1478+
1479+
validationSettings.setFirstFieldInGroupIsDelimiter(false);
1480+
validationSettings.setCheckUnorderedGroupFields(false);
1481+
final NewOrderMultileg noml2 = new NewOrderMultileg();
1482+
noml2.fromString(fixMsg, dataDictionary, validationSettings, true);
1483+
//when firstFieldInGroupIsDelimiter = false and setCheckUnorderedGroupFields = false - exception is thrown
1484+
assertThrows(FieldException.class, () -> dataDictionary.validate(noml2, validationSettings));
1485+
1486+
validationSettings.setFirstFieldInGroupIsDelimiter(false);
1487+
validationSettings.setCheckUnorderedGroupFields(true);
1488+
final NewOrderMultileg noml3 = new NewOrderMultileg();
1489+
noml3.fromString(fixMsg, dataDictionary, validationSettings, true);
1490+
//when firstFieldInGroupIsDelimiter = false and setCheckUnorderedGroupFields = true - exception is thrown
1491+
assertThrows(FieldException.class, () -> dataDictionary.validate(noml3, validationSettings));
1492+
1493+
validationSettings.setFirstFieldInGroupIsDelimiter(true);
1494+
validationSettings.setCheckUnorderedGroupFields(true);
1495+
final NewOrderMultileg noml4 = new NewOrderMultileg();
1496+
noml4.fromString(fixMsg, dataDictionary, validationSettings, true);
1497+
//when firstFieldInGroupIsDelimiter = true and setCheckUnorderedGroupFields = true - exception is thrown, since order of tags is incorrect.
1498+
assertThrows(FieldException.class, () -> dataDictionary.validate(noml4, validationSettings));
1499+
1500+
validationSettings.setFirstFieldInGroupIsDelimiter(true);
1501+
validationSettings.setCheckUnorderedGroupFields(true);
1502+
final NewOrderMultileg noml5 = new NewOrderMultileg();
1503+
noml5.fromString(byDictFixMsg, dataDictionary, validationSettings, true);
1504+
//when firstFieldInGroupIsDelimiter = true and setCheckUnorderedGroupFields = true, message aligns with dictionary - do NOT fail
1505+
dataDictionary.validate(noml5, validationSettings);
1506+
assertTrue(noml5.hasGroup(555));
1507+
assertEquals(3, noml5.getGroupCount(555));
1508+
//delimiter should be dictionary first tag = 600
1509+
assertEquals(600, noml5.getGroup(1, 555).delim());
1510+
}
1511+
14481512
private void assertHeaderField(Message message, String expectedValue, int field)
14491513
throws FieldNotFound {
14501514
assertEquals(expectedValue, message.getHeader().getString(field));

0 commit comments

Comments
 (0)