/**
* Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies
*
* Please see distribution for license.
*/
package com.opengamma.bbg.livedata;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ExecutionException;
import net.sf.ehcache.Cache;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.bloomberglp.blpapi.Element;
import com.bloomberglp.blpapi.Identity;
import com.bloomberglp.blpapi.Request;
import com.google.common.collect.Sets;
import com.google.common.primitives.Ints;
import com.opengamma.OpenGammaRuntimeException;
import com.opengamma.bbg.AbstractBloombergStaticDataProvider;
import com.opengamma.bbg.BloombergConnector;
import com.opengamma.bbg.BloombergConstants;
import com.opengamma.bbg.referencedata.ReferenceData;
import com.opengamma.bbg.referencedata.ReferenceDataProvider;
import com.opengamma.bbg.referencedata.ReferenceDataProviderGetRequest;
import com.opengamma.bbg.referencedata.ReferenceDataProviderGetResult;
import com.opengamma.bbg.util.BloombergDomainIdentifierResolver;
import com.opengamma.livedata.LiveDataSpecification;
import com.opengamma.livedata.UserPrincipal;
import com.opengamma.livedata.entitlement.LiveDataEntitlementChecker;
import com.opengamma.livedata.resolver.DistributionSpecificationResolver;
import com.opengamma.livedata.server.DistributionSpecification;
import com.opengamma.util.ArgumentChecker;
/**
* Checks that the user has entitlement to access Bloomberg.
* <p>
* To understand what's going on this class, read Bloomberg Server API 3.0 Developer Guide, Chapter 7.
*/
public class BloombergEntitlementChecker extends AbstractBloombergStaticDataProvider implements LiveDataEntitlementChecker {
/** Logger. */
private static final Logger s_logger = LoggerFactory.getLogger(BloombergEntitlementChecker.class);
/**
* The length of half a day in seconds.
*/
private static final long HALF_A_DAY_IN_SECONDS = 12 * 60 * 60;
/**
* The Bloomberg reference data provider.
*/
private final ReferenceDataProvider _refDataProvider;
/**
* Cache: UserPrincipal -> Identity
*/
private final Cache _userIdentityCache;
/**
* Cache: DistributionSpecification -> com.bloomberglp.blpapi.Element (containing Bloomberg Entitlement IDs)
*/
private final Cache _eidCache;
/**
* The distribution resolver.
*/
private final DistributionSpecificationResolver _resolver;
/**
* Creates an instance.
*
* @param bloombergConnector the Bloomberg connector, not null
* @param referenceDataProvider the reference data provider, not null
* @param resolver the resolver, not null
*/
public BloombergEntitlementChecker(BloombergConnector bloombergConnector, ReferenceDataProvider referenceDataProvider, DistributionSpecificationResolver resolver) {
super(bloombergConnector, BloombergConstants.AUTH_SVC_NAME);
ArgumentChecker.notNull(referenceDataProvider, "referenceDataProvider");
ArgumentChecker.notNull(resolver, "resolver");
_refDataProvider = referenceDataProvider;
_resolver = resolver;
// Cache will contain max 100 entries, each of which will expire in 12 hours
_userIdentityCache = new Cache("Bloomberg user identity cache", 100, false, false, HALF_A_DAY_IN_SECONDS, HALF_A_DAY_IN_SECONDS);
_userIdentityCache.initialise();
// Cache will contain max 100 entries, each of which will expire in 12 hours
_eidCache = new Cache("Bloomberg EID cache", 100, false, false, HALF_A_DAY_IN_SECONDS, HALF_A_DAY_IN_SECONDS);
_eidCache.initialise();
}
@Override
protected Logger getLogger() {
return s_logger;
}
//-------------------------------------------------------------------------
@Override
public Map<LiveDataSpecification, Boolean> isEntitled(UserPrincipal user, Collection<LiveDataSpecification> requestedSpecifications) {
Map<LiveDataSpecification, Boolean> returnValue = new HashMap<>();
for (LiveDataSpecification spec : requestedSpecifications) {
boolean entitled = isEntitled(user, spec);
returnValue.put(spec, entitled);
}
return returnValue;
}
@Override
public boolean isEntitled(UserPrincipal user, LiveDataSpecification requestedSpecification) {
DistributionSpecification distributionSpecification = _resolver.resolve(requestedSpecification);
return isEntitled(user, distributionSpecification);
}
//-------------------------------------------------------------------------
public boolean isEntitled(UserPrincipal user, DistributionSpecification distributionSpec) {
Identity userIdentity = getUserIdentity(user);
if (userIdentity == null) {
return false;
}
Set<Integer> neededEntitlements = getEids(distributionSpec);
if (neededEntitlements == null || neededEntitlements.isEmpty()) {
return true;
}
List<Integer> failedEntitlements = new ArrayList<Integer>();
boolean isEntitled = userIdentity.hasEntitlements(Ints.toArray(neededEntitlements), getService(), failedEntitlements);
if (!failedEntitlements.isEmpty()) {
s_logger.warn("user: {} is missing entitlements: {}", user, failedEntitlements);
}
return isEntitled;
}
private Identity getUserIdentity(UserPrincipal user) {
net.sf.ehcache.Element cachedUserIdentity = _userIdentityCache.get(user);
if (cachedUserIdentity == null) {
Request authorizationRequest = getService().createAuthorizationRequest();
Integer uuid;
try {
uuid = Integer.parseInt(user.getUserName());
} catch (NumberFormatException e) {
s_logger.info("Bloomberg user IDs are integers - so " + user.getUserName() + " cannot be entitled to anything");
return null;
}
authorizationRequest.set("uuid", uuid);
authorizationRequest.set("ipAddress", user.getIpAddress());
Identity userIdentity = getSession().createIdentity();
try {
List<Element> resultElements = submitAuthorizationRequest(authorizationRequest, userIdentity).get();
if (resultElements == null || resultElements.isEmpty()) {
s_logger.info("Unable to get authorization info from Bloomberg for {}", user);
return null;
}
boolean authorizedSuccessfully = false;
for (Element resultElem : resultElements) {
if (resultElem.name().equals(BloombergConstants.AUTHORIZATION_SUCCESS)) {
cachedUserIdentity = new net.sf.ehcache.Element(user, userIdentity);
_userIdentityCache.put(cachedUserIdentity);
authorizedSuccessfully = true;
} else if (resultElem.name().equals(BloombergConstants.AUTHORIZATION_FAILURE)) {
Element reasonElem = resultElem.getElement(BloombergConstants.REASON);
s_logger.info("Bloomberg authorization failed {}", reasonElem);
} else {
s_logger.info("Bloomberg authorization result {}", resultElem);
}
}
if (!authorizedSuccessfully) {
return null;
}
} catch (InterruptedException | ExecutionException ex) {
s_logger.warn(String.format("Error authenticating user:%s", user), ex);
return null;
}
}
return (Identity) cachedUserIdentity.getObjectValue();
}
@SuppressWarnings("unchecked")
private Set<Integer> getEids(DistributionSpecification distributionSpec) {
net.sf.ehcache.Element cachedEids = _eidCache.get(distributionSpec);
if (cachedEids == null) {
String lookupKey = BloombergDomainIdentifierResolver.toBloombergKey(distributionSpec.getMarketDataId());
Set<String> fields = Sets.newHashSet(BloombergConstants.FIELD_ID_BBG_UNIQUE, // TODO, this is necessary because otherwise the request would not get any real fields
BloombergConstants.FIELD_EID_DATA);
ReferenceDataProviderGetRequest rdRequest = ReferenceDataProviderGetRequest.createGet(Collections.singleton(lookupKey), fields, true);
ReferenceDataProviderGetResult refData = _refDataProvider.getReferenceData(rdRequest);
ReferenceData result = refData.getReferenceData(lookupKey);
if (result.getErrors().size() > 0) {
throw new OpenGammaRuntimeException("Error while obtaining entitlement information: " + lookupKey);
}
cachedEids = new net.sf.ehcache.Element(distributionSpec, new HashSet<>(result.getEidValues()));
_eidCache.put(cachedEids);
}
return (Set<Integer>) cachedEids.getObjectValue();
}
}