package org.marketcetera.marketdata;
import static org.marketcetera.marketdata.AssetClass.EQUITY;
import static org.marketcetera.marketdata.Content.DIVIDEND;
import static org.marketcetera.marketdata.Content.TOP_OF_BOOK;
import static org.marketcetera.marketdata.Messages.*;
import java.io.Serializable;
import java.util.*;
import javax.annotation.concurrent.Immutable;
import javax.xml.bind.annotation.XmlAccessType;
import javax.xml.bind.annotation.XmlAccessorType;
import javax.xml.bind.annotation.XmlElement;
import javax.xml.bind.annotation.XmlRootElement;
import org.apache.commons.lang.builder.EqualsBuilder;
import org.apache.commons.lang.builder.HashCodeBuilder;
import org.marketcetera.util.log.I18NBoundMessage1P;
import org.marketcetera.util.misc.ClassVersion;
/* $License$ */
/**
* Represents a market data request.
*
* <p>Use a {@link MarketDataRequestBuilder builder} to create a <code>MarketDataRequest</code> object.
*
* @author <a href="mailto:colin@marketcetera.com">Colin DuPlantis</a>
* @version $Id: MarketDataRequest.java 16854 2014-03-12 01:54:42Z colin $
* @since 1.0.0
*/
@Immutable
@XmlAccessorType(XmlAccessType.NONE)
@XmlRootElement(name="marketDataRequest")
@ClassVersion("$Id: MarketDataRequest.java 16854 2014-03-12 01:54:42Z colin $")
public class MarketDataRequest
implements Serializable
{
/**
* Get the symbols value.
*
* @return a <code>String[]</code> value
*/
public Set<String> getSymbols()
{
return request.getSymbols();
}
/**
* Get the underlying symbols value.
*
* @return a <code>String[]</code> value
*/
public Set<String> getUnderlyingSymbols()
{
return request.getUnderlyingSymbols();
}
/**
* Get the provider value.
*
* @return a <code>String</code> value
*/
public String getProvider()
{
return request.getProvider();
}
/**
* Get the exchange value.
*
* @return a <code>String</code> value
*/
public String getExchange()
{
return request.getExchange();
}
/**
* Get the content value.
*
* @return a <code>Set<Content></code> value
*/
public Set<Content> getContent()
{
return request.getContent();
}
/**
* Get the map of parameter names and values.
*
* @return an umodifiable map of parameter names and values.
*/
public Map<String,String> getParameters()
{
return request.getParameters();
}
/**
* Get the asset class value.
*
* @return an <code>AssetClass</code> value
*/
public AssetClass getAssetClass()
{
return request.getAssetClass();
}
/**
* Determines if the request is valid apropos the given capabilities.
*
* @param inCapabilities a <code>Content[]</code> value containing the capabilities against which to verify the request
* @return a <code>boolean</code> value indicating whether the request if valid according to the given capabilities
*/
public boolean validateWithCapabilities(Content...inCapabilities)
{
Set<Content> results = new HashSet<Content>(request.getContent());
results.removeAll(Arrays.asList(inCapabilities));
return results.isEmpty();
}
/* (non-Javadoc)
* @see java.lang.Object#hashCode()
*/
@Override
public int hashCode()
{
return new HashCodeBuilder(13,
31).append(request).toHashCode();
}
/* (non-Javadoc)
* @see java.lang.Object#equals(java.lang.Object)
*/
@Override
public boolean equals(Object obj)
{
if(obj == null) {
return false;
}
if(obj == this) {
return true;
}
if(!(obj instanceof MarketDataRequest)) {
return false;
}
MarketDataRequest rhs = (MarketDataRequest)obj;
return new EqualsBuilder().append(request,
rhs.request).isEquals();
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
@Override
public String toString()
{
return request.toString();
}
/**
* Create a new MarketDataRequest instance.
*
* @param inMarketDataRequestBean
*/
MarketDataRequest(MarketDataRequestBean inMarketDataRequestBean)
{
try {
request = inMarketDataRequestBean.clone();
} catch (CloneNotSupportedException e) {
throw new UnsupportedOperationException(e);
}
setDefaults();
validate();
}
/**
* Create a new MarketDataRequest instance.
*/
@SuppressWarnings("unused")
private MarketDataRequest()
{
request = new MarketDataRequestBean();
}
/**
* Validates the <code>MarketDataRequest</code>.
*
* <p>This method is intended to validate a request when it is believed to be complete
* and ready to be submitted. Some validation is performed that is relevant only to
* a completed request.
*
* @throws IllegalArgumentException if the request is invalid
*/
private void validate()
{
validateAssetClass();
validateContent();
validateExchange();
validateParameters();
validateProvider();
doCommonSymbolValidation();
validateSymbols();
validateUnderlyingSymbols();
}
/**
* Sets any unset values to their default if a default exists.
*/
private void setDefaults()
{
if(request.getContent().isEmpty()) {
request.setContent(EnumSet.of(TOP_OF_BOOK));
}
if(request.getAssetClass() == null) {
request.setAssetClass(EQUITY);
}
}
/**
* Verifies that the symbols are valid.
*
* @throws IllegalArgumentException if the symbols are not valid
*/
private void validateSymbols()
{
Set<String> symbols = getSymbols();
// symbols is non-empty, underlying symbols is empty
for(String symbol : symbols) {
if(symbol == null ||
symbol.trim().isEmpty()) {
throw new IllegalArgumentException(new I18NBoundMessage1P(INVALID_SYMBOLS,
String.valueOf(symbols)).getText());
}
}
}
/**
* Executes validation common to symbols and underlying symbols.
*
* @throws IllegalArgumentException if the validation fails
*/
private void doCommonSymbolValidation()
{
Set<String> symbols = getSymbols();
Set<String> underlyingSymbols = getUnderlyingSymbols();
// symbols xor underlying symbols must be specified
if(symbols.isEmpty() &&
underlyingSymbols.isEmpty()) {
throw new IllegalArgumentException(NEITHER_SYMBOLS_NOR_UNDERLYING_SYMBOLS_SPECIFIED.getText(this));
}
if(!symbols.isEmpty() &&
!underlyingSymbols.isEmpty()) {
throw new IllegalArgumentException(BOTH_SYMBOLS_AND_UNDERLYING_SYMBOLS_SPECIFIED.getText(this));
}
}
/**
* Verifies that the underlying symbols are valid.
*
* @throws IllegalArgumentException if the underlying symbols are not valid
*/
private void validateUnderlyingSymbols()
{
Set<String> underlyingSymbols = getUnderlyingSymbols();
// underlying symbols is non-empty, symbols is empty
for(String underlyingSymbol : underlyingSymbols) {
if(underlyingSymbol == null ||
underlyingSymbol.trim().isEmpty()) {
throw new IllegalArgumentException(new I18NBoundMessage1P(INVALID_UNDERLYING_SYMBOLS,
String.valueOf(underlyingSymbols)).getText());
}
}
}
/**
* Verifies that the <code>Exchange</code> is valid.
*/
private void validateExchange()
{
// nothing to do
}
/**
* Verifies that the parameters are valid.
*/
private void validateParameters()
{
// nothing to do
}
/**
* Verifies that the provider is valid.
*/
private void validateProvider()
{
// provider is optional when an MDR is passed directly to a Market Data Module
}
/**
* Verifies that the <code>AssetClass</code> is valid.
*
* @throws IllegalArgumentException if the <code>AssetClass</code> is not valid
*/
private void validateAssetClass()
{
if(getAssetClass() == null) {
throw new IllegalArgumentException(MISSING_ASSET_CLASS.getText());
}
// if underlying symbols are specified then OPTION or FUTURE is required
if(!getUnderlyingSymbols().isEmpty() &&
!getAssetClass().isValidForUnderlyingSymbols()) {
throw new IllegalArgumentException(VALID_UNDERLYING_ASSET_CLASS_REQUIRED.getText(this,
getAssetClass()));
}
}
/**
* Verifies that the <code>Content</code> is valid.
*
* @throws IllegalArgumentException if the <code>Content</code> is not valid
*/
private void validateContent()
{
Set<Content> content = getContent();
// content cannot be empty
if(content.isEmpty()) {
throw new IllegalArgumentException(MISSING_CONTENT.getText());
}
// check to make sure the list of content is valid
if(!isValidEnumList(content)) {
throw new IllegalArgumentException(new I18NBoundMessage1P(INVALID_CONTENT,
String.valueOf(content)).getText());
}
// content dividend requires symbols
if(content.contains(DIVIDEND) &&
getSymbols().isEmpty()) {
throw new IllegalArgumentException(DIVIDEND_REQUIRES_SYMBOLS.getText(this));
}
}
/**
* Checks to see if the given enum collection value is valid.
*
* @param inEnums a <code>Collection<E></code> value
* @return a <code>boolean</code> value
*/
private static <E extends Enum<E>> boolean isValidEnumList(Collection<E> inEnums)
{
if(inEnums == null ||
inEnums.isEmpty()) {
return false;
}
for(Enum<E> e : inEnums) {
if(e == null) {
return false;
}
}
return true;
}
/**
* the request data
*/
@XmlElement(name="requestBody")
private final MarketDataRequestBean request;
private static final long serialVersionUID = 3L;
}