/*
* Copyright (c) 2016, 2018, Oracle and/or its affiliates. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, version 2.0, as published by the
* Free Software Foundation.
*
* This program is also distributed with certain software (including but not
* limited to OpenSSL) that is licensed under separate terms, as designated in a
* particular file or component or in included license documentation. The
* authors of MySQL hereby grant you an additional permission to link the
* program and your derivative works with the separately licensed software that
* they have included with MySQL.
*
* Without limiting anything contained in the foregoing, this file, which is
* part of MySQL Connector/J, is also subject to the Universal FOSS Exception,
* version 1.0, a copy of which can be found at
* http://oss.oracle.com/licenses/universal-foss-exception.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU General Public License, version 2.0,
* for more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
package com.mysql.cj.conf;
import static com.mysql.cj.util.
StringUtils.isNullOrEmpty;
import java.io.
IOException;
import java.io.
InputStream;
import java.sql.
DriverManager;
import java.util.
ArrayList;
import java.util.
Collections;
import java.util.
HashMap;
import java.util.
List;
import java.util.
Map;
import java.util.
Properties;
import java.util.
TreeMap;
import java.util.concurrent.locks.
ReadWriteLock;
import java.util.concurrent.locks.
ReentrantReadWriteLock;
import java.util.stream.
Collectors;
import com.mysql.cj.
Messages;
import com.mysql.cj.exceptions.
CJException;
import com.mysql.cj.exceptions.
ExceptionFactory;
import com.mysql.cj.exceptions.
InvalidConnectionAttributeException;
import com.mysql.cj.exceptions.
UnsupportedConnectionStringException;
import com.mysql.cj.exceptions.
WrongArgumentException;
import com.mysql.cj.util.
LRUCache;
import com.mysql.cj.util.
Util;
/**
* A container for a database URL and a collection of given connection arguments.
* The connection string is parsed and split by its components, each of which is then processed and fixed according to the needs of the connection type.
* This abstract class holds all common behavior to all connection string types. Its subclasses must implement their own specifics such as classifying hosts by
* type or apply validation rules.
*/
public abstract class
ConnectionUrl implements
DatabaseUrlContainer {
private static final
String DEFAULT_HOST = "localhost";
private static final int
DEFAULT_PORT = 3306;
private static final
LRUCache<
String,
ConnectionUrl>
connectionUrlCache = new
LRUCache<>(100);
private static final
ReadWriteLock rwLock = new
ReentrantReadWriteLock();
/**
* The rules describing the number of hosts a database URL may contain.
*/
public enum
HostsCardinality {
SINGLE {
@
Override
public boolean
assertSize(int
n) {
return
n == 1;
}
},
MULTIPLE {
@
Override
public boolean
assertSize(int
n) {
return
n > 1;
}
},
ONE_OR_MORE {
@
Override
public boolean
assertSize(int
n) {
return
n >= 1;
}
};
public abstract boolean
assertSize(int
n);
}
/**
* The database URL type which is determined by the scheme section of the connection string.
*/
public enum
Type {
SINGLE_CONNECTION("jdbc:mysql:",
HostsCardinality.
SINGLE), //
FAILOVER_CONNECTION("jdbc:mysql:",
HostsCardinality.
MULTIPLE), //
LOADBALANCE_CONNECTION("jdbc:mysql:loadbalance:",
HostsCardinality.
ONE_OR_MORE), //
REPLICATION_CONNECTION("jdbc:mysql:replication:",
HostsCardinality.
ONE_OR_MORE), //
XDEVAPI_SESSION("mysqlx:",
HostsCardinality.
ONE_OR_MORE);
private
String scheme;
private
HostsCardinality cardinality;
private
Type(
String scheme,
HostsCardinality cardinality) {
this.
scheme =
scheme;
this.
cardinality =
cardinality;
}
public
String getScheme() {
return this.
scheme;
}
public
HostsCardinality getCardinality() {
return this.
cardinality;
}
/**
* Returns the {@link Type} corresponding to the given scheme and number of hosts, if any.
* Otherwise throws an {@link UnsupportedConnectionStringException}.
* Calling this method with the argument n lower than 0 skips the hosts cardinality validation.
*
* @param scheme
* one of supported schemes
* @param n
* the number of hosts in the database URL
* @return the {@link Type} corresponding to the given protocol and number of hosts
*/
public static
Type fromValue(
String scheme, int
n) {
for (
Type t :
values()) {
if (
t.
getScheme().
equalsIgnoreCase(
scheme) && (
n < 0 ||
t.
getCardinality().
assertSize(
n))) {
return
t;
}
}
if (
n < 0) {
throw
ExceptionFactory.
createException(
UnsupportedConnectionStringException.class,
Messages.
getString("ConnectionString.5", new
Object[] {
scheme }));
}
throw
ExceptionFactory.
createException(
UnsupportedConnectionStringException.class,
Messages.
getString("ConnectionString.6", new
Object[] {
scheme,
n }));
}
/**
* Checks if the given scheme corresponds to one of the connection types the driver supports.
*
* @param scheme
* scheme part from connection string, like "jdbc:mysql:"
* @return true if the given scheme is supported by driver
*/
public static boolean
isSupported(
String scheme) {
for (
Type t :
values()) {
if (
t.
getScheme().
equalsIgnoreCase(
scheme)) {
return true;
}
}
return false;
}
}
protected
Type type;
protected
String originalConnStr;
protected
String originalDatabase;
protected
List<
HostInfo>
hosts = new
ArrayList<>();
protected
Map<
String,
String>
properties = new
HashMap<>();
ConnectionPropertiesTransform propertiesTransformer;
/**
* Static factory method that returns either a new instance of a {@link ConnectionUrl} or a cached one.
* Returns "null" it can't handle the connection string.
*
* @param connString
* the connection string
* @param info
* the connection arguments map
* @return an instance of a {@link ConnectionUrl} or "null" if isn't able to handle the connection string
*/
public static
ConnectionUrl getConnectionUrlInstance(
String connString,
Properties info) {
if (
connString == null) {
throw
ExceptionFactory.
createException(
WrongArgumentException.class,
Messages.
getString("ConnectionString.0"));
}
String connStringCacheKey =
buildConnectionStringCacheKey(
connString,
info);
ConnectionUrl connectionString;
rwLock.
readLock().
lock();
connectionString =
connectionUrlCache.
get(
connStringCacheKey);
if (
connectionString == null) {
rwLock.
readLock().
unlock();
rwLock.
writeLock().
lock();
try {
// Check again, in the meantime it could have been cached by another thread.
connectionString =
connectionUrlCache.
get(
connStringCacheKey);
if (
connectionString == null) {
ConnectionUrlParser connStrParser =
ConnectionUrlParser.
parseConnectionString(
connString);
switch (
Type.
fromValue(
connStrParser.
getScheme(),
connStrParser.
getHosts().
size())) {
case
SINGLE_CONNECTION:
connectionString = (
ConnectionUrl)
Util.
getInstance("com.mysql.cj.conf.url.SingleConnectionUrl",
new
Class<?>[] {
ConnectionUrlParser.class,
Properties.class }, new
Object[] {
connStrParser,
info }, null);
break;
case
FAILOVER_CONNECTION:
connectionString = (
ConnectionUrl)
Util.
getInstance("com.mysql.cj.conf.url.FailoverConnectionUrl",
new
Class<?>[] {
ConnectionUrlParser.class,
Properties.class }, new
Object[] {
connStrParser,
info }, null);
break;
case
LOADBALANCE_CONNECTION:
connectionString = (
ConnectionUrl)
Util.
getInstance("com.mysql.cj.conf.url.LoadbalanceConnectionUrl",
new
Class<?>[] {
ConnectionUrlParser.class,
Properties.class }, new
Object[] {
connStrParser,
info }, null);
break;
case
REPLICATION_CONNECTION:
connectionString = (
ConnectionUrl)
Util.
getInstance("com.mysql.cj.conf.url.ReplicationConnectionUrl",
new
Class<?>[] {
ConnectionUrlParser.class,
Properties.class }, new
Object[] {
connStrParser,
info }, null);
break;
case
XDEVAPI_SESSION:
connectionString = (
ConnectionUrl)
Util.
getInstance("com.mysql.cj.conf.url.XDevAPIConnectionUrl",
new
Class<?>[] {
ConnectionUrlParser.class,
Properties.class }, new
Object[] {
connStrParser,
info }, null);
break;
default:
return null; // should not happen
}
connectionUrlCache.
put(
connStringCacheKey,
connectionString);
}
rwLock.
readLock().
lock();
} finally {
rwLock.
writeLock().
unlock();
}
}
rwLock.
readLock().
unlock();
return
connectionString;
}
/**
* Builds a connection URL cache map key based on the connection string itself plus the string representation of the given connection properties.
*
* @param connString
* the connection string
* @param info
* the connection arguments map
* @return a connection string cache map key
*/
private static
String buildConnectionStringCacheKey(
String connString,
Properties info) {
StringBuilder sbKey = new
StringBuilder(
connString);
sbKey.
append("\u00A7"); // Section sign.
sbKey.
append(
info == null ? null :
info.
stringPropertyNames().
stream().
map(
k ->
k + "=" +
info.
getProperty(
k)).
collect(
Collectors.
joining(", ", "{", "}")));
return
sbKey.
toString();
}
/**
* Checks if this {@link ConnectionUrl} is able to process the given database URL.
*
* @param connString
* the connection string
* @return true if this class is able to process the given URL, false otherwise
*/
public static boolean
acceptsUrl(
String connString) {
return
ConnectionUrlParser.
isConnectionStringSupported(
connString);
}
/**
* Empty constructor. Required for subclasses initialization.
*/
protected
ConnectionUrl() {
}
/**
* Constructor for unsupported URLs
*
* @param origUrl
* URLs
*/
public
ConnectionUrl(
String origUrl) {
this.
originalConnStr =
origUrl;
}
/**
* Constructs an instance of {@link ConnectionUrl}, performing all the required initializations.
*
* @param connStrParser
* a {@link ConnectionUrlParser} instance containing the parsed version of the original connection string
* @param info
* the connection arguments map
*/
protected
ConnectionUrl(
ConnectionUrlParser connStrParser,
Properties info) {
this.
originalConnStr =
connStrParser.
getDatabaseUrl();
this.
originalDatabase =
connStrParser.
getPath() == null ? "" :
connStrParser.
getPath();
collectProperties(
connStrParser,
info); // Fill properties before filling hosts info.
collectHostsInfo(
connStrParser);
}
/**
* Joins the connection arguments from the connection string with the ones from the given connection arguments map collecting them in a single map.
* Additionally may also collect other connection arguments from configuration files.
*
* @param connStrParser
* the {@link ConnectionUrlParser} from where to collect the properties
* @param info
* the connection arguments map
*/
protected void
collectProperties(
ConnectionUrlParser connStrParser,
Properties info) {
// Fill in the properties from the connection string.
connStrParser.
getProperties().
entrySet().
stream().
forEach(
e -> this.
properties.
put(
PropertyKey.
normalizeCase(
e.
getKey()),
e.
getValue()));
// Properties passed in override the ones from the connection string.
if (
info != null) {
info.
stringPropertyNames().
stream().
forEach(
k -> this.
properties.
put(
PropertyKey.
normalizeCase(
k),
info.
getProperty(
k)));
}
// Collect properties from additional sources.
setupPropertiesTransformer();
expandPropertiesFromConfigFiles(this.
properties);
injectPerTypeProperties(this.
properties);
}
/**
* Sets up the {@link ConnectionPropertiesTransform} if one was provided.
*/
protected void
setupPropertiesTransformer() {
String propertiesTransformClassName = this.
properties.
get(
PropertyKey.
propertiesTransform.
getKeyName());
if (!
isNullOrEmpty(
propertiesTransformClassName)) {
try {
this.
propertiesTransformer = (
ConnectionPropertiesTransform)
Class.
forName(
propertiesTransformClassName).
newInstance();
} catch (
InstantiationException |
IllegalAccessException |
ClassNotFoundException |
CJException e) {
throw
ExceptionFactory.
createException(
InvalidConnectionAttributeException.class,
Messages.
getString("ConnectionString.9", new
Object[] {
propertiesTransformClassName,
e.
toString() }),
e);
}
}
}
/**
* Expands the connection argument "useConfig" by reading the mentioned configuration files.
*
* @param props
* a connection arguments map from where to read the "useConfig" property and where to save the loaded properties.
*/
protected void
expandPropertiesFromConfigFiles(
Map<
String,
String>
props) {
// Properties from config files should not override the existing ones.
String configFiles =
props.
get(
PropertyKey.
useConfigs.
getKeyName());
if (!
isNullOrEmpty(
configFiles)) {
Properties configProps =
getPropertiesFromConfigFiles(
configFiles);
configProps.
stringPropertyNames().
stream().
map(
PropertyKey::normalizeCase).
filter(
k -> !
props.
containsKey(
k))
.
forEach(
k ->
props.
put(
k,
configProps.
getProperty(
k)));
}
}
/**
* Returns a map containing the properties read from the given configuration files. Multiple files can be referenced using a comma as separator.
*
* @param configFiles
* the list of the configuration files to read
* @return the map containing all the properties read
*/
public static
Properties getPropertiesFromConfigFiles(
String configFiles) {
Properties configProps = new
Properties();
for (
String configFile :
configFiles.
split(",")) {
try (
InputStream configAsStream =
ConnectionUrl.class.
getResourceAsStream("/com/mysql/cj/configurations/" +
configFile + ".properties")) {
if (
configAsStream == null) {
throw
ExceptionFactory.
createException(
InvalidConnectionAttributeException.class,
Messages.
getString("ConnectionString.10", new
Object[] {
configFile }));
}
configProps.
load(
configAsStream);
} catch (
IOException e) {
throw
ExceptionFactory.
createException(
InvalidConnectionAttributeException.class,
Messages.
getString("ConnectionString.11", new
Object[] {
configFile }),
e);
}
}
return
configProps;
}
/**
* Subclasses must override this method if they need to inject additional properties in the connection arguments map while it's being constructed.
*
* @param props
* the properties already containing all known connection arguments
*/
protected void
injectPerTypeProperties(
Map<
String,
String>
props) {
return;
}
/**
* Some acceptable property values have changed in c/J 8.0 but old values remain hardcoded in a widely used software.
* So we need to accept old values and translate them to new ones.
*
* @param props
* the host properties map to fix
*/
protected void
replaceLegacyPropertyValues(
Map<
String,
String>
props) {
// Workaround for zeroDateTimeBehavior=convertToNull hardcoded in NetBeans
String zeroDateTimeBehavior =
props.
get(
PropertyKey.
zeroDateTimeBehavior.
getKeyName());
if (
zeroDateTimeBehavior != null &&
zeroDateTimeBehavior.
equalsIgnoreCase("convertToNull")) {
props.
put(
PropertyKey.
zeroDateTimeBehavior.
getKeyName(), "CONVERT_TO_NULL");
}
}
/**
* Collects the hosts information from the {@link ConnectionUrlParser}.
*
* @param connStrParser
* the {@link ConnectionUrlParser} from where to collect the hosts information
*/
protected void
collectHostsInfo(
ConnectionUrlParser connStrParser) {
connStrParser.
getHosts().
stream().
map(this::fixHostInfo).
forEach(this.
hosts::add);
}
/**
* Fixes the host information by moving data around and filling in missing data.
* Applies properties transformations to the collected properties if {@link ConnectionPropertiesTransform} was declared in the connection arguments.
*
* @param hi
* the host information data to fix
* @return a new {@link HostInfo} with all required data
*/
protected
HostInfo fixHostInfo(
HostInfo hi) {
Map<
String,
String>
hostProps = new
HashMap<>();
// Add global connection arguments.
hostProps.
putAll(this.
properties);
// Add/override host specific connection arguments.
hi.
getHostProperties().
entrySet().
stream().
forEach(
e ->
hostProps.
put(
PropertyKey.
normalizeCase(
e.
getKey()),
e.
getValue()));
// Add the database name
hostProps.
put(
PropertyKey.
DBNAME.
getKeyName(),
getDatabase());
preprocessPerTypeHostProperties(
hostProps);
String host =
hostProps.
remove(
PropertyKey.
HOST.
getKeyName());
if (!
isNullOrEmpty(
hi.
getHost())) {
host =
hi.
getHost();
} else if (
isNullOrEmpty(
host)) {
host =
getDefaultHost();
}
String portAsString =
hostProps.
remove(
PropertyKey.
PORT.
getKeyName());
int
port =
hi.
getPort();
if (
port == -1 && !
isNullOrEmpty(
portAsString)) {
try {
port =
Integer.
valueOf(
portAsString);
} catch (
NumberFormatException e) {
throw
ExceptionFactory.
createException(
WrongArgumentException.class,
Messages.
getString("ConnectionString.7", new
Object[] {
hostProps.
get(
PropertyKey.
PORT.
getKeyName()) }),
e);
}
}
if (
port == -1) {
port =
getDefaultPort();
}
String user =
hostProps.
remove(
PropertyKey.
USER.
getKeyName());
if (!
isNullOrEmpty(
hi.
getUser())) {
user =
hi.
getUser();
} else if (
isNullOrEmpty(
user)) {
user =
getDefaultUser();
}
boolean
isPasswordless =
hi.
isPasswordless();
String password =
hostProps.
remove(
PropertyKey.
PASSWORD.
getKeyName());
if (!
isPasswordless) {
password =
hi.
getPassword();
} else if (
password == null) {
password =
getDefaultPassword();
isPasswordless = true;
} else {
isPasswordless = false;
}
expandPropertiesFromConfigFiles(
hostProps);
fixProtocolDependencies(
hostProps);
replaceLegacyPropertyValues(
hostProps);
return
buildHostInfo(
host,
port,
user,
password,
isPasswordless,
hostProps);
}
/**
* Subclasses should override this to perform any required pre-processing on the host information properties.
*
* @param hostProps
* the host properties map to process
*/
protected void
preprocessPerTypeHostProperties(
Map<
String,
String>
hostProps) {
// To be overridden in subclasses if needed.
}
/**
* Returns the default host. Subclasses must override this method if they have different default host value.
*
* @return the default host
*/
public
String getDefaultHost() {
return
DEFAULT_HOST;
}
/**
* Returns the default port. Subclasses must override this method if they have different default port value.
*
* @return the default port
*/
public int
getDefaultPort() {
return
DEFAULT_PORT;
}
/**
* Returns the default user. Usually the one provided in the method {@link DriverManager#getConnection(String, String, String)} or as connection argument.
*
* @return the default user
*/
public
String getDefaultUser() {
String user = this.
properties.
get(
PropertyKey.
USER.
getKeyName());
return
isNullOrEmpty(
user) ? "" :
user;
}
/**
* Returns the default password. Usually the one provided in the method {@link DriverManager#getConnection(String, String, String)} or as connection
* argument.
*
* @return the default password
*/
public
String getDefaultPassword() {
String password = this.
properties.
get(
PropertyKey.
PASSWORD.
getKeyName());
return
isNullOrEmpty(
password) ? "" :
password;
}
/**
* Fixes the protocol (TCP vs PIPE) dependencies for the given host properties map.
*
* @param hostProps
* the host properties map to fix
*/
protected void
fixProtocolDependencies(
Map<
String,
String>
hostProps) {
String protocol =
hostProps.
get(
PropertyKey.
PROTOCOL.
getKeyName());
if (!
isNullOrEmpty(
protocol) &&
protocol.
equalsIgnoreCase("PIPE")) {
if (!
hostProps.
containsKey(
PropertyKey.
socketFactory.
getKeyName())) {
hostProps.
put(
PropertyKey.
socketFactory.
getKeyName(), "com.mysql.cj.protocol.NamedPipeSocketFactory");
}
}
}
/**
* Returns this connection URL type.
*
* @return the connection URL type
*/
public
Type getType() {
return this.
type;
}
/**
* Returns the original database URL that produced this connection string.
*
* @return the original database URL
*/
@
Override
public
String getDatabaseUrl() {
return this.
originalConnStr;
}
/**
* Returns the database from this connection URL. Note that a "DBNAME" property overrides the database identified in the connection string.
*
* @return the database name
*/
public
String getDatabase() {
return this.
properties.
containsKey(
PropertyKey.
DBNAME.
getKeyName()) ? this.
properties.
get(
PropertyKey.
DBNAME.
getKeyName()) : this.
originalDatabase;
}
/**
* Returns the number of hosts in this connection URL.
*
* @return the number of hosts
*/
public int
hostsCount() {
return this.
hosts.
size();
}
/**
* Returns the single or first host info structure.
*
* @return the first host info structure
*/
public
HostInfo getMainHost() {
return this.
hosts.
isEmpty() ? null : this.
hosts.
get(0);
}
/**
* Returns a list of the hosts in this connection URL.
*
* @return the hosts list from this connection URL
*/
public
List<
HostInfo>
getHostsList() {
return
Collections.
unmodifiableList(this.
hosts);
}
/**
* Returns an existing host info with the same host:port part or spawns a new isolated host info based on this connection URL if none was found.
*
* @param hostPortPair
* the host:port part to search for
* @return the existing host info or a new independent one
*/
public
HostInfo getHostOrSpawnIsolated(
String hostPortPair) {
return
getHostOrSpawnIsolated(
hostPortPair, this.
hosts);
}
/**
* Returns an existing host info with the same host:port part or spawns a new isolated host info based on this connection URL if none was found.
*
* @param hostPortPair
* the host:port part to search for
* @param hostsList
* the hosts list from where to search the host list
* @return the existing host info or a new independent one
*/
public
HostInfo getHostOrSpawnIsolated(
String hostPortPair,
List<
HostInfo>
hostsList) {
for (
HostInfo hi :
hostsList) {
if (
hostPortPair.
equals(
hi.
getHostPortPair())) {
return
hi;
}
}
ConnectionUrlParser.
Pair<
String,
Integer>
hostAndPort =
ConnectionUrlParser.
parseHostPortPair(
hostPortPair);
String host =
hostAndPort.
left;
Integer port =
hostAndPort.
right;
String user =
getDefaultUser();
String password =
getDefaultPassword();
return
buildHostInfo(
host,
port,
user,
password, true, this.
properties);
}
/**
* Creates a new {@link HostInfo} structure with the given components, passing through the properties transformer if there is one defined in this connection
* string;
*
* @param host
* the host
* @param port
* the port
* @param user
* the user name
* @param password
* the password
* @param isDefaultPwd
* no password was provided in the connection URL or arguments?
* @param hostProps
* the host properties map
* @return a new instance of {@link HostInfo}
*/
private
HostInfo buildHostInfo(
String host, int
port,
String user,
String password, boolean
isDefaultPwd,
Map<
String,
String>
hostProps) {
// Apply properties transformations if needed.
if (this.
propertiesTransformer != null) {
Properties props = new
Properties();
props.
putAll(
hostProps);
props.
setProperty(
PropertyKey.
HOST.
getKeyName(),
host);
props.
setProperty(
PropertyKey.
PORT.
getKeyName(),
String.
valueOf(
port));
props.
setProperty(
PropertyKey.
USER.
getKeyName(),
user);
props.
setProperty(
PropertyKey.
PASSWORD.
getKeyName(),
password);
Properties transformedProps = this.
propertiesTransformer.
transformProperties(
props);
host =
transformedProps.
getProperty(
PropertyKey.
PORT.
getKeyName());
try {
port =
Integer.
parseInt(
transformedProps.
getProperty(
PropertyKey.
PORT.
getKeyName()));
} catch (
NumberFormatException e) {
throw
ExceptionFactory.
createException(
WrongArgumentException.class,
Messages.
getString("ConnectionString.8",
new
Object[] {
PropertyKey.
PORT.
getKeyName(),
transformedProps.
getProperty(
PropertyKey.
PORT.
getKeyName()) }),
e);
}
user =
transformedProps.
getProperty(
PropertyKey.
USER.
getKeyName());
password =
transformedProps.
getProperty(
PropertyKey.
PASSWORD.
getKeyName());
Map<
String,
String>
transformedHostProps = new
TreeMap<>(
String.
CASE_INSENSITIVE_ORDER);
transformedProps.
stringPropertyNames().
stream().
forEach(
k ->
transformedHostProps.
put(
k,
transformedProps.
getProperty(
k)));
// Remove surplus keys.
transformedHostProps.
remove(
PropertyKey.
HOST.
getKeyName());
transformedHostProps.
remove(
PropertyKey.
PORT.
getKeyName());
transformedHostProps.
remove(
PropertyKey.
USER.
getKeyName());
transformedHostProps.
remove(
PropertyKey.
PASSWORD.
getKeyName());
hostProps =
transformedHostProps;
}
return new
HostInfo(this,
host,
port,
user,
password,
isDefaultPwd,
hostProps);
}
/**
* Returns the original (common to all hosts) connection arguments as provided in the connection string query section.
*
* @return the original (common to all hosts) connection arguments
*/
public
Map<
String,
String>
getOriginalProperties() {
return
Collections.
unmodifiableMap(this.
properties);
}
/**
* Returns a {@link Properties} instance containing the connection arguments extracted from the URL query section, i.e., per host attributes are excluded.
* Applies properties transformations to the collected properties if {@link ConnectionPropertiesTransform} was declared in the connection arguments.
*
* @return a {@link Properties} instance containing the common connection arguments.
*/
public
Properties getConnectionArgumentsAsProperties() {
Properties props = new
Properties();
if (this.
properties != null) {
props.
putAll(this.
properties);
}
return this.
propertiesTransformer != null ? this.
propertiesTransformer.
transformProperties(
props) :
props;
}
/**
* Returns a string representation of this object.
*
* @return a string representation of this object
*/
@
Override
public
String toString() {
StringBuilder asStr = new
StringBuilder(super.toString());
asStr.
append(
String.
format(" :: {type: \"%s\", hosts: %s, database: \"%s\", properties: %s, propertiesTransformer: %s}", this.
type, this.
hosts,
this.
originalDatabase, this.
properties, this.
propertiesTransformer));
return
asStr.
toString();
}
}