/* * 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.net.MalformedURLException; import java.sql.Connection; import java.sql.SQLFeatureNotSupportedException; import java.util.Properties; import java.util.logging.Logger; import javax.sql.XAConnection; import org.teiid.net.TeiidURL; /** * The Teiid JDBC DataSource implementation class of {@link javax.sql.DataSource} and * {@link javax.sql.XADataSource}. * <p> * The {@link javax.sql.DataSource} interface follows the JavaBean design pattern, * meaning the implementation class has <i>properties</i> that are accessed with getter methods * and set using setter methods, and where the getter and setter methods follow the JavaBean * naming convention (e.g., <code>get</code><i>PropertyName</i><code>() : </code><i>PropertyType</i> * and <code>set</code><i>PropertyName</i><code>(</code><i>PropertyType</i><code>) : void</code>). * </p> * The {@link javax.sql.XADataSource} interface is almost identical to the {@link javax.sql.DataSource} * interface, but rather than returning {@link java.sql.Connection} instances, there are methods that * return {@link javax.sql.XAConnection} instances that can be used with distributed transactions. * <p> * The following are the properties for this DataSource: * <table cellspacing="0" cellpadding="0" border="1" width="100%"> * <tr><td><b>Property Name</b></td><td><b>Type</b></td><td><b>Description</b></td></tr> * <tr><td>portNumber </td><td><code>int </code></td><td>The port number where a Teiid Server is listening * for requests.</td></tr> * <tr><td>serverName </td><td><code>String</code></td><td>The hostname or IP address of the Teiid Server.</td></tr> * <table> * If "serverName" property is not set then data source will try to create a embedded connection to the Teiid server. * </p> */ public class TeiidDataSource extends BaseDataSource { private static final long serialVersionUID = -5170316154373144878L; /** * The port number where a server is listening for requests. * This property name is one of the standard property names defined by the JDBC 2.0 specification, * and is <i>required</i>. */ private int portNumber; /** * The name of the host where the sServer is running. * This property name is one of the standard property names defined by the JDBC 2.0 specification, * and is <i>required</i>. */ private String serverName; /** * Specify whether to make a secure (SSL, mms:) connection or a normal non-SSL mm: connection. * the default is to use a non-secure connection. * @since 5.0.2 */ private boolean secure = false; /** * Holds a comma delimited list of alternate Server(s):Port(s) that can * be used for connection fail-over. * @since 5.5 */ private String alternateServers; /** * The auto failover mode for calls made to the query engine. If true query engine calls that fail will * allow the connection to choose another process. */ private String autoFailover; private String discoveryStrategy; /** * when "true", in the "embedded" scenario, authentication is information is read in pass though manner. */ private boolean passthroughAuthentication = false; /** * Name of the jass configuration to use from the -Djava.security.auth.login.config=login.conf property */ private String jaasName; /** * Name of Kerberos KDC service principle name */ private String kerberosServicePrincipleName; /** * If not using ssl determines whether requests with the associated command payload should be encrypted */ private boolean encryptRequests; private final TeiidDriver driver; private boolean loadBalance = true; public TeiidDataSource() { this.driver = new TeiidDriver(); } TeiidDataSource(TeiidDriver driver) { this.driver = driver; } // -------------------------------------------------------------------------------------------- // H E L P E R M E T H O D S // -------------------------------------------------------------------------------------------- protected Properties buildProperties(final String userName, final String password) { Properties props = super.buildProperties(userName, password); if (this.getAutoFailover() != null) { props.setProperty(TeiidURL.CONNECTION.AUTO_FAILOVER, this.getAutoFailover()); } if (this.getDiscoveryStrategy() != null) { props.setProperty(TeiidURL.CONNECTION.DISCOVERY_STRATEGY, this.getDiscoveryStrategy()); } if (this.encryptRequests) { props.setProperty(TeiidURL.CONNECTION.ENCRYPT_REQUESTS, Boolean.TRUE.toString()); } if (getLoginTimeout() > 0) { props.setProperty(TeiidURL.CONNECTION.LOGIN_TIMEOUT, String.valueOf(getLoginTimeout())); } if (getJaasName() != null) { props.setProperty(TeiidURL.CONNECTION.JAAS_NAME, getJaasName()); } if (getKerberosServicePrincipleName() != null) { props.setProperty(TeiidURL.CONNECTION.KERBEROS_SERVICE_PRINCIPLE_NAME, getKerberosServicePrincipleName()); } return props; } protected String buildServerURL() throws TeiidSQLException { if (serverName == null) { return null; } if ( this.alternateServers == null || this.alternateServers.length() == 0) { // Format: "mm://server:port" return new TeiidURL(this.serverName, this.portNumber, this.secure).getAppServerURL(); } // Format: "mm://server1:port,server2:port,..." String serverURL = this.secure ? TeiidURL.SECURE_PROTOCOL : TeiidURL.DEFAULT_PROTOCOL; if (this.serverName.indexOf(':') != -1 && !this.serverName.startsWith("[")) { //$NON-NLS-1$ serverURL += "[" + this.serverName + "]"; //$NON-NLS-1$ //$NON-NLS-2$ } else { serverURL += this.serverName; } serverURL += TeiidURL.COLON_DELIMITER + this.portNumber; //add in the port number if not specified String[] as = this.alternateServers.split( TeiidURL.COMMA_DELIMITER); for ( int i = 0; i < as.length; i++ ) { String server = as[i].trim(); //ipv6 without port if (server.startsWith("[") && server.endsWith("]")) { //$NON-NLS-1$ //$NON-NLS-2$ String msg = reasonWhyInvalidServerName(server.substring(1, server.length() - 1)); if (msg != null) { throw createConnectionError(JDBCPlugin.Util.getString("MMDataSource.alternateServer_is_invalid", msg)); //$NON-NLS-1$ } serverURL += (TeiidURL.COMMA_DELIMITER +as[i] + TeiidURL.COLON_DELIMITER + this.portNumber); } else { String[] serverParts = server.split(TeiidURL.COLON_DELIMITER, 2); String msg = reasonWhyInvalidServerName(serverParts[0]); if (msg != null) { throw createConnectionError(JDBCPlugin.Util.getString("MMDataSource.alternateServer_is_invalid", msg)); //$NON-NLS-1$ } serverURL += (TeiidURL.COMMA_DELIMITER + serverParts[0] + TeiidURL.COLON_DELIMITER); if ( serverParts.length > 1 ) { try { TeiidURL.validatePort(serverParts[1]); } catch (MalformedURLException e) { throw createConnectionError(JDBCPlugin.Util.getString("MMDataSource.alternateServer_is_invalid", e.getMessage())); //$NON-NLS-1$ } serverURL += serverParts[1]; } else { serverURL += this.portNumber; } } } try { return new TeiidURL(serverURL).getAppServerURL(); } catch (MalformedURLException e) { throw TeiidSQLException.create(e); } } protected JDBCURL buildURL() throws TeiidSQLException { return new JDBCURL(this.getDatabaseName(), buildServerURL(), buildProperties(getUser(), getPassword())); } protected void validateProperties( final String userName, final String password) throws java.sql.SQLException { super.validateProperties(userName, password); String reason = reasonWhyInvalidPortNumber(this.portNumber); if ( reason != null ) { throw createConnectionError(reason); } reason = reasonWhyInvalidServerName(this.serverName); if ( reason != null ) { throw createConnectionError(reason); } } private TeiidSQLException createConnectionError(String reason) { String msg = JDBCPlugin.Util.getString("MMDataSource.Err_connecting", reason); //$NON-NLS-1$ return new TeiidSQLException(msg); } // -------------------------------------------------------------------------------------------- // D A T A S O U R C E M E T H O D S // -------------------------------------------------------------------------------------------- /** * Attempt to establish a database connection. * @return a Connection to the database * @throws java.sql.SQLException if a database-access error occurs * @see javax.sql.DataSource#getConnection() */ public Connection getConnection() throws java.sql.SQLException { return getConnection(null,null); } /** * Attempt to establish a database connection. * @param userName the database user on whose behalf the Connection is being made * @param password the user's password * @return a Connection to the database * @throws java.sql.SQLException if a database-access error occurs * @see javax.sql.DataSource#getConnection(java.lang.String, java.lang.String) */ public Connection getConnection(String userName, String password) throws java.sql.SQLException { // check if this is embedded connection if (getServerName() == null) { super.validateProperties(userName, password); final Properties props = buildEmbeddedProperties(userName, password); String url = new JDBCURL(getDatabaseName(), null, null).getJDBCURL(); return driver.connect(url, props); } // if not proceed with socket connection. validateProperties(userName,password); final Properties props = buildProperties(userName, password); return driver.connect(new JDBCURL(this.getDatabaseName(), buildServerURL(), null).getJDBCURL(), props); } private Properties buildEmbeddedProperties(final String userName, final String password) { Properties props = buildProperties(userName, password); props.setProperty(TeiidURL.CONNECTION.PASSTHROUGH_AUTHENTICATION, Boolean.toString(this.passthroughAuthentication)); return props; } /** * @see java.lang.Object#toString() */ public String toString() { try { return buildURL().getJDBCURL(); } catch (TeiidSQLException e) { return e.getMessage(); } } // -------------------------------------------------------------------------------------------- // P R O P E R T Y M E T H O D S // -------------------------------------------------------------------------------------------- /** * Returns the port number. * @return the port number */ public int getPortNumber() { return portNumber; } /** * Returns the name of the server. * @return the name of the server */ public String getServerName() { return serverName; } /** * Returns a flag indicating whether to create a secure connection or not. * @return True if using secure mms: protocol, false for normal mm: protocol. * @since 5.0.2 */ public boolean isSecure() { return this.secure; } /** * Same as "isSecure". Required by the reflection login in connection pools to identify the type * @return */ public boolean getSecure() { return this.secure; } /** * Returns a string containing a comma delimited list of alternate * server(s). * * The list will be in the form of server2[:port2][,server3[:port3]]. If no * alternate servers have been defined <code>null</code> is returned. * @return A comma delimited list of server:port or <code>null</code> If * no alternate servers are defined. * @since 5.5 */ public String getAlternateServers() { if ( this.alternateServers != null && this.alternateServers.length() < 1 ) return null; return this.alternateServers; } /** * Sets the portNumber. * @param portNumber The portNumber to set */ public void setPortNumber(final int portNumber) { this.portNumber = portNumber; } /** * Sets the serverName. * @param serverName The serverName to set */ public void setServerName(final String serverName) { this.serverName = serverName; } /** * Sets the secure flag to use mms: protocol instead of the default mm: protocol. * @param secure True to use mms: * @since 5.0.2 */ public void setSecure(final boolean secure) { this.secure = secure; } /** * Sets a list of alternate server(s) that can be used for * connection fail-over. * * The form of the list should be server2[:port2][,server3:[port3][,...]]. * * If ":port" is omitted, the port defined by <code>portNumber</code> is used. * * If <code>servers</code> is empty or <code>null</code>, the value of * <code>alternateServers</code> is cleared. * @param servers A comma delimited list of alternate * Server(s):Port(s) to use for connection fail-over. If blank or * <code>null</code>, the list is cleared. * @since 5.5 */ public void setAlternateServers(final String servers) { this.alternateServers = servers; if ( this.alternateServers != null && this.alternateServers.length() < 1 ) this.alternateServers = null; } // -------------------------------------------------------------------------------------------- // V A L I D A T I O N M E T H O D S // -------------------------------------------------------------------------------------------- /** * Return the reason why the supplied port number may be invalid, or null * if it is considered valid. * @param portNumber a possible value for the property * @return the reason why the property is invalid, or null if it is considered valid * @see #setPortNumber(int) */ public static String reasonWhyInvalidPortNumber( final int portNumber) { return TeiidURL.validatePort(portNumber); } /** * Return the reason why the supplied server name may be invalid, or null * if it is considered valid. * @param serverName a possible value for the property * @return the reason why the property is invalid, or null if it is considered valid * @see #setServerName(String) * */ public static String reasonWhyInvalidServerName( final String serverName ) { if ( serverName == null || serverName.trim().length() == 0 ) { return JDBCPlugin.Util.getString("MMDataSource.Server_name_required"); //$NON-NLS-1$ } return null; } /** * The reason why "socketsPerVM" is invalid. * @param value of "socketsPerVM" property * @return reason */ public static String reasonWhyInvalidSocketsPerVM(final String socketsPerVM) { if (socketsPerVM != null) { int value = -1; try { value = Integer.parseInt(socketsPerVM); } catch (Exception e) { } if (value <= 0) { return JDBCPlugin.Util.getString("MMDataSource.Sockets_per_vm_invalid"); //$NON-NLS-1$ } } return null; } /** * The reason why "stickyConnections" is invalid. * @param value of "stickyConnections" property * @return reason */ public static String reasonWhyInvalidStickyConnections(final String stickyConnections) { if (stickyConnections != null) { if ((! stickyConnections.equalsIgnoreCase("true")) && //$NON-NLS-1$ (! stickyConnections.equalsIgnoreCase("false"))) { //$NON-NLS-1$ return JDBCPlugin.Util.getString("MMDataSource.Sticky_connections_invalid"); //$NON-NLS-1$ } } return null; } /** * @return Returns the transparentFailover. */ public String getAutoFailover() { return this.autoFailover; } /** * @param transparentFailover The transparentFailover to set. */ public void setAutoFailover(String autoFailover) { this.autoFailover = autoFailover; } public String getDiscoveryStrategy() { return discoveryStrategy; } public void setDiscoveryStrategy(String discoveryStrategy) { this.discoveryStrategy = discoveryStrategy; } /** * When true, this connection uses the passed in security domain to do the authentication. * @return */ public boolean isPassthroughAuthentication() { return passthroughAuthentication; } /** * Same as "isPassthroughAuthentication". Required by the reflection login in connection pools to identify the type * @return */ public boolean getPassthroughAuthentication() { return passthroughAuthentication; } /** * When set to true, the connection uses the passed in security domain to do the authentication. * @since 7.1 * @return */ public void setPassthroughAuthentication(final boolean passthroughAuthentication) { this.passthroughAuthentication = passthroughAuthentication; } /** * Application name from JAAS Login Config file * @since 7.6 * @return */ public String getJaasName() { return jaasName; } /** * Application name from JAAS Login Config file * @since 7.6 */ public void setJaasName(String jaasApplicationName) { this.jaasName = jaasApplicationName; } /** * Kerberos KDC service principle name * @since 7.6 * @return */ public String getKerberosServicePrincipleName() { return kerberosServicePrincipleName; } /** * Kerberos KDC service principle name * @since 7.6 */ public void setKerberosServicePrincipleName(String kerberosServerName) { this.kerberosServicePrincipleName = kerberosServerName; } public Logger getParentLogger() throws SQLFeatureNotSupportedException { return TeiidDriver.logger; } public void setEncryptRequests(boolean encryptRequests) { this.encryptRequests = encryptRequests; } public boolean isEncryptRequests() { return encryptRequests; } public boolean getEncryptRequests() { return encryptRequests; } public boolean isLoadBalance() { return loadBalance; } public boolean getLoadBalance() { return loadBalance; } public void setLoadBalance(boolean loadBalance) { this.loadBalance = loadBalance; } /** * Attempt to establish a database connection that can be used with distributed transactions. * @param userName the database user on whose behalf the XAConnection is being made * @param password the user's password * @return an XAConnection to the database * @throws java.sql.SQLException if a database-access error occurs * @see javax.sql.XADataSource#getXAConnection(java.lang.String, java.lang.String) */ public XAConnection getXAConnection(final String userName, final String password) throws java.sql.SQLException { XAConnectionImpl result = new XAConnectionImpl((ConnectionImpl) getConnection(userName, password)); result.setLoadBalance(loadBalance); return result; } }