/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.component;
import static com.opengamma.sesame.config.ConfigBuilder.argument;
import static com.opengamma.sesame.config.ConfigBuilder.arguments;
import static com.opengamma.sesame.config.ConfigBuilder.config;
import static com.opengamma.sesame.config.ConfigBuilder.configureView;
import static com.opengamma.sesame.config.ConfigBuilder.function;
import static com.opengamma.sesame.config.ConfigBuilder.nonPortfolioOutput;
import static com.opengamma.sesame.config.ConfigBuilder.output;
import static org.hamcrest.CoreMatchers.not;
import static org.hamcrest.CoreMatchers.nullValue;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.Is.is;
import static org.testng.Assert.fail;
import java.io.IOException;
import java.net.URI;
import java.util.ArrayList;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.apache.activemq.ActiveMQConnectionFactory;
import org.apache.commons.lang.math.RandomUtils;
import org.fudgemsg.MutableFudgeMsg;
import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.testng.annotations.AfterClass;
import org.testng.annotations.BeforeClass;
import org.testng.annotations.Test;
import org.threeten.bp.LocalDate;
import org.threeten.bp.Period;
import org.threeten.bp.ZonedDateTime;
import com.google.common.collect.ImmutableList;
import com.opengamma.component.ComponentFactory;
import com.opengamma.component.ComponentLogger;
import com.opengamma.component.ComponentRepository;
import com.opengamma.component.factory.EmbeddedJettyComponentFactory;
import com.opengamma.core.link.ConfigLink;
import com.opengamma.core.value.MarketDataRequirementNames;
import com.opengamma.engine.marketdata.spec.FixedHistoricalMarketDataSpecification;
import com.opengamma.engine.marketdata.spec.LiveMarketDataSpecification;
import com.opengamma.engine.marketdata.spec.MarketDataSpecification;
import com.opengamma.financial.analytics.curve.CurveConstructionConfiguration;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.livedata.LiveDataClient;
import com.opengamma.livedata.LiveDataListener;
import com.opengamma.livedata.LiveDataSpecification;
import com.opengamma.livedata.LiveDataValueUpdate;
import com.opengamma.livedata.LiveDataValueUpdateBean;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.livedata.msg.LiveDataSubscriptionResponse;
import com.opengamma.livedata.msg.LiveDataSubscriptionResult;
import com.opengamma.sesame.DefaultCurveNodeConverterFn;
import com.opengamma.sesame.DefaultDiscountingMulticurveBundleFn;
import com.opengamma.sesame.DefaultDiscountingMulticurveBundleResolverFn;
import com.opengamma.sesame.MarketDataResourcesLoader;
import com.opengamma.sesame.MulticurveBundle;
import com.opengamma.sesame.OutputNames;
import com.opengamma.sesame.RootFinderConfiguration;
import com.opengamma.sesame.config.ViewConfig;
import com.opengamma.sesame.engine.ResultItem;
import com.opengamma.sesame.engine.Results;
import com.opengamma.sesame.engine.ViewInputs;
import com.opengamma.sesame.interestrate.InterestRateMockSources;
import com.opengamma.sesame.server.FunctionServer;
import com.opengamma.sesame.server.FunctionServerRequest;
import com.opengamma.sesame.server.GlobalCycleOptions;
import com.opengamma.sesame.server.IndividualCycleOptions;
import com.opengamma.sesame.server.RemoteFunctionServer;
import com.opengamma.util.fudgemsg.OpenGammaFudgeContext;
import com.opengamma.util.jms.JmsConnector;
import com.opengamma.util.jms.JmsConnectorFactoryBean;
import com.opengamma.util.result.Result;
/**
* Tests that remoting to the new engine works. Starts up an engine on a
* separate thread in the setup method and then makes requests to it via
* REST. This test should run fast as all component parts are local and is
* therefore classified as a UNIT test. However, in many respects it is
* closer to an INTEGRATION test and therefore may need to be reclassified.
*/
// TODO this will need to be fixed and re-enabled once the new engine API is don@Test(groups = TestGroup.UNIT)
public class RemotingTest {
public static final String CLASSIFIER = "test";
private ComponentRepository _componentRepository;
private String _serverUrl;
@Test(enabled = false)
public void testSingleExecution() {
String curveBundleOutputName = "Curve Bundle";
ViewConfig viewConfig = createCurveBundleConfig(curveBundleOutputName);
// Send the config to the server, along with version
// correction, MD requirements, valuation date and
// cycle specifics (once/multiple/infinite)
// Proxy options?
FunctionServer functionServer = new RemoteFunctionServer(URI.create(_serverUrl));
MarketDataSpecification marketDataSpecification =
new FixedHistoricalMarketDataSpecification(LocalDate.now().minusDays(2));
IndividualCycleOptions cycleOptions = IndividualCycleOptions.builder()
.valuationTime(ZonedDateTime.now())
.marketDataSpecs(ImmutableList.of(marketDataSpecification))
.build();
FunctionServerRequest<IndividualCycleOptions> request =
FunctionServerRequest.<IndividualCycleOptions>builder()
.viewConfig(viewConfig)
//.withVersionCorrection(...)
//.withSecurities(...)
.cycleOptions(cycleOptions)
.build();
Results results = functionServer.executeSingleCycle(request);
System.out.println(results);
assertThat(results, is(not(nullValue())));
checkCurveBundleResult(curveBundleOutputName, results);
}
@Test(enabled = false)
public void testExecutionWithCapture() {
String curveBundleOutputName = "Curve Bundle";
ViewConfig viewConfig = createCurveBundleConfig(curveBundleOutputName);
// Send the config to the server, along with version
// correction, MD requirements, valuation date and
// cycle specifics (once/multiple/infinite)
// Proxy options?
FunctionServer functionServer = new RemoteFunctionServer(URI.create(_serverUrl));
ZonedDateTime now = ZonedDateTime.now();
MarketDataSpecification marketDataSpecification =
new FixedHistoricalMarketDataSpecification(LocalDate.now().minusDays(2));
IndividualCycleOptions cycleOptions = IndividualCycleOptions.builder()
.valuationTime(now)
.marketDataSpecs(ImmutableList.of(marketDataSpecification))
.captureInputs(true)
.build();
FunctionServerRequest<IndividualCycleOptions> request =
FunctionServerRequest.<IndividualCycleOptions>builder()
.viewConfig(viewConfig)
//.withVersionCorrection(...)
//.withSecurities(...)
.cycleOptions(cycleOptions)
.build();
Results results = functionServer.executeSingleCycle(request);
System.out.println(results);
assertThat(results, is(not(nullValue())));
checkCurveBundleResult(curveBundleOutputName, results);
ViewInputs viewInputs = results.getViewInputs();
assertThat(viewInputs, is(not(nullValue())));
assertThat(viewInputs.getValuationTime(), is(now));
assertThat(viewInputs.getConfigData().isEmpty(), is(false));
// TODO does MarketDataEnvironment need isEmpty()? seems a bit much just for testing
//assertThat(viewInputs.getMarketDataEnvironment().isEmpty(), is(false));
assertThat(viewInputs.getTradeInputs().isEmpty(), is(true));
// Following line would work were it not for Fudge compressing
// values and converting (Int) 1000 to (Short) 1000
// BeanAssert.assertBeanEquals(viewInputs.getViewConfig(), viewConfig);
}
private void checkCurveBundleResult(String curveBundleOutputName, Results results) {
ResultItem resultItem = results.get(curveBundleOutputName);
assertThat(resultItem, is(not(nullValue())));
Result<Object> result = resultItem.getResult();
if (!result.isSuccess()) {
fail("Expected success but got: " + result);
}
@SuppressWarnings("unchecked") MulticurveBundle pair = (MulticurveBundle) result.getValue();
assertThat(pair.getMulticurveProvider(), is(not(nullValue())));
assertThat(pair.getCurveBuildingBlockBundle(), is(not(nullValue())));
}
@Test(enabled = false)
public void testMultipleExecution() throws InterruptedException {
String curveBundleOutputName = "Curve Bundle";
ViewConfig viewConfig = createCurveBundleConfig(curveBundleOutputName);
// Send the config to the server, along with version
// correction, MD requirements, valuation date and
// cycle specifics (once/multiple/infinite)
// Proxy options?
FunctionServer functionServer = new RemoteFunctionServer(URI.create(_serverUrl));
GlobalCycleOptions cycleOptions = GlobalCycleOptions.builder()
.valuationTime(ZonedDateTime.now())
.marketDataSpec(new FixedHistoricalMarketDataSpecification(LocalDate.now().minusDays(2)))
.numCycles(2)
.build();
FunctionServerRequest<GlobalCycleOptions> request =
FunctionServerRequest.<GlobalCycleOptions>builder()
.viewConfig(viewConfig)
//.withVersionCorrection(...)
//.withSecurities(...)
.cycleOptions(cycleOptions)
.build();
List<Results> results = functionServer.executeMultipleCycles(request);
System.out.println(results);
assertThat(results, is(not(nullValue())));
assertThat(results.size(), is(2));
checkCurveBundleResult(curveBundleOutputName, results.get(0));
checkCurveBundleResult(curveBundleOutputName, results.get(1));
}
@Test(enabled = false)
public void testLiveExecution() {
String curveBundleOutputName = "Curve Bundle";
ViewConfig viewConfig = createCurveBundleConfig(curveBundleOutputName);
// Send the config to the server, along with version
// correction, MD requirements, valuation date and
// cycle specifics (once/multiple/infinite)
// Proxy options?
FunctionServer functionServer = new RemoteFunctionServer(URI.create(_serverUrl));
MarketDataSpecification liveSpec = LiveMarketDataSpecification.LIVE_SPEC;
IndividualCycleOptions cycleOptions = IndividualCycleOptions.builder()
.valuationTime(ZonedDateTime.now())
.marketDataSpecs(ImmutableList.of(liveSpec))
.build();
FunctionServerRequest<IndividualCycleOptions> request =
FunctionServerRequest.<IndividualCycleOptions>builder()
.viewConfig(viewConfig)
//.withVersionCorrection(...)
//.withSecurities(...)
.cycleOptions(cycleOptions)
.build();
Results results = functionServer.executeSingleCycle(request);
System.out.println(results);
assertThat(results, is(not(nullValue())));
checkCurveBundleResult(curveBundleOutputName, results);
}
private ViewConfig createCurveBundleConfig(String curveBundleOutputName) {
CurveConstructionConfiguration curveConstructionConfiguration =
ConfigLink.resolvable("USD_ON-OIS_LIBOR3M-FRAIRS_1U", CurveConstructionConfiguration.class).resolve();
return
configureView(
"Curve Bundle only",
nonPortfolioOutput(
curveBundleOutputName,
output(
OutputNames.DISCOUNTING_MULTICURVE_BUNDLE,
config(
arguments(
function(
RootFinderConfiguration.class,
argument("rootFinderAbsoluteTolerance", 1e-9),
argument("rootFinderRelativeTolerance", 1e-9),
argument("rootFinderMaxIterations", 1000)),
function(
DefaultCurveNodeConverterFn.class,
argument("timeSeriesDuration", RetrievalPeriod.of(Period.ofYears(1)))),
function(
DefaultDiscountingMulticurveBundleResolverFn.class,
argument("curveConfig", curveConstructionConfiguration)),
function(
DefaultDiscountingMulticurveBundleFn.class,
argument("impliedCurveNames", StringSet.of())))))));
}
// test execution with streaming results
@BeforeClass
public void setUp() throws Exception {
_componentRepository = new ComponentRepository(new ComponentLogger.Console(1));
Map<Class<?>, Object> componentMap = InterestRateMockSources.generateBaseComponents();
LinkedHashMap<String, String> properties = addComponentsToRepo(componentMap);
// initialise engine
ViewFactoryComponentFactory engineComponentFactory = new ViewFactoryComponentFactory();
engineComponentFactory.setClassifier(CLASSIFIER);
register(engineComponentFactory, _componentRepository, properties);
registerFunctionServerComponentFactory();
// todo - keeping the below as we should port tests to be like this one
/*
// initialise pricer
NewEngineFXForwardPricingManagerComponentFactory pricingManagerComponentFactory =
new NewEngineFXForwardPricingManagerComponentFactory();
pricingManagerComponentFactory.setEngine(_componentRepository.getInstance(Engine.class, "main"));
pricingManagerComponentFactory.setAvailableImplementations(_componentRepository.getInstance(AvailableImplementations.class,
"main"));
pricingManagerComponentFactory.setAvailableOutputs(_componentRepository.getInstance(AvailableOutputs.class, "main"));
register(pricingManagerComponentFactory, _componentRepository);*/
// initialize server
// Pick a random port in the ephemeral port range (49152-65535)
// TODO - We need to detect if the port is in use and pick another one
int serverPort = 49152 + RandomUtils.nextInt(65535 - 49152);
_serverUrl = "http://localhost:" + serverPort + "/jax";
// initialise Jetty server
EmbeddedJettyComponentFactory jettyComponentFactory = new EmbeddedJettyComponentFactory();
jettyComponentFactory.setPort(serverPort);
// TODO - can we supply the config required directly rather than a file?
Resource resource = new ClassPathResource("web-engine");
jettyComponentFactory.setResourceBase(resource);
register(jettyComponentFactory, _componentRepository);
_componentRepository.start();
}
private void registerFunctionServerComponentFactory() throws Exception {
FunctionServerComponentFactory serverComponentFactory = new FunctionServerComponentFactory();
serverComponentFactory.setClassifier(CLASSIFIER);
register(serverComponentFactory, _componentRepository);
}
private LiveDataClient createMockLiveDataClient() throws IOException {
return new LiveDataClient() {
final Map<ExternalIdBundle, Double> marketData = MarketDataResourcesLoader.getData(
"/usdMarketQuotes-20140122.properties", "Ticker");
long counter = 0;
@Override
public void subscribe(UserPrincipal user,
LiveDataSpecification requestedSpecification,
LiveDataListener listener) { }
@Override
public void subscribe(UserPrincipal user,
Collection<LiveDataSpecification> requestedSpecifications,
LiveDataListener listener) {
List<LiveDataSubscriptionResponse> subResponses = new ArrayList<>();
List<LiveDataValueUpdate> dataValues = new ArrayList<>();
for (LiveDataSpecification specification : requestedSpecifications) {
if (marketData.containsKey(specification.getIdentifiers())) {
subResponses.add(new LiveDataSubscriptionResponse(specification, LiveDataSubscriptionResult.SUCCESS, null, specification, null, null));
MutableFudgeMsg msg = OpenGammaFudgeContext.getInstance().newMessage();
msg.add(MarketDataRequirementNames.MARKET_VALUE, marketData.get(specification.getIdentifiers()));
dataValues.add(new LiveDataValueUpdateBean(counter++, specification, msg));
} else {
subResponses.add(new LiveDataSubscriptionResponse(specification, LiveDataSubscriptionResult.NOT_PRESENT));
}
}
listener.subscriptionResultsReceived(subResponses);
for (LiveDataValueUpdate value : dataValues) {
listener.valueUpdate(value);
}
}
@Override
public void unsubscribe(UserPrincipal user,
LiveDataSpecification fullyQualifiedSpecification,
LiveDataListener listener) { }
@Override
public void unsubscribe(UserPrincipal user,
Collection<LiveDataSpecification> fullyQualifiedSpecifications,
LiveDataListener listener) { }
@Override
public LiveDataSubscriptionResponse snapshot(UserPrincipal user,
LiveDataSpecification requestedSpecification,
long timeout) {
return null;
}
@Override
public Collection<LiveDataSubscriptionResponse> snapshot(UserPrincipal user,
Collection<LiveDataSpecification> requestedSpecifications,
long timeout) {
return null;
}
@Override
public String getDefaultNormalizationRuleSetId() {
return null;
}
@Override
public void close() {
}
@Override
public boolean isEntitled(UserPrincipal user, LiveDataSpecification requestedSpecification) {
return false;
}
@Override
public Map<LiveDataSpecification, Boolean> isEntitled(UserPrincipal user,
Collection<LiveDataSpecification> requestedSpecifications) {
return null;
}
};
}
private JmsConnector createJmsConnector() {
final JmsConnectorFactoryBean factory = new JmsConnectorFactoryBean();
factory.setName(getClass().getSimpleName());
factory.setClientBrokerUri(URI.create("vm://remotingTestBroker"));
factory.setConnectionFactory(new ActiveMQConnectionFactory(factory.getClientBrokerUri()));
return factory.getObjectCreating();
}
@SuppressWarnings("unchecked")
private LinkedHashMap<String, String> addComponentsToRepo(Map<Class<?>, Object> componentMap) {
LinkedHashMap<String, String> props = new LinkedHashMap<>();
for (Map.Entry<Class<?>, Object> entry : componentMap.entrySet()) {
final Class<?> clss = entry.getKey();
_componentRepository.registerComponent((Class<Object>) clss, CLASSIFIER, entry.getValue());
props.put(clss.getSimpleName(), clss.getSimpleName() + "::" + CLASSIFIER);
}
return props;
}
@AfterClass
public void tearDown() {
System.out.println("Shutting down components");
_componentRepository.stop();
}
private void register(ComponentFactory componentFactory,
ComponentRepository repo) throws Exception {
register(componentFactory, repo, new LinkedHashMap<String, String>());
}
private void register(ComponentFactory componentFactory,
ComponentRepository repo, LinkedHashMap<String, String> configuration) throws Exception {
componentFactory.init(repo, configuration);
}
}