Skip to content

Provide an alternate default message factory to work with FIX50SP2 #167

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -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<String, MessageFactory> messageFactories = new ConcurrentHashMap<>();

/**
* Constructs a DefaultMessageFactory, which dynamically loads and delegates to
* the default version-specific message factories, if they are available at runtime.
* <p>
* 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.
* <p>
* 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<? extends MessageFactory> factoryClass = null;
try {
// try using our own classloader
factoryClass = (Class<? extends MessageFactory>) 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<? extends MessageFactory> 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);
}
}
12 changes: 12 additions & 0 deletions quickfixj-core/src/main/java/quickfix/MessageFactoryException.java
Original file line number Diff line number Diff line change
@@ -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);
}
}
Original file line number Diff line number Diff line change
@@ -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));
}
}