/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.financial.view.rest;
import java.net.URI;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import javax.ws.rs.Consumes;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriBuilder;
import javax.ws.rs.core.UriInfo;
import org.fudgemsg.FudgeContext;
import com.opengamma.core.config.impl.DataConfigSourceResource;
import com.opengamma.core.historicaltimeseries.HistoricalTimeSeriesSource;
import com.opengamma.engine.ComputationTargetResolver;
import com.opengamma.engine.marketdata.snapshot.MarketDataSnapshotter;
import com.opengamma.engine.marketdata.snapshot.MarketDataSnapshotter.Mode;
import com.opengamma.engine.view.ViewProcess;
import com.opengamma.engine.view.ViewProcessor;
import com.opengamma.engine.view.client.ViewClient;
import com.opengamma.financial.analytics.volatility.cube.VolatilityCubeDefinitionSource;
import com.opengamma.financial.marketdatasnapshot.MarketDataSnapshotterImpl;
import com.opengamma.financial.rest.RestfulJmsResultPublisherExpiryJob;
import com.opengamma.id.UniqueId;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.transport.jaxrs.FudgeRest;
import com.opengamma.util.jms.JmsConnector;
import com.opengamma.util.rest.AbstractDataResource;
/**
* RESTful resource for a {@link ViewProcessor}.
*/
public class DataViewProcessorResource extends AbstractDataResource {
/**
* The period after which, if a view client has not been accessed, it may be shut down.
*/
public static final long VIEW_CLIENT_TIMEOUT_MILLIS = 30000;
/**
* URI path to the config source.
*/
public static final String PATH_CONFIG_SOURCE = "configSource";
/**
* URI path to the market data repository.
*/
public static final String PATH_NAMED_MARKET_DATA_SPEC_REPOSITORY = "namedMarketDataSpecRepository";
/**
* URI path to the name.
*/
public static final String PATH_NAME = "name";
/**
* URI path to the clients.
*/
public static final String PATH_CLIENTS = "clients";
/**
* URI path to the processes.
*/
public static final String PATH_PROCESSES = "processes";
/**
* URI path to the cycles.
*/
public static final String PATH_CYCLES = "cycles";
/**
* URI path to the snapshotter.
*/
public static final String PATH_SNAPSHOTTER = "marketDataSnapshotter";
/**
* The view processor.
*/
private final ViewProcessor _viewProcessor;
/**
* The computation target resolver.
*/
private final ComputationTargetResolver _targetResolver;
/**
* The volatility cube definition.
*/
private final VolatilityCubeDefinitionSource _volatilityCubeDefinitionSource;
/**
* The connection factory.
*/
private final JmsConnector _jmsConnector;
/**
* The executor service.
*/
private final ScheduledExecutorService _scheduler;
/**
* The cycle manager.
*/
private final AtomicReference<DataViewCycleManagerResource> _cycleManagerResource = new AtomicReference<DataViewCycleManagerResource>();
/**
* The view clients.
*/
private final ConcurrentMap<UniqueId, DataViewClientResource> _createdViewClients = new ConcurrentHashMap<UniqueId, DataViewClientResource>();
/**
* The hts source
*/
private final HistoricalTimeSeriesSource _htsSource;
/**
* Creates an instance.
*
* @param viewProcessor the view processor, not null
* @param targetResolver the target resolver, not null
* @param volatilityCubeDefinitionSource the volatility cube, not null
* @param jmsConnector the JMS connector, may be null
* @param fudgeContext the Fudge context, not null
* @param scheduler the scheduler, not null
* @param htsSource the hts source, may be null
*/
public DataViewProcessorResource(final ViewProcessor viewProcessor,
final ComputationTargetResolver targetResolver,
final VolatilityCubeDefinitionSource volatilityCubeDefinitionSource,
final JmsConnector jmsConnector,
final FudgeContext fudgeContext,
final ScheduledExecutorService scheduler,
final HistoricalTimeSeriesSource htsSource) {
_viewProcessor = viewProcessor;
_htsSource = htsSource;
_targetResolver = targetResolver;
_volatilityCubeDefinitionSource = volatilityCubeDefinitionSource;
_jmsConnector = jmsConnector;
_scheduler = scheduler;
_scheduler.scheduleAtFixedRate(createExpiryJob(),
VIEW_CLIENT_TIMEOUT_MILLIS,
VIEW_CLIENT_TIMEOUT_MILLIS,
TimeUnit.MILLISECONDS);
}
private RestfulJmsResultPublisherExpiryJob<DataViewClientResource> createExpiryJob() {
return new RestfulJmsResultPublisherExpiryJob<>(
_createdViewClients.values(), VIEW_CLIENT_TIMEOUT_MILLIS);
}
/**
* Gets the viewProcessor field.
*
* @return the viewProcessor
*/
public ViewProcessor getViewProcessor() {
return _viewProcessor;
}
//-------------------------------------------------------------------------
@GET
public Response getHateaos(@Context final UriInfo uriInfo) {
return hateoasResponse(uriInfo);
}
@GET
@Path(PATH_NAME)
public Response getName() {
return responseOk(getViewProcessor().getName());
}
@Path(PATH_CONFIG_SOURCE)
public DataConfigSourceResource getConfigSource() {
return new DataConfigSourceResource(getViewProcessor().getConfigSource());
}
@Path(PATH_NAMED_MARKET_DATA_SPEC_REPOSITORY)
public DataNamedMarketDataSpecificationRepositoryResource getLiveMarketDataSourceRegistry() {
return new DataNamedMarketDataSpecificationRepositoryResource(getViewProcessor().getNamedMarketDataSpecificationRepository());
}
@Path(PATH_SNAPSHOTTER + "/{mode}")
public DataMarketDataSnapshotterResource getMarketDataSnapshotterImpl(@PathParam("mode") final String mode) {
final MarketDataSnapshotter snp = new MarketDataSnapshotterImpl(_targetResolver,
_htsSource,
Mode.valueOf(Mode.class, mode));
return new DataMarketDataSnapshotterResource(getViewProcessor(), snp);
}
//-------------------------------------------------------------------------
@Path(PATH_PROCESSES + "/{viewProcessId}")
public DataViewProcessResource getViewProcess(@PathParam("viewProcessId") final String viewProcessId) {
final ViewProcess view = getViewProcessor().getViewProcess(UniqueId.parse(viewProcessId));
return new DataViewProcessResource(view);
}
//-------------------------------------------------------------------------
@Path(PATH_CLIENTS + "/{viewClientId}")
public DataViewClientResource getViewClient(@Context final UriInfo uriInfo,
@PathParam("viewClientId") final String viewClientIdString) {
final UniqueId viewClientId = UniqueId.parse(viewClientIdString);
final DataViewClientResource viewClientResource = _createdViewClients.get(viewClientId);
if (viewClientResource != null) {
return viewClientResource;
}
final ViewClient viewClient = getViewProcessor().getViewClient(viewClientId);
final URI viewProcessorUri = getViewProcessorUri(uriInfo);
return createViewClientResource(viewClient, viewProcessorUri);
}
@POST
@Path(PATH_CLIENTS)
@Consumes(FudgeRest.MEDIA)
public Response createViewClient(@Context final UriInfo uriInfo, final UserPrincipal user) {
final ViewClient client = getViewProcessor().createViewClient(user);
final URI viewProcessorUri = getViewProcessorUri(uriInfo);
// Required for heartbeating, but also acts as an optimisation for getViewClient because view clients created
// through the REST API should be accessed again through the same API, potentially many times.
final DataViewClientResource viewClientResource = createViewClientResource(client, viewProcessorUri);
_createdViewClients.put(client.getUniqueId(), viewClientResource);
final URI createdUri = uriClient(uriInfo.getRequestUri(), client.getUniqueId());
return responseCreated(createdUri);
}
//-------------------------------------------------------------------------
@Path(PATH_CYCLES)
public DataViewCycleManagerResource getViewCycleManager(@Context final UriInfo uriInfo) {
return getOrCreateDataViewCycleManagerResource(getViewProcessorUri(uriInfo));
}
//-------------------------------------------------------------------------
public static URI uriViewProcess(final URI baseUri, final UniqueId viewProcessId) {
// WARNING: '/' characters could well appear in the view name
// There is a bug(?) in UriBuilder where, even though segment() is meant to treat the item as a single path segment
// and therefore encode '/' characters, it does not encode '/' characters which come from a variable substitution.
return UriBuilder.fromUri(baseUri).path("processes").segment(viewProcessId.toString()).build();
}
public static URI uriClient(final URI clientsBaseUri, final UniqueId viewClientId) {
return UriBuilder.fromUri(clientsBaseUri).segment(viewClientId.toString()).build();
}
public static URI uriSnapshotter(final URI clientsBaseUri, final Mode mode) {
return UriBuilder.fromUri(clientsBaseUri).path(PATH_SNAPSHOTTER).segment(mode.name()).build();
}
private URI getViewProcessorUri(final UriInfo uriInfo) {
return uriInfo.getBaseUri().resolve(UriBuilder.fromUri(uriInfo.getMatchedURIs().get(1)).build());
}
private DataViewCycleManagerResource getOrCreateDataViewCycleManagerResource(final URI viewProcessorUri) {
DataViewCycleManagerResource resource = _cycleManagerResource.get();
if (resource == null) {
final URI baseUri = UriBuilder.fromUri(viewProcessorUri).path(PATH_CYCLES).build();
final DataViewCycleManagerResource newResource = new DataViewCycleManagerResource(baseUri,
getViewProcessor().getViewCycleManager());
if (_cycleManagerResource.compareAndSet(null, newResource)) {
resource = newResource;
final DataViewCycleManagerResource.ReleaseExpiredReferencesRunnable task = newResource.createReleaseExpiredReferencesTask();
task.setScheduler(_scheduler);
} else {
resource = _cycleManagerResource.get();
}
}
return resource;
}
private DataViewClientResource createViewClientResource(final ViewClient viewClient, final URI viewProcessorUri) {
final DataViewCycleManagerResource cycleManagerResource = getOrCreateDataViewCycleManagerResource(viewProcessorUri);
return new DataViewClientResource(viewClient, cycleManagerResource, _jmsConnector, _scheduler);
}
}