/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.bbg;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Future;
import java.util.concurrent.atomic.AtomicLong;
import org.slf4j.Logger;
import org.springframework.context.Lifecycle;
import com.bloomberglp.blpapi.CorrelationID;
import com.bloomberglp.blpapi.Element;
import com.bloomberglp.blpapi.Event;
import com.bloomberglp.blpapi.Identity;
import com.bloomberglp.blpapi.Message;
import com.bloomberglp.blpapi.MessageIterator;
import com.bloomberglp.blpapi.Request;
import com.bloomberglp.blpapi.Service;
import com.bloomberglp.blpapi.Session;
import com.google.common.collect.Lists;
import com.google.common.util.concurrent.SettableFuture;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.bbg.permission.BloombergBpipeApplicationUserIdentityProvider;
import com.opengamma.livedata.ConnectionUnavailableException;
import com.opengamma.util.ArgumentChecker;
import com.opengamma.util.TerminatableJob;
/**
* Abstract data provider for connecting to Bloomberg.
*/
public abstract class AbstractBloombergStaticDataProvider implements Lifecycle {
/**
* The Bloomberg session options.
*/
private final BloombergConnector _bloombergConnector;
/**
* The provider of correlation identifiers.
*/
private final AtomicLong _nextCorrelationId = new AtomicLong(1L);
/**
* Result futures
*/
private final Map<CorrelationID, SettableFuture<List<Element>>> _responseFutures = new ConcurrentHashMap<>();
/**
* Actual results
*/
private final Map<CorrelationID, List<Element>> _responseMessages = new ConcurrentHashMap<>();
/**
* The event processor listening to Bloomberg.
*/
private BloombergSessionEventProcessor _eventProcessor;
/**
* The thread hosting the event processor.
*/
private Thread _thread;
/**
* Manages the Bloomberg session and service.
*/
private final SessionProvider _sessionProvider;
/**
* The service name
*/
private final String _serviceName;
/**
* Whether authentication is needed.
*/
private final boolean _requiresAuthentication;
/**
* The bpipe applicatiion user identity.
*/
private volatile Identity _applicationIdentity;
/**
* Creates an instance.
*
* @param bloombergConnector the Bloomberg connector, not null
* @param serviceName the Bloomberg service to start, not null
*/
public AbstractBloombergStaticDataProvider(BloombergConnector bloombergConnector, String serviceName) {
ArgumentChecker.notNull(bloombergConnector, "bloombergConnector");
ArgumentChecker.notNull(bloombergConnector.getSessionOptions(), "bloombergConnector.sessionOptions");
ArgumentChecker.notEmpty(serviceName, "serviceName");
_requiresAuthentication = bloombergConnector.requiresAuthentication();
_serviceName = serviceName;
_bloombergConnector = bloombergConnector;
_sessionProvider = new SessionProvider(_bloombergConnector, getServiceNames());
}
private List<String> getServiceNames() {
List<String> serviceNames = Lists.newArrayList(_serviceName);
if (_requiresAuthentication) {
serviceNames.add(BloombergConstants.AUTH_SVC_NAME);
}
return serviceNames;
}
//-------------------------------------------------------------------------
/**
* Gets the Bloomberg session options.
*
* @return the session options
*/
public BloombergConnector getBloombergConnector() {
return _bloombergConnector;
}
/**
* Gets the active logger.
*
* @return the logger.
*/
protected abstract Logger getLogger();
//-------------------------------------------------------------------------
/**
* Gets the Bloomberg session.
*
* @return the session
* @throws OpenGammaRuntimeException If no connection to Bloomberg is available
*/
protected Session getSession() {
return _sessionProvider.getSession();
}
/**
* @return The Bloomberg service.
* @throws OpenGammaRuntimeException If no connection to Bloomberg is available
*/
protected Service getService() {
return _sessionProvider.getService(_serviceName);
}
private synchronized void releaseBlockedRequests() {
for (Entry<CorrelationID, SettableFuture<List<Element>>> entry : _responseFutures.entrySet()) {
List<Element> messages = _responseMessages.remove(entry.getKey());
if (messages == null) {
messages = new ArrayList<>();
}
entry.getValue().set(messages);
}
_responseFutures.clear();
_responseMessages.clear();
}
/**
* Shuts down the Bloomberg session and service, releasing any resources.
*/
private void invalidateSession() {
_sessionProvider.invalidateSession();
releaseBlockedRequests();
}
//-------------------------------------------------------------------------
/**
* Sends a request to Bloomberg, waiting for the response.
*
* @param request the request to send, not null
* @return the correlation identifier, not null
*/
protected Future<List<Element>> submitRequest(Request request) {
Session session = getSession();
CorrelationID cid = new CorrelationID(generateCorrelationID());
SettableFuture<List<Element>> resultFuture = SettableFuture.<List<Element>>create();
ArrayList<Element> result = new ArrayList<>();
try {
if (_requiresAuthentication) {
getLogger().debug("submitting authorized request {} with cid {}", request, cid);
session.sendRequest(request, _applicationIdentity, cid);
} else {
getLogger().debug("submitting normal request {} with cid {}", request, cid);
session.sendRequest(request, cid);
}
_responseMessages.put(cid, result);
_responseFutures.put(cid, resultFuture);
} catch (IOException ex) {
getLogger().warn("Error executing bloomberg reference data request", ex);
resultFuture.set(result);
}
return resultFuture;
}
/**
* Sends an authorization request to Bloomberg, waiting for the response.
*
* @param request the request to send, not null
* @param userIdentity the user identity, not null
* @return the collection of results, not null
*/
protected Future<List<Element>> submitAuthorizationRequest(Request request, Identity userIdentity) {
getLogger().debug("Sending Request={}", request);
Session session = getSession();
CorrelationID cid = new CorrelationID(generateCorrelationID());
SettableFuture<List<Element>> resultFuture = SettableFuture.<List<Element>>create();
ArrayList<Element> result = new ArrayList<>();
try {
session.sendAuthorizationRequest(request, userIdentity, cid);
_responseMessages.put(cid, result);
_responseFutures.put(cid, resultFuture);
} catch (IOException ex) {
getLogger().warn("Error executing bloomberg reference data request", ex);
resultFuture.set(result);
}
return resultFuture;
}
/**
* Generates a correlation identifier.
*
* @return the correlation identifier, not null
*/
protected long generateCorrelationID() {
return _nextCorrelationId.getAndIncrement();
}
//-------------------------------------------------------------------------
/**
* Starts the Bloomberg service.
*/
@Override
public synchronized void start() {
if (isRunning()) {
getLogger().info("Bloomberg already started");
return;
}
getLogger().info("Bloomberg event processor being started...");
_sessionProvider.start();
_eventProcessor = new BloombergSessionEventProcessor();
_thread = new Thread(_eventProcessor, "BSM Event Processor");
_thread.setDaemon(true);
_thread.start();
getLogger().info("Bloomberg event processor started");
getLogger().info("Bloomberg started");
if (_requiresAuthentication) {
// we need authorization done
BloombergBpipeApplicationUserIdentityProvider identityProvider = new BloombergBpipeApplicationUserIdentityProvider(_sessionProvider);
_applicationIdentity = identityProvider.getIdentity();
}
}
//-------------------------------------------------------------------------
/**
* Checks if the Bloomberg service is running.
*
* @return true if running
*/
@Override
public synchronized boolean isRunning() {
if (_thread == null) {
return false;
}
return _thread.isAlive();
}
/**
* Ensures that the Bloomberg session has been started.
*/
protected void ensureStarted() {
getSession();
if (_thread == null || _thread.isAlive() == false) {
throw new IllegalStateException("Event polling thread not alive; has start() been called?");
}
}
//-------------------------------------------------------------------------
/**
* Stops the Bloomberg service.
*/
@Override
public synchronized void stop() {
if (isRunning() == false) {
getLogger().info("Bloomberg already stopped");
return;
}
getLogger().info("Bloomberg event processor being stopped...");
_eventProcessor.terminate();
try {
_thread.join();
} catch (InterruptedException e) {
Thread.interrupted();
}
_eventProcessor = null;
_thread = null;
releaseBlockedRequests();
getLogger().debug("shutting down identity scheduler task");
_sessionProvider.stop();
getLogger().info("Bloomberg event processor stopped");
}
//-------------------------------------------------------------------------
/**
* Thread runner that handles Bloomberg events.
*/
private class BloombergSessionEventProcessor extends TerminatableJob {
/** Time to wait between attepts if there is no Bloomberg connection available. */
private static final long RETRY_PERIOD = 30000;
@Override
protected void runOneCycle() {
Event event;
try {
event = getSession().nextEvent(1000L);
} catch (InterruptedException e) {
Thread.interrupted();
getLogger().warn("Unable to retrieve the next event available for processing on this session", e);
return;
} catch (ConnectionUnavailableException e) {
getLogger().warn("No connection to Bloomberg available, failed to get next event", e);
try {
Thread.sleep(RETRY_PERIOD);
} catch (InterruptedException e1) {
getLogger().warn("Interrupted waiting to retry", e1);
}
return;
} catch (RuntimeException e) {
getLogger().warn("Unable to retrieve the next event available for processing on this session", e);
return;
}
if (event == null) {
//getLogger().debug("Got NULL event");
return;
}
getLogger().debug("Got event of type {}", event.eventType());
if (getLogger().isDebugEnabled()) {
for (Message msg : event) {
getLogger().debug("{}", msg);
}
}
MessageIterator msgIter = event.messageIterator();
while (msgIter.hasNext()) {
Message msg = msgIter.next();
if (event.eventType() == Event.EventType.SESSION_STATUS) {
if (msg.messageType().toString().equals("SessionTerminated")) {
getLogger().error("Session terminated");
// Invalidate the session (which will release any blocked threads)
invalidateSession();
return;
}
}
final CorrelationID responseCid = msg.correlationID();
Element element = msg.asElement();
getLogger().debug("got msg with cid={} msg.asElement={}", responseCid, msg.asElement());
if (responseCid != null) {
List<Element> messages = _responseMessages.get(responseCid);
if (messages != null) {
messages.add(element);
}
}
}
if (event.eventType() == Event.EventType.RESPONSE) {
for (Message message : event) {
CorrelationID correlationID = message.correlationID();
List<Element> result = _responseMessages.remove(correlationID);
if (result != null) {
SettableFuture<List<Element>> responseFuture = _responseFutures.remove(correlationID);
if (responseFuture != null) {
responseFuture.set(result);
}
}
}
}
}
}
}