diff --git a/quickfixj-base/src/main/java/quickfix/DataDictionary.java b/quickfixj-base/src/main/java/quickfix/DataDictionary.java index 012c63ccf6..b12df32eac 100644 --- a/quickfixj-base/src/main/java/quickfix/DataDictionary.java +++ b/quickfixj-base/src/main/java/quickfix/DataDictionary.java @@ -19,6 +19,8 @@ package quickfix; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; import org.w3c.dom.Document; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -47,6 +49,7 @@ import java.util.List; import java.util.Map; import java.util.Set; +import java.util.function.Consumer; import java.util.function.Supplier; import javax.xml.XMLConstants; @@ -73,6 +76,8 @@ public class DataDictionary { private static final String JDK_DOCUMENT_BUILDER_FACTORY_NAME = "com.sun.org.apache.xerces.internal.jaxp.DocumentBuilderFactoryImpl"; private static final Supplier DEFAULT_DOCUMENT_BUILDER_FACTORY_SUPPLIER = createDocumentBuilderFactorySupplier(); + protected static final Logger LOG = LoggerFactory.getLogger(DataDictionary.class); + private static Supplier createDocumentBuilderFactorySupplier() { return () -> { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); @@ -108,6 +113,7 @@ private static Supplier createDocumentBuilderFactorySupp private final StringIntegerMap groups = new StringIntegerMap<>(); private final Map components = new HashMap<>(); private int[] orderedFieldsArray; + private static Consumer callback = LOG::warn; private DataDictionary() { } @@ -616,11 +622,23 @@ public void validate(Message message, ValidationSettings settings) throws Incorr * @throws FieldNotFound if a field cannot be found * @throws IncorrectDataFormat if a field value has a wrong data type */ + public void validate(Message message, boolean bodyOnly, ValidationSettings settings, Consumer customCallback) throws IncorrectTagValue, + FieldNotFound, IncorrectDataFormat { + validate(message, bodyOnly ? null : this, this, settings, customCallback); + } + public void validate(Message message, boolean bodyOnly, ValidationSettings settings) throws IncorrectTagValue, FieldNotFound, IncorrectDataFormat { validate(message, bodyOnly ? null : this, this, settings); } + static void validate(Message message, DataDictionary sessionDataDictionary, + DataDictionary applicationDataDictionary, ValidationSettings settings, Consumer sessionCallback) throws IncorrectTagValue, FieldNotFound, + IncorrectDataFormat { + callback = sessionCallback; + validate(message, sessionDataDictionary, applicationDataDictionary, settings); + } + static void validate(Message message, DataDictionary sessionDataDictionary, DataDictionary applicationDataDictionary, ValidationSettings settings) throws IncorrectTagValue, FieldNotFound, IncorrectDataFormat { @@ -670,7 +688,7 @@ private void iterate(ValidationSettings settings, FieldMap map, String msgType, if (hasVersion) { checkValidFormat(settings, field); - checkValue(field); + checkValue(settings, field); } if (beginString != null) { @@ -720,8 +738,16 @@ boolean checkFieldFailure(ValidationSettings settings, int field, boolean messag boolean fail; if (field < USER_DEFINED_TAG_MIN) { fail = !messageField && !settings.allowUnknownMessageFields; + + if (settings.fieldValidationLogging && !messageField && settings.allowUnknownMessageFields) { + callback.accept("Unknown Message Field " + field + " detected"); + } } else { fail = !messageField && settings.checkUserDefinedFields; + if (settings.fieldValidationLogging && !messageField && settings.checkUserDefinedFields) { + callback.accept("Unknown User Defined Field " + field + " detected"); + } + } return fail; } @@ -788,10 +814,14 @@ private void checkValidFormat(ValidationSettings settings, StringField field) th } } - private void checkValue(StringField field) throws IncorrectTagValue { + private void checkValue(ValidationSettings settings, StringField field) throws IncorrectTagValue { int tag = field.getField(); if (hasFieldValue(tag) && !isFieldValue(tag, field.getValue())) { - throw new IncorrectTagValue(tag); + if (settings.fieldValidationLogging) { + LOG.warn("Unknown Enum value {} for tag {} is detected", field.getValue(), tag); + } else { + 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 f2d242eac6..e52cdcad67 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 fieldValidationLogging = false; public ValidationSettings() {} @@ -34,6 +35,7 @@ public ValidationSettings(ValidationSettings validationSettings) { this.checkUserDefinedFields = validationSettings.checkUserDefinedFields; this.checkUnorderedGroupFields = validationSettings.checkUnorderedGroupFields; this.allowUnknownMessageFields = validationSettings.allowUnknownMessageFields; + this.fieldValidationLogging = validationSettings.fieldValidationLogging; } /** @@ -65,6 +67,10 @@ public boolean isAllowUnknownMessageFields() { return allowUnknownMessageFields; } + public boolean isFieldValidationLogging() { + return fieldValidationLogging; + } + /** * Controls whether group fields are in the same order * @@ -95,4 +101,8 @@ public void setCheckUserDefinedFields(boolean flag) { public void setAllowUnknownMessageFields(boolean allowUnknownFields) { allowUnknownMessageFields = allowUnknownFields; } + + public void setFieldValidationLogging(boolean fieldValidation) { + fieldValidationLogging = fieldValidation; + } } diff --git a/quickfixj-base/src/test/java/quickfix/ValidationSettingsTest.java b/quickfixj-base/src/test/java/quickfix/ValidationSettingsTest.java index a46eefff8d..faffab722f 100644 --- a/quickfixj-base/src/test/java/quickfix/ValidationSettingsTest.java +++ b/quickfixj-base/src/test/java/quickfix/ValidationSettingsTest.java @@ -14,6 +14,7 @@ public void copyConstructor_retains_settings() { validationSettings.setCheckFieldsOutOfOrder(false); validationSettings.setCheckUnorderedGroupFields(false); validationSettings.setCheckUserDefinedFields(false); + validationSettings.setFieldValidationLogging(true); ValidationSettings validationSettingsCopy = new ValidationSettings(validationSettings); @@ -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.isFieldValidationLogging(), validationSettings.isFieldValidationLogging()); } } diff --git a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html index da01eb46e2..95d9dab4d5 100644 --- a/quickfixj-core/src/main/doc/usermanual/usage/configuration.html +++ b/quickfixj-core/src/main/doc/usermanual/usage/configuration.html @@ -406,6 +406,24 @@

QuickFIX Settings

N N + + FieldValidationLogging + If set to Y, then +
    +
  • + if AllowUnknownMsgFields=Y, will print information about detected unknown non user defined fields (field with tag < 5000) and these fields will not be rejected +
  • +
  • + if ValidateUserDefinedFields=Y, will print information about detected unknown user defined fields (field with tag >= 5000) and these fields will not be rejected +
  • +
  • + will print information about fields with unknown Enum values and these fields will not be rejected. +
  • +
+ Y
+ N + N + CheckCompID If set to Y, messages must be received from the counterparty with the correct SenderCompID and TargetCompID. diff --git a/quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java b/quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java index 9da621fe7e..37241d7ad7 100644 --- a/quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java +++ b/quickfixj-core/src/main/java/quickfix/DefaultSessionFactory.java @@ -314,6 +314,11 @@ private ValidationSettings createValidationSettings(SessionID sessionID, Session Session.SETTING_ALLOW_UNKNOWN_MSG_FIELDS)); } + if (settings.isSetting(sessionID, Session.SETTING_FIELD_VALIDATION_LOGGING)) { + validationSettings.setFieldValidationLogging(settings.getBool(sessionID, + Session.SETTING_FIELD_VALIDATION_LOGGING)); + } + return validationSettings; } diff --git a/quickfixj-core/src/main/java/quickfix/Session.java b/quickfixj-core/src/main/java/quickfix/Session.java index 847f5c2885..acbc92c0bd 100644 --- a/quickfixj-core/src/main/java/quickfix/Session.java +++ b/quickfixj-core/src/main/java/quickfix/Session.java @@ -323,6 +323,8 @@ public class Session implements Closeable { */ public static final String SETTING_ALLOW_UNKNOWN_MSG_FIELDS = "AllowUnknownMsgFields"; + public static final String SETTING_FIELD_VALIDATION_LOGGING = "FieldValidationLogging"; + public static final String SETTING_DEFAULT_APPL_VER_ID = "DefaultApplVerID"; /** @@ -1061,7 +1063,8 @@ private void next(Message message, boolean isProcessingQueuedMessages) throws Fi // related to QFJ-367 : just warn invalid incoming field/tags try { DataDictionary.validate(message, sessionDataDictionary, - applicationDataDictionary, validationSettings); + applicationDataDictionary, validationSettings, + text -> getLog().onWarnEvent("incoming message " + text + ": " + getMessageToLog(message))); } catch (final IncorrectTagValue e) { if (rejectInvalidMessage) { throw e; diff --git a/quickfixj-core/src/test/java/quickfix/DefaultSessionFactoryTest.java b/quickfixj-core/src/test/java/quickfix/DefaultSessionFactoryTest.java index 57eb7bea7b..b0ceeac76b 100644 --- a/quickfixj-core/src/test/java/quickfix/DefaultSessionFactoryTest.java +++ b/quickfixj-core/src/test/java/quickfix/DefaultSessionFactoryTest.java @@ -100,6 +100,7 @@ public void testFixtDataDictionaryConfiguration() throws Exception { settings.setString(sessionID, Session.SETTING_APP_DATA_DICTIONARY + "." + FixVersions.BEGINSTRING_FIX40, "FIX40.xml"); settings.setString(sessionID, Session.SETTING_ALLOW_UNKNOWN_MSG_FIELDS, "Y"); settings.setString(sessionID, Session.SETTING_VALIDATE_UNORDERED_GROUP_FIELDS, "N"); + settings.setString(sessionID, Session.SETTING_FIELD_VALIDATION_LOGGING, "Y"); try (Session session = factory.create(sessionID, settings)) { @@ -113,6 +114,7 @@ public void testFixtDataDictionaryConfiguration() throws Exception { is(notNullValue())); assertTrue(session.getValidationSettings().isAllowUnknownMessageFields()); assertFalse(session.getValidationSettings().isCheckUnorderedGroupFields()); + assertTrue(session.getValidationSettings().isFieldValidationLogging()); } } @@ -142,6 +144,7 @@ public void testFixtDataDictionaryConfigurationWithDefaultAppDataDictionary() th is(notNullValue())); assertTrue(session.getValidationSettings().isAllowUnknownMessageFields()); assertFalse(session.getValidationSettings().isCheckUnorderedGroupFields()); + assertFalse(session.getValidationSettings().isFieldValidationLogging()); } }