/**
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.camel.component.quickfixj;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.lang.reflect.Method;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.apache.camel.CamelContext;
import org.apache.camel.Consumer;
import org.apache.camel.Endpoint;
import org.apache.camel.Exchange;
import org.apache.camel.ExchangePattern;
import org.apache.camel.MultipleConsumersSupport;
import org.apache.camel.Processor;
import org.apache.camel.Producer;
import org.apache.camel.StatefulService;
import org.apache.camel.component.quickfixj.converter.QuickfixjConverters;
import org.apache.camel.impl.DefaultCamelContext;
import org.apache.camel.impl.converter.StaticMethodTypeConverter;
import org.apache.camel.util.IOHelper;
import org.apache.camel.util.ServiceHelper;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import quickfix.Acceptor;
import quickfix.DefaultMessageFactory;
import quickfix.FixVersions;
import quickfix.Initiator;
import quickfix.LogFactory;
import quickfix.MemoryStoreFactory;
import quickfix.MessageFactory;
import quickfix.MessageStoreFactory;
import quickfix.ScreenLogFactory;
import quickfix.Session;
import quickfix.SessionFactory;
import quickfix.SessionID;
import quickfix.SessionSettings;
import quickfix.field.EmailThreadID;
import quickfix.field.EmailType;
import quickfix.field.SenderCompID;
import quickfix.field.Subject;
import quickfix.field.TargetCompID;
import quickfix.fix44.Email;
import quickfix.mina.ProtocolFactory;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.junit.Assert.assertThat;
import static org.junit.Assert.assertTrue;
public class QuickfixjComponentTest {
private File settingsFile;
private File settingsFile2;
private File tempdir;
private File tempdir2;
private ClassLoader contextClassLoader;
private SessionID sessionID;
private SessionSettings settings;
private QuickfixjComponent component;
private CamelContext camelContext;
private MessageFactory engineMessageFactory;
private MessageStoreFactory engineMessageStoreFactory;
private LogFactory engineLogFactory;
private void setSessionID(SessionSettings sessionSettings, SessionID sessionID) {
sessionSettings.setString(sessionID, SessionSettings.BEGINSTRING, sessionID.getBeginString());
sessionSettings.setString(sessionID, SessionSettings.SENDERCOMPID, sessionID.getSenderCompID());
sessionSettings.setString(sessionID, SessionSettings.TARGETCOMPID, sessionID.getTargetCompID());
}
private String getEndpointUri(final String configFilename, SessionID sid) {
String uri = "quickfix:" + configFilename;
if (sid != null) {
uri += "?sessionID=" + sid;
}
return uri;
}
@Before
public void setUp() throws Exception {
settingsFile = File.createTempFile("quickfixj_test_", ".cfg");
settingsFile2 = File.createTempFile("quickfixj_test2_", ".cfg");
tempdir = settingsFile.getParentFile();
tempdir2 = settingsFile.getParentFile();
URL[] urls = new URL[] {tempdir.toURI().toURL(), tempdir2.toURI().toURL()};
sessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "FOO", "BAR");
settings = new SessionSettings();
settings.setString(Acceptor.SETTING_SOCKET_ACCEPT_PROTOCOL, ProtocolFactory.getTypeString(ProtocolFactory.VM_PIPE));
settings.setString(Initiator.SETTING_SOCKET_CONNECT_PROTOCOL, ProtocolFactory.getTypeString(ProtocolFactory.VM_PIPE));
settings.setBool(Session.SETTING_USE_DATA_DICTIONARY, false);
setSessionID(settings, sessionID);
contextClassLoader = Thread.currentThread().getContextClassLoader();
ClassLoader testClassLoader = new URLClassLoader(urls, contextClassLoader);
Thread.currentThread().setContextClassLoader(testClassLoader);
}
private void setUpComponent() throws IOException, MalformedURLException, NoSuchMethodException {
setUpComponent(false);
}
private void setUpComponent(boolean injectQfjPlugins) throws IOException, MalformedURLException, NoSuchMethodException {
camelContext = new DefaultCamelContext();
component = new QuickfixjComponent();
component.setCamelContext(camelContext);
camelContext.addComponent("quickfix", component);
if (injectQfjPlugins) {
engineMessageFactory = new DefaultMessageFactory();
engineMessageStoreFactory = new MemoryStoreFactory();
engineLogFactory = new ScreenLogFactory();
component.setMessageFactory(engineMessageFactory);
component.setMessageStoreFactory(engineMessageStoreFactory);
component.setLogFactory(engineLogFactory);
}
assertThat(component.getEngines().size(), is(0));
Method converterMethod = QuickfixjConverters.class.getMethod("toSessionID", new Class<?>[] {String.class});
camelContext.getTypeConverterRegistry().addTypeConverter(SessionID.class, String.class, new StaticMethodTypeConverter(converterMethod, false));
}
@After
public void tearDown() throws Exception {
Thread.currentThread().setContextClassLoader(contextClassLoader);
if (component != null) {
component.stop();
}
if (camelContext != null) {
camelContext.stop();
}
}
@Test
public void createEndpointBeforeComponentStart() throws Exception {
setUpComponent();
settings.setString(sessionID, SessionFactory.SETTING_CONNECTION_TYPE, SessionFactory.INITIATOR_CONNECTION_TYPE);
settings.setLong(sessionID, Initiator.SETTING_SOCKET_CONNECT_PORT, 1234);
writeSettings(settings, true);
// Should use cached QFJ engine
Endpoint e1 = component.createEndpoint(getEndpointUri(settingsFile.getName(), null));
assertThat(component.getProvisionalEngines().size(), is(1));
assertThat(component.getProvisionalEngines().get(settingsFile.getName()), is(notNullValue()));
assertThat(component.getProvisionalEngines().get(settingsFile.getName()).isInitialized(), is(true));
assertThat(component.getProvisionalEngines().get(settingsFile.getName()).isStarted(), is(false));
assertThat(component.getEngines().size(), is(0));
assertThat(((QuickfixjEndpoint)e1).getSessionID(), is(nullValue()));
writeSettings(settings, false);
// Should use cached QFJ engine
Endpoint e2 = component.createEndpoint(getEndpointUri(settingsFile2.getName(), null));
assertThat(component.getProvisionalEngines().size(), is(2));
assertThat(component.getProvisionalEngines().get(settingsFile.getName()), is(notNullValue()));
assertThat(component.getProvisionalEngines().get(settingsFile.getName()).isInitialized(), is(true));
assertThat(component.getProvisionalEngines().get(settingsFile.getName()).isStarted(), is(false));
assertThat(component.getEngines().size(), is(0));
assertThat(((QuickfixjEndpoint)e2).getSessionID(), is(nullValue()));
// will start the component
camelContext.start();
assertThat(component.getProvisionalEngines().size(), is(0));
assertThat(component.getEngines().size(), is(2));
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(true));
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(true));
// Move these too an endpoint testcase if one exists
assertThat(e1.isSingleton(), is(true));
assertThat(((MultipleConsumersSupport)e1).isMultipleConsumersSupported(), is(true));
assertThat(e2.isSingleton(), is(true));
assertThat(((MultipleConsumersSupport)e2).isMultipleConsumersSupported(), is(true));
}
@Test
public void createEndpointAfterComponentStart() throws Exception {
setUpComponent();
settings.setString(sessionID, SessionFactory.SETTING_CONNECTION_TYPE, SessionFactory.INITIATOR_CONNECTION_TYPE);
settings.setLong(sessionID, Initiator.SETTING_SOCKET_CONNECT_PORT, 1234);
writeSettings();
// will start the component
camelContext.start();
Endpoint e1 = component.createEndpoint(getEndpointUri(settingsFile.getName(), null));
assertThat(component.getEngines().size(), is(1));
assertThat(component.getEngines().get(settingsFile.getName()), is(notNullValue()));
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(true));
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(true));
assertThat(component.getProvisionalEngines().size(), is(0));
assertThat(((QuickfixjEndpoint)e1).getSessionID(), is(nullValue()));
Endpoint e2 = component.createEndpoint(getEndpointUri(settingsFile.getName(), sessionID));
assertThat(component.getEngines().size(), is(1));
assertThat(component.getEngines().get(settingsFile.getName()), is(notNullValue()));
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(true));
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(true));
assertThat(component.getProvisionalEngines().size(), is(0));
assertThat(((QuickfixjEndpoint)e2).getSessionID(), is(sessionID));
}
@Test
public void createEnginesLazily() throws Exception {
setUpComponent();
component.setLazyCreateEngines(true);
settings.setString(sessionID, SessionFactory.SETTING_CONNECTION_TYPE, SessionFactory.INITIATOR_CONNECTION_TYPE);
settings.setLong(sessionID, Initiator.SETTING_SOCKET_CONNECT_PORT, 1234);
writeSettings();
// start the component
camelContext.start();
QuickfixjEndpoint e1 = (QuickfixjEndpoint) component.createEndpoint(getEndpointUri(settingsFile.getName(), null));
assertThat(component.getEngines().size(), is(1));
assertThat(component.getProvisionalEngines().size(), is(0));
assertThat(component.getEngines().get(settingsFile.getName()), is(notNullValue()));
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(false));
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(false));
e1.ensureInitialized();
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(true));
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(true));
}
@Test
public void createEndpointsInNonLazyComponent() throws Exception {
setUpComponent();
// configuration will be done per endpoint
component.setLazyCreateEngines(false);
settings.setString(sessionID, SessionFactory.SETTING_CONNECTION_TYPE, SessionFactory.INITIATOR_CONNECTION_TYPE);
settings.setLong(sessionID, Initiator.SETTING_SOCKET_CONNECT_PORT, 1234);
writeSettings();
// will start the component
camelContext.start();
QuickfixjEndpoint e1 = (QuickfixjEndpoint) component.createEndpoint(getEndpointUri(settingsFile.getName(), null) + "?lazyCreateEngine=true");
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(false));
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(false));
assertThat(component.getEngines().get(settingsFile.getName()).isLazy(), is(true));
e1.ensureInitialized();
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(true));
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(true));
writeSettings(settings, false);
// will use connector's lazyCreateEngines setting
component.createEndpoint(getEndpointUri(settingsFile2.getName(), sessionID));
assertThat(component.getEngines().get(settingsFile2.getName()).isInitialized(), is(true));
assertThat(component.getEngines().get(settingsFile2.getName()).isStarted(), is(true));
assertThat(component.getEngines().get(settingsFile2.getName()).isLazy(), is(false));
}
@Test
public void createEndpointsInLazyComponent() throws Exception {
setUpComponent();
component.setLazyCreateEngines(true);
settings.setString(sessionID, SessionFactory.SETTING_CONNECTION_TYPE, SessionFactory.INITIATOR_CONNECTION_TYPE);
settings.setLong(sessionID, Initiator.SETTING_SOCKET_CONNECT_PORT, 1234);
writeSettings();
// will start the component
camelContext.start();
// will use connector's lazyCreateEngines setting
QuickfixjEndpoint e1 = (QuickfixjEndpoint) component.createEndpoint(getEndpointUri(settingsFile.getName(), null));
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(false));
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(false));
assertThat(component.getEngines().get(settingsFile.getName()).isLazy(), is(true));
e1.ensureInitialized();
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(true));
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(true));
writeSettings(settings, false);
// will override connector's lazyCreateEngines setting
component.createEndpoint(getEndpointUri(settingsFile2.getName(), sessionID) + "&lazyCreateEngine=false");
assertThat(component.getEngines().get(settingsFile2.getName()).isInitialized(), is(true));
assertThat(component.getEngines().get(settingsFile2.getName()).isStarted(), is(true));
assertThat(component.getEngines().get(settingsFile2.getName()).isLazy(), is(false));
}
@Test
public void componentStop() throws Exception {
setUpComponent();
settings.setString(sessionID, SessionFactory.SETTING_CONNECTION_TYPE, SessionFactory.INITIATOR_CONNECTION_TYPE);
settings.setLong(sessionID, Initiator.SETTING_SOCKET_CONNECT_PORT, 1234);
writeSettings();
Endpoint endpoint = component.createEndpoint(getEndpointUri(settingsFile.getName(), null));
final CountDownLatch latch = new CountDownLatch(1);
Consumer consumer = endpoint.createConsumer(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
QuickfixjEventCategory eventCategory =
(QuickfixjEventCategory) exchange.getIn().getHeader(QuickfixjEndpoint.EVENT_CATEGORY_KEY);
if (eventCategory == QuickfixjEventCategory.SessionCreated) {
latch.countDown();
}
}
});
ServiceHelper.startService(consumer);
// Endpoint automatically starts the consumer
assertThat(((StatefulService)consumer).isStarted(), is(true));
// will start the component
camelContext.start();
assertTrue("Session not created", latch.await(5000, TimeUnit.MILLISECONDS));
component.stop();
assertThat(component.getEngines().get(settingsFile.getName()).isStarted(), is(false));
// it should still be initialized (ready to start again)
assertThat(component.getEngines().get(settingsFile.getName()).isInitialized(), is(true));
}
@Test
public void messagePublication() throws Exception {
setUpComponent();
// Create settings file with both acceptor and initiator
SessionSettings settings = new SessionSettings();
settings.setString(Acceptor.SETTING_SOCKET_ACCEPT_PROTOCOL, ProtocolFactory.getTypeString(ProtocolFactory.VM_PIPE));
settings.setString(Initiator.SETTING_SOCKET_CONNECT_PROTOCOL, ProtocolFactory.getTypeString(ProtocolFactory.VM_PIPE));
settings.setBool(Session.SETTING_USE_DATA_DICTIONARY, false);
SessionID acceptorSessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "ACCEPTOR", "INITIATOR");
settings.setString(acceptorSessionID, SessionFactory.SETTING_CONNECTION_TYPE, SessionFactory.ACCEPTOR_CONNECTION_TYPE);
settings.setLong(acceptorSessionID, Acceptor.SETTING_SOCKET_ACCEPT_PORT, 1234);
setSessionID(settings, acceptorSessionID);
SessionID initiatorSessionID = new SessionID(FixVersions.BEGINSTRING_FIX44, "INITIATOR", "ACCEPTOR");
settings.setString(initiatorSessionID, SessionFactory.SETTING_CONNECTION_TYPE, SessionFactory.INITIATOR_CONNECTION_TYPE);
settings.setLong(initiatorSessionID, Initiator.SETTING_SOCKET_CONNECT_PORT, 1234);
settings.setLong(initiatorSessionID, Initiator.SETTING_RECONNECT_INTERVAL, 1);
setSessionID(settings, initiatorSessionID);
writeSettings(settings, true);
Endpoint endpoint = component.createEndpoint(getEndpointUri(settingsFile.getName(), null));
// Start the component and wait for the FIX sessions to be logged on
final CountDownLatch logonLatch = new CountDownLatch(2);
final CountDownLatch messageLatch = new CountDownLatch(2);
Consumer consumer = endpoint.createConsumer(new Processor() {
@Override
public void process(Exchange exchange) throws Exception {
QuickfixjEventCategory eventCategory =
(QuickfixjEventCategory) exchange.getIn().getHeader(QuickfixjEndpoint.EVENT_CATEGORY_KEY);
if (eventCategory == QuickfixjEventCategory.SessionLogon) {
logonLatch.countDown();
} else if (eventCategory == QuickfixjEventCategory.AppMessageReceived) {
messageLatch.countDown();
}
}
});
ServiceHelper.startService(consumer);
// will start the component
camelContext.start();
assertTrue("Session not created", logonLatch.await(5000, TimeUnit.MILLISECONDS));
Endpoint producerEndpoint = component.createEndpoint(getEndpointUri(settingsFile.getName(), acceptorSessionID));
Producer producer = producerEndpoint.createProducer();
// FIX message to send
Email email = new Email(new EmailThreadID("ID"), new EmailType(EmailType.NEW), new Subject("Test"));
Exchange exchange = producer.createExchange(ExchangePattern.InOnly);
exchange.getIn().setBody(email);
producer.process(exchange);
// Produce with no session ID specified, session ID must be in message
Producer producer2 = endpoint.createProducer();
email.getHeader().setString(SenderCompID.FIELD, acceptorSessionID.getSenderCompID());
email.getHeader().setString(TargetCompID.FIELD, acceptorSessionID.getTargetCompID());
producer2.process(exchange);
assertTrue("Messages not received", messageLatch.await(5000, TimeUnit.MILLISECONDS));
}
@Test
public void userSpecifiedQuickfixjPlugins() throws Exception {
setUpComponent(true);
settings.setString(sessionID, SessionFactory.SETTING_CONNECTION_TYPE, SessionFactory.INITIATOR_CONNECTION_TYPE);
settings.setLong(sessionID, Initiator.SETTING_SOCKET_CONNECT_PORT, 1234);
writeSettings();
component.createEndpoint(getEndpointUri(settingsFile.getName(), null));
// will start the component
camelContext.start();
assertThat(component.getEngines().size(), is(1));
QuickfixjEngine engine = component.getEngines().values().iterator().next();
assertThat(engine.getMessageFactory(), is(engineMessageFactory));
assertThat(engine.getMessageStoreFactory(), is(engineMessageStoreFactory));
assertThat(engine.getLogFactory(), is(engineLogFactory));
}
private void writeSettings() throws IOException {
writeSettings(settings, true);
}
private void writeSettings(SessionSettings settings, boolean firstSettingsFile) throws IOException {
FileOutputStream settingsOut = new FileOutputStream(firstSettingsFile ? settingsFile : settingsFile2);
try {
settings.toStream(settingsOut);
} finally {
IOHelper.close(settingsOut);
}
}
}