diff --git a/.gitignore b/.gitignore index acb9b8d91..b8095970d 100644 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,8 @@ local.properties .settings/ .gradle/ .loadpath +outdated-dependencies.txt +pom.xml.versionBackup # External tool builders .externalToolBuilders/ diff --git a/.travis.yml b/.travis.yml index 47c919414..a9a2d8f11 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,8 +2,13 @@ language: java jdk: - - oraclejdk8 + - oraclejdk9 +addons: + apt: + packages: + - oracle-java9-installer + services: - docker @@ -29,7 +34,7 @@ install: - keytool -importkeystore -destkeystore clientcert.jks -deststorepass password -srckeystore identity.p12 -srcstoretype PKCS12 -srcstorepass password - keytool -list -v -keystore clientcert.jks -storepass "password" > JavaKeyStore.txt - cd .. - - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild41 + - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild43 - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild42 before_script: @@ -39,6 +44,6 @@ before_script: script: - docker ps -a -##Test for JDBC Specification 41 & 42 and submit coverage report. - - mvn test -B -Pbuild41 jacoco:report && bash <(curl -s https://codecov.io/bash) -cF JDBC41 +##Test for JDBC Specification 43 & 42 and submit coverage report. + - mvn test -B -Pbuild41 jacoco:report && bash <(curl -s https://codecov.io/bash) -cF JDBC43 - mvn test -B -Pbuild42 jacoco:report && bash <(curl -s https://codecov.io/bash) -cF JDBC42 diff --git a/README.md b/README.md index 088b6c278..6d2d0a746 100644 --- a/README.md +++ b/README.md @@ -36,19 +36,19 @@ What's coming next? We will look into adding a more comprehensive set of tests, ## Build ### Prerequisites -* Java 8 +* Java 9 * [Maven](http://maven.apache.org/download.cgi) * An instance of SQL Server or Azure SQL Database that you can connect to. ### Build the JAR files Maven builds automatically trigger a set of verification tests to run. For these tests to pass, you will first need to add an environment variable in your system called `mssql_jdbc_test_connection_properties` to provide the [correct connection properties](https://msdn.microsoft.com/en-us/library/ms378428(v=sql.110).aspx) for your SQL Server or Azure SQL Database instance. -To build the jar files, you must use Java 8 with Maven. You can choose to build a JDBC 4.1 compliant jar file (for use with JRE 7) and/or a JDBC 4.2 compliant jar file (for use with JRE 8). +To build the jar files, you must use Java 9 with Maven. You can choose to build a JDBC 4.3 compliant jar file (for use with JRE 9) and/or a JDBC 4.2 compliant jar file (for use with JRE 8). * Maven: 1. If you have not already done so, add the environment variable `mssql_jdbc_test_connection_properties` in your system with the connection properties for your SQL Server or SQL DB instance. - 2. Run one of the commands below to build a JDBC 4.1 compliant jar or JDBC 4.2 compliant jar in the \target directory. - * Run `mvn install -Pbuild41`. This creates JDBC 4.1 compliant jar in \target directory + 2. Run one of the commands below to build a JDBC 4.3 compliant jar or JDBC 4.2 compliant jar in the \target directory. + * Run `mvn install -Pbuild43`. This creates JDBC 4.3 compliant jar in \target directory * Run `mvn install -Pbuild42`. This creates JDBC 4.2 compliant jar in \target directory **NOTE**: Beginning release v6.1.7, we will no longer be maintaining the existing [Gradle build script](build.gradle) and it will be left in the repository for reference. Please refer to issue [#62](https://github.com/Microsoft/mssql-jdbc/issues/62) for this decision. diff --git a/appveyor.yml b/appveyor.yml index cbd4229fe..8caae887f 100644 --- a/appveyor.yml +++ b/appveyor.yml @@ -32,9 +32,9 @@ build_script: - keytool -importkeystore -srckeystore cert.pfx -srcstoretype pkcs12 -destkeystore clientcert.jks -deststoretype JKS -srcstorepass password -deststorepass password - keytool -list -v -keystore clientcert.jks -storepass "password" > JavaKeyStore.txt - cd.. - - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild41 - - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild42 + # - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild41 + # - mvn install -DskipTests=true -Dmaven.javadoc.skip=true -B -V -Pbuild42 -test_script: - - mvn test -B -Pbuild41 - - mvn test -B -Pbuild42 +#test_script: +# - mvn test -B -Pbuild41 +# - mvn test -B -Pbuild42 diff --git a/maven-version-rules.xml b/maven-version-rules.xml new file mode 100644 index 000000000..6d126d278 --- /dev/null +++ b/maven-version-rules.xml @@ -0,0 +1,26 @@ + + + + + (?i).*Alpha(?:-?\d+)? + (?i).*Beta(?:-?\d+)? + (?i).*-B(?:-?\d+)? + (?i).*RC(?:-?\d+)? + (?i).*EA(?:-?\d+)? + (?i).*SNAPSHOT(?:-?\d+)? + + (?i).*M(?:-?\d+)? + + + + + + + + \ No newline at end of file diff --git a/pom.xml b/pom.xml index 1caec83f8..c6e459c4e 100644 --- a/pom.xml +++ b/pom.xml @@ -5,7 +5,7 @@ com.microsoft.sqlserver mssql-jdbc - 6.3.6.${jreVersion}-preview + 6.4.0-SNAPSHOT.${jreVersion} jar Microsoft JDBC Driver for SQL Server @@ -55,7 +55,7 @@ com.microsoft.azure adal4j - 1.3.0 + 1.4.0 true @@ -118,7 +118,7 @@ com.zaxxer HikariCP - 2.6.1 + 2.7.4 test @@ -139,10 +139,10 @@ - build41 + build42 - jre7 + jre8 @@ -152,10 +152,10 @@ 3.6.0 - **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java + **/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java - 1.7 - 1.7 + 1.8 + 1.8 @@ -167,21 +167,19 @@ ${project.build.outputDirectory}/META-INF/MANIFEST.MF - - + - + - build42 - + build43 true - jre8 + jre9 @@ -191,10 +189,10 @@ 3.6.0 - **/com/microsoft/sqlserver/jdbc/SQLServerJdbc41.java + **/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java - 1.8 - 1.8 + 9 + 9 @@ -279,7 +277,7 @@ org.apache.felix maven-bundle-plugin - 3.2.0 + 3.3.0 true @@ -301,7 +299,7 @@ org.apache.maven.plugins maven-javadoc-plugin - 2.10.4 + 3.0.0-M1 true @@ -336,6 +334,16 @@ + + + org.codehaus.mojo + versions-maven-plugin + true + + outdated-dependencies.txt + file:///${session.executionRootDirectory}/maven-version-rules.xml + + diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java b/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java index d86d481f4..112aac152 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/AuthenticationJNI.java @@ -41,6 +41,10 @@ final class AuthenticationJNI extends SSPIAuthentication { static int GetMaxSSPIBlobSize() { return sspiBlobMaxlen; } + + static boolean isDllLoaded() { + return enabled; + } static { UnsatisfiedLinkError temp = null; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java index 2fa7d0afe..1e90ef726 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/DDC.java @@ -232,7 +232,7 @@ static final Object convertFloatToObject(float floatVal, return new BigDecimal(Float.toString(floatVal)); case FLOAT: case DOUBLE: - return ((Float)floatVal).doubleValue(); + return (Float.valueOf(floatVal)).doubleValue(); case BINARY: return convertIntToBytes(Float.floatToRawIntBits(floatVal), 4); default: @@ -275,7 +275,7 @@ static final Object convertDoubleToObject(double doubleVal, case DOUBLE: return doubleVal; case REAL: - return ((Double)doubleVal).floatValue(); + return (Double.valueOf(doubleVal)).floatValue(); case INTEGER: return (int) doubleVal; case SMALLINT: // small and tinyint returned as short diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java b/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java index 0e905bc7d..cdfe4fb83 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/FailOverInfo.java @@ -71,7 +71,7 @@ private void setupInfo(SQLServerConnection con) throws SQLServerException { instancePort = con.getInstancePort(failoverPartner, instanceValue); try { - portNumber = new Integer(instancePort); + portNumber = Integer.parseInt(instancePort); } catch (NumberFormatException e) { // Should not get here as the server should give a proper port number anyway. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java index d6b28a14a..82513ea17 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/IOBuffer.java @@ -73,7 +73,7 @@ import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; import javax.net.ssl.X509TrustManager; -import javax.xml.bind.DatatypeConverter; +import java.nio.Buffer; final class TDS { // TDS protocol versions @@ -3107,7 +3107,7 @@ boolean isEOMSent() { void preparePacket() throws SQLServerException { if (tdsChannel.isLoggingPackets()) { Arrays.fill(logBuffer.array(), (byte) 0xFE); - logBuffer.clear(); + ((Buffer)logBuffer).clear(); } // Write a placeholder packet header. This will be replaced @@ -3183,8 +3183,8 @@ void startMessage(TDSCommand command, currentPacketSize = negotiatedPacketSize; } - socketBuffer.position(socketBuffer.limit()); - stagingBuffer.clear(); + ((Buffer) socketBuffer).position(((Buffer) socketBuffer).limit()); + ((Buffer)stagingBuffer).clear(); preparePacket(); writeMessageHeader(); @@ -3226,7 +3226,7 @@ void writeByte(byte value) throws SQLServerException { if (dataIsLoggable) logBuffer.put(value); else - logBuffer.position(logBuffer.position() + 1); + ((Buffer)logBuffer).position(((Buffer)logBuffer).position() + 1); } } else { @@ -3253,7 +3253,7 @@ void writeChar(char value) throws SQLServerException { if (dataIsLoggable) logBuffer.putChar(value); else - logBuffer.position(logBuffer.position() + 2); + ((Buffer)logBuffer).position( ((Buffer)logBuffer).position() + 2); } } else { @@ -3269,7 +3269,7 @@ void writeShort(short value) throws SQLServerException { if (dataIsLoggable) logBuffer.putShort(value); else - logBuffer.position(logBuffer.position() + 2); + ((Buffer)logBuffer).position( ((Buffer)logBuffer).position() + 2); } } else { @@ -3285,7 +3285,7 @@ void writeInt(int value) throws SQLServerException { if (dataIsLoggable) logBuffer.putInt(value); else - logBuffer.position(logBuffer.position() + 4); + ((Buffer)logBuffer).position( ((Buffer)logBuffer).position() + 4); } } else { @@ -3317,7 +3317,7 @@ void writeDouble(double value) throws SQLServerException { if (dataIsLoggable) logBuffer.putDouble(value); else - logBuffer.position(logBuffer.position() + 8); + ((Buffer)logBuffer).position( ((Buffer)logBuffer).position() + 8); } } else { @@ -3695,7 +3695,7 @@ void writeLong(long value) throws SQLServerException { if (dataIsLoggable) logBuffer.putLong(value); else - logBuffer.position(logBuffer.position() + 8); + ((Buffer)logBuffer).position( ((Buffer)logBuffer).position() + 8); } } else { @@ -3738,7 +3738,7 @@ void writeBytes(byte[] value, if (dataIsLoggable) logBuffer.put(value, offset + bytesWritten, bytesToWrite); else - logBuffer.position(logBuffer.position() + bytesToWrite); + ((Buffer)logBuffer).position( ((Buffer)logBuffer).position() + bytesToWrite); } bytesWritten += bytesToWrite; @@ -3765,7 +3765,7 @@ void writeWrappedBytes(byte value[], if (dataIsLoggable) logBuffer.put(value, 0, remaining); else - logBuffer.position(logBuffer.position() + remaining); + ((Buffer)logBuffer).position( ((Buffer)logBuffer).position() + remaining); } } @@ -3778,7 +3778,7 @@ void writeWrappedBytes(byte value[], if (dataIsLoggable) logBuffer.put(value, remaining, valueLength - remaining); else - logBuffer.position(logBuffer.position() + remaining); + ((Buffer)logBuffer).position( ((Buffer)logBuffer).position() + remaining); } } @@ -4100,7 +4100,7 @@ private void writePacket(int tdsMessageStatus) throws SQLServerException { } private void writePacketHeader(int tdsMessageStatus) { - int tdsMessageLength = stagingBuffer.position(); + int tdsMessageLength = ((Buffer)stagingBuffer).position(); ++packetNum; // Write the TDS packet header back at the start of the staging buffer @@ -4128,13 +4128,13 @@ private void writePacketHeader(int tdsMessageStatus) { void flush(boolean atEOM) throws SQLServerException { // First, flush any data left in the socket buffer. - tdsChannel.write(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining()); - socketBuffer.position(socketBuffer.limit()); + tdsChannel.write(socketBuffer.array(), ((Buffer)socketBuffer).position(), socketBuffer.remaining()); + ((Buffer)socketBuffer).position(((Buffer)socketBuffer).limit()); // If there is data in the staging buffer that needs to be written // to the socket, the socket buffer is now empty, so swap buffers // and start writing data from the staging buffer. - if (stagingBuffer.position() >= TDS_PACKET_HEADER_SIZE) { + if (((Buffer)stagingBuffer).position() >= TDS_PACKET_HEADER_SIZE) { // Swap the packet buffers ... ByteBuffer swapBuffer = stagingBuffer; stagingBuffer = socketBuffer; @@ -4146,14 +4146,14 @@ void flush(boolean atEOM) throws SQLServerException { // We need to use flip() rather than rewind() here so that // the socket buffer's limit is properly set for the last // packet, which may be shorter than the other packets. - socketBuffer.flip(); - stagingBuffer.clear(); + ((Buffer)socketBuffer).flip(); + ((Buffer)stagingBuffer).clear(); // If we are logging TDS packets then log the packet we're about // to send over the wire now. if (tdsChannel.isLoggingPackets()) { - tdsChannel.logPacket(logBuffer.array(), 0, socketBuffer.limit(), - this.toString() + " sending packet (" + socketBuffer.limit() + " bytes)"); + tdsChannel.logPacket(logBuffer.array(), 0, ((Buffer) socketBuffer).limit(), + this.toString() + " sending packet (" + ((Buffer) socketBuffer).limit() + " bytes)"); } // Prepare for the next packet @@ -4161,8 +4161,8 @@ void flush(boolean atEOM) throws SQLServerException { preparePacket(); // Finally, start sending data from the new socket buffer. - tdsChannel.write(socketBuffer.array(), socketBuffer.position(), socketBuffer.remaining()); - socketBuffer.position(socketBuffer.limit()); + tdsChannel.write(socketBuffer.array(), ((Buffer)socketBuffer).position(), socketBuffer.remaining()); + ((Buffer)socketBuffer).position( ((Buffer)socketBuffer).limit()); } } @@ -4625,7 +4625,7 @@ void writeTVPRows(TVP value) throws SQLServerException { if (con.equals(src_stmt.getConnection()) && 0 != resultSetServerCursorId) { cachedTVPHeaders = ByteBuffer.allocate(stagingBuffer.capacity()).order(stagingBuffer.order()); - cachedTVPHeaders.put(stagingBuffer.array(), 0, stagingBuffer.position()); + cachedTVPHeaders.put(stagingBuffer.array(), 0, ((Buffer)stagingBuffer).position()); cachedCommand = this.command; @@ -4651,9 +4651,9 @@ void writeTVPRows(TVP value) throws SQLServerException { if (tdsWritterCached) { command = cachedCommand; - stagingBuffer.clear(); - logBuffer.clear(); - writeBytes(cachedTVPHeaders.array(), 0, cachedTVPHeaders.position()); + ((Buffer)stagingBuffer).clear(); + ((Buffer)logBuffer).clear(); + writeBytes(cachedTVPHeaders.array(), 0, ((Buffer)cachedTVPHeaders).position()); } Object[] rowData = value.getRowData(); @@ -4952,7 +4952,7 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) isShortValue = columnPair.getValue().precision <= DataTypes.SHORT_VARTYPE_MAX_BYTES; isNull = (null == currentObject); if (currentObject instanceof String) - dataLength = isNull ? 0 : (toByteArray(currentObject.toString())).length; + dataLength = isNull ? 0 : (ParameterUtils.HexToBin(currentObject.toString())).length; else dataLength = isNull ? 0 : ((byte[]) currentObject).length; if (!isShortValue) { @@ -4971,7 +4971,7 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) if (dataLength > 0) { writeInt(dataLength); if (currentObject instanceof String) - writeBytes(toByteArray(currentObject.toString())); + writeBytes(ParameterUtils.HexToBin(currentObject.toString())); else writeBytes((byte[]) currentObject); } @@ -4985,7 +4985,7 @@ else if (DataTypes.UNKNOWN_STREAM_LENGTH == dataLength) else { writeShort((short) dataLength); if (currentObject instanceof String) - writeBytes(toByteArray(currentObject.toString())); + writeBytes(ParameterUtils.HexToBin(currentObject.toString())); else writeBytes((byte[]) currentObject); } @@ -5022,9 +5022,6 @@ private void writeTVPSqlVariantHeader(int length, writeByte(probBytes); } - private static byte[] toByteArray(String s) { - return DatatypeConverter.parseHexBinary(s); - } void writeTVPColumnMetaData(TVP value) throws SQLServerException { boolean isShortValue; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection43.java b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection43.java new file mode 100644 index 000000000..1689cde87 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ISQLServerConnection43.java @@ -0,0 +1,19 @@ +package com.microsoft.sqlserver.jdbc; + +import java.sql.ShardingKey; + +public interface ISQLServerConnection43 extends ISQLServerConnection { + + public void setShardingKey(ShardingKey shardingKey) throws SQLServerException; + + public void setShardingKey(ShardingKey shardingKey, + ShardingKey superShardingKey) throws SQLServerException; + + public boolean setShardingKeyIfValid(ShardingKey shardingKey, + int timeout) throws SQLServerException; + + public boolean setShardingKeyIfValid(ShardingKey shardingKey, + ShardingKey superShardingKey, + int timeout) throws SQLServerException; + +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/ReaderInputStream.java b/src/main/java/com/microsoft/sqlserver/jdbc/ReaderInputStream.java index 2667d5af0..1a2513816 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/ReaderInputStream.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/ReaderInputStream.java @@ -11,6 +11,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.Reader; +import java.nio.Buffer; import java.nio.ByteBuffer; import java.nio.CharBuffer; import java.nio.charset.Charset; @@ -182,7 +183,7 @@ private boolean encodeChars() throws IOException { } else { // Flip the buffer to be ready for put (reader read) operations. - rawChars.clear(); + ((Buffer)rawChars).clear(); } // Try to fill up the raw character buffer by reading available characters @@ -194,7 +195,7 @@ private boolean encodeChars() throws IOException { // - the reader throws any kind of Exception (driver throws an IOException) // - the reader violates its interface contract (driver throws an IOException) while (rawChars.hasRemaining()) { - int lastPosition = rawChars.position(); + int lastPosition = ((Buffer)rawChars).position(); int charsRead = 0; // Try reading from the app-supplied Reader @@ -218,7 +219,7 @@ private boolean encodeChars() throws IOException { if (-1 == charsRead) { // If the reader violates its interface contract then throw an exception. - if (rawChars.position() != lastPosition) + if (((Buffer)rawChars).position() != lastPosition) throw new IOException(SQLServerException.getErrString("R_streamReadReturnedInvalidValue")); // Check that the reader has returned exactly the amount of data we expect @@ -228,7 +229,7 @@ private boolean encodeChars() throws IOException { } // If there are no characters left to encode then we're done. - if (0 == rawChars.position()) { + if (0 == ((Buffer)rawChars).position()) { rawChars = null; atEndOfStream = true; return false; @@ -241,7 +242,7 @@ private boolean encodeChars() throws IOException { assert charsRead > 0; // If the reader violates its interface contract then throw an exception. - if (charsRead != rawChars.position() - lastPosition) + if (charsRead != ((Buffer)rawChars).position() - lastPosition) throw new IOException(SQLServerException.getErrString("R_streamReadReturnedInvalidValue")); // Check that the reader isn't trying to return more data than we expect @@ -255,7 +256,7 @@ private boolean encodeChars() throws IOException { // The raw character buffer may now have characters available for encoding. // Flip the buffer back to be ready for get (charset encode) operations. - rawChars.flip(); + ((Buffer)rawChars).flip(); } // If the raw character buffer remains empty, despite our efforts to (re)populate it, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerADAL4JUtils.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerADAL4JUtils.java index e3e10dd06..096543ffd 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerADAL4JUtils.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerADAL4JUtils.java @@ -1,11 +1,15 @@ package com.microsoft.sqlserver.jdbc; +import java.io.IOException; import java.net.MalformedURLException; import java.text.MessageFormat; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; +import java.util.logging.Level; + +import javax.security.auth.kerberos.KerberosPrincipal; import com.microsoft.aad.adal4j.AuthenticationContext; import com.microsoft.aad.adal4j.AuthenticationException; @@ -15,10 +19,13 @@ class SQLServerADAL4JUtils { + static final private java.util.logging.Logger adal4jLogger = java.util.logging.Logger + .getLogger("com.microsoft.sqlserver.jdbc.internals.SQLServerADAL4JUtils"); + static SqlFedAuthToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo, - String user, - String password, - String authenticationString) throws SQLServerException { + String user, + String password, + String authenticationString) throws SQLServerException { ExecutorService executorService = Executors.newFixedThreadPool(1); try { AuthenticationContext context = new AuthenticationContext(fedAuthInfo.stsurl, false, executorService); @@ -42,7 +49,8 @@ static SqlFedAuthToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo, String correctedErrorMessage = e.getCause().getMessage().replaceAll("\\\\r\\\\n", "\r\n"); AuthenticationException correctedAuthenticationException = new AuthenticationException(correctedErrorMessage); - // SQLServerException is caused by ExecutionException, which is caused by AuthenticationException + // SQLServerException is caused by ExecutionException, which is caused by + // AuthenticationException // to match the exception tree before error message correction ExecutionException correctedExecutionException = new ExecutionException(correctedAuthenticationException); @@ -52,4 +60,57 @@ static SqlFedAuthToken getSqlFedAuthToken(SqlFedAuthInfo fedAuthInfo, executorService.shutdown(); } } + + static SqlFedAuthToken getSqlFedAuthTokenIntegrated(SqlFedAuthInfo fedAuthInfo, + String authenticationString) throws SQLServerException { + ExecutorService executorService = Executors.newFixedThreadPool(1); + + try { + // principal name does not matter, what matters is the realm name + // it gets the username in principal_name@realm_name format + KerberosPrincipal kerberosPrincipal = new KerberosPrincipal("username"); + String username = kerberosPrincipal.getName(); + + if (adal4jLogger.isLoggable(Level.FINE)) { + adal4jLogger.fine(adal4jLogger.toString() + " realm name is:" + kerberosPrincipal.getRealm()); + } + + AuthenticationContext context = new AuthenticationContext(fedAuthInfo.stsurl, false, executorService); + Future future = context.acquireToken(fedAuthInfo.spn, ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID, + username, null, null); + + AuthenticationResult authenticationResult = future.get(); + SqlFedAuthToken fedAuthToken = new SqlFedAuthToken(authenticationResult.getAccessToken(), authenticationResult.getExpiresOnDate()); + + return fedAuthToken; + } + catch (InterruptedException | IOException e) { + throw new SQLServerException(e.getMessage(), e); + } + catch (ExecutionException e) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution")); + Object[] msgArgs = {"", authenticationString}; + + if (null == e.getCause() || null == e.getCause().getMessage()) { + // the case when Future's outcome has no AuthenticationResult but exception + throw new SQLServerException(form.format(msgArgs), null); + } + else { + // the cause error message uses \\n\\r which does not give correct format + // change it to \r\n to provide correct format + String correctedErrorMessage = e.getCause().getMessage().replaceAll("\\\\r\\\\n", "\r\n"); + AuthenticationException correctedAuthenticationException = new AuthenticationException(correctedErrorMessage); + + // SQLServerException is caused by ExecutionException, which is caused by + // AuthenticationException + // to match the exception tree before error message correction + ExecutionException correctedExecutionException = new ExecutionException(correctedAuthenticationException); + + throw new SQLServerException(form.format(msgArgs), null, 0, correctedExecutionException); + } + } + finally { + executorService.shutdown(); + } + } } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Factory.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Factory.java index 8f71ec6b6..3a170da2d 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Factory.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerAeadAes256CbcHmac256Factory.java @@ -12,8 +12,7 @@ import java.text.MessageFormat; import java.util.concurrent.ConcurrentHashMap; - -import javax.xml.bind.DatatypeConverter; +import java.util.Base64; /** * Factory for SQLServerAeadAes256CbcHmac256Algorithm @@ -38,7 +37,7 @@ SQLServerEncryptionAlgorithm create(SQLServerSymmetricKey columnEncryptionKey, } StringBuilder factoryKeyBuilder = new StringBuilder(); - factoryKeyBuilder.append(DatatypeConverter.printBase64Binary(new String(columnEncryptionKey.getRootKey(), UTF_8).getBytes())); + factoryKeyBuilder.append(Base64.getEncoder().encodeToString(new String(columnEncryptionKey.getRootKey(), UTF_8).getBytes())); factoryKeyBuilder.append(":"); factoryKeyBuilder.append(encryptionType); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java index 458801d19..cce49a416 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCSVFileRecord.java @@ -574,7 +574,7 @@ public Object[] getRowData() throws SQLServerException { case Types.BIGINT: { BigDecimal bd = new BigDecimal(data[pair.getKey() - 1].trim()); try { - dataRow[pair.getKey() - 1] = bd.setScale(0, BigDecimal.ROUND_DOWN).longValueExact(); + dataRow[pair.getKey() - 1] = bd.setScale(0, RoundingMode.DOWN).longValueExact(); } catch (ArithmeticException ex) { String value = "'" + data[pair.getKey() - 1] + "'"; MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_errorConvertingValue")); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java index a383e1946..50f711c29 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerBulkCopy.java @@ -327,7 +327,7 @@ public void run() { public SQLServerBulkCopy(Connection connection) throws SQLServerException { loggerExternal.entering(loggerClassName, "SQLServerBulkCopy", connection); - if (null == connection || !connection.getClass().equals(SQLServerConnection.class)) { + if (null == connection || !(connection instanceof SQLServerConnection)) { SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDestConnection"), null, false); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java index 91a473b73..5f8bc8001 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerColumnEncryptionCertificateStoreProvider.java @@ -22,10 +22,10 @@ import java.security.cert.CertificateException; import java.security.cert.X509Certificate; import java.text.MessageFormat; +import java.util.Base64; import java.util.Enumeration; import java.util.Locale; -import javax.xml.bind.DatatypeConverter; /** * The implementation of the key store provider for the Windows Certificate Store. This class enables using keys stored in the Windows Certificate @@ -140,7 +140,7 @@ private String getThumbPrint(X509Certificate cert) throws NoSuchAlgorithmExcepti byte[] der = cert.getEncoded(); md.update(der); byte[] digest = md.digest(); - return DatatypeConverter.printHexBinary(digest); + return Base64.getEncoder().encodeToString(digest); } private CertificateDetails getCertificateByThumbprint(String storeLocation, diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java index a95ce9c9f..c8232d71a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection.java @@ -1539,11 +1539,6 @@ Connection connectInternal(Properties propsIn, throw new SQLServerException(SQLServerException.getErrString("R_AccessTokenWithUserPassword"), null); } - if ((!System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows")) - && (authenticationString.equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString()))) { - throw new SQLServerException(SQLServerException.getErrString("R_AADIntegratedOnNonWindows"), null); - } - // Turn off TNIR for FedAuth if user does not set TNIR explicitly if (!userSetTNIR) { if ((!authenticationString.equalsIgnoreCase(SqlAuthentication.NotSpecified.toString())) || (null != accessTokenInByte)) { @@ -3186,8 +3181,9 @@ public PreparedStatement prepareStatement(String sql, checkClosed(); PreparedStatement st; - - if (Util.use42Wrapper()) { + + // Make sure SQLServerPreparedStatement42 is used for 4.2 and above. + if (Util.use42Wrapper() || Util.use43Wrapper()) { st = new SQLServerPreparedStatement42(this, sql, resultSetType, resultSetConcurrency, SQLServerStatementColumnEncryptionSetting.UseConnectionSetting); } @@ -3211,7 +3207,8 @@ private PreparedStatement prepareStatement(String sql, PreparedStatement st; - if (Util.use42Wrapper()) { + // Make sure SQLServerPreparedStatement42 is used for 4.2 and above. + if (Util.use42Wrapper() || Util.use43Wrapper()) { st = new SQLServerPreparedStatement42(this, sql, resultSetType, resultSetConcurrency, stmtColEncSetting); } else { @@ -3232,7 +3229,8 @@ public CallableStatement prepareCall(String sql, CallableStatement st; - if (Util.use42Wrapper()) { + // Make sure SQLServerCallableStatement42 is used for 4.2 and above. + if (Util.use42Wrapper() || Util.use43Wrapper()) { st = new SQLServerCallableStatement42(this, sql, resultSetType, resultSetConcurrency, SQLServerStatementColumnEncryptionSetting.UseConnectionSetting); } @@ -3855,18 +3853,13 @@ private SqlFedAuthToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throws SQLSe // fedAuthInfo should not be null. assert null != fedAuthInfo; - // No:of milliseconds to sleep for the inital back off. - int sleepInterval = 100; - - // No:of attempts, for tracing purposes, if we underwent retries. - int numberOfAttempts = 0; - String user = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.USER.toString()); String password = activeConnectionProperties.getProperty(SQLServerDriverStringProperty.PASSWORD.toString()); + // No:of milliseconds to sleep for the inital back off. + int sleepInterval = 100; + while (true) { - numberOfAttempts++; - if (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryPassword.toString())) { fedAuthToken = SQLServerADAL4JUtils.getSqlFedAuthToken(fedAuthInfo, user, password, authenticationString); @@ -3874,69 +3867,80 @@ private SqlFedAuthToken getFedAuthToken(SqlFedAuthInfo fedAuthInfo) throws SQLSe break; } else if (authenticationString.trim().equalsIgnoreCase(SqlAuthentication.ActiveDirectoryIntegrated.toString())) { - try { - long expirationFileTime = 0; - FedAuthDllInfo dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl, fedAuthInfo.spn, - clientConnectionId.toString(), ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID, expirationFileTime); + + // If operating system is windows and sqljdbc_auth is loaded then choose the DLL authentication. + if (System.getProperty("os.name").toLowerCase(Locale.ENGLISH).startsWith("windows") && AuthenticationJNI.isDllLoaded()) { + try { + long expirationFileTime = 0; + FedAuthDllInfo dllInfo = AuthenticationJNI.getAccessTokenForWindowsIntegrated(fedAuthInfo.stsurl, fedAuthInfo.spn, + clientConnectionId.toString(), ActiveDirectoryAuthentication.JDBC_FEDAUTH_CLIENT_ID, expirationFileTime); - // AccessToken should not be null. - assert null != dllInfo.accessTokenBytes; + // AccessToken should not be null. + assert null != dllInfo.accessTokenBytes; - byte[] accessTokenFromDLL = dllInfo.accessTokenBytes; + byte[] accessTokenFromDLL = dllInfo.accessTokenBytes; - String accessToken = new String(accessTokenFromDLL, UTF_16LE); + String accessToken = new String(accessTokenFromDLL, UTF_16LE); - fedAuthToken = new SqlFedAuthToken(accessToken, dllInfo.expiresIn); + fedAuthToken = new SqlFedAuthToken(accessToken, dllInfo.expiresIn); - // Break out of the retry loop in successful case. - break; - } - catch (DLLException adalException) { - - // the sqljdbc_auth.dll return -1 for errorCategory, if unable to load the adalsql.dll - int errorCategory = adalException.GetCategory(); - if (-1 == errorCategory) { - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnableLoadADALSqlDll")); - Object[] msgArgs = {Integer.toHexString(adalException.GetState())}; - throw new SQLServerException(form.format(msgArgs), null); + // Break out of the retry loop in successful case. + break; } + catch (DLLException adalException) { + + // the sqljdbc_auth.dll return -1 for errorCategory, if unable to load the adalsql.dll + int errorCategory = adalException.GetCategory(); + if (-1 == errorCategory) { + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnableLoadADALSqlDll")); + Object[] msgArgs = {Integer.toHexString(adalException.GetState())}; + throw new SQLServerException(form.format(msgArgs), null); + } - int millisecondsRemaining = TimerRemaining(timerExpire); - if (ActiveDirectoryAuthentication.GET_ACCESS_TOKEN_TANSISENT_ERROR != errorCategory || timerHasExpired(timerExpire) - || (sleepInterval >= millisecondsRemaining)) { + int millisecondsRemaining = TimerRemaining(timerExpire); + if (ActiveDirectoryAuthentication.GET_ACCESS_TOKEN_TANSISENT_ERROR != errorCategory || timerHasExpired(timerExpire) + || (sleepInterval >= millisecondsRemaining)) { - String errorStatus = Integer.toHexString(adalException.GetStatus()); + String errorStatus = Integer.toHexString(adalException.GetStatus()); - if (connectionlogger.isLoggable(Level.FINER)) { - connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken.AdalException category:" + errorCategory - + " error: " + errorStatus); - } + if (connectionlogger.isLoggable(Level.FINER)) { + connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken.AdalException category:" + errorCategory + + " error: " + errorStatus); + } - MessageFormat form1 = new MessageFormat(SQLServerException.getErrString("R_ADALAuthenticationMiddleErrorMessage")); - String errorCode = Integer.toHexString(adalException.GetStatus()).toUpperCase(); - Object[] msgArgs1 = {errorCode, adalException.GetState()}; - SQLServerException middleException = new SQLServerException(form1.format(msgArgs1), adalException); + MessageFormat form1 = new MessageFormat(SQLServerException.getErrString("R_ADALAuthenticationMiddleErrorMessage")); + String errorCode = Integer.toHexString(adalException.GetStatus()).toUpperCase(); + Object[] msgArgs1 = {errorCode, adalException.GetState()}; + SQLServerException middleException = new SQLServerException(form1.format(msgArgs1), adalException); - MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution")); - Object[] msgArgs = {user, authenticationString}; - throw new SQLServerException(form.format(msgArgs), null, 0, middleException); - } + MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_ADALExecution")); + Object[] msgArgs = {user, authenticationString}; + throw new SQLServerException(form.format(msgArgs), null, 0, middleException); + } - if (connectionlogger.isLoggable(Level.FINER)) { - connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken sleeping: " + sleepInterval + " milliseconds."); - connectionlogger - .fine(toString() + " SQLServerConnection.getFedAuthToken remaining: " + millisecondsRemaining + " milliseconds."); - } + if (connectionlogger.isLoggable(Level.FINER)) { + connectionlogger.fine(toString() + " SQLServerConnection.getFedAuthToken sleeping: " + sleepInterval + " milliseconds."); + connectionlogger + .fine(toString() + " SQLServerConnection.getFedAuthToken remaining: " + millisecondsRemaining + " milliseconds."); + } - try { - Thread.sleep(sleepInterval); - } - catch (InterruptedException e1) { - // re-interrupt the current thread, in order to restore the thread's interrupt status. - Thread.currentThread().interrupt(); + try { + Thread.sleep(sleepInterval); + } + catch (InterruptedException e1) { + // re-interrupt the current thread, in order to restore the thread's interrupt status. + Thread.currentThread().interrupt(); + } + sleepInterval = sleepInterval * 2; } - sleepInterval = sleepInterval * 2; } + // else choose ADAL4J for integrated authentication. This option is supported for both windows and unix, so we don't need to check the + // OS version here. + else { + fedAuthToken = SQLServerADAL4JUtils.getSqlFedAuthTokenIntegrated(fedAuthInfo, authenticationString); + } + // Break out of the retry loop in successful case. + break; } } @@ -4655,7 +4659,8 @@ public PreparedStatement prepareStatement(java.lang.String sql, PreparedStatement st; - if (Util.use42Wrapper()) { + // Make sure SQLServerPreparedStatement42 is used for 4.2 and above. + if (Util.use42Wrapper() || Util.use43Wrapper()) { st = new SQLServerPreparedStatement42(this, sql, nType, nConcur, stmtColEncSetting); } else { @@ -4690,7 +4695,8 @@ public CallableStatement prepareCall(String sql, CallableStatement st; - if (Util.use42Wrapper()) { + // Make sure SQLServerCallableStatement42 is used for 4.2 and above + if (Util.use42Wrapper() || Util.use43Wrapper()) { st = new SQLServerCallableStatement42(this, sql, nType, nConcur, stmtColEncSetiing); } else { @@ -5248,6 +5254,16 @@ public T unwrap(Class iface) throws SQLException { return t; } + public void beginRequest() throws SQLFeatureNotSupportedException { + DriverJDBCVersion.checkSupportsJDBC43(); + throw new SQLFeatureNotSupportedException("beginRequest not implemented"); + } + + public void endRequest() throws SQLFeatureNotSupportedException { + DriverJDBCVersion.checkSupportsJDBC43(); + throw new SQLFeatureNotSupportedException("endRequest not implemented"); + } + /** * Replace JDBC syntax parameter markets '?' with SQL Server paramter markers @p1, @p2 etc... * diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection43.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection43.java new file mode 100644 index 000000000..217bcfc72 --- /dev/null +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerConnection43.java @@ -0,0 +1,36 @@ +package com.microsoft.sqlserver.jdbc; + +import java.sql.SQLFeatureNotSupportedException; +import java.sql.ShardingKey; + +public class SQLServerConnection43 extends SQLServerConnection implements ISQLServerConnection43 { + + SQLServerConnection43(String parentInfo) throws SQLServerException { + super(parentInfo); + } + + public void setShardingKey(ShardingKey shardingKey) throws SQLServerException { + DriverJDBCVersion.checkSupportsJDBC43(); + throw new SQLServerException("setShardingKey not implemented", new SQLFeatureNotSupportedException("setShardingKey not implemented")); + } + + public void setShardingKey(ShardingKey shardingKey, + ShardingKey superShardingKey) throws SQLServerException { + DriverJDBCVersion.checkSupportsJDBC43(); + throw new SQLServerException("setShardingKey not implemented", new SQLFeatureNotSupportedException("setShardingKey not implemented")) ; + } + + public boolean setShardingKeyIfValid(ShardingKey shardingKey, + int timeout) throws SQLServerException { + DriverJDBCVersion.checkSupportsJDBC43(); + throw new SQLServerException("setShardingKeyIfValid not implemented", new SQLFeatureNotSupportedException("setShardingKeyIfValid not implemented")); + } + + public boolean setShardingKeyIfValid(ShardingKey shardingKey, + ShardingKey superShardingKey, + int timeout) throws SQLServerException { + DriverJDBCVersion.checkSupportsJDBC43(); + throw new SQLServerException("setShardingKeyIfValid not implemented", new SQLFeatureNotSupportedException("setShardingKeyIfValid not implemented")); + } + +} diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java index 854275214..ebe4fee35 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSource.java @@ -864,7 +864,7 @@ private void setIntProperty(Properties props, int propValue) { if (loggerExternal.isLoggable(java.util.logging.Level.FINER)) loggerExternal.entering(getClassNameLogging(), "set" + propKey, propValue); - props.setProperty(propKey, ((Integer)propValue).toString()); + props.setProperty(propKey, Integer.valueOf(propValue).toString()); loggerExternal.exiting(getClassNameLogging(), "set" + propKey); } @@ -1003,7 +1003,13 @@ SQLServerConnection getConnectionInternal(String username, // Create new connection and connect. if (dsLogger.isLoggable(Level.FINER)) dsLogger.finer(toString() + " Begin create new connection."); - SQLServerConnection result = new SQLServerConnection(toString()); + SQLServerConnection result = null; + if (Util.use43Wrapper()) { + result = new SQLServerConnection43(toString()); + } + else { + result = new SQLServerConnection(toString()); + } result.connect(mergedProps, pooledConnection); if (dsLogger.isLoggable(Level.FINER)) dsLogger.finer(toString() + " End create new connection " + result.toString()); @@ -1105,6 +1111,7 @@ public T unwrap(Class iface) throws SQLException { return t; } + // Returns unique id for each DataSource instance. private static int nextDataSourceID() { return baseDataSourceID.incrementAndGet(); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSourceObjectFactory.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSourceObjectFactory.java index 761eb26ae..6518d7057 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSourceObjectFactory.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDataSourceObjectFactory.java @@ -8,6 +8,7 @@ package com.microsoft.sqlserver.jdbc; +import java.lang.reflect.InvocationTargetException; import java.util.Hashtable; import javax.naming.Context; @@ -59,7 +60,7 @@ public Object getObjectInstance(Object ref, // Create class instance and initialize using reference. Class dataSourceClass = Class.forName(className); - Object dataSourceClassInstance = dataSourceClass.newInstance(); + Object dataSourceClassInstance = dataSourceClass.getDeclaredConstructor().newInstance(); // If this class we created does not cast to SQLServerDataSource, then caller // passed in the wrong reference to our factory. @@ -79,6 +80,18 @@ public Object getObjectInstance(Object ref, catch (IllegalAccessException e) { SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDataSourceReference"), null, true); } + catch (IllegalArgumentException e) { + SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDataSourceReference"), null, true); + } + catch (InvocationTargetException e) { + SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDataSourceReference"), null, true); + } + catch (NoSuchMethodException e) { + SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDataSourceReference"), null, true); + } + catch (SecurityException e) { + SQLServerException.makeFromDriverError(null, null, SQLServerException.getErrString("R_invalidDataSourceReference"), null, true); + } // no chance of getting here but to keep the compiler happy return null; diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java index f76c26af1..92ab65877 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDatabaseMetaData.java @@ -401,6 +401,13 @@ public boolean supportsRefCursors() throws SQLException { return false; } + public boolean supportsSharding() throws SQLException { + DriverJDBCVersion.checkSupportsJDBC43(); + checkClosed(); + + return false; + } + /* L0 */ public java.sql.ResultSet getCatalogs() throws SQLServerException { if (loggerExternal.isLoggable(Level.FINER) && Util.IsActivityTraceOn()) { loggerExternal.finer(toString() + " ActivityId: " + ActivityCorrelator.getNext().toString()); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java index 9ea0c7a5b..ae4e1fd09 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerDriver.java @@ -614,7 +614,12 @@ static String getPropertyOnlyName(String name, // Merge connectProperties (from URL) and supplied properties from user. Properties connectProperties = parseAndMergeProperties(Url, suppliedProperties); if (connectProperties != null) { - result = new SQLServerConnection(toString()); + if (Util.use43Wrapper()) { + result = new SQLServerConnection43(toString()); + } + else { + result = new SQLServerConnection(toString()); + } result.connect(connectProperties, null); } loggerExternal.exiting(getClassNameLogging(), "connect", result); @@ -634,7 +639,7 @@ private Properties parseAndMergeProperties(String Url, // put the user properties into the connect properties int nTimeout = DriverManager.getLoginTimeout(); if (nTimeout > 0) { - connectProperties.put(SQLServerDriverIntProperty.LOGIN_TIMEOUT.toString(), ((Integer)nTimeout).toString()); + connectProperties.put(SQLServerDriverIntProperty.LOGIN_TIMEOUT.toString(), Integer.valueOf(nTimeout).toString()); } // Merge connectProperties (from URL) and supplied properties from user. diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java index 0d078cc1a..1950daf62 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc42.java @@ -24,6 +24,10 @@ final class DriverJDBCVersion { static final void checkSupportsJDBC42() { } + + static final void checkSupportsJDBC43() { + throw new UnsupportedOperationException(SQLServerException.getErrString("R_notSupported")); + } static final void throwBatchUpdateException(SQLServerException lastError, long[] updateCounts) throws BatchUpdateException { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc41.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java similarity index 55% rename from src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc41.java rename to src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java index 09bb912bf..1f60cd53a 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc41.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerJdbc43.java @@ -8,24 +8,29 @@ package com.microsoft.sqlserver.jdbc; +import java.sql.BatchUpdateException; + /** - * Shims for JDBC 4.1 JAR. + * Shims for JDBC 4.3 JAR. * - * JDBC 4.1 public methods should always check the SQLServerJdbcVersion first to make sure that they are not operable in any earlier driver version. + * JDBC 4.3 public methods should always check the SQLServerJdbcVersion first to make sure that they are not operable in any earlier driver version. * That is, they should throw an exception, be a no-op, or whatever. */ final class DriverJDBCVersion { + // The 4.3 driver is compliant to JDBC 4.3. static final int major = 4; - static final int minor = 1; + static final int minor = 3; + static final void checkSupportsJDBC43() { + } + static final void checkSupportsJDBC42() { - throw new UnsupportedOperationException(SQLServerException.getErrString("R_notSupported")); } - // Stub for the new overloaded method in BatchUpdateException in JDBC 4.2 static final void throwBatchUpdateException(SQLServerException lastError, - long[] updateCounts) { - throw new UnsupportedOperationException(SQLServerException.getErrString("R_notSupported")); + long[] updateCounts) throws BatchUpdateException { + throw new BatchUpdateException(lastError.getMessage(), lastError.getSQLState(), lastError.getErrorCode(), updateCounts, + new Throwable(lastError.getMessage())); } -} +} \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java index 63e701505..dc21fbbdb 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResource.java @@ -339,7 +339,6 @@ protected Object[][] getContents() { {"R_TVPEmptyMetadata", "There are not enough fields in the Structured type. Structured types must have at least one field."}, {"R_TVPInvalidValue", "The value provided for Table-Valued Parameter {0} is not valid. Only SQLServerDataTable, ResultSet and ISQLServerDataRecord objects are supported."}, {"R_TVPInvalidColumnValue", "Input data is not in correct format."}, - {"R_AADIntegratedOnNonWindows","ActiveDirectoryIntegrated is only supported on Windows operating systems."}, {"R_TVPSortOrdinalGreaterThanFieldCount", "The sort ordinal {0} on field {1} exceeds the total number of fields."}, {"R_TVPMissingSortOrderOrOrdinal", "The sort order and ordinal must either both be specified, or neither should be specified (SortOrder.Unspecified and -1). The values given were: order = {0}, ordinal = {1}."}, {"R_TVPDuplicateSortOrdinal", "The sort ordinal {0} was specified twice."}, @@ -390,7 +389,7 @@ protected Object[][] getContents() { {"R_invalidStringValue", "SQL_VARIANT does not support string values of length greater than 8000."}, {"R_invalidValueForTVPWithSQLVariant", "Use of TVPs containing null sql_variant columns is not supported."}, {"R_invalidDataTypeSupportForSQLVariant", "Unexpected TDS type ' '{0}' ' in SQL_VARIANT."}, - {"R_sslProtocolPropertyDescription", "SSL protocol label from TLS, TLSv1, TLSv1.1 & TLSv1.2. The default is TLS."}, - {"R_invalidSSLProtocol", "SSL Protocol {0} label is not valid. Only TLS, TLSv1, TLSv1.1 & TLSv1.2 are supported."}, + {"R_sslProtocolPropertyDescription", "SSL protocol label from TLS, TLSv1, TLSv1.1, and TLSv1.2. The default is TLS."}, + {"R_invalidSSLProtocol", "SSL Protocol {0} label is not valid. Only TLS, TLSv1, TLSv1.1, and TLSv1.2 are supported."}, }; } \ No newline at end of file diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java index 9b7933705..37e1ac033 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerResultSet.java @@ -939,7 +939,6 @@ private void updateCurrentRow(int rowsToMove) { * and so on. * * @return false when there are no more rows to read - * @return true otherwise */ public boolean next() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "next"); @@ -1041,8 +1040,7 @@ public boolean wasNull() throws SQLServerException { } /** - * @return true if the cursor is before the first row in this result set - * @return false otherwise or if thie result set contains no rows. + * @return true if the cursor is before the first row in this result set, returns false otherwise or if the result set contains no rows. */ public boolean isBeforeFirst() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "isBeforeFirst"); @@ -1144,7 +1142,6 @@ public boolean isAfterLast() throws SQLServerException { * TYPE_SS_SCROLL_STATIC, TYPE_SS_SCROLL_KEYSET, TYPE_SS_SCROLL_DYNAMIC. * * @return true if the cursor is on the first row in this result set - * @return false otherwise */ public boolean isFirst() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "isFirst"); @@ -1182,7 +1179,6 @@ public boolean isFirst() throws SQLServerException { * TYPE_SS_SCROLL_STATIC, TYPE_SS_SCROLL_KEYSET, TYPE_SS_SCROLL_DYNAMIC. * * @return true if the cursor is on the last row in this result set - * @return false otherwise */ public boolean isLast() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "isLast"); @@ -1302,8 +1298,7 @@ private void moveAfterLast() throws SQLServerException { * This method should be called only on ResultSet objects that are scrollable: TYPE_SCROLL_SENSITIVE, TYPE_SCROLL_INSENSITIVE, * TYPE_SS_SCROLL_STATIC, TYPE_SS_SCROLL_KEYSET, TYPE_SS_SCROLL_DYNAMIC. * - * @return true if the cursor is on a valid row - * @return false if there are no rows in this ResultSet object + * @return true if the cursor is on a valid row, otherwise returns false if there are no rows in this ResultSet object */ public boolean first() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "first"); @@ -1353,8 +1348,7 @@ private void moveFirst() throws SQLServerException { * This method should be called only on ResultSet objects that are scrollable: TYPE_SCROLL_SENSITIVE, TYPE_SCROLL_INSENSITIVE, * TYPE_SS_SCROLL_STATIC, TYPE_SS_SCROLL_KEYSET, TYPE_SS_SCROLL_DYNAMIC. * - * @return true if the cursor is on a valid row - * @return false if there are no rows in this ResultSet object + * @return true if the cursor is on a valid row, otherwise returns false if there are no rows in this ResultSet object */ public boolean last() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "last"); @@ -1442,8 +1436,8 @@ public int getRow() throws SQLServerException { * This method should be called only on ResultSet objects that are scrollable: TYPE_SCROLL_SENSITIVE, TYPE_SCROLL_INSENSITIVE, * TYPE_SS_SCROLL_STATIC, TYPE_SS_SCROLL_KEYSET, TYPE_SS_SCROLL_DYNAMIC. * - * @return true if the cursor is on a valid row in this result set - * @return false if the cursor is before the first row or after the last row + * @return true if the cursor is on a valid row in this result set, otherwise returns false if the cursor is before the first row or after the + * last row */ public boolean absolute(int row) throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "absolute"); @@ -1759,7 +1753,6 @@ private int clientMoveAbsolute(int row) throws SQLServerException { * TYPE_SS_SCROLL_STATIC, TYPE_SS_SCROLL_KEYSET, TYPE_SS_SCROLL_DYNAMIC. * * @return true if the cursor is on a valid row in this result set - * @return false otherwise */ public boolean previous() throws SQLServerException { loggerExternal.entering(getClassNameLogging(), "previous"); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSQLXML.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSQLXML.java index 7e8881d8b..3e99ab41f 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSQLXML.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSQLXML.java @@ -23,6 +23,8 @@ import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; +import javax.xml.parsers.SAXParser; +import javax.xml.parsers.SAXParserFactory; import javax.xml.stream.XMLInputFactory; import javax.xml.stream.XMLOutputFactory; import javax.xml.stream.XMLStreamException; @@ -48,8 +50,6 @@ import org.xml.sax.InputSource; import org.xml.sax.SAXException; import org.xml.sax.XMLReader; -import org.xml.sax.helpers.XMLReaderFactory; - /** * SQLServerSQLXML represents an XML object and implements a java.sql.SQLXML. */ @@ -405,12 +405,14 @@ private DOMSource getDOMSource() throws SQLException { private SAXSource getSAXSource() throws SQLException { try { InputSource src = new InputSource(contents); - XMLReader reader = XMLReaderFactory.createXMLReader(); + SAXParserFactory factory = SAXParserFactory.newInstance(); + SAXParser parser = factory.newSAXParser(); + XMLReader reader = parser.getXMLReader(); SAXSource saxSource = new SAXSource(reader, src); return saxSource; } - catch (SAXException e) { + catch (SAXException | ParserConfigurationException e) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_failedToParseXML")); Object[] msgArgs = {e.toString()}; SQLServerException.makeFromDriverError(con, null, form.format(msgArgs), null, true); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java index 9385fd165..696cfa6c5 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerStatement.java @@ -1569,7 +1569,8 @@ boolean onInfo(TDSReader tdsReader) throws SQLServerException { // Not an error. Is it a result set? else if (nextResult.isResultSet()) { - if (Util.use42Wrapper()) { + // Make sure SQLServerResultSet42 is used for 4.2 and above + if (Util.use42Wrapper() || Util.use43Wrapper()) { resultSet = new SQLServerResultSet42(this); } else { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java index 76b886786..04f9335ff 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerSymmetricKeyCache.java @@ -18,7 +18,7 @@ import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.ThreadFactory; -import javax.xml.bind.DatatypeConverter; +import java.util.Base64;; class CacheClear implements Runnable { @@ -98,7 +98,7 @@ SQLServerSymmetricKey getKey(EncryptionKeyInfo keyInfo, String keyLookupValue; keyLookupValuebuffer.append(":"); - keyLookupValuebuffer.append(DatatypeConverter.printBase64Binary((new String(keyInfo.encryptedKey, UTF_8)).getBytes())); + keyLookupValuebuffer.append(Base64.getEncoder().encodeToString((new String(keyInfo.encryptedKey, UTF_8)).getBytes())); keyLookupValuebuffer.append(":"); keyLookupValuebuffer.append(keyInfo.keyStoreName); diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java index c1f8d69fd..424fe52ce 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SQLServerXAConnection.java @@ -44,7 +44,13 @@ public final class SQLServerXAConnection extends SQLServerPooledConnection imple if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer("Creating an internal control connection for" + toString()); - physicalControlConnection = new SQLServerConnection(toString()); + physicalControlConnection = null; + if (Util.use43Wrapper()) { + physicalControlConnection = new SQLServerConnection43(toString()); + } + else { + physicalControlConnection = new SQLServerConnection(toString()); + } physicalControlConnection.connect(controlConnectionProperties, null); if (xaLogger.isLoggable(Level.FINER)) xaLogger.finer("Created an internal control connection" + physicalControlConnection.toString() + " for " + toString() diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/SqlVariant.java b/src/main/java/com/microsoft/sqlserver/jdbc/SqlVariant.java index 63861a49c..868db7102 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/SqlVariant.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/SqlVariant.java @@ -62,7 +62,7 @@ static sqlVariantProbBytes valueOf(int intValue) { if (!(0 <= intValue && intValue < valuesTypes.length) || null == (tdsType = valuesTypes[intValue])) { MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unknownSSType")); - Object[] msgArgs = {(Integer)intValue}; + Object[] msgArgs = {Integer.valueOf(intValue)}; throw new IllegalArgumentException(form.format(msgArgs)); } diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java index 028c5efa0..c8842ae09 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/Util.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/Util.java @@ -1006,6 +1006,28 @@ static synchronized boolean checkIfNeedNewAccessToken(SQLServerConnection connec static boolean use42Wrapper() { return use42Wrapper; } + + static final boolean use43Wrapper; + + static { + boolean supportJDBC43 = true; + try { + DriverJDBCVersion.checkSupportsJDBC43(); + } + catch (UnsupportedOperationException e) { + supportJDBC43 = false; + } + + double jvmVersion = Double.parseDouble(Util.SYSTEM_SPEC_VERSION); + + use43Wrapper = supportJDBC43 && (9 <= jvmVersion); + } + + // if driver is for JDBC 43 and jvm version is 9 or higher, then always return as SQLServerConnection43, + // otherwise return SQLServerConnection + static boolean use43Wrapper() { + return use43Wrapper; + } } final class SQLIdentifier { diff --git a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java index e0ea30378..584cfd681 100644 --- a/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java +++ b/src/main/java/com/microsoft/sqlserver/jdbc/dtv.java @@ -2208,7 +2208,7 @@ void execute(DTV dtv, if (null != bigDecimalValue) { Integer inScale = dtv.getScale(); if (null != inScale && inScale != bigDecimalValue.scale()) - bigDecimalValue = bigDecimalValue.setScale(inScale, BigDecimal.ROUND_DOWN); + bigDecimalValue = bigDecimalValue.setScale(inScale, RoundingMode.DOWN); } dtv.setValue(bigDecimalValue, JavaType.BIGDECIMAL); diff --git a/src/samples/alwaysencrypted/src/main/java/AlwaysEncrypted.java b/src/samples/alwaysencrypted/src/main/java/AlwaysEncrypted.java index a337f0d74..bf91979c1 100644 --- a/src/samples/alwaysencrypted/src/main/java/AlwaysEncrypted.java +++ b/src/samples/alwaysencrypted/src/main/java/AlwaysEncrypted.java @@ -14,7 +14,6 @@ import java.sql.SQLException; import java.sql.Statement; -import javax.xml.bind.DatatypeConverter; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionJavaKeyStoreProvider; import com.microsoft.sqlserver.jdbc.SQLServerColumnEncryptionKeyStoreProvider; @@ -116,7 +115,7 @@ public static void main(String[] args) { */ String createCEKSQL = "CREATE COLUMN ENCRYPTION KEY " + columnEncryptionKey + " WITH VALUES ( " + " COLUMN_MASTER_KEY = " + columnMasterKeyName + " , ALGORITHM = '" + algorithm + "' , ENCRYPTED_VALUE = 0x" - + DatatypeConverter.printHexBinary(encryptedCEK) + " ) "; + + bytesToHexString(encryptedCEK, encryptedCEK.length) + " ) "; try (Statement cekStatement = sourceConnection.createStatement()) { cekStatement.executeUpdate(createCEKSQL); @@ -129,6 +128,27 @@ public static void main(String[] args) { e.printStackTrace(); } } + + /** + * + * @param b + * byte value + * @param length + * length of the array + * @return + */ + final static char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + private static String bytesToHexString(byte[] b, + int length) { + StringBuilder sb = new StringBuilder(length * 2); + for (int i = 0; i < length; i++) { + int hexVal = b[i] & 0xFF; + sb.append(hexChars[(hexVal & 0xF0) >> 4]); + sb.append(hexChars[(hexVal & 0x0F)]); + } + return sb.toString(); + } // To avoid storing the sourceConnection String in your code, // you can retrieve it from a configuration file. diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java index 9ae2445c1..dd4d588df 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/AlwaysEncrypted/AESetup.java @@ -21,7 +21,6 @@ import java.util.LinkedList; import java.util.Properties; -import javax.xml.bind.DatatypeConverter; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -712,7 +711,7 @@ private static void createCEK(SQLServerColumnEncryptionKeyStoreProvider storePro String cekSql = null; byte[] key = storeProvider.encryptColumnEncryptionKey(javaKeyAliases, "RSA_OAEP", valuesDefault); cekSql = "CREATE COLUMN ENCRYPTION KEY " + cekName + " WITH VALUES " + "(COLUMN_MASTER_KEY = " + cmkName - + ", ALGORITHM = 'RSA_OAEP', ENCRYPTED_VALUE = 0x" + DatatypeConverter.printHexBinary(key) + ")" + ";"; + + ", ALGORITHM = 'RSA_OAEP', ENCRYPTED_VALUE = 0x" + Util.bytesToHexString(key, key.length) + ")" + ";"; stmt.execute(cekSql); } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/JDBC43Test.java b/src/test/java/com/microsoft/sqlserver/jdbc/JDBC43Test.java new file mode 100644 index 000000000..6873bbffe --- /dev/null +++ b/src/test/java/com/microsoft/sqlserver/jdbc/JDBC43Test.java @@ -0,0 +1,228 @@ +/* + * Microsoft JDBC Driver for SQL Server + * + * Copyright(c) Microsoft Corporation All rights reserved. + * + * This program is made available under the terms of the MIT License. See the LICENSE file in the project root for more information. + */ +package com.microsoft.sqlserver.jdbc; + +import java.sql.Connection; +import java.sql.Driver; +import java.sql.DriverManager; +import java.sql.JDBCType; +import java.sql.SQLException; +import java.sql.ShardingKey; +import java.util.Enumeration; +import java.util.stream.Stream; + +import javax.sql.ConnectionPoolDataSource; +import javax.sql.PooledConnection; +import javax.sql.XAConnection; + +import org.junit.jupiter.api.Test; +import org.junit.platform.runner.JUnitPlatform; +import org.junit.runner.RunWith; +import org.opentest4j.TestAbortedException; + +import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.util.Util; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; +import static org.junit.jupiter.api.Assumptions.assumeTrue; + +/** + * Tests JDBC 4.3 APIs + * + */ +@RunWith(JUnitPlatform.class) +public class JDBC43Test extends AbstractTest { + ShardingKey superShardingKey = null; + ShardingKey shardingKey = null; + + /** + * Tests that we are throwing the unsupported exception for connectionBuilder() + * @throws SQLException + * @throws TestAbortedException + * + * @since 1.9 + */ + @Test + public void connectionBuilderTest() throws TestAbortedException, SQLException { + assumeTrue(Util.supportJDBC43(connection)); + SQLServerDataSource ds = new SQLServerDataSource(); + try { + superShardingKey = ds.createShardingKeyBuilder().subkey("EASTERN_REGION", JDBCType.VARCHAR).build(); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + + try { + shardingKey = ds.createShardingKeyBuilder().subkey("PITTSBURGH_BRANCH", JDBCType.VARCHAR).build(); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + + try { + Connection con = ds.createConnectionBuilder().user("rafa").password("tennis").shardingKey(shardingKey).superShardingKey(superShardingKey) + .build(); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + } + + /** + * Tests that we are throwing the unsupported exception for connectionBuilder() + * @throws SQLException + * @throws TestAbortedException + * + * @since 1.9 + */ + @Test + public void xaConnectionBuilderTest() throws TestAbortedException, SQLException { + assumeTrue(Util.supportJDBC43(connection)); + SQLServerXADataSource ds = new SQLServerXADataSource(); + try { + superShardingKey = ds.createShardingKeyBuilder().subkey("EASTERN_REGION", JDBCType.VARCHAR).build(); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + + try { + shardingKey = ds.createShardingKeyBuilder().subkey("PITTSBURGH_BRANCH", JDBCType.VARCHAR).build(); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + + try { + XAConnection con = ds.createXAConnectionBuilder().user("rafa").password("tennis").shardingKey(shardingKey) + .superShardingKey(superShardingKey).build(); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + } + + /** + * Tests that we are throwing the unsupported exception for createPooledConnectionBuilder() + * @throws SQLException + * @throws TestAbortedException + * @since 1.9 + */ + @Test + public void connectionPoolDataSourceTest() throws TestAbortedException, SQLException { + assumeTrue(Util.supportJDBC43(connection)); + ConnectionPoolDataSource ds = new SQLServerConnectionPoolDataSource(); + try { + superShardingKey = ds.createShardingKeyBuilder().subkey("EASTERN_REGION", JDBCType.VARCHAR).build(); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + + try { + shardingKey = ds.createShardingKeyBuilder().subkey("PITTSBURGH_BRANCH", JDBCType.VARCHAR).build(); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + try { + PooledConnection con = ds.createPooledConnectionBuilder().user("rafa").password("tennis").shardingKey(shardingKey) + .superShardingKey(superShardingKey).build(); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + } + + /** + * Tests that we are throwing the unsupported exception for setShardingKeyIfValid() + * @throws SQLException + * @throws TestAbortedException + * @since 1.9 + */ + @Test + public void setShardingKeyIfValidTest() throws TestAbortedException, SQLException { + assumeTrue(Util.supportJDBC43(connection)); + SQLServerConnection connection43 = (SQLServerConnection43) DriverManager.getConnection(connectionString); + try { + connection43.setShardingKeyIfValid(shardingKey, 10); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + try { + connection43.setShardingKeyIfValid(shardingKey, superShardingKey, 10); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + + } + + /** + * Tests that we are throwing the unsupported exception for setShardingKey() + * @throws SQLException + * @throws TestAbortedException + * @since 1.9 + */ + @Test + public void setShardingKeyTest() throws TestAbortedException, SQLException { + assumeTrue(Util.supportJDBC43(connection)); + SQLServerConnection connection43 = (SQLServerConnection43) DriverManager.getConnection(connectionString); + try { + connection43.setShardingKey(shardingKey); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + try { + connection43.setShardingKey(shardingKey, superShardingKey); + } + catch (SQLException e) { + assert (e.getMessage().contains("not implemented")); + } + + } + + /** + * Tests the stream drivers() methods in java.sql.DriverManager + * + * @since 1.9 + * @throws ClassNotFoundException + */ + @Test + public void driversTest() throws ClassNotFoundException { + Stream drivers = DriverManager.drivers(); + Object[] driversArray = drivers.toArray(); + assertEquals(driversArray[0].getClass(), Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver")); + } + + /** + * Tests deregister Driver + * + * @throws SQLException + * @throws ClassNotFoundException + */ + @Test + public void deregisterDriverTest() throws SQLException, ClassNotFoundException { + Enumeration drivers = DriverManager.getDrivers(); + Driver current = null; + while (drivers.hasMoreElements()) { + current = drivers.nextElement(); + DriverManager.deregisterDriver(current); + } + Stream currentDrivers = DriverManager.drivers(); + Object[] driversArray = currentDrivers.toArray(); + assertEquals(0, driversArray.length); + + DriverManager.registerDriver(current); + } + +} \ No newline at end of file diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/DriverVersionTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/DriverVersionTest.java index 911120ebb..bbd0ca5d6 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/DriverVersionTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/DriverVersionTest.java @@ -12,13 +12,14 @@ import java.util.Arrays; import java.util.Random; -import javax.xml.bind.DatatypeConverter; import org.junit.jupiter.api.Test; import org.junit.platform.runner.JUnitPlatform; import org.junit.runner.RunWith; +import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.testframework.AbstractTest; +import com.microsoft.sqlserver.testframework.util.Util; /** * This test validates PR #342. In this PR, DatatypeConverter#parseHexBinary is replaced with type casting. This tests validates if the behavior @@ -38,12 +39,13 @@ public class DriverVersionTest extends AbstractTest { /** * validates version byte array generated by the original method and type casting reminds the same. + * @throws SQLServerException */ @Test - public void testConnectionDriver() { + public void testConnectionDriver() throws SQLServerException { // the original way to create version byte array String interfaceLibVersion = generateInterfaceLibVersion(); - byte originalVersionBytes[] = DatatypeConverter.parseHexBinary(interfaceLibVersion); + byte originalVersionBytes[] = Util.hexStringToByte(interfaceLibVersion); String originalBytes = Arrays.toString(originalVersionBytes); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/SSLProtocolTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/SSLProtocolTest.java index 5d06220b8..c1e7e3351 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/SSLProtocolTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/SSLProtocolTest.java @@ -66,11 +66,11 @@ public void testWithUnSupportedProtocols(String sslProtocol) throws Exception { try { String url = connectionString + ";sslProtocol=" + sslProtocol; con = DriverManager.getConnection(url); - assertFalse(true, "Any protocol other than TLSv1, TLSv1.1 & TLSv1.2 should throw Exception"); + assertFalse(true, "Any protocol other than TLSv1, TLSv1.1, and TLSv1.2 should throw Exception"); } catch (SQLServerException e) { assertTrue(true, "Should throw exception"); - String errMsg = "SSL Protocol " + sslProtocol + " label is not valid. Only TLS, TLSv1, TLSv1.1 & TLSv1.2 are supported."; + String errMsg = "SSL Protocol " + sslProtocol + " label is not valid. Only TLS, TLSv1, TLSv1.1, and TLSv1.2 are supported."; assertTrue(errMsg.equals(e.getMessage()), "Message should be from SQL Server resources : " + e.getMessage()); } } diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/connection/WarningTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/connection/WarningTest.java index 66ee1111b..8b99699b1 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/connection/WarningTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/connection/WarningTest.java @@ -13,6 +13,8 @@ import java.sql.DriverManager; import java.sql.SQLException; import java.sql.SQLWarning; +import java.util.Arrays; +import java.util.List; import java.util.Properties; import org.junit.jupiter.api.Test; @@ -40,9 +42,18 @@ public void testWarnings() throws SQLException { } conn.setClientInfo(info2); warn = conn.getWarnings(); - for (int i = 4; i >= 0; i--) { - assertTrue(warn.toString().contains(infoArray[i]), "Warnings not found!"); + for (int i = 0; i < 5; i++) { + boolean found = false; + List list = Arrays.asList(infoArray); + for (String word : list) { + if (warn.toString().contains(word)) { + found = true; + break; + } + } + assertTrue(found, "warning : '" + warn.toString() + "' not found!"); warn = warn.getNextWarning(); + found = false; } conn.clearWarnings(); diff --git a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java index 667e3fc94..c1c12a8d9 100644 --- a/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java +++ b/src/test/java/com/microsoft/sqlserver/jdbc/preparedStatement/BatchExecutionWithNullTest.java @@ -40,8 +40,9 @@ public class BatchExecutionWithNullTest extends AbstractTest { static ResultSet rs = null; /** - * Test with combination of setString and setNull which cause the "Violation of PRIMARY KEY constraint and internally - * "Could not find prepared statement with handle X" error. + * Test with combination of setString and setNull which cause the "Violation of PRIMARY KEY constraint and internally "Could not find prepared + * statement with handle X" error. + * * @throws SQLException */ @Test @@ -118,7 +119,7 @@ public void testSetup() throws TestAbortedException, Exception { @AfterAll public static void terminateVariation() throws SQLException { connection = DriverManager.getConnection(connectionString); - + SQLServerStatement stmt = (SQLServerStatement) connection.createStatement(); Utils.dropTableIfExists("esimple", stmt); diff --git a/src/test/java/com/microsoft/sqlserver/testframework/util/Util.java b/src/test/java/com/microsoft/sqlserver/testframework/util/Util.java index f1e5167c4..336c2d304 100644 --- a/src/test/java/com/microsoft/sqlserver/testframework/util/Util.java +++ b/src/test/java/com/microsoft/sqlserver/testframework/util/Util.java @@ -11,6 +11,7 @@ import com.microsoft.sqlserver.jdbc.SQLServerConnection; import com.microsoft.sqlserver.jdbc.SQLServerDatabaseMetaData; +import com.microsoft.sqlserver.jdbc.SQLServerException; import com.microsoft.sqlserver.jdbc.SQLServerStatementColumnEncryptionSetting; /** @@ -289,4 +290,82 @@ public static boolean supportJDBC42(Connection con) throws SQLException { SQLServerDatabaseMetaData meta = (SQLServerDatabaseMetaData) con.getMetaData(); return (meta.getJDBCMajorVersion() >= 4 && meta.getJDBCMinorVersion() >= 2); } + + /** + * Utility function for checking if the system supports JDBC 4.3 + * + * @param con + * @return + */ + public static boolean supportJDBC43(Connection con) throws SQLException { + SQLServerDatabaseMetaData meta = (SQLServerDatabaseMetaData) con.getMetaData(); + return (meta.getJDBCMajorVersion() >= 4 && meta.getJDBCMinorVersion() >= 3); + } + + /** + * + * @param b + * byte value + * @param length + * length of the array + * @return + */ + final static char[] hexChars = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'}; + + public static String bytesToHexString(byte[] b, + int length) { + StringBuilder sb = new StringBuilder(length * 2); + for (int i = 0; i < length; i++) { + int hexVal = b[i] & 0xFF; + sb.append(hexChars[(hexVal & 0xF0) >> 4]); + sb.append(hexChars[(hexVal & 0x0F)]); + } + return sb.toString(); + } + + /** + * conversion routine valid values 0-9 a-f A-F throws exception when failed to convert + * + * @param value + * charArray + * @return + * @throws SQLServerException + */ + static byte CharToHex(char value) throws SQLServerException { + byte ret = 0; + if (value >= 'A' && value <= 'F') { + ret = (byte) (value - 'A' + 10); + } + else if (value >= 'a' && value <= 'f') { + ret = (byte) (value - 'a' + 10); + } + else if (value >= '0' && value <= '9') { + ret = (byte) (value - '0'); + } + else { + throw new IllegalArgumentException("The string is not in a valid hex format. "); + } + return ret; + } + + /** + * Converts a string to an array of bytes + * + * @param hexV + * a hexized string representation of bytes + * @return + * @throws SQLServerException + */ + public static byte[] hexStringToByte(String hexV) throws SQLServerException { + int len = hexV.length(); + char orig[] = hexV.toCharArray(); + if ((len % 2) != 0) { + throw new IllegalArgumentException("The string is not in a valid hex format: " + hexV); + } + byte[] bin = new byte[len / 2]; + for (int i = 0; i < len / 2; i++) { + bin[i] = (byte) ((CharToHex(orig[2 * i]) << 4) + CharToHex(orig[2 * i + 1])); + } + return bin; + } }