/** * Copyright (C) 2009 - present by OpenGamma Inc. and the OpenGamma group of companies * * Please see distribution for license. */ package com.opengamma.engine.position.csv; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; import java.math.BigDecimal; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.ConcurrentSkipListMap; import org.apache.commons.io.IOUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import au.com.bytecode.opencsv.CSVReader; import com.opengamma.DataNotFoundException; import com.opengamma.OpenGammaRuntimeException; import com.opengamma.core.change.ChangeManager; import com.opengamma.core.change.DummyChangeManager; import com.opengamma.core.position.Portfolio; import com.opengamma.core.position.PortfolioNode; import com.opengamma.core.position.Position; import com.opengamma.core.position.PositionSource; import com.opengamma.core.position.Trade; import com.opengamma.core.position.impl.SimplePortfolio; import com.opengamma.core.position.impl.SimplePortfolioNode; import com.opengamma.core.position.impl.SimplePosition; import com.opengamma.id.ExternalId; import com.opengamma.id.ExternalIdBundle; import com.opengamma.id.ObjectId; import com.opengamma.id.UniqueId; import com.opengamma.id.VersionCorrection; import com.opengamma.util.ArgumentChecker; /** * A source of positions based on CSV-formatted files. */ public class CSVPositionSource implements PositionSource { /** Logger. */ private static final Logger s_logger = LoggerFactory.getLogger(CSVPositionSource.class); /** * The base file directory. */ private final File _baseDirectory; /** * The portfolio by identifier. */ private final ConcurrentMap<ObjectId, Object> _portfolios = new ConcurrentSkipListMap<ObjectId, Object>(); /** * The nodes by identifier. */ private final Map<UniqueId, PortfolioNode> _nodes = new TreeMap<UniqueId, PortfolioNode>(); /** * The positions by identifier. */ private final Map<ObjectId, Position> _positions = new TreeMap<ObjectId, Position>(); /** * The trades by identifier. */ private final Map<UniqueId, Trade> _trades = new TreeMap<UniqueId, Trade>(); /** * Creates an empty CSV position source. */ public CSVPositionSource() { _baseDirectory = null; } /** * Creates a CSV position source using the specified directory. * @param baseDirectoryName the directory name, not null */ public CSVPositionSource(String baseDirectoryName) { this(new File(baseDirectoryName)); } /** * Creates a CSV position source using the specified directory. * @param baseDirectory the directory, not null */ public CSVPositionSource(File baseDirectory) { ArgumentChecker.notNull(baseDirectory, "base directory"); if (baseDirectory.exists() == false) { throw new IllegalArgumentException("Base directory must exist: " + baseDirectory); } if (baseDirectory.isDirectory() == false) { throw new IllegalArgumentException("Base directory must be a directory: " + baseDirectory); } try { _baseDirectory = baseDirectory.getCanonicalFile(); } catch (IOException ex) { throw new OpenGammaRuntimeException("Base directory must resolve to a canonical reference: " + baseDirectory, ex); } populatePortfolioIds(); } /** * Populate the portfolio identifiers from the base directory. */ private void populatePortfolioIds() { File[] filesInBaseDirectory = getBaseDirectory().listFiles(); for (File file : filesInBaseDirectory) { if (file.isFile() == false || file.isHidden() || file.canRead() == false) { continue; } String portfolioName = buildPortfolioName(file.getName()); _portfolios.put(ObjectId.of("CSV-" + file.getName(), portfolioName), file); } } private String buildPortfolioName(String fileName) { if (fileName.endsWith(".csv") || fileName.endsWith(".txt")) { return fileName.substring(0, fileName.length() - 4); } return fileName; } private Position getPosition(final ObjectId positionId) { Position position = _positions.get(positionId); if (position == null) { throw new DataNotFoundException("Unable to find position: " + positionId); } return position; } //------------------------------------------------------------------------- /** * Gets the base directory. * @return the baseDirectory, may be null */ public File getBaseDirectory() { return _baseDirectory; } //------------------------------------------------------------------------- public Set<ObjectId> getPortfolioIds() { return Collections.unmodifiableSet(_portfolios.keySet()); } @Override public Portfolio getPortfolio(UniqueId portfolioId, final VersionCorrection versionCorrection) { // Ignore the version return getPortfolio(portfolioId.getObjectId(), VersionCorrection.LATEST); } @Override public Portfolio getPortfolio(ObjectId objectId, VersionCorrection versionCorrection) { Object portfolio = _portfolios.get(objectId); if (portfolio instanceof File) { Portfolio created = loadPortfolio(objectId, (File) portfolio); _portfolios.replace(objectId, portfolio, created); portfolio = _portfolios.get(objectId); } if (portfolio instanceof Portfolio) { return (Portfolio) portfolio; } throw new DataNotFoundException("Unable to find portfolio: " + objectId); } @Override public PortfolioNode getPortfolioNode(UniqueId nodeId, final VersionCorrection versionCorrection) { PortfolioNode node = _nodes.get(nodeId); if (node == null) { throw new DataNotFoundException("Unable to find node: " + nodeId); } return node; } @Override public Position getPosition(UniqueId positionId) { // Ignore the version return getPosition(positionId.getObjectId()); } @Override public Position getPosition(ObjectId positionId, VersionCorrection versionCorrection) { // Ignore the version return getPosition(positionId); } @Override public Trade getTrade(UniqueId tradeId) { Trade trade = _trades.get(tradeId); if (trade == null) { throw new DataNotFoundException("Unable to find trade: " + tradeId); } return trade; } //------------------------------------------------------------------------- @Override public ChangeManager changeManager() { return DummyChangeManager.INSTANCE; } //------------------------------------------------------------------------- private Portfolio loadPortfolio(ObjectId portfolioId, File file) { FileInputStream fis = null; try { fis = new FileInputStream(file); return loadPortfolio(portfolioId, fis); } catch (IOException ex) { throw new OpenGammaRuntimeException("Unable to parse portfolio file: " + file, ex); } finally { IOUtils.closeQuietly(fis); } } private Portfolio loadPortfolio(ObjectId portfolioId, InputStream inStream) throws IOException { SimplePortfolio portfolio = new SimplePortfolio(portfolioId.atVersion("0"), portfolioId.getValue()); UniqueId rootNodeId = UniqueId.of(portfolioId.getScheme(), "0"); portfolio.getRootNode().setUniqueId(rootNodeId); _nodes.put(rootNodeId, portfolio.getRootNode()); CSVReader csvReader = new CSVReader(new InputStreamReader(inStream)); String[] tokens = null; int curIndex = 1; UniqueId positionId = UniqueId.of(portfolioId.getScheme(), Integer.toString(curIndex)); while ((tokens = csvReader.readNext()) != null) { SimplePosition position = parseLine(tokens, positionId); if (position != null) { ((SimplePortfolioNode) portfolio.getRootNode()).addPosition(position); _positions.put(position.getUniqueId().getObjectId(), position); positionId = UniqueId.of(portfolioId.getScheme(), Integer.toString(++curIndex)); } } s_logger.info("{} parsed stream with {} positions", portfolioId, portfolio.getRootNode().getPositions().size()); return portfolio; } /** * @param line the line to parse, not null * @param positionId the portfolio id, not null * @return the position */ /* package for testing */ static SimplePosition parseLine(String[] tokens, UniqueId positionId) { if (tokens.length < 3) { return null; } // First token is the quantity BigDecimal quantity = new BigDecimal(tokens[0].trim()); // Each set of 2 tokens is then security id domain and then id List<ExternalId> securityIdentifiers = new ArrayList<ExternalId>(); for (int i = 1; i < (tokens.length - 1); i++) { String idScheme = tokens[i].trim(); String idValue = tokens[++i].trim(); ExternalId id = ExternalId.of(idScheme, idValue); securityIdentifiers.add(id); } ExternalIdBundle securityKey = ExternalIdBundle.of(securityIdentifiers); s_logger.debug("Loaded position: {} in {}", quantity, securityKey); return new SimplePosition(positionId, quantity, securityKey); } }