/*
* JBoss, Home of Professional Open Source.
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership. Some portions may be licensed
* to Red Hat, Inc. under one or more contributor license agreements.
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* This library 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
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301 USA.
*/
package org.teiid.jdbc;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.TreeMap;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.teiid.net.TeiidURL;
/**
* @since 4.3
*/
public class JDBCURL {
private static final String UTF_8 = "UTF-8"; //$NON-NLS-1$
public static final String WAIT_FOR_LOAD = "waitForLoad"; //$NON-NLS-1$
public static final String USE_CALLING_THREAD = "useCallingThread"; //$NON-NLS-1$
public static final String JDBC_PROTOCOL = "jdbc:teiid:"; //$NON-NLS-1$
static final String URL_PATTERN = JDBC_PROTOCOL + "([\\w-\\.]+)(?:@([^;]*))?(;.*)?"; //$NON-NLS-1$
static Pattern urlPattern = Pattern.compile(URL_PATTERN);
public static final Map<String, String> EXECUTION_PROPERTIES = Collections.unmodifiableMap(buildProps());
private static Map<String, String> buildProps() {
Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
for (String key : new String[] {ExecutionProperties.PROP_TXN_AUTO_WRAP,
ExecutionProperties.PROP_PARTIAL_RESULTS_MODE,
ExecutionProperties.RESULT_SET_CACHE_MODE,
ExecutionProperties.ANSI_QUOTED_IDENTIFIERS,
ExecutionProperties.SQL_OPTION_SHOWPLAN,
ExecutionProperties.NOEXEC,
ExecutionProperties.PROP_FETCH_SIZE,
ExecutionProperties.PROP_XML_FORMAT,
ExecutionProperties.PROP_XML_VALIDATION,
USE_CALLING_THREAD,
ExecutionProperties.DISABLE_LOCAL_TRANSACTIONS,
ExecutionProperties.JDBC4COLUMNNAMEANDLABELSEMANTICS}) {
result.put(key, key);
}
return result;
}
public static final Map<String, String> KNOWN_PROPERTIES = getKnownProperties();
private static Map<String, String> getKnownProperties() {
Set<String> props = new HashSet<String>(Arrays.asList(
TeiidURL.CONNECTION.APP_NAME,
TeiidURL.JDBC.VDB_NAME,
TeiidURL.JDBC.VERSION,
TeiidURL.JDBC.VDB_VERSION,
TeiidURL.CONNECTION.USER_NAME,
TeiidURL.CONNECTION.PASSWORD,
WAIT_FOR_LOAD,
TeiidURL.CONNECTION.AUTO_FAILOVER,
TeiidURL.CONNECTION.DISCOVERY_STRATEGY,
TeiidURL.CONNECTION.PASSTHROUGH_AUTHENTICATION,
TeiidURL.CONNECTION.JAAS_NAME,
TeiidURL.CONNECTION.KERBEROS_SERVICE_PRINCIPLE_NAME,
TeiidURL.CONNECTION.ENCRYPT_REQUESTS,
TeiidURL.CONNECTION.LOGIN_TIMEOUT,
DatabaseMetaDataImpl.REPORT_AS_VIEWS,
ResultSetImpl.DISABLE_FETCH_SIZE));
props.addAll(EXECUTION_PROPERTIES.keySet());
Map<String, String> result = new TreeMap<String, String>(String.CASE_INSENSITIVE_ORDER);
for (String string : props) {
result.put(string, string);
}
return Collections.unmodifiableMap(result);
}
private String vdbName;
private String connectionURL;
private Properties properties = new Properties();
public enum ConnectionType {
Embedded,
Socket
}
public static ConnectionType acceptsUrl(String url) {
Matcher m = urlPattern.matcher(url);
if (m.matches()) {
return m.group(2) != null?ConnectionType.Socket:ConnectionType.Embedded;
}
return null;
}
private String urlString;
public JDBCURL(String jdbcURL) {
parseURL(jdbcURL);
}
public JDBCURL(String vdbName, String connectionURL, Properties props) {
if (vdbName == null || vdbName.trim().length() == 0) {
throw new IllegalArgumentException();
}
this.vdbName = vdbName;
this.connectionURL = connectionURL;
if (props != null) {
normalizeProperties(props, this.properties);
}
}
public String getVDBName() {
return vdbName;
}
public String getConnectionURL() {
return connectionURL;
}
public Properties getProperties() {
// Make a copy of the properties object, including any non-string values that may be contained in the map.
Properties newProps = new Properties();
newProps.putAll(this.properties);
return newProps;
}
private void parseURL(String jdbcURL) {
if (jdbcURL == null) {
throw new IllegalArgumentException();
}
// Trim extra spaces
jdbcURL = jdbcURL.trim();
if (jdbcURL.length() == 0) {
throw new IllegalArgumentException();
}
Matcher m = urlPattern.matcher(jdbcURL);
if (!m.matches()) {
throw new IllegalArgumentException();
}
vdbName = m.group(1);
connectionURL = m.group(2);
if (connectionURL != null) {
connectionURL = connectionURL.trim();
}
String props = m.group(3);
if (props != null) {
parseConnectionProperties(props, this.properties);
}
}
public static void parseConnectionProperties(String connectionInfo, Properties p) {
String[] connectionParts = connectionInfo.split(";"); //$NON-NLS-1$
if (connectionParts.length != 0) {
// The rest should be connection params
for (int i = 0; i < connectionParts.length; i++) {
parseConnectionProperty(connectionParts[i], p);
}
}
}
static void parseConnectionProperty(String connectionProperty, Properties p) {
if (connectionProperty.length() == 0) {
// Be tolerant of double-semicolons and dangling semicolons
return;
} else if(connectionProperty.length() < 3) {
// key=value must have at least 3 characters
throw new IllegalArgumentException();
}
int firstEquals = connectionProperty.indexOf('=');
if(firstEquals < 1) {
throw new IllegalArgumentException();
}
String key = connectionProperty.substring(0, firstEquals).trim();
String value = connectionProperty.substring(firstEquals+1).trim();
if(value.indexOf('=') >= 0) {
throw new IllegalArgumentException();
}
addNormalizedProperty(key, getValidValue(value), p);
}
public String getJDBCURL() {
if (urlString == null) {
StringBuffer buf = new StringBuffer(JDBC_PROTOCOL)
.append(vdbName);
if (this.connectionURL != null) {
buf.append('@').append(connectionURL);
}
for (Iterator i = properties.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry)i.next();
if (entry.getValue() instanceof String) {
// get only the string properties, because a non-string property could not have been set on the url.
try {
buf.append(';')
.append(entry.getKey())
.append('=')
.append(URLEncoder.encode((String)entry.getValue(), "UTF-8")); //$NON-NLS-1$
} catch (UnsupportedEncodingException e) {
buf.append(entry.getValue());
}
}
}
urlString = buf.toString();
}
return urlString;
}
public String getProperty(String key) {
return properties.getProperty(key);
}
public String getUserName() {
return properties.getProperty(TeiidURL.CONNECTION.USER_NAME);
}
public String getPassword() {
return properties.getProperty(TeiidURL.CONNECTION.PASSWORD);
}
public String getVDBVersion() {
if (properties.contains(TeiidURL.JDBC.VDB_VERSION)) {
return properties.getProperty(TeiidURL.JDBC.VDB_VERSION);
}
return properties.getProperty(TeiidURL.JDBC.VERSION);
}
public String getTransactionAutowrapMode() {
return properties.getProperty(ExecutionProperties.PROP_TXN_AUTO_WRAP);
}
public String getPartialResultsMode() {
return properties.getProperty(ExecutionProperties.PROP_PARTIAL_RESULTS_MODE);
}
public String getResultSetCacheMode() {
return properties.getProperty(ExecutionProperties.RESULT_SET_CACHE_MODE);
}
public String getAnsiQuotedIdentifiers() {
return properties.getProperty(ExecutionProperties.ANSI_QUOTED_IDENTIFIERS);
}
public String getFetchSize() {
return properties.getProperty(ExecutionProperties.PROP_FETCH_SIZE);
}
public String getXMLFormat() {
return properties.getProperty(ExecutionProperties.PROP_XML_FORMAT);
}
public String getXMLValidation() {
return properties.getProperty(ExecutionProperties.PROP_XML_VALIDATION);
}
public String getTransparentFailover() {
return properties.getProperty(TeiidURL.CONNECTION.AUTO_FAILOVER);
}
public String getDisableLocalTransactions() {
return properties.getProperty(ExecutionProperties.DISABLE_LOCAL_TRANSACTIONS);
}
public String toString() {
return getJDBCURL();
}
private static void normalizeProperties(Properties source, Properties target) {
for (Enumeration e = source.propertyNames(); e.hasMoreElements();) {
String key = (String)e.nextElement();
addNormalizedProperty(key, source.get(key), target);
}
}
public static void addNormalizedProperty(String key, Object value, Properties target) {
String validKey = getValidKey(key);
// now add the normalized key and value into the properties object.
target.put(validKey, value);
}
public static String getValidKey(String key) {
String result = KNOWN_PROPERTIES.get(key);
if (result != null) {
return result;
}
return key;
}
private static Object getValidValue(Object value) {
if (value instanceof String) {
try {
// Decode the value of the property if incase they were encoded.
return URLDecoder.decode((String)value, UTF_8);
} catch (UnsupportedEncodingException e) {
// use the original value
}
}
return value;
}
public static Properties normalizeProperties(Properties props) {
normalizeProperties(props, props);
return props;
}
}