/******************************************************************************* * Copyright (c) quickfixengine.org All rights reserved. * * This file is part of the QuickFIX FIX Engine * * This file may be distributed under the terms of the quickfixengine.org * license as defined by quickfixengine.org and appearing in the file * LICENSE included in the packaging of this file. * * This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING * THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A * PARTICULAR PURPOSE. * * See http://www.quickfixengine.org/LICENSE for licensing information. * * Contact ask@quickfixengine.org if any conditions of this licensing * are not clear to you. ******************************************************************************/ package quickfix; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import quickfix.field.converter.BooleanConverter; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.StringWriter; import java.net.InetAddress; import java.net.UnknownHostException; import java.security.InvalidParameterException; import java.util.ArrayList; import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Settings for sessions. Settings are grouped by FIX version and target company * ID. There is also a default settings section that is inherited by the * session-specific sections. * * Setting constants are declared in the classes using the settings. To find the * string constants, navigate to the class constant for the setting, select the * link for the setting and then and select the "Constant Field Values" link in * the detailed field description. * * @see quickfix.FileLogFactory * @see quickfix.ScreenLogFactory * @see quickfix.FileStoreFactory * @see quickfix.JdbcSetting * @see quickfix.Session * @see quickfix.DefaultSessionFactory */ public class SessionSettings { private static final Logger log = LoggerFactory.getLogger(SessionSettings.class); public static final String BEGINSTRING = "BeginString"; public static final String SENDERCOMPID = "SenderCompID"; public static final String SENDERSUBID = "SenderSubID"; public static final String SENDERLOCID = "SenderLocationID"; public static final String TARGETCOMPID = "TargetCompID"; public static final String TARGETSUBID = "TargetSubID"; public static final String TARGETLOCID = "TargetLocationID"; public static final String SESSION_QUALIFIER = "SessionQualifier"; public static final String SETTING_RECONNECT_INTERVAL = "ReconnectInterval"; // This was using the line.separator system property but that caused // problems with moving configuration files between *nix and Windows. private static final String NEWLINE = "\r\n"; private Properties variableValues; private Properties settings; /** * c-tor */ public SessionSettings() { this(new Properties()); } /** * c-tor * * @param settings */ public SessionSettings(final Properties settings) { this.variableValues = System.getProperties(); this.settings = settings; } /** * * @return */ public Properties getProperties() { return this.settings; } /** * Get a settings string. * * @param key * the settings key * @return the string value for the setting * * @throws ConfigError * configurion error, probably a missing setting. * @throws FieldConvertError * error during field type conversion. */ public String getString(String key) throws ConfigError, FieldConvertError { final String value = interpolate(this.settings.getProperty(key)); if (value == null) { throw new ConfigError(key + " not defined"); } return value; } /** * Get a settings value as a long integer. * * @param key the settings key * @return the long integer value for the setting * * @throws ConfigError * configurion error, probably a missing setting. * @throws FieldConvertError * error during field type conversion. */ public long getLong(String key) throws ConfigError, FieldConvertError { try { return Long.parseLong(getString(key)); } catch (final NumberFormatException e) { throw new FieldConvertError(e.getMessage()); } } /** * Get a settings value as a long integer. * * @param key * the settings key * @return the long integer value for the setting * * @throws ConfigError * configurion error, probably a missing setting. * @throws FieldConvertError * error during field type conversion. */ public int getInt(String key) throws ConfigError, FieldConvertError { try { return Integer.parseInt(getString(key)); } catch (final NumberFormatException e) { throw new FieldConvertError(e.getMessage()); } } /** * Get a settings value as a double number. * * @param key * the settings key * @return the double number value for the setting * * @throws ConfigError * configurion error, probably a missing setting. * @throws FieldConvertError * error during field type conversion. */ public double getDouble(String key) throws ConfigError, FieldConvertError { try { return Double.parseDouble(getString(key)); } catch (final NumberFormatException e) { throw new FieldConvertError(e.getMessage()); } } /** * Get a settings value as a boolean value. * * @param key * the settings key * @return the boolean value for the setting * * @throws ConfigError * configurion error, probably a missing setting. * @throws FieldConvertError * error during field type conversion. */ public boolean getBool(String key) throws ConfigError, FieldConvertError { try { return BooleanConverter.convert(getString(key)); } catch (final FieldConvertError e) { throw new ConfigError(e); } } /** * Sets a string-valued session setting. * * @param key * the setting key * @param value * the string value */ public void setString(String key, String value) { this.settings.setProperty(key, value.trim()); } /** * Sets a long integer-valued session setting. * * @param key * the setting key * @param value * the long integer value */ public void setLong(String key, long value) { this.settings.setProperty(key, Long.toString(value)); } /** * Sets a double-valued session setting. * * @param key * the setting key * @param value * the double value */ public void setDouble(String key, double value) { this.settings.setProperty(key, Double.toString(value)); } /** * Sets a boolean-valued session setting. * * @param key * the setting key * @param value * the boolean value */ public void setBool(String key, boolean value) { this.settings.setProperty(key, BooleanConverter.convert(value)); } /** * Predicate for determining if a setting exists. * * @param key * the setting key * @return true is setting exists, false otherwise. */ public boolean isSetting(String key) { return this.settings.getProperty(key) != null; } public void removeSetting(SessionID sessionID, String key) { this.settings.remove(key); } private final Pattern variablePattern = Pattern.compile("\\$\\{(.+?)}"); private String interpolate(String value) { if (value == null || value.indexOf('$') == -1) { return value; } final StringBuffer buffer = new StringBuffer(); final Matcher m = variablePattern.matcher(value); while (m.find()) { if (m.start() > 0 && value.charAt(m.start() - 1) == '\\') { continue; } final String variable = m.group(1); final String variableValue = variableValues.getProperty(variable); if (variableValue != null) { m.appendReplacement(buffer, variableValue); } } m.appendTail(buffer); return buffer.toString(); } /** * Set properties that will be the source of variable values in the settings. A variable * is of the form ${variable} and will be replaced with values from the * map when the setting is retrieved. * * By default, the System properties are used for variable values. If * you use this method, it will override the defaults so use the Properties * default value mechanism if you want to chain a custom properties object * with System properties as the default. * * <code><pre> * // Custom properties with System properties as default * Properties myprops = new Properties(System.getProperties()); * myprops.load(getPropertiesInputStream()); * settings.setVariableValues(myprops); * * // Custom properties with System properties as override * Properties myprops = new Properties(); * myprops.load(getPropertiesInputStream()); * myprops.putAll(System.getProperties()); * settings.setVariableValues(myprops); * </pre></code> * * @param variableValues * * @see java.util.Properties * @see java.lang.System */ public void setVariableValues(Properties variableValues) { this.variableValues = variableValues; } /** * Adds defaults to the settings. Will not delete existing settings not * overlapping with the new defaults, but will overwrite existing settings * specified in this call. * * @param defaults */ public void set(Map<Object, Object> defaults) { this.settings.putAll(defaults); } public void toString(PrintWriter writer) { try { writeSection("[SESSION]", writer, this.settings); } finally { writer.flush(); } } public void toStream(OutputStream out) { toString(new PrintWriter(new OutputStreamWriter(out))); } private void writeSection(String sectionName, PrintWriter writer, Properties properties) { writer.println(sectionName); final Iterator<Object> p = properties.keySet().iterator(); while (p.hasNext()) { final String key = (String) p.next(); writer.print(key); writer.print("="); writer.println(properties.getProperty(key)); } } @Override public String toString() { final StringWriter writer = new StringWriter(); toString(new PrintWriter(writer)); return writer.toString(); } public static int[] parseSettingReconnectInterval(String raw) { if (raw == null || raw.length() == 0) { return null; } final String multiplierCharacter = raw.contains("*") ? "\\*" : "x"; final String[] data = raw.split(";"); final List<Integer> result = new ArrayList<Integer>(); for (final String multi : data) { final String[] timesSec = multi.split(multiplierCharacter); int times; int secs; try { if (timesSec.length > 1) { times = Integer.parseInt(timesSec[0]); secs = Integer.parseInt(timesSec[1]); } else { times = 1; secs = Integer.parseInt(timesSec[0]); } } catch (final NumberFormatException e) { throw new InvalidParameterException( "Invalid number '" + multi + "' in '" + raw + "'. Expected format: [<multiplier>x]<seconds>;[<multiplier>x]<seconds>;..."); } for (int ii = 0; ii != times; ++ii) { result.add(secs); } } final int[] ret = new int[result.size()]; int ii = 0; for (final Integer sec : result) { ret[ii++] = sec; } return ret; } public static Set<InetAddress> parseRemoteAddresses(String raw) { if (raw == null || raw.length() == 0) { return null; } final String[] data = raw.split(","); final Set<InetAddress> result = new HashSet<InetAddress>(); for (final String multi : data) { try { result.add(InetAddress.getByName(multi)); } catch (final UnknownHostException e) { log.error("Ignored unknown host : " + multi, e); } } return result; } }