From 3c615be0745361ef4e30eccb3b1331cce2494d79 Mon Sep 17 00:00:00 2001 From: David Gibbs Date: Tue, 12 Dec 2017 17:35:48 +0000 Subject: [PATCH] provide an alternate DefaultMessageFactory to work with FIX50SP2 --- .../DefaultMessageFactoryFix50sp2.java | 156 ++++++++++++++++++ .../quickfix/MessageFactoryException.java | 12 ++ .../DefaultMessageFactoryFix50sp2Test.java | 53 ++++++ 3 files changed, 221 insertions(+) create mode 100755 quickfixj-core/src/main/java/quickfix/DefaultMessageFactoryFix50sp2.java create mode 100755 quickfixj-core/src/main/java/quickfix/MessageFactoryException.java create mode 100755 quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryFix50sp2Test.java diff --git a/quickfixj-core/src/main/java/quickfix/DefaultMessageFactoryFix50sp2.java b/quickfixj-core/src/main/java/quickfix/DefaultMessageFactoryFix50sp2.java new file mode 100755 index 000000000..0828d8f15 --- /dev/null +++ b/quickfixj-core/src/main/java/quickfix/DefaultMessageFactoryFix50sp2.java @@ -0,0 +1,156 @@ +/******************************************************************************* + * Copyright (c) quickfixengine.org All rights reserved. + * + * This file is part of the QuickFIX FIX Engine + * + * This file may be distributed under the terms of the quickfixengine.org + * license as defined by quickfixengine.org and appearing in the file + * LICENSE included in the packaging of this file. + * + * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING + * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A + * PARTICULAR PURPOSE. + * + * See http://www.quickfixengine.org/LICENSE for licensing information. + * + * Contact ask@quickfixengine.org if any conditions of this licensing + * are not clear to you. + ******************************************************************************/ + +package quickfix; + +import static quickfix.FixVersions.BEGINSTRING_FIX40; +import static quickfix.FixVersions.BEGINSTRING_FIX41; +import static quickfix.FixVersions.BEGINSTRING_FIX42; +import static quickfix.FixVersions.BEGINSTRING_FIX43; +import static quickfix.FixVersions.BEGINSTRING_FIX44; +import static quickfix.FixVersions.BEGINSTRING_FIXT11; +import static quickfix.FixVersions.FIX50; +import static quickfix.FixVersions.FIX50SP1; +import static quickfix.FixVersions.FIX50SP2; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import quickfix.Group; +import quickfix.Message; +import quickfix.MessageFactory; +import quickfix.MessageUtils; +import quickfix.field.MsgType; + +/** + * The default factory for creating FIX message instances. + * This is a cut and paste from quickfixj.DefaultMessageFactory. + * DefaultMessageFactory is not designed for extension + */ +public class DefaultMessageFactoryFix50sp2 implements MessageFactory { + private final Map messageFactories = new ConcurrentHashMap<>(); + + /** + * 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). + */ + public DefaultMessageFactoryFix50sp2() { + // To loosen the coupling between this factory and generated code, the + // message factories are discovered at run time using reflection + addFactory(BEGINSTRING_FIXT11); + addFactory(FIX50SP2); + } + + private void addFactory(String beginString) { + String packageVersion = beginString.replace(".", "").toLowerCase(); + addFactory(beginString, "quickfix." + packageVersion + ".MessageFactory"); + } + + /** + * Adds a factory of the given class, which will be delegated to for creating + * Message instances from messages with the given begin string. + *

+ * 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 beginString the begin string whose messages will be delegated to the factory + * @param factoryClassName the name of the factory class to instantiate and add + * @throws ClassNotFoundException if the named factory class cannot be found + * @throws RuntimeException if the named factory class cannot be instantiated + */ + @SuppressWarnings("unchecked") + public void addFactory(String beginString, String factoryClassName) { + // try to load the class + Class factoryClass = null; + try { + // try using our own classloader + factoryClass = (Class) Class.forName(factoryClassName); + } catch (ClassNotFoundException e) { + // try using context classloader (i.e. allow caller to specify it) + try { + Thread.currentThread().getContextClassLoader().loadClass(factoryClassName); + } catch (ClassNotFoundException e1) { + throw new MessageFactoryException("can't instantiate " + factoryClassName, e); + } + } + // if factory is found, add it + if (factoryClass != null) { + addFactory(beginString, factoryClass); + } + } + + /** + * Adds a factory of the given class, which will be delegated to for creating + * Message instances from messages with the given begin string. + * + * @param beginString the begin string whose messages will be delegated to the factory + * @param factoryClass the class of the factory to instantiate and add + * @throws RuntimeException if the given factory class cannot be instantiated + */ + public void addFactory(String beginString, Class factoryClass) { + try { + MessageFactory factory = factoryClass.newInstance(); + messageFactories.put(beginString, factory); + } catch (Exception e) { + throw new MessageFactoryException("can't instantiate " + factoryClass.getName(), e); + } + } + + public Message create(String beginString, 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(FIX50SP2); + } + } + + if (messageFactory != null) { + return messageFactory.create(beginString, msgType); + } + + Message message = new Message(); + message.getHeader().setString(MsgType.FIELD, msgType); + + return message; + } + + public Group create(String beginString, String msgType, int correspondingFieldID) { + MessageFactory messageFactory = messageFactories.get(beginString); + if (messageFactory != null) { + return messageFactory.create(beginString, msgType, correspondingFieldID); + } + throw new IllegalArgumentException("Unsupported FIX version: " + beginString); + } +} diff --git a/quickfixj-core/src/main/java/quickfix/MessageFactoryException.java b/quickfixj-core/src/main/java/quickfix/MessageFactoryException.java new file mode 100755 index 000000000..4c7238b79 --- /dev/null +++ b/quickfixj-core/src/main/java/quickfix/MessageFactoryException.java @@ -0,0 +1,12 @@ +package quickfix; + +public class MessageFactoryException extends RuntimeException { + /** + * + */ + private static final long serialVersionUID = -9213732643467684379L; + + public MessageFactoryException(String message, Exception e) { + super(message,e); + } +} diff --git a/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryFix50sp2Test.java b/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryFix50sp2Test.java new file mode 100755 index 000000000..a352523e4 --- /dev/null +++ b/quickfixj-core/src/test/java/quickfix/DefaultMessageFactoryFix50sp2Test.java @@ -0,0 +1,53 @@ +package quickfix; + +import static org.junit.Assert.*; +import static quickfix.FixVersions.*; + +import org.junit.Test; + +import quickfix.field.LinesOfText; +import quickfix.field.MsgType; +import quickfix.field.NoLinesOfText; +import quickfix.field.NoMDEntries; +import quickfix.test.util.ExpectedTestFailure; +import quickfix.Message; + +/** + * Verifies the behaviour of the {@link DefaultMessageFactory} class + * + * @author toli + * @version $Id$ + */ +public class DefaultMessageFactoryFix50sp2Test { + private final DefaultMessageFactoryFix50sp2 factory = new DefaultMessageFactoryFix50sp2(); + + @Test + public void testMessageCreate() throws Exception { + assertMessage(quickfix.fix50sp2.Advertisement.class, MsgType.ADVERTISEMENT, factory.create(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)); + } + + @Test + public void testGroupCreate() throws Exception { + + new ExpectedTestFailure(IllegalArgumentException.class, "unknown") { + protected void execute() throws Throwable { + factory.create("unknown", MsgType.NEWS, LinesOfText.FIELD); + } + }.run(); + + assertEquals(quickfix.fix50sp2.News.NoLinesOfText.class, factory.create(FIX50SP2, MsgType.NEWS, NoLinesOfText.FIELD).getClass()); + assertNull("if group can't be created return null", + factory.create(BEGINSTRING_FIXT11, MsgType.MARKET_DATA_SNAPSHOT_FULL_REFRESH, NoMDEntries.FIELD)); + } + + private static void assertMessage(Class expectedMessageClass, String expectedMessageType, Message message) throws Exception { + assertEquals(expectedMessageClass, message.getClass()); + assertEquals(expectedMessageType, message.getHeader().getString(MsgType.FIELD)); + } +}