/* * Copyright (C) 2012 - present by Yann Le Tallec. * Please see distribution for license. */ package com.assylias.jbloomberg; import static com.assylias.jbloomberg.DateUtils.toLocalDate; import static com.assylias.jbloomberg.DateUtils.toOffsetDateTime; import static com.assylias.jbloomberg.DateUtils.toOffsetTime; import com.bloomberglp.blpapi.Datetime; import com.bloomberglp.blpapi.Element; import com.bloomberglp.blpapi.ElementIterator; import com.bloomberglp.blpapi.Schema; import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; import java.nio.charset.Charset; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * A utility class that should not be very useful for the users of the API. */ final class BloombergUtils { private final static Logger logger = LoggerFactory.getLogger(BloombergUtils.class); private static volatile boolean isBbcommStarted = false; private final static String BBCOMM_PROCESS = "bbcomm.exe"; private final static String BBCOMM_FOLDER = "C:/blp/API"; private BloombergUtils() { } /** * Transforms a Bloomberg Element into the most specific Object (for example: Double, Float, Integer, DateTime, * String etc.).<br> * Complex types are returned in the form of collections ({@code List<Object>} for arrays and * {@code Map<String, Object>} * for sequences). */ public static Object getSpecificObjectOf(Element field) { if (field.datatype() == Schema.Datatype.FLOAT64) { //likeliest data type return field.getValueAsFloat64(); } else if (field.datatype() == Schema.Datatype.FLOAT32) { return field.getValueAsFloat32(); } else if (field.datatype() == Schema.Datatype.BOOL) { return field.getValueAsBool(); } else if (field.datatype() == Schema.Datatype.CHAR) { return field.getValueAsChar(); } else if (field.datatype() == Schema.Datatype.INT32) { return field.getValueAsInt32(); } else if (field.datatype() == Schema.Datatype.INT64) { return field.getValueAsInt64(); } else if (field.datatype() == Schema.Datatype.STRING) { return field.getValueAsString(); } else if (field.datatype() == Schema.Datatype.DATE || field.datatype() == Schema.Datatype.TIME || field.datatype() == Schema.Datatype.DATETIME) { Datetime dt = field.getValueAsDatetime(); if (dt.hasParts(Datetime.DATE)) { return dt.hasParts(Datetime.TIME) ? toOffsetDateTime(dt) : toLocalDate(dt); } else { if (dt.hasParts(Datetime.TIME)) return toOffsetTime(dt); logger.warn("Not a valid date time: Element={}, Datetime={}", field, dt); return null; } } else if (field.isArray()) { List<Object> list = new ArrayList<>(field.numValues()); for (int i = 0; i < field.numValues(); i++) { list.add(getSpecificObjectOf(field.getValueAsElement(i))); } return list; } else if (field.datatype() == Schema.Datatype.SEQUENCE || field.datatype() == Schema.Datatype.CHOICE) { //has to be after array because arrays are sequences... ElementIterator it = field.elementIterator(); Map<String, Object> map = new LinkedHashMap<>(field.numElements(), 1.0f); while (it.hasNext()) { Element e = it.next(); map.put(e.name().toString(), getSpecificObjectOf(e)); } return map; } else { return field.toString(); //always works } } /** * Starts the bbcomm process if necessary, which is required to connect to the Bloomberg data feed.<br> * This method will block up to one second if it needs to manually start the process. If the process is not * started by the end of the timeout, this method will return false but the process might start later on. * <p> * @return true if bbcomm was started successfully within one second, false otherwise. */ public static boolean startBloombergProcessIfNecessary() { return isBbcommStarted || isBloombergProcessRunning() || startBloombergProcess(); } /** * * @return true if the bbcomm process is running */ private static boolean isBloombergProcessRunning() { if (ShellUtils.isProcessRunning(BBCOMM_PROCESS)) { logger.info("{} is started", BBCOMM_PROCESS); return true; } return false; } private static boolean startBloombergProcess() { Callable<Boolean> startBloombergProcess = BloombergUtils::getStartingCallable; isBbcommStarted = getResultWithTimeout(startBloombergProcess, 1, TimeUnit.SECONDS); return isBbcommStarted; } private static Boolean getStartingCallable() throws IOException { logger.info("Starting {} manually", BBCOMM_PROCESS); ProcessBuilder pb = new ProcessBuilder(BBCOMM_PROCESS); pb.directory(new File(BBCOMM_FOLDER)); pb.redirectErrorStream(true); Process p = pb.start(); try (BufferedReader reader = new BufferedReader(new InputStreamReader(p.getInputStream(), Charset.forName("UTF-8")))) { String line; while ((line = reader.readLine()) != null) { logger.info("{} > {}", BBCOMM_PROCESS, line); if (line.toLowerCase().contains("started")) { logger.info("{} is started", BBCOMM_PROCESS); return true; } } return false; } } private static boolean getResultWithTimeout(Callable<Boolean> startBloombergProcess, int timeout, TimeUnit timeUnit) { ExecutorService executor = Executors.newSingleThreadExecutor(r -> { Thread t = new Thread(r, "Bloomberg - bbcomm starter thread"); t.setDaemon(true); return t; }); Future<Boolean> future = executor.submit(startBloombergProcess); try { return future.get(timeout, timeUnit); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return false; } catch (ExecutionException | TimeoutException e) { logger.error("Could not start bbcomm", e); return false; } finally { executor.shutdownNow(); try { if (!executor.awaitTermination(100, TimeUnit.MILLISECONDS)) { logger.warn("bbcomm starter thread still running"); } } catch (InterruptedException ex) { Thread.currentThread().interrupt(); } } } }