diff --git a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java index ab64f0227a..64e9f04b0e 100644 --- a/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java +++ b/pgjdbc/src/main/java/org/postgresql/ds/common/BaseDataSource.java @@ -23,6 +23,7 @@ import java.sql.Connection; import java.sql.DriverManager; import java.sql.SQLException; +import java.util.Arrays; import java.util.Properties; import java.util.logging.Level; import java.util.logging.Logger; @@ -39,16 +40,16 @@ * * @author Aaron Mulder (ammulder@chariotsolutions.com) */ -public abstract class BaseDataSource implements CommonDataSource, Referenceable { +public abstract class BaseDataSource implements CommonDataSource, Referenceable { private static final Logger LOGGER = Logger.getLogger(BaseDataSource.class.getName()); // Standard properties, defined in the JDBC 2.0 Optional Package spec - private String serverName = "localhost"; + private String[] serverNames = new String[] {"localhost"}; private String databaseName = ""; private String user; private String password; - private int portNumber = 0; + private int[] portNumbers = new int[] {0}; // Map for all other properties private Properties properties = new Properties(); @@ -129,9 +130,20 @@ public void setLogWriter(PrintWriter printWriter) { * Gets the name of the host the PostgreSQL database is running on. * * @return name of the host the PostgreSQL database is running on + * @deprecated use {@link #getServerNames()} */ + @Deprecated public String getServerName() { - return serverName; + return serverNames[0]; + } + + /** + * Gets the name of the host(s) the PostgreSQL database is running on. + * + * @return name of the host(s) the PostgreSQL database is running on + */ + public String[] getServerNames() { + return serverNames; } /** @@ -139,12 +151,30 @@ public String getServerName() { * only affect future calls to getConnection. The default value is localhost. * * @param serverName name of the host the PostgreSQL database is running on + * @deprecated use {@link #setServerNames(String[])} */ + @Deprecated public void setServerName(String serverName) { - if (serverName == null || serverName.equals("")) { - this.serverName = "localhost"; + this.setServerNames(new String[] { serverName }); + } + + /** + * Sets the name of the host(s) the PostgreSQL database is running on. If this is changed, it will + * only affect future calls to getConnection. The default value is localhost. + * + * @param serverNames name of the host(s) the PostgreSQL database is running on + */ + public void setServerNames(String[] serverNames) { + if (serverNames == null || serverNames.length == 0) { + this.serverNames = new String[] {"localhost"}; } else { - this.serverName = serverName; + serverNames = Arrays.copyOf(serverNames, serverNames.length); + for (int i = 0; i < serverNames.length; i++) { + if (serverNames[i] == null || serverNames[i].equals("")) { + serverNames[i] = "localhost"; + } + } + this.serverNames = serverNames; } } @@ -221,20 +251,50 @@ public void setPassword(String password) { * Gets the port which the PostgreSQL server is listening on for TCP/IP connections. * * @return The port, or 0 if the default port will be used. + * @deprecated use {@link #getPortNumbers()} */ + @Deprecated public int getPortNumber() { - return portNumber; + if (portNumbers == null || portNumbers.length == 0) { + return 0; + } + return portNumbers[0]; + } + + /** + * Gets the port(s) which the PostgreSQL server is listening on for TCP/IP connections. + * + * @return The port(s), or 0 if the default port will be used. + */ + public int[] getPortNumbers() { + return portNumbers; } /** - * Gets the port which the PostgreSQL server is listening on for TCP/IP connections. Be sure the + * Sets the port which the PostgreSQL server is listening on for TCP/IP connections. Be sure the * -i flag is passed to postmaster when PostgreSQL is started. If this is not set, or set to 0, * the default port will be used. * * @param portNumber port which the PostgreSQL server is listening on for TCP/IP + * @deprecated use {@link #setPortNumbers(int[])} */ + @Deprecated public void setPortNumber(int portNumber) { - this.portNumber = portNumber; + setPortNumbers(new int[] { portNumber }); + } + + /** + * Sets the port(s) which the PostgreSQL server is listening on for TCP/IP connections. Be sure the + * -i flag is passed to postmaster when PostgreSQL is started. If this is not set, or set to 0, + * the default port will be used. + * + * @param portNumbers port(s) which the PostgreSQL server is listening on for TCP/IP + */ + public void setPortNumbers(int[] portNumbers) { + if (portNumbers == null || portNumbers.length == 0) { + portNumbers = new int[] { 0 }; + } + this.portNumbers = Arrays.copyOf(portNumbers, portNumbers.length); } /** @@ -1085,9 +1145,14 @@ public void setLoggerFile(String loggerFile) { public String getUrl() { StringBuilder url = new StringBuilder(100); url.append("jdbc:postgresql://"); - url.append(serverName); - if (portNumber != 0) { - url.append(":").append(portNumber); + for (int i = 0; i < serverNames.length; i++) { + if (i > 0) { + url.append(","); + } + url.append(serverNames[i]); + if (portNumbers != null && portNumbers.length >= i && portNumbers[i] != 0) { + url.append(":").append(portNumbers[i]); + } } url.append("/").append(URLCoder.encode(databaseName)); @@ -1179,23 +1244,28 @@ public void setProperty(PGProperty property, String value) { } switch (property) { case PG_HOST: - serverName = value; + setServerNames(value.split(",")); break; case PG_PORT: - try { - portNumber = Integer.parseInt(value); - } catch (NumberFormatException e) { - portNumber = 0; + String[] ps = value.split(","); + int[] ports = new int[ps.length]; + for (int i = 0 ; i < ps.length; i++) { + try { + ports[i] = Integer.parseInt(ps[i]); + } catch (NumberFormatException e) { + ports[i] = 0; + } } + setPortNumbers(ports); break; case PG_DBNAME: - databaseName = value; + setDatabaseName(value); break; case USER: - user = value; + setUser(value); break; case PASSWORD: - password = value; + setPassword(value); break; default: properties.setProperty(property.getName(), value); @@ -1213,10 +1283,25 @@ protected Reference createReference() { public Reference getReference() throws NamingException { Reference ref = createReference(); - ref.add(new StringRefAddr("serverName", serverName)); - if (portNumber != 0) { - ref.add(new StringRefAddr("portNumber", Integer.toString(portNumber))); + StringBuilder serverString = new StringBuilder(); + for (int i = 0; i < serverNames.length; i++) { + if (i > 0) { + serverString.append(","); + } + String serverName = serverNames[i]; + serverString.append(serverName); + } + ref.add(new StringRefAddr("serverName", serverString.toString())); + + StringBuilder portString = new StringBuilder(); + for (int i = 0; i < portNumbers.length; i++) { + if (i > 0) { + portString.append(","); + } + int p = portNumbers[i]; + portString.append(Integer.toString(p)); } + ref.add(new StringRefAddr("portNumber", portString.toString())); ref.add(new StringRefAddr("databaseName", databaseName)); if (user != null) { ref.add(new StringRefAddr("user", user)); @@ -1236,11 +1321,22 @@ public Reference getReference() throws NamingException { public void setFromReference(Reference ref) { databaseName = getReferenceProperty(ref, "databaseName"); - String port = getReferenceProperty(ref, "portNumber"); - if (port != null) { - portNumber = Integer.parseInt(port); + String portNumberString = getReferenceProperty(ref, "portNumber"); + if (portNumberString != null) { + String[] ps = portNumberString.split(","); + int[] ports = new int[ps.length]; + for (int i = 0; i < ps.length; i++) { + try { + ports[i] = Integer.parseInt(ps[i]); + } catch (NumberFormatException e) { + ports[i] = 0; + } + } + setPortNumbers(ports); + } else { + setPortNumbers(null); } - serverName = getReferenceProperty(ref, "serverName"); + setServerNames(getReferenceProperty(ref, "serverName").split(",")); for (PGProperty property : PGProperty.values()) { setProperty(property, getReferenceProperty(ref, property.getName())); @@ -1256,21 +1352,21 @@ private static String getReferenceProperty(Reference ref, String propertyName) { } protected void writeBaseObject(ObjectOutputStream out) throws IOException { - out.writeObject(serverName); + out.writeObject(serverNames); out.writeObject(databaseName); out.writeObject(user); out.writeObject(password); - out.writeInt(portNumber); + out.writeObject(portNumbers); out.writeObject(properties); } protected void readBaseObject(ObjectInputStream in) throws IOException, ClassNotFoundException { - serverName = (String) in.readObject(); + serverNames = (String[]) in.readObject(); databaseName = (String) in.readObject(); user = (String) in.readObject(); password = (String) in.readObject(); - portNumber = in.readInt(); + portNumbers = (int[]) in.readObject(); properties = (Properties) in.readObject(); } diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceFailoverUrlsTest.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceFailoverUrlsTest.java new file mode 100644 index 0000000000..4b0acd65f8 --- /dev/null +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/BaseDataSourceFailoverUrlsTest.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2004, PostgreSQL Global Development Group + * See the LICENSE file in the project root for more information. + */ + +package org.postgresql.test.jdbc2.optional; + +import static org.junit.Assert.assertEquals; + +import org.postgresql.ds.common.BaseDataSource; + +import org.junit.Test; + +import java.io.IOException; +import javax.naming.NamingException; + +/** +* tests that failover urls survive the parse/rebuild roundtrip with and without specific ports +*/ +public class BaseDataSourceFailoverUrlsTest { + + private static final String DEFAULT_PORT = "5432"; + + @Test + public void testFullDefault() throws ClassNotFoundException, NamingException, IOException { + roundTripFromUrl("jdbc:postgresql://server/database", "jdbc:postgresql://server:" + DEFAULT_PORT + "/database"); + } + + @Test + public void testTwoNoPorts() throws ClassNotFoundException, NamingException, IOException { + roundTripFromUrl("jdbc:postgresql://server1,server2/database", "jdbc:postgresql://server1:" + DEFAULT_PORT + ",server2:" + DEFAULT_PORT + "/database"); + } + + @Test + public void testTwoWithPorts() throws ClassNotFoundException, NamingException, IOException { + roundTripFromUrl("jdbc:postgresql://server1:1234,server2:2345/database", "jdbc:postgresql://server1:1234,server2:2345/database"); + } + + @Test + public void testTwoFirstPort() throws ClassNotFoundException, NamingException, IOException { + roundTripFromUrl("jdbc:postgresql://server1,server2:2345/database", "jdbc:postgresql://server1:" + DEFAULT_PORT + ",server2:2345/database"); + } + + @Test + public void testTwoLastPort() throws ClassNotFoundException, NamingException, IOException { + roundTripFromUrl("jdbc:postgresql://server1:2345,server2/database", "jdbc:postgresql://server1:2345,server2:" + DEFAULT_PORT + "/database"); + } + + @Test + public void testNullPorts() { + BaseDataSource bds = newDS(); + bds.setDatabaseName("database"); + bds.setPortNumbers(null); + assertUrlWithoutParamsEquals("jdbc:postgresql://localhost/database", bds.getURL()); + assertEquals(0, bds.getPortNumber()); + assertEquals(0, bds.getPortNumbers()[0]); + } + + @Test + public void testEmptyPorts() { + BaseDataSource bds = newDS(); + bds.setDatabaseName("database"); + bds.setPortNumbers(new int[0]); + assertUrlWithoutParamsEquals("jdbc:postgresql://localhost/database", bds.getURL()); + assertEquals(0, bds.getPortNumber()); + assertEquals(0, bds.getPortNumbers()[0]); + } + + private BaseDataSource newDS() { + return new BaseDataSource() { + @Override + public String getDescription() { + return "BaseDataSourceFailoverUrlsTest-DS"; + } + }; + } + + private void roundTripFromUrl(String in, String expected) throws NamingException, ClassNotFoundException, IOException { + BaseDataSource bds = newDS(); + + bds.setUrl(in); + assertUrlWithoutParamsEquals(expected, bds.getURL()); + + bds.setFromReference(bds.getReference()); + assertUrlWithoutParamsEquals(expected, bds.getURL()); + + bds.initializeFrom(bds); + assertUrlWithoutParamsEquals(expected, bds.getURL()); + } + + private static String jdbcUrlStripParams(String in) { + return in.replaceAll("\\?.*$", ""); + } + + private static void assertUrlWithoutParamsEquals(String expected, String url) { + assertEquals(expected, jdbcUrlStripParams(url)); + } +} diff --git a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java index 00c5d1a0ec..5a43fff45e 100644 --- a/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java +++ b/pgjdbc/src/test/java/org/postgresql/test/jdbc2/optional/OptionalTestSuite.java @@ -20,7 +20,8 @@ SimpleDataSourceWithSetURLTest.class, ConnectionPoolTest.class, PoolingDataSourceTest.class, - CaseOptimiserDataSourceTest.class}) + CaseOptimiserDataSourceTest.class, + BaseDataSourceFailoverUrlsTest.class}) public class OptionalTestSuite { }