From 3b2a1eda4d25e2bc770cb71ff98479c86320c909 Mon Sep 17 00:00:00 2001 From: Chris Kolek Date: Wed, 31 Jan 2018 23:21:50 -0500 Subject: [PATCH 1/3] Overload MessageFactory.create to accept ApplVerID for message creation. Add DefaultMessageFactory constructor which accepts defaultApplVerID value. --- .../java/quickfix/DefaultMessageFactory.java | 50 +++++++++++------ .../main/java/quickfix/MessageFactory.java | 14 +++++ .../src/main/java/quickfix/MessageUtils.java | 2 +- .../quickfix/DefaultMessageFactoryTest.java | 53 ++++++++++++++++--- 4 files changed, 95 insertions(+), 24 deletions(-) diff --git a/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java b/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java index 60a3dfbf2..ffce99621 100644 --- a/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java +++ b/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java @@ -19,9 +19,11 @@ package quickfix; +import quickfix.field.ApplVerID; import quickfix.field.MsgType; import java.util.Map; +import java.util.Objects; import java.util.concurrent.ConcurrentHashMap; import static quickfix.FixVersions.BEGINSTRING_FIX40; @@ -40,6 +42,8 @@ public class DefaultMessageFactory implements MessageFactory { private final Map messageFactories = new ConcurrentHashMap<>(); + private final ApplVerID defaultApplVerID; + /** * Constructs a DefaultMessageFactory, which dynamically loads and delegates to * the default version-specific message factories, if they are available at runtime. @@ -47,8 +51,28 @@ public class DefaultMessageFactory implements MessageFactory { * Callers can set the {@link Thread#setContextClassLoader context classloader}, * which will be used to load the classes if {@link Class#forName Class.forName} * fails to do so (e.g. in an OSGi environment). + *

+ * Equivalent to {@link #DefaultMessageFactory(String) DefaultMessageFactory}({@link ApplVerID#FIX50 ApplVerID.FIX50}). */ public DefaultMessageFactory() { + this(FIX50); + } + + /** + * Constructs a DefaultMessageFactory, which dynamically loads and delegates to + * the default version-specific message factories, if they are available at runtime. + *

+ * Callers can set the {@link Thread#setContextClassLoader context classloader}, + * which will be used to load the classes if {@link Class#forName Class.forName} + * fails to do so (e.g. in an OSGi environment). + * + * @param defaultApplVerID ApplVerID value used by default for {@link #create(String, ApplVerID, String)} + */ + public DefaultMessageFactory(String defaultApplVerID) { + Objects.requireNonNull(defaultApplVerID, "defaultApplVerID"); + + this.defaultApplVerID = new ApplVerID(defaultApplVerID); + // To loosen the coupling between this factory and generated code, the // message factories are discovered at run time using reflection addFactory(BEGINSTRING_FIX40); @@ -118,27 +142,23 @@ public void addFactory(String beginString, Class facto } } + @Override public Message create(String beginString, String msgType) { + return create(beginString, defaultApplVerID, msgType); + } + + @Override + public Message create(String beginString, ApplVerID applVerID, String msgType) { MessageFactory messageFactory = messageFactories.get(beginString); - if (beginString.equals(BEGINSTRING_FIXT11)) { - // The default message factory assumes that only FIX 5.0 will be - // used with FIXT 1.1 sessions. A more flexible approach will require - // an extension to the QF JNI API. Until then, you will need a custom - // message factory if you want to use application messages prior to - // FIX 5.0 with a FIXT 1.1 session. - // - // TODO: how do we support 50/50SP1/50SP2 concurrently? - // - // If you need to determine admin message category based on a data - // dictionary, then use a custom message factory and don't use the - // static method used below. - if (!MessageUtils.isAdminMessage(msgType)) { - messageFactory = messageFactories.get(FIX50); + if (beginString.equals(BEGINSTRING_FIXT11) && !MessageUtils.isAdminMessage(msgType)) { + if (applVerID == null) { + applVerID = new ApplVerID(defaultApplVerID.getValue()); } + messageFactory = messageFactories.get(MessageUtils.toBeginString(applVerID)); } if (messageFactory != null) { - return messageFactory.create(beginString, msgType); + return messageFactory.create(beginString, applVerID, msgType); } Message message = new Message(); diff --git a/quickfixj-core/src/main/java/quickfix/MessageFactory.java b/quickfixj-core/src/main/java/quickfix/MessageFactory.java index 9cf30a1da..cf734f831 100644 --- a/quickfixj-core/src/main/java/quickfix/MessageFactory.java +++ b/quickfixj-core/src/main/java/quickfix/MessageFactory.java @@ -19,6 +19,8 @@ package quickfix; +import quickfix.field.ApplVerID; + /** * Used by a Session to create a Message. * @@ -35,6 +37,18 @@ public interface MessageFactory { */ Message create(String beginString, String msgType); + /** + * Creates a message for a specified type, FIX version, and ApplVerID. + * + * @param beginString the FIX version (for example, "FIX.4.2") + * @param applVerID the ApplVerID (for example "6" for FIX44) + * @param msgType the FIX message type (for example, "D" for an order) + * @return a message instance + */ + default Message create(String beginString, ApplVerID applVerID, String msgType) { + return create(beginString, msgType); + } + /** * Creates a group for the specified parent message type and * for the fields with the corresponding field ID diff --git a/quickfixj-core/src/main/java/quickfix/MessageUtils.java b/quickfixj-core/src/main/java/quickfix/MessageUtils.java index d22766945..01ade452a 100644 --- a/quickfixj-core/src/main/java/quickfix/MessageUtils.java +++ b/quickfixj-core/src/main/java/quickfix/MessageUtils.java @@ -142,7 +142,7 @@ public static Message parse(Session session, String messageString) throws Invali final DataDictionary applicationDataDictionary = ddProvider == null ? null : ddProvider .getApplicationDataDictionary(applVerID); - final quickfix.Message message = messageFactory.create(beginString, msgType); + final quickfix.Message message = messageFactory.create(beginString, applVerID, msgType); final DataDictionary payloadDictionary = MessageUtils.isAdminMessage(msgType) ? sessionDataDictionary : applicationDataDictionary; diff --git a/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryTest.java b/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryTest.java index 2af41cfe8..1fec31694 100644 --- a/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryTest.java +++ b/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryTest.java @@ -2,13 +2,13 @@ import static org.junit.Assert.*; import static quickfix.FixVersions.*; +import static quickfix.field.ApplVerID.*; import org.junit.Test; -import quickfix.field.LinesOfText; -import quickfix.field.MsgType; -import quickfix.field.NoLinesOfText; -import quickfix.field.NoMDEntries; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; +import quickfix.field.*; import quickfix.test.util.ExpectedTestFailure; /** @@ -17,8 +17,19 @@ * @author toli * @version $Id$ */ +@RunWith(Parameterized.class) public class DefaultMessageFactoryTest { - private final DefaultMessageFactory factory = new DefaultMessageFactory(); + private final DefaultMessageFactory factory; + private final Class fixtCreateExpectedClass; + + public DefaultMessageFactoryTest(String defaultApplVerID, Class fixtCreateExpectedClass) { + if (defaultApplVerID != null) { + this.factory = new DefaultMessageFactory(defaultApplVerID); + } else { + this.factory = new DefaultMessageFactory(); + } + this.fixtCreateExpectedClass = fixtCreateExpectedClass; + } @Test public void testMessageCreate() throws Exception { @@ -27,18 +38,28 @@ public void testMessageCreate() throws Exception { assertMessage(quickfix.fix42.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(BEGINSTRING_FIX42, MsgType.ADVERTISEMENT)); assertMessage(quickfix.fix43.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(BEGINSTRING_FIX43, MsgType.ADVERTISEMENT)); assertMessage(quickfix.fix44.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(BEGINSTRING_FIX44, MsgType.ADVERTISEMENT)); - assertMessage(quickfix.fix50.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(FIX50, MsgType.ADVERTISEMENT)); + assertMessage(quickfix.fix50.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(FixVersions.FIX50, MsgType.ADVERTISEMENT)); + assertMessage(quickfix.fix50sp1.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(FixVersions.FIX50SP1, MsgType.ADVERTISEMENT)); + assertMessage(quickfix.fix50sp2.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(FixVersions.FIX50SP2, MsgType.ADVERTISEMENT)); assertMessage(quickfix.Message.class, MsgType.ADVERTISEMENT, factory.create("unknown", MsgType.ADVERTISEMENT)); } @Test public void testFixtCreate() throws Exception { assertMessage(quickfix.fixt11.Logon.class, MsgType.LOGON, factory.create(BEGINSTRING_FIXT11, MsgType.LOGON)); + assertMessage(fixtCreateExpectedClass, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, MsgType.EMAIL)); + assertMessage(quickfix.fix40.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(FIX40), MsgType.EMAIL)); + assertMessage(quickfix.fix41.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(FIX41), MsgType.EMAIL)); + assertMessage(quickfix.fix42.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(FIX42), MsgType.EMAIL)); + assertMessage(quickfix.fix43.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(FIX43), MsgType.EMAIL)); + assertMessage(quickfix.fix44.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(FIX44), MsgType.EMAIL)); + assertMessage(quickfix.fix50.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(ApplVerID.FIX50), MsgType.EMAIL)); + assertMessage(quickfix.fix50sp1.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(ApplVerID.FIX50SP1), MsgType.EMAIL)); + assertMessage(quickfix.fix50sp2.Email.class, MsgType.EMAIL, factory.create(BEGINSTRING_FIXT11, new ApplVerID(ApplVerID.FIX50SP2), MsgType.EMAIL)); } @Test public void testGroupCreate() throws Exception { - new ExpectedTestFailure(IllegalArgumentException.class, "unknown") { protected void execute() throws Throwable { factory.create("unknown", MsgType.NEWS, LinesOfText.FIELD); @@ -50,7 +71,9 @@ protected void execute() throws Throwable { assertEquals(quickfix.fix42.News.LinesOfText.class, factory.create(BEGINSTRING_FIX42, MsgType.NEWS, LinesOfText.FIELD).getClass()); assertEquals(quickfix.fix43.News.LinesOfText.class, factory.create(BEGINSTRING_FIX43, MsgType.NEWS, LinesOfText.FIELD).getClass()); assertEquals(quickfix.fix44.News.LinesOfText.class, factory.create(BEGINSTRING_FIX44, MsgType.NEWS, LinesOfText.FIELD).getClass()); - assertEquals(quickfix.fix50.News.NoLinesOfText.class, factory.create(FIX50, MsgType.NEWS, NoLinesOfText.FIELD).getClass()); + assertEquals(quickfix.fix50.News.NoLinesOfText.class, factory.create(FixVersions.FIX50, MsgType.NEWS, NoLinesOfText.FIELD).getClass()); + assertEquals(quickfix.fix50sp1.News.NoLinesOfText.class, factory.create(FixVersions.FIX50SP1, MsgType.NEWS, NoLinesOfText.FIELD).getClass()); + assertEquals(quickfix.fix50sp2.News.NoLinesOfText.class, factory.create(FixVersions.FIX50SP2, MsgType.NEWS, NoLinesOfText.FIELD).getClass()); assertNull("if group can't be created return null", factory.create(BEGINSTRING_FIX40, MsgType.MARKET_DATA_SNAPSHOT_FULL_REFRESH, NoMDEntries.FIELD)); } @@ -59,4 +82,18 @@ private static void assertMessage(Class expectedMessageClass, String expected assertEquals(expectedMessageClass, message.getClass()); assertEquals(expectedMessageType, message.getHeader().getString(MsgType.FIELD)); } + + @Parameterized.Parameters(name = "defaultApplVerID = {0}") + public static Object[][] getParameters() { + return new Object[][] { + {ApplVerID.FIX40, quickfix.fix40.Email.class}, + {ApplVerID.FIX41, quickfix.fix41.Email.class}, + {ApplVerID.FIX42, quickfix.fix42.Email.class}, + {ApplVerID.FIX43, quickfix.fix43.Email.class}, + {ApplVerID.FIX44, quickfix.fix44.Email.class}, + {ApplVerID.FIX50, quickfix.fix50.Email.class}, + {ApplVerID.FIX50SP1, quickfix.fix50sp1.Email.class}, + {ApplVerID.FIX50SP2, quickfix.fix50sp2.Email.class} + }; + } } From 32bc8f218acd23e3460a93c583446dd0ed0c6910 Mon Sep 17 00:00:00 2001 From: Chris Kolek Date: Thu, 1 Feb 2018 12:44:48 -0500 Subject: [PATCH 2/3] Fix DefaultMessageFactory default constructor to pass ApplVerID value instead of BeginString value --- .../src/main/java/quickfix/DefaultMessageFactory.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java b/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java index ffce99621..744249abe 100644 --- a/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java +++ b/quickfixj-core/src/main/java/quickfix/DefaultMessageFactory.java @@ -55,7 +55,7 @@ public class DefaultMessageFactory implements MessageFactory { * Equivalent to {@link #DefaultMessageFactory(String) DefaultMessageFactory}({@link ApplVerID#FIX50 ApplVerID.FIX50}). */ public DefaultMessageFactory() { - this(FIX50); + this(ApplVerID.FIX50); } /** From 13b96a545d8166e6522dd10d41b24768343d2fb3 Mon Sep 17 00:00:00 2001 From: Chris Kolek Date: Thu, 1 Feb 2018 12:45:43 -0500 Subject: [PATCH 3/3] Crack NOS for FIX50SPX in ATMessageCracker --- .../quickfix/test/acceptance/ATMessageCracker.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/quickfixj-core/src/test/java/quickfix/test/acceptance/ATMessageCracker.java b/quickfixj-core/src/test/java/quickfix/test/acceptance/ATMessageCracker.java index 9fd66e377..42443528b 100644 --- a/quickfixj-core/src/test/java/quickfix/test/acceptance/ATMessageCracker.java +++ b/quickfixj-core/src/test/java/quickfix/test/acceptance/ATMessageCracker.java @@ -65,6 +65,16 @@ public void onMessage(quickfix.fix50.NewOrderSingle message, SessionID sessionID process(message, sessionID); } + public void onMessage(quickfix.fix50sp1.NewOrderSingle message, SessionID sessionID) + throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue { + process(message, sessionID); + } + + public void onMessage(quickfix.fix50sp2.NewOrderSingle message, SessionID sessionID) + throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue { + process(message, sessionID); + } + public void onMessage(quickfix.fix50.SecurityDefinition message, SessionID sessionID) throws FieldNotFound, UnsupportedMessageType, IncorrectTagValue { try {