/**
* Copyright (C) 2014 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.sesame.engine;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.opengamma.core.config.ConfigSource;
import com.opengamma.core.config.impl.NarrowingConfigSource;
import com.opengamma.core.convention.ConventionSource;
import com.opengamma.core.convention.impl.NarrowingConventionSource;
import com.opengamma.core.historicaltimeseries.HistoricalTimeSeries;
import com.opengamma.core.historicaltimeseries.HistoricalTimeSeriesSource;
import com.opengamma.core.holiday.HolidaySource;
import com.opengamma.core.holiday.NarrowingHolidaySource;
import com.opengamma.core.region.RegionSource;
import com.opengamma.core.region.impl.NarrowingRegionSource;
import com.opengamma.core.security.SecuritySource;
import com.opengamma.core.security.impl.NarrowingSecuritySource;
import com.opengamma.id.ExternalIdBundle;
import com.opengamma.id.UniqueIdentifiable;
import com.opengamma.service.ServiceContext;
import com.opengamma.sesame.config.EngineUtils;
import com.opengamma.sesame.config.ViewConfig;
import com.opengamma.sesame.graph.FunctionBuilder;
import com.opengamma.sesame.graph.Graph;
import com.opengamma.sesame.graph.GraphModel;
import com.opengamma.sesame.marketdata.MarketDataEnvironment;
import com.opengamma.util.ArgumentChecker;
/**
* A cycle initializer to be used for capturing the
* inputs to a cycle. This requires additional work as
* we need to replace the component map so that we can
* capture the data requested. Similarly we need to
* switch market data source so we can capture market
* data requests.
*/
class CapturingCycleInitializer implements CycleInitializer {
private final ServiceContext _serviceContext;
private final DefaultCycleRecorder _recorder;
private final Graph _graph;
private final CacheBuilder<Object, Object> _cacheBuilder;
/**
* Creates cycle initializer for a capturing cycle.
*
* @param serviceContext the current service context
* @param calculationArguments the cycle arguments
* @param graphModel the graph model for the view
* @param viewConfig the config for the view
* @param cacheBuilder for building an empty cache only used for a single run
* @param inputs the trade inputs
*/
public CapturingCycleInitializer(ServiceContext serviceContext,
ComponentMap componentMap,
CalculationArguments calculationArguments,
MarketDataEnvironment marketDataEnvironment,
GraphModel graphModel,
ViewConfig viewConfig,
CacheBuilder<Object, Object> cacheBuilder,
List<?> inputs) {
_cacheBuilder = ArgumentChecker.notNull(cacheBuilder, "cacheBuilder");
ProxiedComponentMap collector = new DefaultProxiedComponentMap();
// The component map has its components wrapped so they can
// used for cache invalidation when required. It will receive
// callbacks from all views which is correct as the cache spans
// all views but is not what is required here. We need a
// component map that only receives calls from this view so we
// need to wrap again
ComponentMap wrappedComponents = wrap(componentMap, collector);
// If we are capturing the inputs then we don't want to use
// memoized functions from the normal cache as that would
// prevent us hitting the sources
FunctionBuilder functionBuilder = new FunctionBuilder(false);
_graph = graphModel.build(wrappedComponents, functionBuilder);
_serviceContext = serviceContext.with(wrappedComponents.getComponents());
_recorder = new DefaultCycleRecorder(viewConfig, inputs, calculationArguments, marketDataEnvironment, collector);
}
@Override
public ServiceContext getServiceContext() {
return _serviceContext;
}
@Override
public Graph getGraph() {
return _graph;
}
/**
* @return a new, empty cache.
*/
@Override
public Cache<Object, Object> getCache() {
return _cacheBuilder.build();
}
@Override
public Results complete(Results results) {
return _recorder.complete(results);
}
// TODO - this could maybe go on component map itself
private ComponentMap wrap(ComponentMap components, final ProxiedComponentMap collector) {
Map<Class<?>, Object> wrapped = new HashMap<>();
// Wrap each component in the map with a proxy, which will
// automatically notify the collector whenever requests are
// made from one of the components
for (Map.Entry<Class<?>, Object> entry : components.getComponents().entrySet()) {
final Class<?> key = entry.getKey();
final Object component = entry.getValue();
// This proxy mechanism works correctly but unfortunately the
// sources and other components in proxies are not so well behaved.
// In order to get consistent data recorded we need to use
// source implementations that are well behaved as well.
InvocationHandler handler = new InvocationHandler() {
@Override
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {
try {
if (!method.getName().equals("get")) {
throw new UnsupportedOperationException("Only calls to get are supported through this " + key.getSimpleName() + " proxy");
}
Object result = method.invoke(component, args);
if (result != null) {
if (Map.class.isAssignableFrom(method.getReturnType())) {
for (Object item : Map.class.cast(result).values()) {
collector.receivedCall(key, (UniqueIdentifiable) item);
}
} else if (Collection.class.isAssignableFrom(method.getReturnType())) {
for (Object item : Collection.class.cast(result)) {
collector.receivedCall(key, (UniqueIdentifiable) item);
}
} else {
collector.receivedCall(key, (UniqueIdentifiable) result);
}
}
return result;
} catch (Exception e) {
throw EngineUtils.getCause(e);
}
}
};
Object proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{key}, handler);
if (key == ConfigSource.class) {
proxy = new NarrowingConfigSource((ConfigSource) proxy);
} else if (key == SecuritySource.class) {
proxy = new NarrowingSecuritySource((SecuritySource) proxy);
} else if (key == ConventionSource.class) {
proxy = new NarrowingConventionSource((ConventionSource) proxy);
} else if (key == RegionSource.class) {
proxy = new NarrowingRegionSource((RegionSource) proxy);
} else if (key == HolidaySource.class) {
proxy = new NarrowingHolidaySource((HolidaySource) proxy);
} else if (key == HistoricalTimeSeriesSource.class) {
InvocationHandler htsHandler = new InvocationHandler() {
@Override
public Object invoke(Object proxy,
Method method,
Object[] args) throws Throwable {
try {
if (!method.getName().equals("getHistoricalTimeSeries")) {
throw new UnsupportedOperationException(
"Only calls to getHistoricalTimeSeries are supported through this HTS proxy");
}
Object result = method.invoke(component, args);
if (result != null) {
switch (args.length) {
case 4:
collector.receivedHtsCall(
(ExternalIdBundle) args[0], (String) args[1], (String) args[2], (String) args[3],
((HistoricalTimeSeries) result).getTimeSeries());
break;
case 7:
collector.receivedHtsCall(
(ExternalIdBundle) args[1], null, null, (String) args[0],
((HistoricalTimeSeries) result).getTimeSeries());
break;
default:
throw new UnsupportedOperationException(
"Unable to handle calls to " + args.length + " arg version");
}
}
return result;
} catch (Exception e) {
throw EngineUtils.getCause(e);
}
}
};
proxy = Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(), new Class<?>[]{key}, htsHandler);
} else {
// Don't proxy any other components
proxy = component;
}
wrapped.put(key, proxy);
}
return ComponentMap.of(wrapped);
}
}