diff --git a/quickfixj-base/src/main/java/quickfix/DataDictionary.java b/quickfixj-base/src/main/java/quickfix/DataDictionary.java
index 00a7c1c9b..f7345a38f 100644
--- a/quickfixj-base/src/main/java/quickfix/DataDictionary.java
+++ b/quickfixj-base/src/main/java/quickfix/DataDictionary.java
@@ -670,7 +670,7 @@ private void iterate(ValidationSettings settings, FieldMap map, String msgType,
if (hasVersion) {
checkValidFormat(settings, field);
- checkValue(field);
+ checkValue(settings.allowUnknownEnumValues, field);
}
if (beginString != null) {
@@ -788,7 +788,10 @@ private void checkValidFormat(ValidationSettings settings, StringField field) th
}
}
- private void checkValue(StringField field) throws IncorrectTagValue {
+ private void checkValue(boolean allowUnknownEnumValues, StringField field) throws IncorrectTagValue {
+ if (allowUnknownEnumValues) {
+ return;
+ }
int tag = field.getField();
if (hasFieldValue(tag) && !isFieldValue(tag, field.getValue())) {
throw new IncorrectTagValue(tag);
diff --git a/quickfixj-base/src/main/java/quickfix/ValidationSettings.java b/quickfixj-base/src/main/java/quickfix/ValidationSettings.java
index b19332a63..cb5d1c72d 100644
--- a/quickfixj-base/src/main/java/quickfix/ValidationSettings.java
+++ b/quickfixj-base/src/main/java/quickfix/ValidationSettings.java
@@ -25,6 +25,7 @@ public class ValidationSettings {
boolean checkUserDefinedFields = true;
boolean checkUnorderedGroupFields = true;
boolean allowUnknownMessageFields = false;
+ boolean allowUnknownEnumValues = false;
boolean firstFieldInGroupIsDelimiter = false;
public ValidationSettings() {}
@@ -35,6 +36,7 @@ public ValidationSettings(ValidationSettings validationSettings) {
this.checkUserDefinedFields = validationSettings.checkUserDefinedFields;
this.checkUnorderedGroupFields = validationSettings.checkUnorderedGroupFields;
this.allowUnknownMessageFields = validationSettings.allowUnknownMessageFields;
+ this.allowUnknownEnumValues = validationSettings.allowUnknownEnumValues;
this.firstFieldInGroupIsDelimiter = validationSettings.firstFieldInGroupIsDelimiter;
}
@@ -102,6 +104,21 @@ public void setAllowUnknownMessageFields(boolean allowUnknownFields) {
allowUnknownMessageFields = allowUnknownFields;
}
+ public boolean isAllowUnknownEnumValues() {
+ return allowUnknownEnumValues;
+ }
+
+ /**
+ * Controls whether unknown enumeration values are allowed,
+ * i.e. whether the validation of field values against the
+ * enumerated values defined in the data dictionary is skipped.
+ *
+ * @param allowUnknownEnumValues true = allowed (not validated), false = validated
+ */
+ public void setAllowUnknownEnumValues(boolean allowUnknownEnumValues) {
+ this.allowUnknownEnumValues = allowUnknownEnumValues;
+ }
+
/**
* Controls whether any field which is
* first in the repeating group would be used as delimiter
diff --git a/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java b/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java
index 5281099e5..9e73298fe 100644
--- a/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java
+++ b/quickfixj-base/src/test/java/quickfix/DataDictionaryTest.java
@@ -22,6 +22,7 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertThrows;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -669,6 +670,53 @@ public void testValidateFieldsOutOfOrderForGroups() throws Exception {
dictionary.validate(messageWithGroupLevel2, validationSettings);
}
+ @Test
+ public void testUnknownEnumValueIsRejectedByDefault() throws Exception {
+ final DataDictionary dictionary = new DataDictionary(getDictionary());
+ final ValidationSettings validationSettings = new ValidationSettings();
+ // TimeInForce(59)=Z is not a valid enum value in FIX 4.4
+ Message message = new Message(
+ "8=FIX.4.4\0019=136\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" +
+ "60=20110412-13:43:00\0011=testAccount\00111=123\00121=3\00138=42\00140=2\00144=42.37\001" +
+ "54=1\00155=QFJ\00159=Z\00110=239\001",
+ dictionary, validationSettings, false);
+
+ assertThrows(IncorrectTagValue.class, () -> dictionary.validate(message, validationSettings));
+
+ // multiple-value field ExecInst(18) with one invalid value (@)
+ Message messageWithMultipleValueField = new Message(
+ "8=FIX.4.4\0019=145\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" +
+ "60=20110412-13:43:00\0011=testAccount\00111=123\00118=A @ D\00121=3\00138=42\00140=2\00144=42.37\001" +
+ "54=1\00155=QFJ\00159=0\00110=113\001",
+ dictionary, validationSettings, false);
+
+ assertThrows(IncorrectTagValue.class, () -> dictionary.validate(messageWithMultipleValueField, validationSettings));
+ }
+
+ @Test
+ public void testUnknownEnumValueIsAcceptedWhenAllowUnknownEnumValuesIsSet() throws Exception {
+ final DataDictionary dictionary = new DataDictionary(getDictionary());
+ final ValidationSettings validationSettings = new ValidationSettings();
+ validationSettings.setAllowUnknownEnumValues(true);
+ // TimeInForce(59)=Z is not a valid enum value in FIX 4.4
+ Message message = new Message(
+ "8=FIX.4.4\0019=136\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" +
+ "60=20110412-13:43:00\0011=testAccount\00111=123\00121=3\00138=42\00140=2\00144=42.37\001" +
+ "54=1\00155=QFJ\00159=Z\00110=239\001",
+ dictionary, validationSettings, false);
+
+ dictionary.validate(message, validationSettings);
+
+ // multiple-value field ExecInst(18) with one invalid value (@)
+ Message messageWithMultipleValueField = new Message(
+ "8=FIX.4.4\0019=145\00135=D\00134=25\00149=SENDER\00156=TARGET\00152=20110412-13:43:00\001" +
+ "60=20110412-13:43:00\0011=testAccount\00111=123\00118=A @ D\00121=3\00138=42\00140=2\00144=42.37\001" +
+ "54=1\00155=QFJ\00159=0\00110=113\001",
+ dictionary, validationSettings, false);
+
+ dictionary.validate(messageWithMultipleValueField, validationSettings);
+ }
+
@Test
public void shouldLoadDictionaryWhenExternalDTDisEnabled() throws ConfigError {
ExternalDtdDataDictionaryLoader.load("FIX_External_DTD.xml");
diff --git a/quickfixj-base/src/test/java/quickfix/ValidationSettingsTest.java b/quickfixj-base/src/test/java/quickfix/ValidationSettingsTest.java
index 6b50a8f5c..eef048e5a 100644
--- a/quickfixj-base/src/test/java/quickfix/ValidationSettingsTest.java
+++ b/quickfixj-base/src/test/java/quickfix/ValidationSettingsTest.java
@@ -10,6 +10,7 @@ public void copyConstructor_retains_settings() {
final ValidationSettings validationSettings = new ValidationSettings();
validationSettings.setAllowUnknownMessageFields(true);
+ validationSettings.setAllowUnknownEnumValues(true);
validationSettings.setCheckFieldsHaveValues(false);
validationSettings.setCheckFieldsOutOfOrder(false);
validationSettings.setCheckUnorderedGroupFields(false);
@@ -19,6 +20,7 @@ public void copyConstructor_retains_settings() {
ValidationSettings validationSettingsCopy = new ValidationSettings(validationSettings);
assertEquals(validationSettingsCopy.isAllowUnknownMessageFields(), validationSettings.isAllowUnknownMessageFields());
+ assertEquals(validationSettingsCopy.isAllowUnknownEnumValues(), validationSettings.isAllowUnknownEnumValues());
assertEquals(validationSettingsCopy.isCheckFieldsHaveValues(), validationSettings.isCheckFieldsHaveValues());
assertEquals(validationSettingsCopy.isCheckFieldsOutOfOrder(), validationSettings.isCheckFieldsOutOfOrder());
assertEquals(validationSettingsCopy.isCheckUnorderedGroupFields(), validationSettings.isCheckUnorderedGroupFields());
diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.md b/quickfixj-core/src/main/doc/usermanual/usage/configuration.md
index fbd3d6584..440469cbc 100644
--- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.md
+++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.md
@@ -98,6 +98,7 @@ with QuickFIX, followed by an example.
| `ValidateSequenceNumbers` | Check the next expected target `SeqNum` against the received `SeqNum`. If enabled and a mismatch is detected: if lower than expected, logout; if higher, send a resend request. If not enabled and a mismatch is detected, nothing is done. Must be enabled for `EnableNextExpectedMsgSeqNum` to work. | `Y`
`N` | `Y` |
| `ValidateChecksum` | If set to `N`, checksum validation will not be executed on messages. This setting cannot be set to `N` together with `RejectGarbledMessage` set to `Y`; in that case a `ConfigError` will be thrown. | `Y`
`N` | `Y` |
| `AllowUnknownMsgFields` | If set to `Y`, non user defined fields (field with tag < 5000) will not be rejected if they are not defined in the data dictionary, or are present in messages they do not belong to. | `Y`
`N` | `N` |
+| `AllowUnknownEnumValues` | If set to `Y`, field values are not validated against the enumerated values defined in the data dictionary, i.e. messages containing unknown enum values are not rejected. Values of fields that have no enumerated values defined are not affected. To allow unknown values only for individual fields, use the `allowOtherValues` attribute on the field definition in the data dictionary instead. | `Y`
`N` | `N` |
| `CheckCompID` | If set to `Y`, messages must be received from the counterparty with the correct `SenderCompID` and `TargetCompID`. Some systems will send you different CompIDs by design, so you must set this to `N`. | `Y`
`N` | `Y` |
| `CheckLatency` | If set to `Y`, messages must be received from the counterparty within a defined number of seconds (see `MaxLatency`). It is useful to turn this off if a system uses local time for its timestamps instead of GMT. | `Y`
`N` | `Y` |
| `MaxLatency` | If `CheckLatency` is set to `Y`, this defines the number of seconds latency allowed for a message to be processed. | positive integer | `120` |
diff --git a/quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java b/quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java
index f38d36e11..59b8dd112 100644
--- a/quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java
+++ b/quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java
@@ -307,6 +307,9 @@ private ValidationSettings createValidationSettings(SessionID sessionID, Session
validationSettings.setAllowUnknownMessageFields(settings.getBoolOrDefault(sessionID,
Session.SETTING_ALLOW_UNKNOWN_MSG_FIELDS, validationSettings.isAllowUnknownMessageFields()));
+ validationSettings.setAllowUnknownEnumValues(settings.getBoolOrDefault(sessionID,
+ Session.SETTING_ALLOW_UNKNOWN_ENUM_VALUES, validationSettings.isAllowUnknownEnumValues()));
+
validationSettings.setFirstFieldInGroupIsDelimiter(settings.getBoolOrDefault(sessionID,
Session.SETTING_FIRST_FIELD_IN_GROUP_IS_DELIMITER, validationSettings.isFirstFieldInGroupIsDelimiter()));
diff --git a/quickfixj-core/src/main/java/quickfix/Session.java b/quickfixj-core/src/main/java/quickfix/Session.java
index 9037913c5..87e6451f9 100644
--- a/quickfixj-core/src/main/java/quickfix/Session.java
+++ b/quickfixj-core/src/main/java/quickfix/Session.java
@@ -329,6 +329,14 @@ public class Session implements Closeable {
*/
public static final String SETTING_ALLOW_UNKNOWN_MSG_FIELDS = "AllowUnknownMsgFields";
+ /**
+ * Allow field values that are not defined as enumerated values in the data
+ * dictionary, i.e. skip the validation of field values against the enumerated
+ * values defined for the field. Values of fields without enumerated values
+ * are not affected.
+ */
+ public static final String SETTING_ALLOW_UNKNOWN_ENUM_VALUES = "AllowUnknownEnumValues";
+
public static final String SETTING_DEFAULT_APPL_VER_ID = "DefaultApplVerID";
/**
diff --git a/quickfixj-core/src/test/java/quickfix/DefaultSessionFactoryTest.java b/quickfixj-core/src/test/java/quickfix/DefaultSessionFactoryTest.java
index 57eb7bea7..d3b6d3cdb 100644
--- a/quickfixj-core/src/test/java/quickfix/DefaultSessionFactoryTest.java
+++ b/quickfixj-core/src/test/java/quickfix/DefaultSessionFactoryTest.java
@@ -63,6 +63,21 @@ public void testMinimalSettings() throws Exception {
factory.create(sessionID, settings);
}
+ @Test
+ public void testAllowUnknownEnumValuesSetting() throws Exception {
+ settings.setString(sessionID, Session.SETTING_ALLOW_UNKNOWN_ENUM_VALUES, "Y");
+ try (Session session = factory.create(sessionID, settings)) {
+ assertTrue(session.getValidationSettings().isAllowUnknownEnumValues());
+ }
+ }
+
+ @Test
+ public void testAllowUnknownEnumValuesDefaultsToFalse() throws Exception {
+ try (Session session = factory.create(sessionID, settings)) {
+ assertFalse(session.getValidationSettings().isAllowUnknownEnumValues());
+ }
+ }
+
@Test
public void testFixTMinimalSettings() throws Exception {
sessionID = new SessionID(FixVersions.BEGINSTRING_FIXT11, "SENDER", "TARGET");