/*
* (C) Copyright IBM Corp. 2008
*
* LICENSE: Eclipse Public License v1.0
* http://www.eclipse.org/legal/epl-v10.html
*/
package com.ibm.gaiandb;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.Random;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.regex.Pattern;
import org.apache.derby.iapi.types.TypeId;
import com.ibm.db2j.FileImport;
import com.ibm.gaiandb.apps.SecurityClientAgent;
import com.ibm.gaiandb.diags.GDBMessages;
import com.ibm.gaiandb.lite.LiteDriver;
import com.ibm.gaiandb.policyframework.SQLQueryFilter;
import com.ibm.gaiandb.policyframework.SQLResultFilter;
import com.ibm.gaiandb.utils.DriverWrapper;
/**
* @author DavidVyvyan
*/
public class GaianDBConfig {
// Use PROPRIETARY notice if class contains a main() method, otherwise use COPYRIGHT notice.
public static final String COPYRIGHT_NOTICE = "(c) Copyright IBM Corp. 2008";
private static final Logger logger = new Logger( "GaianDBConfig", 40 );
// private static String gaiandbWorkspace = null; // Initialised to null, meaning local/working directory.
// public static final String DEFAULT_NODE_DESCRIPTION_SUFFIX = ":" + DEFAULT_PORT + "/" + DEFAULT_DB;
static final String DEFAULT_CONFIG = "gaiandb_config";
private static final String UPROPS_EXTN = ".properties";
// private static final String SPROPS_EXTN = ".sysinfo";
// Note GAIAN_NODE_DB is commented out, meaning the physical db name for any GaianNode is fixed, in order
// to make the connection/discovery mechanism a little simpler between Gaian Nodes.
// For convenience this 'hard-wired' physical db may contain user data, but users may just as well store their data
// in another derby db also managed by the Derby Network Server attached to this GaianNode. This would need federating
// in the config file. Performance in accessing either of these dbs would be the same.
// private static final String GAIAN_NODE_DB = "GAIAN_NODE_DB";
static final String GAIAN_NODE_USR = "GAIAN_NODE_USR"; // Must match user in derby.properties if authentication is enabled there
static final String GAIAN_NODE_PWD = "GAIAN_NODE_PWD"; // If this is set in gaian config, then it overrides property 'derby.user.gaian' in derby.properties
public static final String GAIAN_NODE_DEFAULT_USR = "gaiandb"; //"GAIANDB";
public static final String GAIAN_NODE_DEFAULT_PWD = "passw0rd"; //"passw0rd";
public static final String GAIAN_NODE_DEFAULT_AUSER = "xpclient"; // default "actual" user
public static final String GAIAN_NODE_DEFAULT_DOMAIN = "SDPDOM.LOCAL"; // default domain name
private static String derbyGaianNodeUser = null;
private static String derbyGaianNodePassword = null;
private static int THIRTY_TWO_KB = 32768;
// private static final String GDBADMIN_USER = "gdbadmin";
// private static final String GDBADMIN_DEFAULT_PASSWORD = "adm1n"; //"passw0rd";
// private static String derbyGaianNodeAdminPassword = null;
private static Properties derbyProperties = null;
static Properties getDerbyProperties() { return derbyProperties; }
static {
try {
// resolve workspace dir independently from GaianNode initialisation - because methods in this class may be used before the node has been started.
String workspaceDir = System.getProperty("derby.system.home");
if ( null == workspaceDir ) workspaceDir = System.getProperty("user.dir");
if ( null == workspaceDir ) workspaceDir = ".";
logger.logThreadInfo("Loading derby properties at: " + workspaceDir + "/derby.properties");
FileInputStream fis = new FileInputStream( new File( workspaceDir + "/derby.properties") );
derbyProperties = new Properties();
derbyProperties.load(new InputStreamReader(fis, "UTF8"));
fis.close();
} catch (Exception e) { System.out.println("\n\t***** WARNING: Authentication disabled: " + e + "\n"); }
}
private static String configFileLocation = DEFAULT_CONFIG + UPROPS_EXTN;
private static File configFile = null;
// private static String sysInfoFileName = DEFAULT_CONFIG + SPROPS_EXTN;
// private static File sysInfoFile = null;
private static long latestLoadedPropertiesFileTimestamp = 0;
// upr contains user properties and spr contains system properties, e.g. DISCOVERED_GAIAN_CONNECTIONS and the connection defs themselves.
static Properties upr = null, spr = new Properties();
// These are properties of data sources whose definition is kept in memory only. They only exit for the lifetime of the node.
static Properties inMemoryDataSourceProperties = new Properties();
private static int derbyServerListenerPort = 0; // Initial port value, used if using embedded driver
private static String gaianNodeID = null;
public static final String GAIANDB_NAME = "gaiandb"; //gdb";
private static String DBNAME = null; // shd be any db name given on the cmd line
public static final String SUBQUERY_LT="SUBQUERY";
public static final String LOCALDERBY_CONNECTION="LOCALDERBY";
public static final String DERBY_EMBEDDED_DRIVER="org.apache.derby.jdbc.EmbeddedDriver";
public static final String DERBY_CLIENT_DRIVER="org.apache.derby.jdbc.ClientDriver";
//public static final String GAIAN_SECURITY_DRIVER="client.SecureClientDriver";
// Denis : GDB_UDP_DRIVER="com.ibm.gaiandb.udpdriver.client.UDPDriver"
public static final String GDB_UDP_DRIVER="com.ibm.gaiandb.udpdriver.client.UDPDriver";
public static final String GDB_LITE_DRIVER="com.ibm.gaiandb.lite.LiteDriver";
// NOTE PROVENANCE AND EXPLAIN COLS MUST NOT CONTAIN DECIMAL TYPES -
// OTHERWISE THE CONSEQUENCES OF USING getColumnDefArray() MUST BE LOOKED INTO AS IT REPLACES ',' with ':'
public static final String GDB_PREFIX = "GDB";
public static final String GDB_NODE = GDB_PREFIX + "_NODE";
public static final String GDB_LEAF = GDB_PREFIX + "_LEAF";
private static final String PROVENANCE_COLDEFS = GDB_NODE + " "+Util.TSTR+", " + GDB_LEAF + " VARCHAR(50)"; // these sizes are just default display widths - they are not constraining
public static final String[] PROVENANCE_COLS = getColumnsDefArray( PROVENANCE_COLDEFS );
public static final String GDB_QRYID = GDB_PREFIX + "_QRYID";
public static final String GDB_QRYSTEPS = GDB_PREFIX + "_QRYSTEPS";
// public static final String GDB_QRYFWDER = GDB_PREFIX + "_QRYFWDER";
// Allow plenty of chars for qry id len.. expected to be generally less than 50 e.g. 'VeryLongLongLongHostname:6415:1233070884453:12345678'
public static final String QRYID_COLDEFS = GDB_QRYID + " VARCHAR(100), " + GDB_QRYSTEPS + " INT"; //, " + GDB_QRYFWDER + " VARCHAR(50)";
// Data encoded using our RSA encryption code generates a byte array of length: 128 bytes.
// We allow for up to 255 of these maximum, as the max size of a LONG VARCHAR FOR BIT DATA is 32,700 bytes
public static final String GDB_CREDENTIALS = GDB_PREFIX + "_CREDENTIALS";
public static final String GDB_CREDENTIALS_COLDEF = GDB_CREDENTIALS + " VARCHAR(1028)";
public static final String GDB_EXPLAIN_PREFIX = GDB_PREFIX + "X";
public static final String EXPLAIN_FROM = GDB_EXPLAIN_PREFIX + "_FROM_NODE";
public static final String EXPLAIN_TO = GDB_EXPLAIN_PREFIX + "_TO_NODE";
public static final String EXPLAIN_DEPTH = GDB_EXPLAIN_PREFIX + "_DEPTH";
public static final String EXPLAIN_PRECEDENCE = GDB_EXPLAIN_PREFIX + "_PRECEDENCE";
public static final String EXPLAIN_COUNT = GDB_EXPLAIN_PREFIX + "_COUNT";
private static final String EXPLAIN_COLDEFS =
EXPLAIN_FROM + " VARCHAR(20), " + EXPLAIN_TO + " VARCHAR(20), " +
EXPLAIN_DEPTH + " INT, " + EXPLAIN_PRECEDENCE + " CHAR, " + EXPLAIN_COUNT + " BIGINT";
public static final String[] EXPLAIN_COLS = getColumnsDefArray( EXPLAIN_COLDEFS );
public static final String[] HIDDEN_COL_NAMES = getColumnsDefArray( GDB_NODE + "," + GDB_LEAF + "," +
EXPLAIN_FROM + "," + EXPLAIN_TO + "," + EXPLAIN_DEPTH + "," + EXPLAIN_PRECEDENCE + "," + EXPLAIN_COUNT );
public static final int NUM_HIDDEN_COLS = GaianDBConfig.PROVENANCE_COLS.length + GaianDBConfig.EXPLAIN_COLS.length;
static final String SOURCELIST_SUFFIX = "_SOURCELIST";
static final String LTDEF_SUFFIX = "_DEF";
static final String CONNECTION_SUFFIX = "_CONNECTION";
static final String TABLE_SUFFIX = "_TABLE";
static final String VTI_SUFFIX = "_VTI";
static final String ARGS_SUFFIX = "_ARGS";
static final String SCHEMA_SUFFIX = "_SCHEMA";
static final String OPTIONS_SUFFIX = "_OPTIONS";
static final String INMEMORY = "INMEMORY";
static final String PLURALIZED = "PLURALIZED";
static final String MAP_COLUMNS_BY_POSITION = "MAP_COLUMNS_BY_POSITION"; // IGNORE_PHYSICAL_SCHEMA, MIRRORED_LT_DEF = Ignore physical columns definition.
static final String CONSTANTS_SUFFIX = "_CONSTANTS";
static final String NODE_CONSTANTS = "NODE_CONSTANTS";
static final String COLUMN_LABEL = "_C";
// public static final int EXPLAIN_COUNT_COLUMN_INDEX = 1;
// public static final int EXPLAIN_FROM_COLUMN_INDEX = 2;
// public static final int EXPLAIN_TO_COLUMN_INDEX = 3;
// public static final int EXPLAIN_DEPTH_COLUMN_INDEX = 4;
// public static final int EXPLAIN_PRECEDENCE_COLUMN_INDEX = 5;
// public static final String EXPLAIN_COLDEFS =
// "GDBX_FROM_NODE VARCHAR(20), GDBX_TO_NODE VARCHAR(20), GDBX_DEPTH INT, GDBX_PRECEDENCE INT, GDBX_COUNT INT";
// // Disable row caching - USE WITH CARE! DISTRIBUTED JOINS WILL FAIL IF THIS IS SET
// private static final String DISABLE_ROW_CACHING_REQUIRED_FOR_JOINS = "DISABLE_ROW_CACHING_REQUIRED_FOR_JOINS";
// private static final boolean DEFAULT_DISABLE_ROW_CACHING_REQUIRED_FOR_JOINS = false;
// public static boolean getDisableRowCaching() {
// String s = getUserProperty( DISABLE_ROW_CACHING_REQUIRED_FOR_JOINS );
// return null==s ? DEFAULT_DISABLE_ROW_CACHING_REQUIRED_FOR_JOINS : Boolean.parseBoolean(s);
// }
// Flag to switch to Derby Table Functions interface (as alternative to VTIs) to obtain support from Derby community if an issue arises.
static final String MANAGE_LTVIEWS_WITH_TABLE_FUNCTIONS = "MANAGE_LTVIEWS_WITH_TABLE_FUNCTIONS";
private static boolean DEFAULT_MANAGE_LTVIEWS_WITH_TABLE_FUNCTIONS = false;
public static boolean isManageViewsWithTableFunctions() {
return getBooleanPropertyOrDefault(MANAGE_LTVIEWS_WITH_TABLE_FUNCTIONS, DEFAULT_MANAGE_LTVIEWS_WITH_TABLE_FUNCTIONS);
}
// Timeout for detecting long running queries
static final String EXEC_TIMEOUT_MS = "EXEC_TIMEOUT_MS";
private static final int DEFAULT_EXEC_TIMEOUT_MS = 1000;
public static int getExecTimeoutMillis() {
return getIntPropertyOrDefault(EXEC_TIMEOUT_MS, DEFAULT_EXEC_TIMEOUT_MS);
}
// Flag to enable propagated insert/update/delete/call statements (default is disabled)
static final String ALLOW_PROPAGATED_WRITES = "ALLOW_PROPAGATED_WRITES";
private static final boolean DEFAULT_ALLOW_PROPAGATED_WRITES = false; // ADD A THIRD VALUE "RESTRICTED" IN FUTURE TO GOVERN BY AUTHORIZED USER
public static boolean isAllowedPropagatedWrites() {
return getBooleanPropertyOrDefault(ALLOW_PROPAGATED_WRITES, DEFAULT_ALLOW_PROPAGATED_WRITES);
}
// Flag to enable SQL API configuration statements (default is disabled), e.g. setxxx(), removexxx() and addxxx() apis
static final String ALLOW_SQL_API_CONFIGURATION = "ALLOW_SQL_API_CONFIGURATION";
private static final boolean DEFAULT_ALLOW_SQL_API_CONFIGURATION = false; // ADD A THIRD VALUE "RESTRICTED" IN FUTURE TO GOVERN BY AUTHORIZED USER
public static boolean isAllowedAPIConfiguration() {
return getBooleanPropertyOrDefault(ALLOW_SQL_API_CONFIGURATION, DEFAULT_ALLOW_SQL_API_CONFIGURATION);
}
// Fetch buffer sizes for result rows and recycled rows in GaianResult.
private static final String FETCH_BUFFER_SIZE = "FETCH_BUFFER_SIZE";
private static final int DEFAULT_FETCH_BUFFER_SIZE = 100;
public static int getFetchBufferSize() {
return getIntPropertyOrDefault(FETCH_BUFFER_SIZE, DEFAULT_FETCH_BUFFER_SIZE);
}
// Fetch buffer sizes for result rows and recycled rows in GaianResult.
private static final String ROWS_BATCH_SIZE = "ROWS_BATCH_SIZE";
private static final int DEFAULT_ROWS_BATCH_SIZE = 20;
public static int getRowsBatchSize() {
return getIntPropertyOrDefault(ROWS_BATCH_SIZE, DEFAULT_ROWS_BATCH_SIZE);
}
// Fetch buffer sizes for result rows and recycled rows in GaianResult.
private static final String DISK_CACHING_THRESHOLD_FOR_JOINED_INNER_TABLES = "DISK_CACHING_THRESHOLD_FOR_JOINED_INNER_TABLES";
private static final long DEFAULT_DISK_CACHING_THRESHOLD_FOR_JOINED_INNER_TABLES = 1000;
public static long getDiskCachingThresholdForJoinedInnerTables() {
return getLongPropertyOrDefault(DISK_CACHING_THRESHOLD_FOR_JOINED_INNER_TABLES, DEFAULT_DISK_CACHING_THRESHOLD_FOR_JOINED_INNER_TABLES);
}
// Denis : NETWORK_DRIVER definition
// NETWORK_DRIVER indicates the driver to use for the communication between the nodes
// At the moment, it can be : JDBC Derby driver (TCP) or the GaianDB UDP Driver
private static final String NETWORK_DRIVER = "NETWORK_DRIVER";
public static final String DERBY = "DERBY";
public static final String GDBUDP = "GDBUDP";
private static final String DEFAULT_NETWORK_DRIVER = DERBY;
public static String getNetworkDriver() {
return getStringPropertyOrDefault(NETWORK_DRIVER, DEFAULT_NETWORK_DRIVER);
}
// Denis : NETWORK_DRIVER_GDBUDP_DATAGRAMSIZE definition
// NETWORK_DRIVER_GDBUDP_DATAGRAMSIZE indicates the datagram size (in bytes) for the GaianDB UDP Driver if this one is selected.
private static final String NETWORK_DRIVER_GDBUDP_DATAGRAMSIZE = "NETWORK_DRIVER_GDBUDP_DATAGRAMSIZE";
private static final int DEFAULT_NETWORK_DRIVER_GDBUDP_DATAGRAMSIZE = 1450;
public static int getNetworkDriverGDBUDPDatagramSize() {
return getIntPropertyOrDefault(NETWORK_DRIVER_GDBUDP_DATAGRAMSIZE,
DEFAULT_NETWORK_DRIVER_GDBUDP_DATAGRAMSIZE);
}
// Denis : NETWORK_DRIVER_GDBUDP_TIMEOUT definition
// NETWORK_DRIVER_GDBUDP_TIMEOUT indicates the timeout (in milliseconds) for the GaianDB UDP Driver if this one is selected.
private static final String NETWORK_DRIVER_GDBUDP_TIMEOUT = "NETWORK_DRIVER_GDBUDP_TIMEOUT";
private static final int DEFAULT_NETWORK_DRIVER_GDBUDP_TIMEOUT = 5000;
public static int getNetworkDriverGDBUDPTimeout() {
return getIntPropertyOrDefault(NETWORK_DRIVER_GDBUDP_TIMEOUT,
DEFAULT_NETWORK_DRIVER_GDBUDP_TIMEOUT);
}
// Denis : NETWORK_DRIVER_GDBUDP_SOCKET_BUFFER_SIZE
// NETWORK_DRIVER_GDBUDP_SOCKET_BUFFER_SIZE indicates the buffer size (in bytes) of the sockets used by the GaianDB UDP Driver if this one is selected.
private static final String NETWORK_DRIVER_GDBUDP_SOCKET_BUFFER_SIZE = "NETWORK_DRIVER_GDBUDP_SOCKET_BUFFER_SIZE";
private static final int DEFAULT_NETWORK_DRIVER_GDBUDP_SOCKET_BUFFER_SIZE = 64000;
public static int getNetworkDriverGDBUDPSocketBufferSize() {
return getIntPropertyOrDefault(NETWORK_DRIVER_GDBUDP_SOCKET_BUFFER_SIZE,
DEFAULT_NETWORK_DRIVER_GDBUDP_SOCKET_BUFFER_SIZE);
}
// Heartbeat between checks on maintained connections or connections actively executing long running queries.
static final String GAIAN_CONNECTIONS_CHECKER_HEARTBEAT_MS = "GAIAN_CONNECTIONS_CHECKER_HEARTBEAT_MS";
private static final int DEFAULT_GAIAN_CONNECTIONS_CHECKER_HEARTBEAT_MS = 5000;
public static int getConnectionsCheckerHeartbeat() {
return getIntPropertyOrDefault(GAIAN_CONNECTIONS_CHECKER_HEARTBEAT_MS, DEFAULT_GAIAN_CONNECTIONS_CHECKER_HEARTBEAT_MS);
}
// DriverManager.setLoginTimeout(timeout_ms) cannot be relied on because not all operating systems support setting
// a timeout on blocking socket calls.
// DEFAULT_JDBC_CONNECTION_ATTEMPT_TIMEOUT_MS and DEFAULT_MAX_CONCURRENT_JDBC_CONNECTION_ATTEMPTS_PER_DATASOURCE
// are determined by the default JDBC connection timeout with Derby which appears to be about 2mins 30secs.
// 10 concurrent connections, where 3 are made concurrently immediately...
// This leaves 7 to be made each every 20 seconds, i.e. completing after 2 minutes and 20 secs
// The Derby JDBC timeout appears to be about 2 minutes 30 secs itself so more connection attempts can be made in a
// controlled way as failed ones timeout every 20 seconds after that...
// If our timeout is lower relative to our max number of concurrent connections, then there will be a longer wait when
// the max number of concurrent connection attempts is reached.
// This value must be 1 - Derby locks up on multiple connection attempts
// // Max number of concurrent connections on an rdbms datasource
// private static final String MAX_CONCURRENT_JDBC_CONNECTION_ATTEMPTS_PER_DATASOURCE = "MAX_CONCURRENT_JDBC_CONNECTION_ATTEMPTS_PER_DATASOURCE";
// private static final int DEFAULT_MAX_CONCURRENT_JDBC_CONNECTION_ATTEMPTS_PER_DATASOURCE = 1;
// public static int getMaxConcurrentConnectionAttempts() {
// return getIntPropertyOrDefault(MAX_CONCURRENT_JDBC_CONNECTION_ATTEMPTS_PER_DATASOURCE,
// DEFAULT_MAX_CONCURRENT_JDBC_CONNECTION_ATTEMPTS_PER_DATASOURCE);
// }
//
// // Time given before abandoning a connection attempt to give room for a new fresh one.
// private static final String JDBC_CONNECTION_ATTEMPT_TIMEOUT_MS = "JDBC_CONNECTION_ATTEMPT_TIMEOUT_MS";
// private static final int DEFAULT_JDBC_CONNECTION_ATTEMPT_TIMEOUT_MS = 10000;
// public static int getConnectionAttemptTimeout() {
// return getIntPropertyOrDefault(JDBC_CONNECTION_ATTEMPT_TIMEOUT_MS, DEFAULT_JDBC_CONNECTION_ATTEMPT_TIMEOUT_MS);
// }
// Maximum number of gaian connections that may be discovered (outbound (maintained) + inbound)
private static final String MAX_DISCOVERED_CONNECTIONS = "MAX_DISCOVERED_CONNECTIONS";
private static final int DEFAULT_MAX_DISCOVERED_CONNECTIONS = 10;
public static int getMaxDiscoveredConnections() {
return getIntPropertyOrDefault(MAX_DISCOVERED_CONNECTIONS, DEFAULT_MAX_DISCOVERED_CONNECTIONS);
}
private static final String LOGFILE_MAX_SIZE_MB = "LOGFILE_MAX_SIZE_MB";
private static final int DEFAULT_LOGFILE_MAX_SIZE_MB = 100;
public static int getLogfileMaxSizeMB() {
return getIntPropertyOrDefault(LOGFILE_MAX_SIZE_MB, DEFAULT_LOGFILE_MAX_SIZE_MB);
}
// All properties having default values and their associated default values
public static String[][] getAllDefaultProperties() {
String defaultInterface = "unresolved";
try { defaultInterface = Util.stripToSlash( InetAddress.getByName(GaianNodeSeeker.getDefaultLocalIP()).toString() ).trim(); }
catch ( Exception e ) {}
return new String[][] {
{ LOGLEVEL, Logger.POSSIBLE_LEVELS[ Logger.LOG_DEFAULT ] },
{ DISCOVERY_IP, GaianNodeSeeker.DEFAULT_MULTICAST_GROUP_IP },
{ GAIAN_NODE_USR, GAIAN_NODE_DEFAULT_USR },
{ ALLOW_PROPAGATED_WRITES, Boolean.toString(DEFAULT_ALLOW_PROPAGATED_WRITES) },
{ ALLOW_SQL_API_CONFIGURATION, Boolean.toString(DEFAULT_ALLOW_SQL_API_CONFIGURATION) },
{ FETCH_BUFFER_SIZE, Integer.toString(DEFAULT_FETCH_BUFFER_SIZE) },
{ ROWS_BATCH_SIZE, Integer.toString(DEFAULT_ROWS_BATCH_SIZE) },
{ DISK_CACHING_THRESHOLD_FOR_JOINED_INNER_TABLES, Long.toString(DEFAULT_DISK_CACHING_THRESHOLD_FOR_JOINED_INNER_TABLES) },
{ NETWORK_DRIVER, DEFAULT_NETWORK_DRIVER },
{ NETWORK_DRIVER_GDBUDP_DATAGRAMSIZE, Integer.toString(DEFAULT_NETWORK_DRIVER_GDBUDP_DATAGRAMSIZE) },
{ NETWORK_DRIVER_GDBUDP_TIMEOUT, Integer.toString(DEFAULT_NETWORK_DRIVER_GDBUDP_TIMEOUT) },
{ NETWORK_DRIVER_GDBUDP_SOCKET_BUFFER_SIZE, Integer.toString(DEFAULT_NETWORK_DRIVER_GDBUDP_SOCKET_BUFFER_SIZE) },
{ GAIAN_CONNECTIONS_CHECKER_HEARTBEAT_MS, Integer.toString(DEFAULT_GAIAN_CONNECTIONS_CHECKER_HEARTBEAT_MS) },
// { JDBC_CONNECTION_ATTEMPT_TIMEOUT_MS, Integer.toString(DEFAULT_JDBC_CONNECTION_ATTEMPT_TIMEOUT_MS) },
{ MAX_DISCOVERED_CONNECTIONS, Integer.toString(DEFAULT_MAX_DISCOVERED_CONNECTIONS) },
{ LOGFILE_MAX_SIZE_MB, Integer.toString(DEFAULT_LOGFILE_MAX_SIZE_MB) },
{ MAX_PROPAGATION, Integer.toString(DEFAULT_MAX_PROPAGATION) },
{ MAX_POOLSIZES, Integer.toString(DEFAULT_MAX_POOLSIZE) },
{ MAX_INBOUND_CONNECTION_THREADS, Integer.toString(DEFAULT_MAX_INBOUND_CONNECTION_THREADS) },
{ DISCOVERY_PORT, Integer.toString(DEFAULT_DISCOVERY_PORT) },
{ ACCESS_CLUSTERS, "<no cluster membership restrictions>" },
{ ACCESS_HOSTS_PERMITTED, "<all node connections permitted>" },
{ ACCESS_HOSTS_DENIED, "<no node connections denied>" },
{ MIN_DISCOVERED_CONNECTIONS, "0" },
{ MULTICAST_INTERFACES, defaultInterface }, // default interface only
{ DISCOVERY_GATEWAYS, "<no gateways for discovery to other subnets>" },
{ DEFINED_GAIAN_CONNECTIONS, "<no hard-wired gaian connections>" },
{ MSGBROKER_HOST, "<message storing disabled>" },
{ MSGBROKER_PORT, "<message storing disabled>" },
{ MSGBROKER_TOPIC, "<no default message storing topic>" },
{ MSGSTORER_MSGCOLS, "<use single column for message - no decomposition>" },
{ MSGSTORER_ROWEXPIRY_HOURS, "<no expiry for stored messages>" },
};
}
private static String getStringPropertyOrDefault( String propName, String defaultValue ) {
String s = getUserProperty( propName );
return null==s ? defaultValue : s.trim();
}
private static int getIntPropertyOrDefault( String propName, int defaultValue ) {
String s = getUserProperty( propName );
try { return null==s ? defaultValue : Integer.parseInt(s.trim()); }
catch( Exception e ) {
logger.logWarning(GDBMessages.CONFIG_PROP_INT_VALUE_INVALID, "Invalid Int property value for " + propName + ": " + s +
" (using default: " + defaultValue + ")");
return defaultValue;
}
}
private static long getLongPropertyOrDefault( String propName, long defaultValue ) {
String s = getUserProperty( propName );
try { return null==s ? defaultValue : Long.parseLong(s.trim()); }
catch( Exception e ) {
logger.logWarning(GDBMessages.CONFIG_PROP_LONG_VALUE_INVALID, "Invalid Long property value for " + propName + ": " + s +
" (using default: " + defaultValue + ")");
return defaultValue;
}
}
private static boolean getBooleanPropertyOrDefault( String propName, boolean defaultValue ) {
String s = getUserProperty( propName );
try { return null==s ? defaultValue : Boolean.parseBoolean(s.trim()); }
catch( Exception e ) {
logger.logWarning(GDBMessages.CONFIG_PROP_BOOLEAN_VALUE_INVALID, "Invalid Boolean property value for " + propName + ": " + s +
" (using default: " + defaultValue + ")");
return defaultValue;
}
}
private static HashSet<String> loadedJdbcDrivers = new HashSet<String>();
private static char queryIDSequenceCounter = 0;
// private static final String AUTO_GENERATED_PREFIX = "AUTO_";
static final String LOGLEVEL = "LOGLEVEL";
static final String LOGPERF = "LOGPERF";
static final String SQL_RESULT_FILTER = "SQL_RESULT_FILTER";
static final String SQL_QUERY_FILTER = "SQL_QUERY_FILTER";
static final String MSGBROKER_HOST = "MSGBROKER_HOST";
static final String MSGBROKER_PORT = "MSGBROKER_PORT";
static final String MSGBROKER_TOPIC = "MSGBROKER_TOPIC";
private static final String MSGSTORER_ROWEXPIRY_HOURS = "MSGSTORER_ROWEXPIRY_HOURS";
private static final String MSGSTORER_MSGCOLS = "MSGSTORER_MSGCOLS";
static final String MAX_PROPAGATION = "MAX_PROPAGATION";
private static final int DEFAULT_MAX_PROPAGATION = -1; // Propagate out to the whole network
public static int getMaxPropagation() {
return getIntPropertyOrDefault(MAX_PROPAGATION, DEFAULT_MAX_PROPAGATION);
}
static final String MAX_POOLSIZES = "MAX_POOLSIZES";
private static final int DEFAULT_MAX_POOLSIZE = 10; // Don't hold too many connections open
public static int getMaxPoolsizes() {
return getIntPropertyOrDefault(MAX_POOLSIZES, DEFAULT_MAX_POOLSIZE);
}
private static final String MAX_INBOUND_CONNECTION_THREADS = "MAX_INBOUND_CONNECTION_THREADS";
private static final int DEFAULT_MAX_INBOUND_CONNECTION_THREADS = 1000;
public static int getMaxInboundConnectionThreads() {
return getIntPropertyOrDefault(MAX_INBOUND_CONNECTION_THREADS, DEFAULT_MAX_INBOUND_CONNECTION_THREADS);
}
static final String MIN_DISCOVERED_CONNECTIONS = "MIN_DISCOVERED_CONNECTIONS";
static final String DISCOVERY_IP = "DISCOVERY_IP";
static final String DISCOVERY_GATEWAYS = "DISCOVERY_GATEWAYS";
private static final String DISCOVERY_HOSTS = "DISCOVERY_HOSTS"; // deprecated - use property below
static final String ACCESS_HOSTS_PERMITTED = "ACCESS_HOSTS_PERMITTED";
static final String ACCESS_HOSTS_DENIED = "ACCESS_HOSTS_DENIED";
public static final String ACCESS_CLUSTERS = "ACCESS_CLUSTERS";
public static final String CONNECTION_STRATEGY = "CONNECTION_STRATEGY";
// The values of valid connection strategies (i.e. fitness functions)
public static final String ATTACHMENT_PREFERENTIAL_ON_HIGH_CONNECTIVITY = "ATTACHMENT_PREFERENTIAL_ON_HIGH_CONNECTIVITY";
public static final String ATTACHMENT_RANDOM = "ATTACHMENT_RANDOM";
public static final String ATTACHMENT_TO_USER_DB_NODE = "ATTACHMENT_TO_USER_DB_NODE";
public static final String DISCOVERY_PORT = "DISCOVERY_PORT";
private static final int DEFAULT_DISCOVERY_PORT = 7777;
public static int getDiscoveryPort() {
return getIntPropertyOrDefault(DISCOVERY_PORT, DEFAULT_DISCOVERY_PORT);
}
static final String MULTICAST_INTERFACES = "MULTICAST_INTERFACES";
// static final String MULTICAST_DOMAINS = "MULTICAST_DOMAINS";
private static final String DISCOVERED_GAIAN_CONNECTIONS = "DISCOVERED_GAIAN_CONNECTIONS";
static final String DEFINED_GAIAN_CONNECTIONS = "DEFINED_GAIAN_CONNECTIONS";
static final String LABEL_DRIVER = "_DRIVER";
static final String LABEL_URL = "_URL";
static final String LABEL_USR = "_USR";
static final String LABEL_PWD = "_PWD";
private static final String LABEL_DNODEID = "_DNODEID"; // discovered node id - only used for discovered connections
// Derby authentication mode
private static final String DERBY_AUTH_MODE_KEY = "derby.authentication.mode";
private static final String DERBY_SSL_MODE = "derby.drda.sslMode";
public static final String DERBY_AUTH_MODE_DEFAULT = "1";
public static final String DERBY_AUTH_MODE_ID_ASSERT = "2";
private static String gaianNodeHostName = null;
// private static Properties getUserProperties() { if ( null == upr ) reloadUserProperties(); return upr; }
// private static Properties getSystemProperties() { if ( null == spr ) spr = new Properties(); return spr; }
/**
* Utility to retrieve a resource from the properties file without having to handle
* the IOException.
* Instead, when the property can't be accessed, we log an Exception and return null.
*
* @param key
* @return
*/
static String getUserProperty( String key ) {
// Reload on startup - note that GaianDBConfig can also work as a standalone config class -
// i.e. with a Derby Network Server launched independently...
if ( null == upr ) {
reloadUserProperties();
if ( null == upr ) return null;
}
synchronized ( upr ) {
return getPropertyInEitherCase( upr, key ); //upr.getProperty( key );
}
}
private static String getPropertyInEitherCase( Properties pr, String key ) {
String p = pr.getProperty( key );
if ( null == p ) p = pr.getProperty( key.toUpperCase() ); // upper case takes priority over lower case
if ( null == p ) p = pr.getProperty( key.toLowerCase() );
// logger.logDetail("Getting property " + key + "; Returning value " + p);
return p;
}
private static String getSystemProperty( String key ) {
synchronized ( spr ) {
return spr.getProperty( key );
}
}
static String getCrossOverProperty( String key ) {
String p = getUserProperty( key );
if ( null == p ) p = getSystemProperty( key );
return p;
}
static void setDerbyServerListenerPort( int port ) {
derbyServerListenerPort = port;
}
// static void setWorkspace( String workspace ) {
// gaiandbWorkspace = workspace;
// }
public static int getDerbyServerListenerPort() {
return derbyServerListenerPort;
}
// Derby listener port must be set before this method is called
public static void setConfigFile( String configFileLocationWithoutExtention ) throws Exception {
configFileLocation = configFileLocationWithoutExtention + UPROPS_EXTN;
configFile = null;
// sysInfoFileName = configFileNameWithoutExtention +
// ( GaianNode.DEFAULT_PORT == derbyServerListenerPort ? "" : "_" + derbyServerListenerPort ) + SPROPS_EXTN;
// sysInfoFile = null;
// Pre-load properties - if this fails we will know early on
reloadUserProperties();
if ( null == upr ) throw new Exception("Could not load user properties");
}
public static String getVTIProperty( Class<?> klass, String property ) {
return getVTIProperty( klass, property, true );
}
public static String getVTIProperty( Class<?> klass, String property, boolean doLog ) {
String fullProp = klass.getName() + "." + property;
String val = getUserProperty( fullProp );
if ( null == val ) {
fullProp = klass.getSimpleName() + "." + property;
val = getUserProperty( fullProp );
}
if ( doLog ) logger.logInfo("Got property: " + fullProp + " = " + val);
return val;
}
// public static synchronized boolean addNewNode(
// String connectionDetails, String table, String vtiArgs, String options, String[] columnNameMappings) {
// }
// public static synchronized boolean addNewGaianNode(
// String connectionDetails, String[] columnNameMappings) {
// }
public static String getPolicyClassNameForSQLResultFilter() {
return getUserProperty(SQL_RESULT_FILTER);
}
private static String lastLoadedPolicyClass = null;
public static void initialisePolicyClasses() {
final String className = getPolicyClassNameForSQLResultFilter();
// return if policy is disabled or if it has already been initialized.
if ( null == className ) { lastLoadedPolicyClass = null; return; }
if ( className.equals(lastLoadedPolicyClass) ) return;
System.out.println("Loading policy class: " + className);
// GaianDBConfig.class.getClassLoader().loadClass( className ); // not as effective as Class.forName() used below
try { Class.forName(className, true, GaianDBConfig.class.getClassLoader() ); lastLoadedPolicyClass = className; }
catch ( Exception e ) { logger.logWarning(GDBMessages.CONFIG_SQL_RESULT_FILTER_ERROR, "Cannot initialise class: "+className+", cause: " + e); }
}
public static SQLResultFilter getSQLResultFilter() {
final String className = getPolicyClassNameForSQLResultFilter();
if ( null != className)
try { return (SQLResultFilter) GaianNode.getClassUsingGaianClassLoader(className).newInstance(); }
catch ( Exception e ) { logger.logWarning(GDBMessages.CONFIG_SQL_RESULT_FILTER_ERROR, "Cannot load class: "+className+", cause: " + e); }
return null;
}
public static SQLQueryFilter getSQLQueryFilter() {
final String className = getUserProperty(SQL_QUERY_FILTER);
if ( null != className)
try { return (SQLQueryFilter) GaianNode.getClassUsingGaianClassLoader(className).newInstance(); }
catch ( Exception e ) { logger.logWarning(GDBMessages.CONFIG_SQL_QUERY_FILTER_ERROR, "Cannot load class: "+className+", cause: " + e); }
return null;
}
public static int getMinConnectionsToDiscover() {
String s = getUserProperty( MIN_DISCOVERED_CONNECTIONS );
return null == s ? 0 : Integer.parseInt(s);
}
// /**
// * Returns false if the property is already set to the same value in the registry.
// * This allows us not to have to reload the meta data into new vtis every time.
// *
// * Otherwise, sets the property and returns true to indicate that the properties have changed.
// *
// * @param ltName
// * @param definition
// * @return true if the properties need reloading into vtis.
// * @throws IOException
// */
// public static boolean setTransientLogicalTableDefinition(String ltName, String definition) throws IOException {
// Properties props = getProperties();
// if ( definition.equalsIgnoreCase( (String) props.get( ltName+"_TRANSIENT_DEF" ) ) )
// return false;
//
// props.setProperty( ltName+"_TRANSIENT_DEF", definition );
// return true;
// }
// public static String removeTransientDefinition( String ltName ) throws IOException {
// return (String) getProperties().remove( ltName+"_TRANSIENT_DEF" );
// }
public static boolean isDiscoveredOrDefinedConnection( String connectionID ) {
return getGaianConnectionsAsSet().contains( connectionID );
}
public static String getDiscoveredNodeID( String connectionID ) {
return getSystemProperty( connectionID + LABEL_DNODEID );
}
public static boolean isDiscoveredConnection( String connectionID ) {
return null != getDiscoveredNodeID(connectionID);
}
public static boolean isDefinedConnection( String connectionID ) {
for ( String dc : getDefinedConnections() )
if ( dc.equals(connectionID) ) return true;
return false;
}
// public static String getDiscoveredConnectionID( String nodeID ) {
// synchronized( spr ) {
// for ( Iterator<Object> keys = spr.keySet().iterator(); keys.hasNext(); ) {
// String key = (String) keys.next();
// if ( key.endsWith( LABEL_DNODEID ) && spr.getProperty(key).equals(nodeID) )
// return key;
// }
// }
// return null;
// }
public static String getDiscoveredConnectionID( String nodeID ) {
if ( null == nodeID ) return null;
String dnodes = getSystemProperty(DISCOVERED_GAIAN_CONNECTIONS);
if ( null == dnodes ) return null;
synchronized ( spr ) {
for ( String dnode : Util.splitByCommas(dnodes) )
if ( nodeID.equals(spr.getProperty(dnode+LABEL_DNODEID)) ) return dnode;
}
return null;
}
public static boolean isDiscoveredNode( String nodeID ) {
return null != getDiscoveredConnectionID(nodeID);
}
// /**
// * If the connection is a DEFINED gaian connection then it is not only removed from config, but config is
// * also reloaded such that its associated stack pool is purged and all VTIs referencing the connection are refreshed.
// *
// * If the connection is a user defined one, then the connections stack pool is cleared for that connection.
// */
// public static void lostConnection( String connectionID ) {
//
// // For now only do something about lost DISCOVERED connections (which are under system properties).
// // Manually defined connections need dealing with differently. They must not be removed from config, but rather
// // just have all their copies cleaned out of the connections pool.
// if ( spr.containsKey( connectionID + LABEL_HOST ) ) // no need to synchronize on spr - as either it exists already or it is a user property
// GaianNodeSeeker.lostConnection( connectionID );
// else
// DataSourcesManager.clearSourceHandlesStackPool( getRDBConnectionDetailsAsString(connectionID) );
// }
public static boolean removeDiscoveredGaianNode( String connectionID ) {
logger.logInfo("Removing Discovered Node: " + connectionID);
synchronized ( DISCOVERED_GAIAN_CONNECTIONS ) {
String discoveredConnectionsList = getSystemProperty( DISCOVERED_GAIAN_CONNECTIONS );
int index;
if ( null == discoveredConnectionsList || -1 == ( index = discoveredConnectionsList.indexOf(connectionID) ) ) {
logger.logThreadInfo("Connection does not exist - no changes made to registry");
return false;
}
StringBuffer buf = new StringBuffer( discoveredConnectionsList );
if ( 0 == index ) index++;
buf.delete( index-1, index + connectionID.length() );
synchronized( spr ) {
spr.put( DISCOVERED_GAIAN_CONNECTIONS, buf.toString() );
spr.remove( connectionID + LABEL_DRIVER );
spr.remove( connectionID + LABEL_URL );
spr.remove( connectionID + LABEL_USR );
spr.remove( connectionID + LABEL_PWD );
spr.remove( connectionID + LABEL_DNODEID );
}
}
// dumpRegistryIfLoggingOn();
return true;
}
// public static void addGaianNode( String connectionDetails, String details ) {
// addDiscoveredGaianNode( connectionDetails, details );
// }
// public static boolean arraysMatch( String[] a, String[] b ) {
// return Arrays.asList(a).toString().intern() == Arrays.asList(b).toString().intern();
// }
/**
* Attaches a new gaian node child definition to every logical table.
* The definition is described by the connection details.
*
* If any of the child nodes of any logical table is a gaian node and uses the same
* connection details, then this method returns null to indicate that the gaian node
* child is already attached to this node - this is a valid outcome (i.e. not an exception)
*
* The nodeID arg is a unique description of the db connection in the form: "hostname:port/dbname"
*
* @return The new connection ID upon success, or null if a connection with these properties already exists
*/
public static String addDiscoveredGaianNode(
final String driver, final String url, final String usr, final String scrambledpwd, final String nodeID ) {
logger.logDetail("Attempting to add Discovered Node: " + nodeID);
// unscramble pwd based on foreign node id
String pwd = unscramble(scrambledpwd, nodeID);
// System.out.println("unscramble: " + scrambledpwd + ", nodeID " + nodeID + " -> " + pwd);
String connectionDetails = /*GaianDBConfig.DERBY_CLIENT_DRIVER*/ driver + "'" + url + "'" + usr + "'" + pwd;
String ckey = null; // The new connection key
synchronized ( DISCOVERED_GAIAN_CONNECTIONS ) {
// final String cid = lookupAllRegisteredCIDs().get( connectionDetails );
// if ( null != cid ) { logger.logDetail("Gaian connection ("+cid+") already exists to: " + nodeID); return cid; }
String[] ggcs = getDiscoveredConnections();
// Check if the new connection already exists amongst global gaian ones
// Note that any overlapping user defined ones will just be ignored when the DataSourcesManager loads them.
for ( int i=0; i<ggcs.length; i++ ) {
try {
// System.out.println("ggcsi resolved: " + getRDBConnectionDetailsAsString( ggcs[i] ) + " new connection details: " + connectionDetails);
if ( getRDBConnectionDetailsAsString( ggcs[i] ).equalsIgnoreCase(connectionDetails) ) {
logger.logDetail("Gaian connection (" + ggcs[i] + ") already exists to: " + nodeID);
return ggcs[i];
}
} catch (Exception e) { continue; } //ignore (shouldnt happen) - warning is logged at load time anyway
}
int i=1;
do { ckey = "C"+(i++); } while (
null != getCrossOverProperty(ckey+LABEL_DNODEID ) ||
null != getCrossOverProperty(ckey+LABEL_DRIVER ) ||
null != getCrossOverProperty(ckey+LABEL_URL ) ||
null != getCrossOverProperty(ckey+LABEL_USR ) ||
null != getCrossOverProperty(ckey+LABEL_PWD )
);
logger.logInfo("New Discovered Node: " + nodeID + " - Creating associated Gaian Connection: " + ckey);
// System.out.println("Current spr: " + Arrays.asList(spr));
synchronized(spr) {
spr.setProperty( ckey+LABEL_DRIVER, driver );
spr.setProperty( ckey+LABEL_URL, url );
spr.setProperty( ckey+LABEL_USR, usr );
spr.setProperty( ckey+LABEL_PWD, '\'' + scramble( pwd, ckey ) ); // re-scramble pwd based on new connection key
spr.setProperty( ckey+LABEL_DNODEID, nodeID );
String discoveredConnectionsList = getSystemProperty( DISCOVERED_GAIAN_CONNECTIONS );
spr.setProperty( DISCOVERED_GAIAN_CONNECTIONS,
( null == discoveredConnectionsList || 0 == discoveredConnectionsList.length() ? ckey :
discoveredConnectionsList + "," + ckey ) );
if ( null != registeredConnectionIDs ) {
String cids = registeredConnectionIDs.get(url+"'"+usr);
registeredConnectionIDs.put(url+"'"+usr, null==cids?ckey:cids+","+ckey);
}
}
}
// dumpRegistryIfLoggingOn();
return ckey;
}
// This holds all RDBMS URLs/USRs refs in config - including system ones in memory, and those that are not referenced/loaded.
private static Map<String, String> registeredConnectionIDs = null; // Reverse map lookup for: url'usr -> cid
private static Set<String> registeredBlueMixUrlUsrKeys = null;
static final Map<String, String> lookupAllRegisteredCIDs() {
if ( null == registeredConnectionIDs ) {
registeredConnectionIDs = new HashMap<String, String>();
for ( Properties props : new Properties[] { upr, spr } )
synchronized( props ) {
for ( Iterator<Object> keys = props.keySet().iterator(); keys.hasNext(); ) {
String key = (String) keys.next(), url=null;
if ( key.endsWith( LABEL_URL ) && (url = props.getProperty(key)).startsWith("jdbc:") ) {
final String cid = key.substring(0, key.length()-LABEL_URL.length());
final String usr = (String)props.get(cid+LABEL_USR);
if ( null != usr && props.containsKey(cid+LABEL_PWD) ) {
String cids = registeredConnectionIDs.get(url+"'"+usr);
registeredConnectionIDs.put(url+"'"+usr, null==cids?cid:cids+","+cid);
}
}
}
}
}
return registeredConnectionIDs;
}
/**
* Adds/removes registered connection IDs based on whether they are in availableConnectionsToSynchTo
*
* @param cidPrefix
* @param latestAvailableConnections Mapping of: "url'usr" -> pwd
* @return boolean to indicate whether in-memory config was changed (true) or stayed the same (false)
*/
public static final boolean synchronizeSystemRDBMSConnections( final String cidPrefix, final Map<String,String> availableConnectionsToSynchTo ) {
boolean isConfigChanged = false;
// Remove system cids for url/usr keys that are no longer available
if ( null != registeredBlueMixUrlUsrKeys ) {
if ( null != registeredConnectionIDs )
for ( String urlAndUsr : registeredBlueMixUrlUsrKeys )
if ( null == availableConnectionsToSynchTo || false == availableConnectionsToSynchTo.keySet().contains(urlAndUsr) ) {
String cid = registeredConnectionIDs.remove(urlAndUsr); // The value for this key can only be a single cid (not a list)
if ( null != cid ) synchronized(spr) { spr.remove(cid+LABEL_URL); spr.remove(cid+LABEL_USR); spr.remove(cid+LABEL_PWD); }
isConfigChanged = true;
}
// Now ignore ones we have already registered
if ( null != availableConnectionsToSynchTo )
availableConnectionsToSynchTo.keySet().removeAll(registeredBlueMixUrlUsrKeys);
registeredBlueMixUrlUsrKeys.clear();
}
if ( null == availableConnectionsToSynchTo || 1 > availableConnectionsToSynchTo.size() )
return isConfigChanged; // no new available connections
registeredBlueMixUrlUsrKeys = availableConnectionsToSynchTo.keySet();
// Add new ones
for ( String urlAndUsr : registeredBlueMixUrlUsrKeys ) {
if ( lookupAllRegisteredCIDs().containsKey(urlAndUsr) ) continue;
int idx = urlAndUsr.lastIndexOf('\'');
String url = urlAndUsr.substring(0,idx), usr = urlAndUsr.substring(idx+1), pwd = availableConnectionsToSynchTo.get(urlAndUsr);
int idx1 = "jdbc:".length(), idx2 = url.indexOf(':', idx1); if ( 0 > idx2 ) continue; // strange jdbc url, skip it
final String rdbmsName = url.substring(idx1, idx2).trim().toUpperCase();
String cid = null; int i=1;
do { cid = cidPrefix+"_"+rdbmsName+"_C"+(i++); } while (
null != getCrossOverProperty(cid+LABEL_DRIVER ) ||
null != getCrossOverProperty(cid+LABEL_URL ) ||
null != getCrossOverProperty(cid+LABEL_USR ) ||
null != getCrossOverProperty(cid+LABEL_PWD )
);
// System.out.println("Registering new cid for url: " + url + " -> " + cid);
synchronized(spr) {
spr.setProperty( cid+LABEL_URL, url );
spr.setProperty( cid+LABEL_USR, usr );
spr.setProperty( cid+LABEL_PWD, '\'' + scramble( pwd, cid ) ); // scramble pwd based on connection id
// Register this RDBMS - cannot have an existing entry for this urlAndUsr because an API or file update resets registeredConnectionIDs.
if ( null != registeredConnectionIDs ) registeredConnectionIDs.put(url+"'"+usr, cid);
}
isConfigChanged = true;
}
return isConfigChanged;
}
// public static final String addSystemRDBMSConnectionIfNotExists(
// final String cidPrefix, final String url, final String usr, final String pwd ) {
//
// final String urlAndUsr = url+"'"+usr;
// if ( lookupAllRegisteredCIDs().containsKey(urlAndUsr) ) return null;
//
// String cid = null; int i=1;
// do { cid = cidPrefix+(i++); } while (
// null != getCrossOverProperty(cid+LABEL_DRIVER ) ||
// null != getCrossOverProperty(cid+LABEL_URL ) ||
// null != getCrossOverProperty(cid+LABEL_USR ) ||
// null != getCrossOverProperty(cid+LABEL_PWD )
// );
//
//// System.out.println("Registering new cid for url: " + url + " -> " + cid);
// synchronized(spr) {
// spr.setProperty( cid+LABEL_URL, url );
// spr.setProperty( cid+LABEL_USR, usr );
// spr.setProperty( cid+LABEL_PWD, '\'' + scramble( pwd, cid ) ); // scramble pwd based on connection id
// if ( null != registeredConnectionIDs ) {
// String cids = registeredConnectionIDs.get(url+"'"+usr);
// registeredConnectionIDs.put(url+"'"+usr, null==cids?cid:cids+","+cid);
// }
// }
//
// return cid;
// }
private static File getConfigFile() throws FileNotFoundException {
// If configFile was set but the file has been replaced entirely with a new one then canRead() will fail
if ( null == configFile || !configFile.canRead() ) {
configFile = new File( configFileLocation );
if ( false == configFile.exists() ) throw new FileNotFoundException("File not found: " + configFileLocation);
}
return configFile;
}
public static String getConfigFileName() throws FileNotFoundException { return getConfigFile().getName(); }
public static String getConfigFilePath() throws IOException { return getConfigFile().getCanonicalPath(); }
protected static long getConfigFileLastModified() throws FileNotFoundException { return getConfigFile().lastModified(); }
private static void reloadUserProperties() {
if ( null == upr ) upr = new Properties();
String cfgFileAbsolutePath = "<Unresolved Location>";
try {
long latestConfigFileLastModified = getConfigFileLastModified();
File cfgFile = getConfigFile();
FileInputStream fis = new FileInputStream( cfgFile );
cfgFileAbsolutePath = cfgFile.getCanonicalPath();
synchronized( upr ) {
upr.clear();
if ( null != registeredConnectionIDs ) { registeredConnectionIDs.clear(); registeredConnectionIDs = null; }
if ( null != registeredBlueMixUrlUsrKeys ) { registeredBlueMixUrlUsrKeys.clear(); registeredBlueMixUrlUsrKeys = null; }
upr.putAll( inMemoryDataSourceProperties ); // these could be overridden by persistent properties...
upr.load( new InputStreamReader(fis, "UTF8") );
// The properties below can be overridden by re-defining them manually *IN UPPERCASE* in the config file
// (Note that the GAIAN_NODE_VERSION is already in upper case here so it cannot be changed)
upr.setProperty("derby_tables_def", "TABNAME VARCHAR(30), TABTYPE CHAR, COLNAME VARCHAR(30), COLTYPE VARCHAR(80)");
upr.setProperty("derby_tables_ds0_connection", "localderby sys.systables,sys.syscolumns where tableid=referenceid");
upr.setProperty("derby_tables_ds0_c1", "TABLENAME");
upr.setProperty("derby_tables_ds0_c2", "TABLETYPE");
upr.setProperty("derby_tables_ds0_c3", "COLUMNNAME");
upr.setProperty("derby_tables_ds0_c4", "COLUMNDATATYPE");
upr.setProperty("gdb_ltlog_def", "COLUMN1 VARCHAR(255)");
upr.setProperty("gdb_ltlog_ds0_vti", "com.ibm.db2j.FileImport");
upr.setProperty("gdb_ltlog_ds0_args", GaianNode.getWorkspaceDir() + "/" + GaianDBConfig.getGaianNodeDatabaseName() + ".log");
upr.setProperty("GAIAN_NODE_VERSION", GaianNode.GDB_VERSION + " - " + GaianNode.JAR_TSTAMPS[0] + " - " + GaianNode.JAR_SIZES[0] + " bytes");
upr.setProperty("gdb_ltnull_def", "CNULL CHAR(1)"); // getColumnCount() on a VTI must be > 0
// Only now should we set the definitive latest config file timestamp of loaded properties.
// logger.logInfo("Updating config file lastModified from: " + latestLoadedPropertiesFileTimestamp + ", to: " + latestConfigFileLastModified + ", stack trace: " + Util.getStackTraceDigest());
latestLoadedPropertiesFileTimestamp = latestConfigFileLastModified;
if ( null != derbyProperties ) {
derbyGaianNodePassword = derbyProperties.getProperty("derby.user." + getGaianNodeUser());
// derbyGaianNodeAdminPassword = derbyProperties.getProperty("derby.user." + GDBADMIN_USER);
}
}
fis.close();
// special case below generates a password from "passw0rd" that has a backslash '\' in it -
// this is silently dropped by the load() so had to write a new method: Util.escapeBackslashes()
// System.out.println("pwd: " + upr.getProperty("G105-39_PWD"));
// System.out.println("pwd: " + unscramble( upr.getProperty("G105-39_PWD").substring(1), "G105-39") );
// logger.logInfo("Reloaded user properties: " + upr);
} catch ( IOException e ) {
logger.logWarning( GDBMessages.CONFIG_USER_PROPS_RELOAD_ERROR, "Unable to reload user properties at: " + cfgFileAbsolutePath +
", cause: " + Util.getStackTraceDigest(e) );
}
}
// Commented out for now: No need for a path.
// Derby already looks up it's dbs relative to property: derby.system.home (Unless a full path is specified in the URL)
// public static String getGaianNodeDatabasePath() {
// return getGaianNodeDatabaseName(); // This works, as its a fixed physical db and always in the working directory.
// }
public static String getGaianNodeDatabaseName() {
// String db = getUserProperty( GAIAN_NODE_DB );
// if ( null != db ) return db; //null == gaiandbWorkspace ? db : gaiandbWorkspace + File.separator + db;
if ( null == DBNAME ) {
// db = DEFAULT_ROOT_DBNAME + (derbyServerListenerPort == GaianNode.DEFAULT_PORT ? "" : derbyServerListenerPort+"");
// DBNAME = null == gaiandbWorkspace ? db : gaiandbWorkspace + File.separator + db;
DBNAME = GAIANDB_NAME + (derbyServerListenerPort == GaianNode.DEFAULT_PORT ? "" : derbyServerListenerPort+"");
}
return DBNAME;
}
public static String getGaianNodeHostName() {
if ( null == gaianNodeHostName ) {
try {
gaianNodeHostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException e) {
logger.logException( GDBMessages.CONFIG_GET_NODE_HOSTNAME_ERROR, "Unable to resolve Gaian Node host name: ", e );
}
}
return gaianNodeHostName;
}
public static String getGaianNodeUser() {
if ( null == derbyGaianNodeUser ) {
derbyGaianNodeUser = getUserProperty( GAIAN_NODE_USR );
if ( null == derbyGaianNodeUser ) derbyGaianNodeUser = GAIAN_NODE_DEFAULT_USR;
}
return derbyGaianNodeUser;
}
// If authentication is switched off (i.e. derby.properties file not set up for it) return a dummy password
// Otherwise, return the password set in the derby.properties file for the gaiandb user.
public static String getGaianNodePassword() {
String pwd = getUserProperty( GAIAN_NODE_PWD );
return null == pwd ? ( null == derbyGaianNodePassword ? GAIAN_NODE_DEFAULT_PWD : derbyGaianNodePassword ) :
0 < pwd.length() && '\'' == pwd.charAt(0) ? unscramble(pwd.substring(1), GAIAN_NODE_PWD) : pwd;
// return null == derbyGaianNodePassword ? GAIAN_NODE_DEFAULT_PWD : derbyGaianNodePassword;
}
public static String getDerbyAuthMode() {
return null == derbyProperties ? null : derbyProperties.getProperty(DERBY_AUTH_MODE_KEY);
}
public static String getSSLMode() {
if ( null == derbyProperties ) return "off";
final String p = derbyProperties.getProperty(DERBY_SSL_MODE);
return null == p || ( !p.equals("basic") && !p.equals("peerAuthentication") ) ? "off" : p;
}
// public static String getGaianNodeAdminUser() {
// return GDBADMIN_USER;
// }
//
// static String getGaianNodeAdminPassword() {
// return null == derbyGaianNodeAdminPassword ? GDBADMIN_DEFAULT_PASSWORD : derbyGaianNodeAdminPassword;
// }
static String getGaianNodePasswordScrambled() {
return scramble( getGaianNodePassword(), getGaianNodeID() );
}
// public static String getGaianNodeDescription() {
//// String nid = getUniqueGaianNodeID();
//// return null==nid ? null : nid + "/" + getGaianNodeDatabase();
// return getGaianNodeName() + ":" + derbyServerListenerPort + "/" + getGaianNodeDatabase();
// }
// public static String getGaianNodeConnectionDetails() {
// return SQLDerbyRunner.DERBY_CLIENT_NETWORK_DRIVER + " " + getGaianNodeDatabase() + " " +
// getGaianNodeUser() + " " + getGaianNodePassword();
// }
public static final String removeProblematicChars( String s ) {
if ( null == s ) return s;
// return s.replaceAll("[\\s'\",;*@]", ""); // Remove all whitespace chars, single/double quotes, comma/semi-colons and *,@
return s.replaceAll("\\W", ""); // Remove all chars other than word chars [a-zA-Z_0-9]
}
public static void setGaianNodeName( String nodeName ) {
if ( null != nodeName && null == gaianNodeID ) {
gaianNodeID = removeProblematicChars( nodeName );
gaianNodeID += GaianNode.DEFAULT_PORT == derbyServerListenerPort ? "" : ":" + derbyServerListenerPort;
}
}
public static synchronized String getGaianNodeID() {
if ( null == gaianNodeID || 0 == gaianNodeID.length()) {
gaianNodeID = removeProblematicChars( getGaianNodeHostName() );
if ( null == gaianNodeID || 0 == gaianNodeID.length() )
gaianNodeID = new Integer( new Random().nextInt() ).toString();
gaianNodeID += GaianNode.DEFAULT_PORT == derbyServerListenerPort ? "" : ":" + derbyServerListenerPort;
}
return gaianNodeID;
}
/**
* This method must be synchronized because 2 threads could both call 'counter++' before either
* of them evaluates the encapsulating expression - thus returning the same query id.
*
* @return a unique query id.
*/
public synchronized static String generateUniqueQueryID() {
String nid = getGaianNodeID();
// Use currentTimeMillis() timestamp to differentiate queries coming from the same node after a restart of the node.
return null==nid ? null : nid + ":" + System.currentTimeMillis() + ":" + (int)queryIDSequenceCounter++;
}
public static String getNodeDefVTI( String nodeDefName ) {
return getUserProperty( nodeDefName + VTI_SUFFIX );
}
private static String[] getDataSourceOptions( String nodeDefName ) {
String optionsProp = getUserProperty( nodeDefName + OPTIONS_SUFFIX );
return Util.splitByCommas( optionsProp );
}
public static boolean isNodeInMemoryOptionSet( String nodeDefName ) { return isNodeOptionSet( nodeDefName, INMEMORY ); }
public static boolean isNodePluralizedOptionSet( String nodeDefName ) { return isNodeOptionSet( nodeDefName, PLURALIZED ); }
private static final List<String> permittedSuffixes = Arrays.asList( "", "_0", "_1", "_P", "_X", "_XF" );
private static final Map<String, String> ltViewTailOptions = new HashMap<String, String>() {{
put( "", ", ''"); put( "_0", ", 'maxDepth=0'"); put( "_1", ", 'maxDepth=1'");
put( "_P", ", 'with_provenance'"); put( "_X", ", 'explain'"); put( "_XF", ", 'explain in graph.dot'");
}};
// private static final String[] ltViewTailOptions = { ", ''", ", 'maxDepth=0'", ", 'maxDepth=1'", ", 'with_provenance'", ", 'explain'", ", 'explain in graph.dot'" };
public static String getLogicalTableTailOptionsForViewSuffix( String suffix ) { return ltViewTailOptions.get(suffix); }
private static final String LOGICAL_TABLES_HAVING_BASIC_VIEWS_ONLY = "LOGICAL_TABLES_HAVING_BASIC_VIEWS_ONLY";
public static Collection<String> getLogicalTableRequiredViewSuffixes( String ltName ) {
// String propVal = getUserProperty( ltName + "_VIEW_SUFFIXES" );
// if ( null == propVal ) return permittedSuffixes;
// Set<String> userSuffixesSet = new HashSet<String>( Arrays.asList( (',' + propVal).trim().split("[\\s]*,[\\s]*", -1 ) ) );
// for ( String s : userSuffixesSet ) if ( false == permittedSuffixes.contains(s) ) return permittedSuffixes;
String propVal = getUserProperty( LOGICAL_TABLES_HAVING_BASIC_VIEWS_ONLY );
if ( null == propVal ) return permittedSuffixes;
Set<String> ltsWithBasicViewOnly = new HashSet<String>( Arrays.asList( Util.splitByCommas( propVal ) ));
for ( String s : ltsWithBasicViewOnly ) {
int slen = s.length();
if ( ltName.equals(s) || ( '*' == s.charAt(slen-1) && ltName.startsWith( s.substring(0,slen-1) ) ))
return Arrays.asList("");
}
// logger.logInfo("Resolved " + userSuffixesSet.size() + " user-defined view suffixes for LT: " + ltName + " -> " + userSuffixesSet);
return permittedSuffixes;
}
private static boolean isNodeOptionSet( String nodeDefName, String optionID ) {
String[] options = getDataSourceOptions( nodeDefName );
for (int i=0; i<options.length; i++) {
// System.out.println("option " + i + ":" + options[i]);
if ( Util.splitByTrimmedDelimiter( options[i], ' ' )[0].equals( optionID ) )
return true;
}
return false;
}
public static boolean isPluralizedOptionUsingRegex( String nodeDefName ) {
String[] options = getDataSourceOptions( nodeDefName );
for (int i=0; i<options.length; i++) {
final String option = options[i];
if ( option.startsWith(PLURALIZED) ) {
String[] tokens = Util.splitByWhitespace( option );
if ( 2 > tokens.length ) break;
if ( 3 == tokens.length && "USING".equals(tokens[1]) )
if ("REGEX".equals(tokens[2])) return true;
else if ("WILDCARD".equals(tokens[2])) return false;
logger.logWarning(GDBMessages.CONFIG_INVALID_FILE_OPTION_PLURALIZED_SYNTAX,
"Invalid keywords declaration (" + option + "). Syntax should be: PLURALIZED [USING WILDCARD|REGEX]");
}
}
return false; // Not pluralized using a regex mask
}
private static final String PLURALIZED_COL_MAPPINGS_SYNTAX_HELP =
"Syntax should be for example to map to LT cols 5 and 3: LTX_DSY_OPTIONS=PLURALIZED WITH ENDPOINT CONSTANTS C5 C3";
public static int[] getDataSourceWrapperPluralizedEndpointConstantColMappings( String nodeDefName ) {
String[] options = getDataSourceOptions( nodeDefName );
for (int i=0; i<options.length; i++) {
final String option = options[i];
if ( option.startsWith(PLURALIZED) ) {
String[] tokens = Util.splitByWhitespace( option );
if ( 2 > tokens.length ) break;
String errTxt = null;
if ( 4 < tokens.length && "WITH".equals(tokens[1]) && "ENDPOINT".equals(tokens[2]) && "CONSTANTS".equals(tokens[3]) ) {
int[] colMappings = new int[ tokens.length - 4 ];
for ( int j=4; j<tokens.length; j++ ) {
String s = tokens[j];
try { if ( 'C' == s.charAt(0) ) colMappings[j-4] = Integer.parseInt( s.substring(1) ); continue; }
catch ( Exception e ) { errTxt = e.toString(); }
if ( null == errTxt ) errTxt = PLURALIZED_COL_MAPPINGS_SYNTAX_HELP;
logger.logWarning(GDBMessages.CONFIG_INVALID_ENDPOINT_COL_MAPPING_SYNTAX, "Invalid mapping syntax (" + s + "): " + errTxt);
break;
}
if ( null == errTxt ) return colMappings;
} else logger.logWarning(GDBMessages.CONFIG_INVALID_ENDPOINT_COL_MAPPING_SYNTAX,
"Invalid keywords declaration (" + option + "). " + PLURALIZED_COL_MAPPINGS_SYNTAX_HELP);
}
}
return null; // no PLURALIZED option or not specified mappings for: endpoint constants -> lt cols
}
private static final Pattern inMemoryOptionExpiryPattern = Pattern.compile("[\\s]*INMEMORY[\\s]+.*EXPIRY[\\s]+([1-9][\\d]*)[\\s]*");
public static long getDataSourceCacheExpirySeconds( String nodeDefName ) {
String[] options = getDataSourceOptions( nodeDefName );
for (int i=0; i<options.length; i++) {
String expiryAsString = inMemoryOptionExpiryPattern.matcher(options[i]).replaceFirst("$1");
if ( expiryAsString.equals(options[i]) ) continue;
return Long.parseLong( expiryAsString );
}
return -1;
}
public static boolean isLogPerformanceOn() {
String logPerf = getUserProperty(LOGPERF);
return "ON".equals(logPerf);
}
/**
* Returns a Hashtable mapping of: physical source colId -> logical column type
*
* The physical col ids are those that should be indexed if the INMEMORY option is set.
*
* If INMEMORY is not set - or if PLURALIZED *is set* - then null is returned.
*
* @param nodeDefName
* @return
*/
public static ConcurrentMap<Integer, Integer> getInMemoryIndexes( String nodeDefName,
String[] ltPhysicaColNames, int[] columnsMapping, GaianResultSetMetaData ltrsmd ) {
String[] options = getDataSourceOptions( nodeDefName );
for (int i=0; i<options.length; i++)
if ( options[i].startsWith( PLURALIZED ) ) return null;
for (int i=0; i<options.length; i++) {
// logger.logInfo("Looking for InMemory indexes: checking relevance of option: " + options[i]);
if ( options[i].startsWith( INMEMORY ) ) {
String[] elmts = Util.splitByTrimmedDelimiter( options[i] , ' ' ); //.split("[ |\t]*");
ConcurrentMap<Integer, Integer> indexes = new ConcurrentHashMap<Integer, Integer>();
if ( 1 < elmts.length && elmts[1].equals("INDEX") ) {
if ( 3 < elmts.length && elmts[2].equals("ON")) {
if ( 4 < elmts.length && false == elmts[4].equals("EXPIRY") ) {
logger.logThreadWarning( GDBMessages.CONFIG_INDEX_DEF_IGNORE,
nodeDefName + " getInMemoryIndexes(): Only 1 unique column index may be specified - ignoring index definition" );
} else {
int j = 3;
// for ( int j=4; j<elmts.length; j++ ) {
String col = elmts[j];
String ltPhysicalColName = null;
for ( int k=0; k<ltPhysicaColNames.length; k++ ) {
ltPhysicalColName = ltPhysicaColNames[k];
if ( ltPhysicalColName.equals(col) ) {
int pSourceColIndex = columnsMapping[k];
int ltColType = ltrsmd.getColumnType(k+1);
indexes.put( new Integer( pSourceColIndex ), new Integer( ltColType ) );
logger.logThreadInfo( nodeDefName + " getInMemoryIndexes(): Recognised physical column " + col +
" to be source col ID "+ pSourceColIndex + ", logical col type " +
TypeId.getBuiltInTypeId(ltColType).getSQLTypeName());
break;
}
}
if ( 0 == indexes.size() )
logger.logThreadWarning( GDBMessages.CONFIG_COLUMN_NOT_RECOGNISED,
nodeDefName + " getInMemoryIndexes(): Unrecognised physical column name for indexing: " + col );
// }
}
} else
logger.logThreadWarning( GDBMessages.CONFIG_INDEX_DEF_ERROR,
nodeDefName + " getInMemoryIndexes(): Incorrect index definition (ignored), should be: INMEMORY INDEX ON <col_name>" );
}
// int[] idxs = new int[ indexes.size() ];
// for ( int j=0; j<idxs.length; j++ ) {
// idxs[j] = ((Integer) indexes.get(j)).intValue();
// }
// logger.logInfo("returning in mem indexes: " + Arrays.asList( indexes.keySet() ));
return indexes;
}
}
return null; // only return null if INMEMORY is not even set.
}
public static boolean isPropertyPotentiallyImpactingALogicalTable( String key ) {
if ( null == key ) return false;
if ( key.endsWith(LTDEF_SUFFIX) || key.endsWith(CONSTANTS_SUFFIX) ) return true;
int dsIdx = -1;
// Determine if this might be a data source property of a defined logical table.
while ( -1 < ( dsIdx = key.indexOf("_DS", dsIdx+1) ) )
if ( null != getUserProperty( key.substring(0, dsIdx) + LTDEF_SUFFIX ))
return true;
if ( key.endsWith(LABEL_DRIVER) || key.endsWith(LABEL_URL) || key.endsWith(LABEL_USR) || key.endsWith(LABEL_PWD) )
return true;
return false;
}
static boolean isNodeDefExists( String nodeDefName ) {
return isNodeDefRDBMS(nodeDefName) || isNodeDefVTI(nodeDefName);
}
public static boolean isNodeDefVTI( String nodeDefName ) {
return ( null != getUserProperty( nodeDefName + VTI_SUFFIX ) );
}
public static boolean isNodeDefRDBMS( String nodeDefName ) {
return ( null != getCrossOverProperty( nodeDefName + CONNECTION_SUFFIX ) );
}
public static boolean isNodeDefGaian( String nodeDefName ) {
return ( null != getCrossOverProperty( nodeDefName + CONNECTION_SUFFIX )
&& null == getNodeDefRDBMSTable( nodeDefName )
&& null == getNodeDefVTI( nodeDefName ) );
}
public static String getNodeDefRDBMSTable( String nodeDefName ) {
String table = getUserProperty( nodeDefName + TABLE_SUFFIX );
if ( null != table ) return table;
// shorthand version for defining table names: e.g. LT0_N0_CONNECTION=DERBY TABLE1
String c = getCrossOverProperty( nodeDefName + CONNECTION_SUFFIX );
if ( null==c ) return null;
int spacepos = c.indexOf(' ');
if ( -1 == spacepos ) return null;
else return c.substring( spacepos+1 );
}
public static String getNodeDefRDBMSConnectionID( String nodeDefName ) {
String c = getCrossOverProperty( nodeDefName + CONNECTION_SUFFIX );
if ( null == c ) return null;
c = c.toUpperCase();
int spacepos = c.indexOf(' ');
if ( -1 == spacepos ) return c;
else return c.substring( 0, spacepos );
}
public static String getVTIArguments( String nodeDefName ) {
String prop = getUserProperty( nodeDefName + ARGS_SUFFIX );
return null == prop ? null : resolvePathTags( prop );
}
private static final String GW_TAG = "<GAIAN_WORKSPACE>";
public static String resolvePathTags( String path ) {
return path.startsWith(GW_TAG) ? GaianNode.getWorkspaceDir() + path.substring(GW_TAG.length()) : path;
}
public static String getVTISchema( String nodeDefName ) {
String prop = getUserProperty( nodeDefName + SCHEMA_SUFFIX );
if ( null == prop ) return null;
return prop;//.split(",");
}
public static boolean isRegistryNeedsReloadingFromFile() {
try {
// Class klass = getRB().getClass().getSuperclass();
// Field field = klass.getDeclaredField("cacheList");
// field.setAccessible(true);
// sun.misc.SoftCache cache = (sun.misc.SoftCache)field.get(null);
// cache.clear();
// field.setAccessible(false);
// loadedConfigLastModified = getConfigFileLastModified();
// No need to refresh if we haven't loaded the properties at all yet, or if they haven't changed
if ( null == upr ) return false;
long latestConfigFileLastModified = getConfigFileLastModified();
if ( latestLoadedPropertiesFileTimestamp == latestConfigFileLastModified ) return false;
} catch ( Exception e ) {
logger.logException( GDBMessages.CONFIG_CHECK_PROPS_ERROR, "Unable to check resource properties file modification timestamp: ", e);
return false;
}
return true;
}
/**
* No refresh if we haven't loaded the properties at all yet, or if they haven't changed
* @throws FileNotFoundException
*
*/
public static boolean refreshRegistryIfNecessary() {
if ( !isRegistryNeedsReloadingFromFile() )
return false;
reloadUserProperties();
return true;
}
static Set<String> getGaianConnectionsAsSet() {
return new HashSet<String>( Arrays.asList(getGaianConnections()) );
}
public static String[] getGaianConnections() {
return Util.splitByCommas(getGaianConnectionsList());
}
public static String[] getGaianConnectedNodes( String[] gcs ) {
String[] nodes = new String[gcs.length];
for ( int i=0; i<gcs.length; i++ ) {
nodes[i] = getGaianNodeID( gcs[i] );
}
return nodes;
}
public static String[] getDiscoveredConnections() { return Util.splitByCommas(getSystemProperty(DISCOVERED_GAIAN_CONNECTIONS)); }
public static String[] getDefinedConnections() { return Util.splitByCommas(getUserProperty(DEFINED_GAIAN_CONNECTIONS)); }
public static String getAccessHostsPermitted() {
String ahp = getUserProperty(ACCESS_HOSTS_PERMITTED);
return null != ahp ? ahp : getUserProperty(DISCOVERY_HOSTS);
}
public static String getAccessHostsDenied() { return getUserProperty(ACCESS_HOSTS_DENIED); }
public static String getAccessClusters() { return getUserProperty(ACCESS_CLUSTERS); }
public static String getConnectionStrategy() { return getUserProperty(CONNECTION_STRATEGY); }
public static String getDiscoveryIP() { return getUserProperty(DISCOVERY_IP); }
public static String getDiscoveryGateways() { return getUserProperty(DISCOVERY_GATEWAYS); }
public static String getMulticastInterfaces() { return getUserProperty(MULTICAST_INTERFACES); }
// public static String[] getMulticastDomains() {
// String mifs = getUserProperty(MULTICAST_DOMAINS);
// return null == mifs ? null : Util.splitByCommas(mifs);
// }
private static String getGaianConnectionsList() {
String gcl1 = getSystemProperty(DISCOVERED_GAIAN_CONNECTIONS);
String gcl2 = getUserProperty(DEFINED_GAIAN_CONNECTIONS);
String gcl;
if (null == gcl1 || 0 == gcl1.length()) {
if (null == gcl2 || 0 == gcl2.length())
return "";
gcl = gcl2;
} else if (null == gcl2 || 0 == gcl2.length())
gcl = gcl1;
else
gcl = gcl1 + "," + gcl2;
return gcl;
}
/**
* Returns all property keys that have the given suffix, without the suffix
* @param prefix
* @return
*/
static Set<String> getAllUserPropertiesWithSuffixAndRemoveSuffix( String suffix ) {
Set<String> set = new HashSet<String>();
if ( null == upr ) reloadUserProperties();
synchronized( upr ) {
for ( Iterator<Object> keys = upr.keySet().iterator(); keys.hasNext(); ) {
String key = ((String) keys.next()).toUpperCase();
if ( key.endsWith(suffix) ) {
int offset = key.lastIndexOf(suffix);
set.add( key.substring(0, offset) );
}
}
}
return set; //(String []) set.toArray( new String[0] );
}
/**
* Returns all property values mapped to keys which end with the given suffix.
* @param suffix
* @return
*/
static String[] getAllUserPropertyValuesForKeysWithSuffix( String suffix ) {
HashSet<String> s = new HashSet<String>();
if ( null == upr ) reloadUserProperties();
synchronized( upr ) {
for ( Iterator<Object> keys = upr.keySet().iterator(); keys.hasNext(); ) {
String key = ((String) keys.next()).toUpperCase();
if ( key.endsWith(suffix) ) {
String val = upr.getProperty(key);
if ( null != val ) s.add( val );
}
}
}
return s.toArray( new String[0] );
}
public static GaianDBConfigLogicalTables getAllLogicalTableDefinitionsAndReferences() {
Set<String> lts = getAllUserPropertiesWithSuffixAndRemoveSuffix( LTDEF_SUFFIX ); // getAllLogicalTableNames();
Set<String> vtiDefs = new HashSet<String>();
Set<String> jdbcRefs = new HashSet<String>();
if ( null == upr ) reloadUserProperties();
synchronized( upr ) {
for ( Iterator<Object> keys = upr.keySet().iterator(); keys.hasNext(); ) {
String key = ((String) keys.next()).toUpperCase();
// The logical table name itself may have a sufix of "_DS" - this is a valid case
int ltEndIdx = key.lastIndexOf("_DS");
// We are only interested in properties that define a data source
if ( -1 < ltEndIdx && ( key.endsWith(VTI_SUFFIX) || key.endsWith(CONNECTION_SUFFIX) ) ) {
final String ltName = key.substring(0, ltEndIdx);
if ( false == lts.contains( ltName ) ) continue; // ignore data sources that are not attached to a logical table
int offset = key.indexOf('_', ltName.length() + 2);
String dsName = key.substring(0, offset);
if ( key.equals(dsName+CONNECTION_SUFFIX) ) {
String val = upr.getProperty(key);
if ( null != val )
try {
jdbcRefs.add( getRDBConnectionDetailsAsString(dsName) );
} catch (Exception e) {
logger.logWarning(GDBMessages.CONFIG_LT_DS_GET_CONN_DETAILS_ERROR, "Unable to get JDBC connection details of LT data source (skipped): " +
dsName + ": " + e);
}
} else if ( key.equals(dsName+VTI_SUFFIX) ) {
String val = upr.getProperty(key);
if ( null != val ) {
if ( FileImport.class.getName().equals(val) )
// Special case - if we consolidate with other VTIs in future, we'd also need to VTIFile's sourceID in VTIFile constructor
vtiDefs.add( getVTIArguments(dsName) );
else
vtiDefs.add( val + ':' + getVTIArguments(dsName) );
// vtiDefs.add( val + ':' + VTIBasic.derivePrefixArgFromCSVArgs(getVTIArguments(dsName)) );
}
}
}
}
}
return new GaianDBConfigLogicalTables( lts, vtiDefs, jdbcRefs );
}
/**
* Returns all property keys that have the given prefix
* @param prefix
* @return
*/
static String[] getAllUserPropertiesWithPrefix( String prefix ) {
HashSet<String> set = new HashSet<String>();
if ( null == upr ) reloadUserProperties();
synchronized( upr ) {
for ( Iterator<Object> keys = upr.keySet().iterator(); keys.hasNext(); ) {
String key = ((String) keys.next()).toUpperCase();
if ( key.startsWith(prefix ) )
set.add( key );
}
}
return set.toArray( new String[0] );
}
static String getLogicalTableViewSignature( String ltName ) {
String ltDef = getLogicalTableDef(ltName);
String ltConstants = getUserProperty( ltName + CONSTANTS_SUFFIX );
return null == ltDef && null == ltConstants ? null : ltDef + "; Constant Cols: " + getConstantColumnsDef(ltName);
}
static Map<String, String> getLogicalTableStructuralSignature( String ltname ) {
Map<String, String> m = new HashMap<String, String>();
m.put( NODE_CONSTANTS, getUserProperty(NODE_CONSTANTS) );
Set<String> referencedConnections = new HashSet<String>();
if ( null == upr ) reloadUserProperties();
synchronized( upr ) {
for ( Iterator<Object> i = upr.keySet().iterator(); i.hasNext(); ) {
String key = ((String) i.next()).toUpperCase();
if ( key.equals( ltname + LTDEF_SUFFIX ) || key.equals( ltname + CONSTANTS_SUFFIX ) || key.startsWith( ltname + "_DS" ) ) {
String value = getUserProperty(key);
m.put( key, value );
if ( key.endsWith(CONNECTION_SUFFIX) && null != value && 0 < value.length() ) {
int spacepos = value.indexOf(' ');
referencedConnections.add( -1 == spacepos ? value : value.substring(0, spacepos) );
}
}
}
synchronized( spr ) {
for ( Iterator<String> i = referencedConnections.iterator(); i.hasNext(); ) {
String key = i.next();
String[] ks = new String[] { key + LABEL_DRIVER, key + LABEL_URL, key + LABEL_USR, key + LABEL_PWD };
for ( int k=0; k<ks.length; k++ ) m.put( ks[k], getCrossOverProperty(ks[k]) );
}
}
}
return m;
}
public static Set<String> getDataSourceDefs( String logicalTableName ) {
HashSet<String> nodeDefs = new HashSet<String>();
if ( null == upr ) reloadUserProperties();
synchronized( upr ) {
for ( Iterator<Object> keys = upr.keySet().iterator(); keys.hasNext(); ) {
String key = ((String) keys.next()).toUpperCase();
if ( key.startsWith( logicalTableName + "_DS" ) && ( key.endsWith(VTI_SUFFIX) || key.endsWith(CONNECTION_SUFFIX) ) ) {
// DRV 19/01/2013 - Fixed nasty bug here... take the index of the LAST underscore ('_') to remove the "_VTI" or "_CONNECTION" suffix
int offset = key.lastIndexOf('_'); //, logicalTableName.length() + 2);
nodeDefs.add( key.substring(0, offset) );
// logInfo("--remove this comment: getLatestLogicalTableNodeDefNames(): Added node def: " + key.substring(0, offset));
}
}
}
return nodeDefs;
}
public static String getColumnName( String physicalNodeName, int columnIndex, ResultSetMetaData ltrsmd ) throws SQLException {
// Try getting a physical column name mapping first...
String columnName = getUserProperty( physicalNodeName + COLUMN_LABEL + (columnIndex+1) ); // 1-based
if ( null != columnName )
return columnName; // Sybase is case sensitive, so return case as-is.
//return columnName.toUpperCase();
// No physical name mapping was defined, so default to the logical column one
columnName = ltrsmd.getColumnName( columnIndex+1 ); // 1-based
// Its OK if the default column is already used in the physical mapping definiton for another column
// If types don't match then a failure will occur at runtime.
// // Check that the logical column name isn't used in the physical mapping definiton for another column
// int colCount = ltrsmd.getColumnCount();
// for (int i=0; i<colCount; i++) {
// String nextMappedCol = physicalNodeName + COLUMN_LABEL + i;
// if ( columnName.equals( nextMappedCol ) )
// return null; // This column cannot be used by this
// }
return columnName;
}
public static boolean isColumnMappingExplicitlyDefined( String physicalNodeName, int columnIndex ) {
return null != getUserProperty( physicalNodeName + COLUMN_LABEL + (columnIndex+1) );
}
public static boolean isColumnNamesMirroredFromLTDef( String physicalNodeName ) {
String[] dsOptions = Util.splitByCommas( getUserProperty( physicalNodeName + OPTIONS_SUFFIX ) );
return new HashSet<String>( Arrays.asList(dsOptions) ).contains( MAP_COLUMNS_BY_POSITION );
}
// private static String getWrappingLogicalTable( String physicalNodeName ) {
// return physicalNodeName.substring( 0, physicalNodeName.lastIndexOf('_') );
// }
private static boolean cmdLineLogLevel = false;
public static void assignLogLevel( String logLevelSetOnCmdLine ) {
// Command line option takes precedence over user property (config file value)
if ( cmdLineLogLevel )
return; // Value already assigned on cmd line - cannot be changed
if ( null != logLevelSetOnCmdLine ) {
cmdLineLogLevel = true;
Logger.setLogLevel( logLevelSetOnCmdLine );
return;
}
// Get log level from config
String s = getUserProperty( LOGLEVEL );
if ( s == null ) return;
Logger.setLogLevel(s);
}
public static String getBrokerHost() {
return getUserProperty( MSGBROKER_HOST );
}
public static int getBrokerPort() {
String port = getUserProperty( MSGBROKER_PORT );
if ( null == port ) return -1;
try { return Integer.parseInt( port ); }
catch ( NumberFormatException e ) {
logger.logWarning(GDBMessages.CONFIG_BROKER_PORT_GET_ERROR, "Invalid property value: " + MSGBROKER_PORT + " must be a number");
return -1;
}
}
// Call this only on a refresh
public static String getBrokerTopic() {
return getUserProperty( MSGBROKER_TOPIC );
}
// Call this only on a refresh
public static int getMsgStorerRowExpiry() {
String expiry = getUserProperty( MSGSTORER_ROWEXPIRY_HOURS );
if ( null == expiry ) return -1;
try { return Integer.parseInt( expiry ); }
catch ( NumberFormatException e ) {
logger.logWarning(GDBMessages.CONFIG_ROW_EXPIRY_GET_ERROR, "Invalid property value: " + MSGSTORER_ROWEXPIRY_HOURS + " must be a number");
return -1;
}
}
public static String getMsgStorerMsgCols() {
return getUserProperty( MSGSTORER_MSGCOLS );
}
// public static String getBrokerTopic() {
// return getUserProperty( MSGBROKER_TOPIC );
// }
static void loadDriver( String driver ) throws SQLException {
if ( loadedJdbcDrivers.contains( driver ) ) return;
try {
// if ( driver.equals(GDB_LITE_DRIVER) ) return;
if ( GaianNode.isLite() && driver.equals(DERBY_EMBEDDED_DRIVER) )
throw new Exception("Unable to load embedded derby driver in LITE mode: " + driver);
DriverManager.registerDriver( new DriverWrapper( (Driver) GaianNode.getClassUsingGaianClassLoader(driver).newInstance() ));
// ClassLoader.getSystemClassLoader().loadClass( driver );
loadedJdbcDrivers.add( driver );
logger.logInfo("Loaded JDBC driver: " + driver);
} catch (Exception e) {
logger.logInfo("Unable to load JDBC driver: " + driver + ", cause: " + e);
// throw new SQLException("Unable to load db driver: " + driver + ", cause: " + e);
}
}
// public static String[] getIPsOfDiscoveredNodes() {
//
// String[] discoveredConnections = getDiscoveredConnections();
// String[] discoveredIPs = new String[discoveredConnections.length];
// for ( int i=0; i<discoveredIPs.length; i++ )
// discoveredIPs[i] = getIPFromConnectionID( discoveredConnections[i] );
//
// return discoveredIPs;
// }
// private static String getIPFromConnectionID( String cid ) {
// String url = getCrossOverProperty( cid + LABEL_URL );
// if ( null == url ) return null;
// return url.substring( urlPrefixLength, url.lastIndexOf(':') ); //url.length() - urlSuffixLength );
// }
public static String getConnectionURL( String cid ) {
return getCrossOverProperty( cid + LABEL_URL );
}
/**
* Get the url of the given gaian node. This must be a connection identifier, not a user
* defined node with an indirect _CONNECTION reference.
* The later is not considered a GAIAN node identifier.
*
* @param nodeDefName
* @return
*/
private static String getGaianNodeIDFromURL( String nodeDefName ) {
String url = getCrossOverProperty( nodeDefName + LABEL_URL );
if ( null == url ) return null;
return url.substring( urlPrefixLength, url.lastIndexOf('/') ); //url.length() - urlSuffixLength );
}
private static final int urlPrefixLength = "jdbc:derby://".length();
// private static final int urlSuffixLength = ";create=true".length();
// Note a Gaian Node URL connection (which is not the local one) must be defined as:
// "jdbc:derby://<host or ip>:<port>/<db>[;create=<false or true>]"
public static String getGaianNodeID( String nodeDefName ) {
String nodeID = getSystemProperty( nodeDefName.substring(nodeDefName.lastIndexOf('_')+1) + LABEL_DNODEID );
if ( null == nodeID ) // Derive node id from url if it is not defined explicitely
// By design, the nodeID *should not* start with an IP as it should have a NODEID property when the URL contains an IP.
nodeID = getGaianNodeIDFromURL( nodeDefName );
if ( null == nodeID )
nodeID = "UNIDENTIFIED_NODE";
int standardPortIndex = nodeID.lastIndexOf(":" + GaianNode.DEFAULT_PORT);
if ( -1 != standardPortIndex ) // Remove port def in nodeID if the port is the default 6414
nodeID = nodeID.substring( 0, standardPortIndex );
// logger.logInfo(nodeDefName + " getGaianNodeID() returning: " + nodeID);
return nodeID;
}
// Check the gaian connection nodeID (<host or ip> + :<port>) doesn't already exist for:
// 1) An ipaddress: check that it doesn't exist in a URL def
// 2) A hostname: check that it doesn't exist in a NODEID or in a URL def
public static boolean isNodeConnectionDefined( String nodeID ) {
for ( String cid : getGaianConnections() )
if ( nodeID.equals( getCrossOverProperty(cid+LABEL_DNODEID) ) ||
nodeID.equals( getGaianNodeIDFromURL(cid) ) )
return true;
return false;
}
// Matches host as a defined gateway - matches either an ipaddress or a hostname
public static boolean isGatewayConnectionDefined( String host ) {
for ( String cid : getGaianConnections() ) {
String chost = getHostFromNodeID( getCrossOverProperty(cid+LABEL_DNODEID) );
if ( null != chost && chost.startsWith(host) ) return true;
chost = getHostFromNodeID( getGaianNodeIDFromURL(cid) );
if ( null != chost && chost.startsWith(host) ) return true;
}
return false;
}
private static String getHostFromNodeID( String nodeid ) {
if ( null == nodeid ) return null;
int idx = nodeid.lastIndexOf(':');
if ( -1 < idx ) return nodeid.substring(0,idx);
return nodeid;
}
public static String getLocalDefaultConnectionID() {
return GaianNode.isLite() ?
GDB_LITE_DRIVER + "'" + LiteDriver.LITE_DRIVER_URL_PREFIX + "'" + getGaianNodeUser() + "'" + getGaianNodePassword() :
getLocalDerbyConnectionID();
// return new String[] { DERBY_EMBEDDED_DRIVER , "jdbc:derby:" + getGaianNodeDatabase() + ";create=true",
// getGaianNodeUser(), getGaianNodePassword() };
}
public static String getLocalDerbyConnectionID() {
// Note that when the db name is not given a path, then it is relative to property "derby.system.home".
// If the property is not set, then it is relative to the working directory of the process
return DERBY_EMBEDDED_DRIVER + "'" + "jdbc:derby:" + getGaianNodeDatabaseName() + ";create=true" +
"'" + getGaianNodeUser() + "'" + getGaianNodePassword();
}
public static String getRDBConnectionDetailsAsString( String connectionKey ) throws Exception {
return getRDBConnectionDetailsAsString( connectionKey, true );
}
/**
* Direct or indirect lookup of the RDB connection details identified by the key.
*
* When a direct key is passed, the properties will include:
* directkey_DRIVER=xxx, directkey_URL=yyy, etc.
*
* When indirect, they will include:
* indirectkey_CONNECTION=directkey, directkey_DRIVER=xxx, directkey_URL=yyy, etc.
*
* When a mixture of both exist, the direct properties take precedence.
*
* @param connectionKey
* @return
*/
static String getRDBConnectionDetailsAsString( String connectionKey, boolean exceptionOnEmptyDef ) throws Exception {
// The default connection details are those for the local gaian node derby db
if ( 1 > connectionKey.length() || connectionKey.equalsIgnoreCase(LOCALDERBY_CONNECTION) )
return getLocalDefaultConnectionID();
// If key contains a single quote, then assume it actually already holds the connectionDetails in format driver'url'usr'pwd
if ( -1 < connectionKey.indexOf('\'') ) return connectionKey;
// Don't blank nulls here - if they are null, they will be set again via the indirect connection property
String driver = getCrossOverProperty( connectionKey + LABEL_DRIVER );
String url = getCrossOverProperty( connectionKey + LABEL_URL );
String usr = getCrossOverProperty( connectionKey + LABEL_USR );
String pwd = getCrossOverProperty( connectionKey + LABEL_PWD );
String connectionProperty = getCrossOverProperty( connectionKey + CONNECTION_SUFFIX );
boolean isIndirectDef = null != connectionProperty;
// Indirect connection definition - However the direct method would take precedence if it returned results.
if ( isIndirectDef ) {
// e.g. To expose an rdbms table "SALES" of a db connection specified under connection property "CDEF", as a data
// source "DS1" of logical table "LT", the syntax is: LT_DS1_CONNECTION=CDEF SALES
// Note that the table name can also be specified independantly: LT_DS1_TABLE=SALES
connectionProperty = connectionProperty.split(" ")[0];
// The default connection details are those for the local gaian node derby db
if ( connectionProperty.equalsIgnoreCase(LOCALDERBY_CONNECTION) )
return getLocalDefaultConnectionID();
if ( null == driver ) driver = getCrossOverProperty( connectionProperty + LABEL_DRIVER );
if ( null == url ) url = getCrossOverProperty( connectionProperty + LABEL_URL );
if ( null == usr ) usr = getCrossOverProperty( connectionProperty + LABEL_USR );
if ( null == pwd ) pwd = getCrossOverProperty( connectionProperty + LABEL_PWD );
} else
connectionProperty = connectionKey; // We need the root value for this for unscrambling
if ( /*null == driver ||*/ null == url || null == usr || null == pwd ) { // driver can be null
if ( null == driver && null == url && null == usr && null == pwd )
if ( exceptionOnEmptyDef ) throw new Exception("Connection Not Defined in Config: " + connectionKey);
else return null;
String p = isIndirectDef ? connectionProperty : connectionKey;
throw new Exception("Missing Connection Configuration Properties:" +
/*( null == driver ? " " + p + LABEL_DRIVER : "" ) + */ ( null == url ? " " + p + LABEL_URL : "" ) +
( null == usr ? " " + p + LABEL_USR : "" ) + ( null == pwd ? " " + p + LABEL_PWD : "" )
);
}
// System.out.println("connection property: " + connectionProperty);
logger.logDetail( connectionKey + " connection: " + driver + ", " + url + ", " + usr + ", pwd" );
// System.out.println( connectionKey + " connectionA: " + driver + ", " + url + ", " + usr + ", " + pwd );
pwd = '\'' == pwd.charAt(0) ? unscramble(pwd.substring(1), connectionProperty) : pwd;
// System.out.println( connectionKey + " connectionB: " + driver + ", " + url + ", " + usr + ", " + pwd);
return (null==driver?"":driver) + "'" + url + "'" + usr + "'" + pwd;
}
/**
* Gets the connection token needed from the connection details string given
* by getRDBConnectionDetailsAsString().
*
* Note the connection details string passed in must be the same as the one produced by
* the method getRDBConnectionDetailsAsString(), or it may also have extra characters
* at the end of it as long as they are separated from the beggining of the string by
* a single quote ("'") character.
*
* @param connectionDetails
* @return
*/
public static String[] getConnectionTokens( String connectionDetails ) {
return connectionDetails.split("'", -1); // -1 means blank trailing usr/pwd values are not discarded
}
public static DatabaseConnector getNewDBConnector( String[] cargs ) throws SQLException {
String driver = cargs[0];
String url = cargs[1];
String usr = cargs[2];
String pwd = cargs[3];
// logger.logThreadInfo( "loadDriver() " + Arrays.asList(cargs) );
if ( null != driver && 0 < driver.trim().length() ) {
// int lt = DriverManager.getLoginTimeout();
// DriverManager.setLoginTimeout(1);
// logger.logThreadInfo( "loadDriver( " + driver + " ), setLoginTimeout from " + lt + " to " + 1 );
loadDriver( driver );
}
// Connection c = DriverManager.getConnection( url, usr, pwd );
// c.setAutoCommit( false ); // when we re-enable this, we need to explicitely commit, esp. before closing ResultSets
return new DatabaseConnector( url, usr, pwd );
}
public static Connection getEmbeddedDerbyConnection() throws SQLException {
return DriverManager.getConnection("jdbc:derby:" + getGaianNodeDatabaseName(), getGaianNodeUser(), getGaianNodePassword());
// return DriverManager.getConnection("jdbc:derby://localhost:6414/gaiandb", getGaianNodeUser(), getGaianNodePassword());
}
public static Set<String> getConnectionDefsReferencedByAllSourceLists() { // getAllReferencedJDBCConnections() {
HashSet<String> connectionDefs = new HashSet<String>();
Set<String> allSourceLists = getAllUserPropertiesWithSuffixAndRemoveSuffix( SOURCELIST_SUFFIX );
for ( Iterator<String> i=allSourceLists.iterator(); i.hasNext(); ) { //int i=0; i<allSourceLists.length; i++ ) {
String[] dataSources = getSourceListSources( i.next() ) ; //allSourceLists[i] );
for ( int j=0; j<dataSources.length; j++ ) {
try { connectionDefs.add( getRDBConnectionDetailsAsString(dataSources[j]) ); }
catch (Exception e) { continue; } // ignore/skip - warning is logged at load time
}
}
// String[] allNodeConnectionDefs = getAllUserPropertiesWithSUFFIX( CONNECTION_SUFFIX );
// for ( int j=0; j<allNodeConnectionDefs.length; j++ )
// connectionDefs.add( getRDBConnectionDetailsAsString( allNodeConnectionDefs[j].split(" ")[0] ) );
return connectionDefs;
}
public static Set<String> getAllSourceListsConnectionsIDs() {
Set<String> asls = new HashSet<String>();
Set<String> sourceListSources = getAllUserPropertiesWithSuffixAndRemoveSuffix( SOURCELIST_SUFFIX );
for (Iterator<String> i=sourceListSources.iterator(); i.hasNext(); ) { //int i=0; i<sourceListSources.length; i++) {
asls.addAll( Arrays.asList( Util.splitByCommas(i.next()) ) ); //sourceListSources[i] ) );
}
return asls;
}
private static final String URLUSR = ";user=", URLPWD = ";password=";
public static String[] getSourceListSources(String sourceList) {
// We need to check whether the sourceList is the list itself, or if it is an ID to reference another list.
String[] sources = Util.splitByCommas(sourceList);
if ( 1 > sources.length ) return new String[] { LOCALDERBY_CONNECTION };
else if ( 1 == sources.length ) {
String dereferencedList = getUserProperty( sources[0] + SOURCELIST_SUFFIX );
if ( null != dereferencedList ) sources = Util.splitByCommas(dereferencedList);
}
for ( int i=0; i<sources.length; i++ ) {
String s = sources[i];
int idx1 = s.lastIndexOf(URLUSR), idx2 = s.lastIndexOf(URLPWD);
if ( 0>idx1 || idx1 > idx2 ) continue;
// transform: "<url>;user=<usr>;password=<pwd>" to Gaian connection details format: "<driver (can be empty)>'<url>'<usr>'<pwd>"
sources[i] = "'" + s.substring(0,idx1) + "'" + s.substring(idx1+URLUSR.length(),idx2) + "'" + s.substring(idx2+URLPWD.length());
}
return sources; // list of cids
}
// public static int getColumnCount( String ltName ) {
// return new StringTokenizer( ltName + LTDEF_SUFFIX || _TRANSIENT_DEF, "," ).countTokens();
// }
// This method deals with trimming size, scale and precision type modifier definitions, e.g:
// "CUSTOMER VARCHAR( 12 )" or "COST DECIMAL( 2 , 3 )" => "CUSTOMER VARCHAR(12)" "COST DECIMAL(2,3)"
// Note: scale can be negative in some cases (e.g. with oracle, type numeric(5,-2) for value 12345 rounds values to multiples of 100, i.e. 12300)
public static String[] getColumnsDefArray( String colDefs ) {
// Use this line below to start working on defect 45015. This would replace the code under it but has repercussions elsewhere...
// return Util.splitByTrimmedDelimiterNonNestedInBracketsOrDoubleQuotes(colDefs, ',');
String colDefsWithMassagedTypeArgs = colDefs.
replaceAll( "\\s*\\(\\s*([0-9]+)\\s*", "($1" ). // Trim around first int arg of any type
replaceAll( ",\\s*(-?[0-9]+)\\s*\\)\\s*", ":$1)" ); // Trim around subsequent int type modifiers, and replace comma delimiter with a colon
// replaceAll( "[ |\t]*\\([ |\t]*([0-9]+)[ |\t]*", "($1" ). // Trim around first int arg of any type
// replaceAll( ",[ |\t]*(-?[0-9]+)[ |\t]*", ":$1" ); // Trim around subsequent int type modifiers, and replace comma delimiter with a colon
String[] colsArray = Util.splitByCommas( colDefsWithMassagedTypeArgs );
for ( int i=0; i<colsArray.length; i++ )
colsArray[i] = colsArray[i].replace( ':', ',' );
return colsArray;
}
public static String getLogicalTableDef( String ltName ) {
String ltdef = getUserProperty(ltName+LTDEF_SUFFIX);
// Note: this code will need changing in future to avoid folding columns to upper case if they are delimited with double quotes.
// Note that the 'shortcut apis': setltforfile(), setltforrdbtable(), setltforexcel(), etc. also fold columns to upper case by default when
// creating the logical table def... if users wanted to avoid this, they would have to use setlt() followed by setdsxxx() instead of the shortcut api.
return null == ltdef ? null : ltdef.toUpperCase();
}
public static String getLogicalTableVisibleColumns( String ltName ) {
String variableColumns = getLogicalTableDef( ltName );
if ( null == variableColumns ) return null;
String constantCols = getConstantColumnsDef( ltName );
return null == constantCols || 0 == constantCols.length() ? variableColumns :
( 0 == variableColumns.length() ? constantCols : variableColumns + ", " + constantCols );
}
// /**
// * Split definition into columns array, and include any constant columns that may be
// * associated with the given table name. As constant node columns are associated with
// * any logical table, they will always be included, even if 'null' is specified as table name.
// *
// * @param tableDefinition
// * @param ltName
// * @return
// */
// private static String[] getColumnsIncludingConstants( String tableDefinition, String ltName ) {
// String constCols = getConstantColumnsDefinition(ltName);
// return Util.splitByCommas( null == constCols || 0 == constCols.length() ?
// tableDefinition : tableDefinition + ", " + constCols );
// }
//
// public static String[] getColumnsIncludingConstantsUsingDefinition( String tableDefinition ) {
// return getColumnsIncludingConstants( tableDefinition, null );
// }
// public static String[] getLogicalColumns( String ltName ) {
////
////// logInfo("Getting column names for logical table: " + ltName);
////
//// Vector names = new Vector();
//// String name = null;
//// int colIndex = 0;
////
//// while ( null != ( name = getUserProperty( ltName + COLUMN_LABEL + colIndex++ ) ) )
//// names.add( name );
////
//// return (String[]) names.toArray( new String[0] );
//
// String ltDef = getLogicalTableDefinition(ltName);
//// if ( null == ltDef ) ltDef = getLogicalTableDefinition(ltName+"_TRANSIENT");
// if ( null == ltDef ) return null;
//
// return getColumnDefArray( ltDef );
// }
/**
* Returns constant columns for a specific table.
* If ltName is null, only global constant columns are returned.
*/
public static String getConstantColumnsDef( String ltName ) {
String constantGlobalCols = getUserProperty( NODE_CONSTANTS );
String constantLTCols = null==ltName ? null : getUserProperty( ltName + CONSTANTS_SUFFIX );
if ( null == constantGlobalCols && null == constantLTCols ) return "";
if ( null==constantLTCols || 1 > constantLTCols.length() )
return constantGlobalCols;
if ( null==constantGlobalCols || 1 > constantGlobalCols.length() )
return constantLTCols;
return constantLTCols + ", " + constantGlobalCols;
}
/**
* Returns special columns for a specific table.
* If ltName is null, no table-specific constant columns will be included - just global ones are.
*/
public static String getSpecialColumnsDef( String ltName ) {
String hiddenColDefs = getHiddenColumns();
String constantColsDef = getConstantColumnsDef( ltName );
return /*getColumnDefArray(*/ null == constantColsDef || 0 == constantColsDef.length() ?
hiddenColDefs : constantColsDef + ", " + hiddenColDefs; // );
}
public static String getHiddenColumns() {
return PROVENANCE_COLDEFS + ", " + EXPLAIN_COLDEFS;
}
public static String[] getColumnSplitBySpaces( String colDef ) {
return colDef.split("[\\s]+");
}
/**
* Simple mirror image scrambling function
* @param s
* @return
*/
// private static String scramble( String s ) {
// // For this to succeed, all chars of s must be in the ascii range: 33-126, i.e one of the following:
// // !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
//
// char[] cs = s.toCharArray();
// char[] scrambled = new char[cs.length];
// for ( int i=0; i<cs.length; i++ ) {
// int ci = cs[i];
// if ( ci<33 || ci>126 ) return s;
// scrambled[i] = (char) (159-ci);
// }
//
// return new String( scrambled );
// }
/**
* Slightly more complex scrambling/unscrambling function to make same pwds have different scrambled char sequences
* based on another input string.
*
* @param s
* @param k
* @param undo
* @return
*/
private static String scramble( String s, String k ) {
return scramble( s, k, false );
}
public static String unscramble( String s, String k ) {
return scramble( s, k, true );
}
private static String scramble( String s, String k, boolean undo ) {
// For this to succeed, all chars of s must be in the ascii range: 33-126, i.e one of the following:
// !"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmnopqrstuvwxyz{|}~
if ( null == k || 0 == k.length() ) {
logger.logWarning(GDBMessages.CONFIG_KEY_UNSCRAMBLE_ERROR, "Cannot (un)scramble pwd off key: " + k);
return null;
}
char[] ka = k.toCharArray();
int klen = ka.length;
int[] shifts = new int[klen];
for ( int i=0; i<klen; i++ ) shifts[i] = Math.abs(79-ka[i]);
// System.out.println("avg " + avg + (char)avg + ", len " + ka.length + ", shift = " + shift);
char[] cs = s.toCharArray();
int cslen = cs.length;
char[] scrambled = new char[cslen];
for ( int i=0; i<cslen; i++ ) {
// Cycle through chars of k to use as shift values for each char of s to be scrambled.
int shift = shifts[i%klen];
int c = cs[i];
// Apply a shift function of the 2nd string so that same char sequences will have different scrambled sequences based on it.
// Cycle the shift if it causes the scrambled char to fall out of bounds
if ( !undo ) { c -= shift; /* if ( c<33 ) c+=94; */ if ( c<33 || c<80 && c>79-shift ) c+=47; }
c = 159-c; // mirror image of half the chars onto the other
if ( undo ) { c += shift; /* if ( c>126 ) c-=94; */ if ( c>126 || c>79 && c<80+shift ) c-=47; }
scrambled[i] = (char)c;
}
return new String( scrambled );
}
// Node credentials here are used to implement specific GaianDB security permitting policy access control to GaianDB data source.
// This is in contrast to Derby user/password which is lightweight security used simply to connect to the Derby/GaianDB node.
private static final String CREDENTIALS_PREFIX = "CREDENTIALS_";
private static final String GDB_USR = GDB_PREFIX + LABEL_USR;
private static final String GDB_PWD = GDB_PREFIX + LABEL_PWD;
public static void refreshRemoteAccessCredentials( SecurityClientAgent securityClientAgent ) {
int idx = 1;
String node;
Set<String> allNodesHavingValidCredentialsDefs = new HashSet<String>();
while ( null != ( node = getUserProperty( CREDENTIALS_PREFIX + idx + "_" + GDB_NODE ) ) ) {
String usr = getUserProperty( CREDENTIALS_PREFIX + idx + "_" + GDB_USR );
String pwd = getUserProperty( CREDENTIALS_PREFIX + idx + "_" + GDB_PWD );
if ( null == usr || null == pwd || 0 == node.length() || 0==usr.length() || 0==pwd.length() ) {
logger.logWarning(GDBMessages.CONFIG_CREDENTIALS_DEF_ERROR, "Ignoring incorrectly defined field definitions for: " + CREDENTIALS_PREFIX + idx);
continue;
}
// Unscramble pwd if necessary - remember the scrambling key is based off the property key minus the _PWD label
if ( 0 < pwd.length() )
pwd = '\'' == pwd.charAt(0) ? unscramble(pwd.substring(1), CREDENTIALS_PREFIX + idx + "_" + GDB_PREFIX) : pwd;
securityClientAgent.setRemoteAccessCredentials(node, usr, pwd);
allNodesHavingValidCredentialsDefs.add(node);
idx++;
}
// Avoid synchronization issue by removing only the credentials for nodes that are no longer defined.
securityClientAgent.retainAllRemoteAccessCredentialsForNodes( allNodesHavingValidCredentialsDefs );
}
private static final SimpleDateFormat SDF = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
public static void persistAndApplyConfigUpdates( Map<String, String> updates ) throws Exception {
persistAndApplyConfigUpdatesForSpecificLTs( updates, null ); // updates could affect all logical tables.
}
public static void persistAndApplyConfigUpdatesAffectingNoLogicalTables( Map<String, String> updates ) throws Exception {
persistAndApplyConfigUpdatesForSpecificLTs( updates, Util.EMPTY_SET ); // updates could affect all logical tables.
}
static void persistAndApplyConfigUpdatesForSpecificLT( Map<String, String> updates, String ltAffected ) throws Exception {
persistAndApplyConfigUpdatesForSpecificLTs( updates, new HashSet<String>(Arrays.asList( new String[] { ltAffected } )) );
}
// Incomplete batch-update implemention - see commented out code in GaianDBConfigProcedures as well... GaianDBConfigProcedures.isCommitAPIsOn() ...
// private static Map<String, String> batchUpdatesToPersist = null;
// private static Set<String> batchUpdatesAffectedLTs = null;
/**
* Persist and apply config file updates to the GaianDB config file.
*
* @param updates
* @param ltsAffected Set of logical tables affected by the updates (if known). This is to limit/optimise refresh requirements.
* @return true if update went well, e.g. without concurrent updates which may have been overwritten.
* @throws Exception if updates could not be persisted/loaded
*/
static void persistAndApplyConfigUpdatesForSpecificLTs( Map<String, String> updates, Set<String> ltsAffected ) throws Exception {
// Incomplete batch-update implemention - see commented out code in GaianDBConfigProcedures as well... GaianDBConfigProcedures.isCommitAPIsOn() ...
// if ( false == GaianDBConfigProcedures.isCommitAPIsOn() ) {
// if ( null == batchUpdatesToPersist ) batchUpdatesToPersist = updates; else batchUpdatesToPersist.putAll( updates );
// if ( null == batchUpdatesAffectedLTs ) batchUpdatesAffectedLTs = ltsAffected; else batchUpdatesAffectedLTs.addAll( ltsAffected );
// }
boolean wasConfigFileUntouchedSinceLastAPIUpdate = persistAndApplyConfigUpdates(updates, getConfigFile());
if ( false == wasConfigFileUntouchedSinceLastAPIUpdate ) {
logger.logWarning(GDBMessages.CONFIG_FILE_CONCURRENT_UPDATE,
"GaianDB config file was changed and not reloaded before this config update completed - " +
"any concurrent changes (which happened DURING our update) will be lost");
ltsAffected = null; // Override - check ALL logical tables for reload to ensure consistency.
}
// Only need to reload if this is being called internally.
synchronized( DataSourcesManager.class ) {
// if ( false == refreshRegistryIfNecessary() )
// synchronized( upr ) {
// for ( String key : updates.keySet() ) {
// String val = updates.get(key);
// if ( null == val ) upr.remove(key);
// else upr.put(key, val);
// }
// }
reloadUserProperties();
logger.logInfo("Reloading config after auto-update...");
DataSourcesManager.refresh( ltsAffected );
// // We don't need to reload the file + data sources if the file didn't change...
// if ( refreshRegistryIfNecessary() ) {
// logger.logInfo("Reloading config after auto-update...");
// DataSourcesManager.refresh();
// }
}
}
/**
* This method should really just be named "persistConfigUpdates()" - but it is used externally as a utility method by other projects...
* Persist and apply config file updates to a config file.
*
* @param updates
* @return false if the config file was updated concurrently - this means we may have overwritten some updates.
* @throws Exception if updates could not be persisted/loaded
*/
public static boolean persistAndApplyConfigUpdates( Map<String, String> updatesIn, File configFile ) throws Exception {
Set<String> alreadyUpdated = new HashSet<String>(); // properties already found and which we created a newProperties line for
boolean isUpdateRequired = false; // whether or not we will need to re-write the file
try {
FileReader fr = new FileReader( configFile );
BufferedReader br = new BufferedReader( fr );
StringBuffer newProperties = new StringBuffer();
if ( null == updatesIn ) updatesIn = new HashMap<String, String>();
Map<String, String> updatesReduced = new HashMap<String, String>( updatesIn ); // used later to hold encrypted PWDs
// Don't log the updates as they potentially contain unencrypted PWDs
// logger.logInfo("Processing API Update: " + updates );
int lineNumber = 0;
// Get wilcard null updates out of updates Map. These are prefixes designating collections of properties that need to be removed...
Set<String> wildcardPrefixesOfPropertiesToBeRemoved = new HashSet<String>();
for ( Iterator<String> iter = updatesReduced.keySet().iterator(); iter.hasNext(); ) {
String key = iter.next();
if ( '*' == key.charAt(key.length()-1) ) {
iter.remove(); // We know this is a wildcard property (they should not finish with a star character) with no value attached to it
wildcardPrefixesOfPropertiesToBeRemoved.add( key.substring(0, key.length()-1) );
}
}
// Go through all properties creating a newProperties StringBuffer to include all existing properties and updated values for the required ones.
// Note we could use: System.getProperty("line.separator") instead of just appending "\n"... this would also need doing for new properties added after this loop.
// ..the result is that it would append "\r\n" on windows instead of "\n", thus allowing simple editors like notepad to read the config file properly...
// However - we'd need to test that if a user copied a "\r\n" delimited file to a unix platform, it wouldn't cause GaianDB to break on that system...
for ( String line; null != ( line = br.readLine() ); newProperties.append( null==line ? "" : line+"\n" ) ) {
lineNumber++;
int idx = line.trim().indexOf('=');
if ( -1 == idx ) continue;
String key = line.substring(0, idx).trim().intern();
String oldVal = Util.stripBackSlashesDownOneNestingLevel( line.substring(idx+1).trim() ).intern();
if ( -1 != key.indexOf('#') ) continue;
if ( alreadyUpdated.contains(key) ) {
// Comment out duplicate defs of properties that were updated.
logger.logInfo("Updating duplicate def line " + lineNumber + ", " + key + " (commenting it out)");
isUpdateRequired = true;
line = "#" + line;
continue;
}
String val = null;
if ( !updatesReduced.containsKey(key) ) {
// Check if the property matches one of the wildcards designating properties that need removing - if so delete the line.
for ( String wildcardPrefix : wildcardPrefixesOfPropertiesToBeRemoved )
if ( key.startsWith(wildcardPrefix) ) {
logger.logInfo("Removing line " + lineNumber + ", " + key);
isUpdateRequired = true;
line = null; // set it to null, dont bother commenting it out, e.g. "#" + line;
break;
}
if ( null == line ) continue; // this line has been removed, move to the next one...
if ( key.endsWith(LABEL_PWD) && 0 < oldVal.length() && '\'' != oldVal.charAt(0) ) {
// Of the properties not in the updates, scramble any existing passwords that haven't been scrambled yet
val = '\'' + Util.escapeBackslashes( scramble(oldVal, key.substring(0, key.length()-LABEL_PWD.length())));
updatesIn.put(key, val);
} else continue; // nothing we want to do to this property - so continue...
} else {
// update existing property that has been changed
val = (String) updatesReduced.remove(key); // Note val could be null (if we are removing this property)
// property not previously found and which needs updating
alreadyUpdated.add(key);
if ( null!=val && val.intern() == oldVal ) continue; // no change
if ( null == val ) { // property needs removing - delete the line
logger.logInfo("Removing line " + lineNumber + ", " + key);
isUpdateRequired = true;
line = null; // set it to null, dont bother commenting it out, e.g. "#" + line;
continue;
}
logger.logInfo("Updating line " + lineNumber + ", " + key + " value: " + oldVal + " -> " + val);
if ( key.endsWith(LABEL_PWD) ) {
val = '\'' + Util.escapeBackslashes( scramble(val, key.substring(0, key.length()-LABEL_PWD.length())));
updatesIn.put(key, val);
}
}
isUpdateRequired = true;
line = key + "=" + val; // apply update
}
br.close(); br = null;
fr.close(); fr = null;
final int numUpdates = updatesReduced.size();
// Did update have an impact on the file ?
// If not, no need to report whether some manual update occured... this will be detected by the watchdog.
if ( 1 > numUpdates && !isUpdateRequired ) return true;
if ( 0 < numUpdates ) {
// Add new properties remaining in the updates - make sure to scramble the passwords
if ( 1 < numUpdates ) newProperties.append('\n');
for ( String key : updatesReduced.keySet() ) {
String val = (String) updatesReduced.get(key);
if ( null == val ) continue; // this update has no effect - the key didnt exist in the first place!
if ( key.endsWith(LABEL_PWD) ) {
val = '\'' + Util.escapeBackslashes( scramble(val, key.substring(0, key.length()-LABEL_PWD.length())));
updatesIn.put(key, val);
}
String newProp = key + "=" + val + "\n";
newProperties.append( newProp );
}
}
// Now there should no longer be any unscrambled PWDs in updatesOrig - so we can log the updates
if ( Logger.logLevel > Logger.LOG_LESS )
logger.logInfo("Applying Config Updates: " + updatesIn);
if ( Logger.logLevel > Logger.LOG_MORE ) {
newProperties.append("# Auto Update: " +
SDF.format( new Date(System.currentTimeMillis()) ) + ": " + updatesIn + "\n" );
}
// Resolve folder location of the config file
String configFileName = configFile.getName();
String configFilePath = configFile.getCanonicalPath();
configFilePath = configFilePath.substring(0, configFilePath.lastIndexOf(configFileName));
File writeAheadFile = new File( configFilePath + "." + configFileName + ".tmp" );
BufferedWriter bw = new BufferedWriter( new FileWriter(writeAheadFile), THIRTY_TWO_KB );
bw.write( newProperties.toString().trim() );
bw.close();
// Config file might have been updated by another thread, between the time we read it and the time we are about to write it.
// This should be advised against and we should warn that concurrent updates may sometimes be lost.. however if it happens,
// we need to make sure we reload the whole config after at least, so that the loaded config at least is always consistent with the file.
long cfgLastModified = getConfigFileLastModified();
boolean wasConfigFileUntouchedSinceLastAPIUpdate = latestLoadedPropertiesFileTimestamp == cfgLastModified;
// lastModified is often rounded to the second.. not precise enough to detect changes sometimes.
// Substract 1 second from the lastModified TS so that we can detect any subsequent manual updates to the file...
final long newCfgLastModified = writeAheadFile.lastModified() - 1000;
writeAheadFile.setLastModified( newCfgLastModified );
Util.moveFileAndLogOutcome( writeAheadFile, configFile );
logger.logInfo("Config file lastModified was: " + cfgLastModified + ", onLastLoad: " + latestLoadedPropertiesFileTimestamp +
" (untouched since last API call?: " + wasConfigFileUntouchedSinceLastAPIUpdate +
"), writeAhead lastModified: " + newCfgLastModified + ", new file lastModified: " + configFile.lastModified());
return wasConfigFileUntouchedSinceLastAPIUpdate;
} catch ( Exception e ) {
logger.logException( GDBMessages.CONFIG_PERSIST_UPDATE_ERROR, "Unable to persist config updates, cause: ", e);
throw new Exception("See Persistence Warning");
}
}
}