All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.microsoft.sqlserver.jdbc.Util Maven / Gradle / Ivy

There is a newer version: 12.8.1.jre11
Show newest version
/*
 * 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.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.text.DecimalFormat;
import java.text.MessageFormat;
import java.util.Date;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.logging.Logger;


/**
 * Various driver utilities.
 *
 */
final class Util {
    final static String SYSTEM_SPEC_VERSION = System.getProperty("java.specification.version");
    final static char[] HEXCHARS = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F'};
    final static String WSID_NOT_AVAILABLE = ""; // default string when WSID is not available

    final static String ACTIVITY_ID_TRACE_PROPERTY = "com.microsoft.sqlserver.jdbc.traceactivity";

    private Util() {
        throw new UnsupportedOperationException(SQLServerException.getErrString("R_notSupported"));
    }

    // The JRE is identified by the string below so that the driver can make
    // any vendor or version specific decisions
    static final String SYSTEM_JRE = System.getProperty("java.vendor") + " " + System.getProperty("java.version");
    private static final Lock LOCK = new ReentrantLock();

    static boolean isIBM() {
        String vmName = System.getProperty("java.vm.name");
        return SYSTEM_JRE.startsWith("IBM") && vmName.startsWith("IBM");
    }

    static String getJVMArchOnWindows() {
        return System.getProperty("os.arch").contains("64") ? "x64" : "x86";
    }

    static final boolean isCharType(int jdbcType) {
        switch (jdbcType) {
            case java.sql.Types.CHAR:
            case java.sql.Types.NCHAR:
            case java.sql.Types.VARCHAR:
            case java.sql.Types.NVARCHAR:
            case java.sql.Types.LONGVARCHAR:
            case java.sql.Types.LONGNVARCHAR:
                return true;
            default:
                return false;
        }
    }

    static final Boolean isCharType(SSType ssType) {
        switch (ssType) {
            case CHAR:
            case NCHAR:
            case VARCHAR:
            case NVARCHAR:
            case VARCHARMAX:
            case NVARCHARMAX:
                return true;
            default:
                return false;
        }
    }

    static final Boolean isBinaryType(SSType ssType) {
        switch (ssType) {
            case BINARY:
            case VARBINARY:
            case VARBINARYMAX:
            case IMAGE:
                return true;
            default:
                return false;
        }
    }

    static final Boolean isBinaryType(int jdbcType) {
        switch (jdbcType) {
            case java.sql.Types.BINARY:
            case java.sql.Types.VARBINARY:
            case java.sql.Types.LONGVARBINARY:
                return true;
            default:
                return false;
        }
    }

    /**
     * Read a short int from a byte stream
     * 
     * @param data
     *        the databytes
     * @param nOffset
     *        offset to read from
     * @return the value
     */
    static short readShort(byte[] data, int nOffset) {
        return (short) ((data[nOffset] & 0xff) | ((data[nOffset + 1] & 0xff) << 8));
    }

    /**
     * Read an unsigned short int (16 bits) from a byte stream
     * 
     * @param data
     *        the databytes
     * @param nOffset
     *        offset to read from
     * @return the value
     */
    static int readUnsignedShort(byte[] data, int nOffset) {
        return ((data[nOffset] & 0xff) | ((data[nOffset + 1] & 0xff) << 8));
    }

    static int readUnsignedShortBigEndian(byte[] data, int nOffset) {
        return ((data[nOffset] & 0xFF) << 8) | (data[nOffset + 1] & 0xFF);
    }

    static void writeShort(short value, byte[] valueBytes, int offset) {
        valueBytes[offset + 0] = (byte) ((value >> 0) & 0xFF);
        valueBytes[offset + 1] = (byte) ((value >> 8) & 0xFF);
    }

    static void writeShortBigEndian(short value, byte[] valueBytes, int offset) {
        valueBytes[offset + 0] = (byte) ((value >> 8) & 0xFF);
        valueBytes[offset + 1] = (byte) ((value >> 0) & 0xFF);
    }

    /**
     * Read an int from a byte stream
     * 
     * @param data
     *        the databytes
     * @param nOffset
     *        offset to read from
     * @return the value
     */
    static int readInt(byte[] data, int nOffset) {
        int b1 = ((int) data[nOffset + 0] & 0xff);
        int b2 = ((int) data[nOffset + 1] & 0xff) << 8;
        int b3 = ((int) data[nOffset + 2] & 0xff) << 16;
        int b4 = ((int) data[nOffset + 3] & 0xff) << 24;
        return b4 | b3 | b2 | b1;
    }

    static int readIntBigEndian(byte[] data, int nOffset) {
        return ((data[nOffset + 3] & 0xFF) << 0) | ((data[nOffset + 2] & 0xFF) << 8)
                | ((data[nOffset + 1] & 0xFF) << 16) | ((data[nOffset + 0] & 0xFF) << 24);
    }

    static void writeInt(int value, byte[] valueBytes, int offset) {
        valueBytes[offset + 0] = (byte) ((value >> 0) & 0xFF);
        valueBytes[offset + 1] = (byte) ((value >> 8) & 0xFF);
        valueBytes[offset + 2] = (byte) ((value >> 16) & 0xFF);
        valueBytes[offset + 3] = (byte) ((value >> 24) & 0xFF);
    }

    static void writeIntBigEndian(int value, byte[] valueBytes, int offset) {
        valueBytes[offset + 0] = (byte) ((value >> 24) & 0xFF);
        valueBytes[offset + 1] = (byte) ((value >> 16) & 0xFF);
        valueBytes[offset + 2] = (byte) ((value >> 8) & 0xFF);
        valueBytes[offset + 3] = (byte) ((value >> 0) & 0xFF);
    }

    static void writeLongBigEndian(long value, byte[] valueBytes, int offset) {
        valueBytes[offset + 0] = (byte) ((value >> 56) & 0xFF);
        valueBytes[offset + 1] = (byte) ((value >> 48) & 0xFF);
        valueBytes[offset + 2] = (byte) ((value >> 40) & 0xFF);
        valueBytes[offset + 3] = (byte) ((value >> 32) & 0xFF);
        valueBytes[offset + 4] = (byte) ((value >> 24) & 0xFF);
        valueBytes[offset + 5] = (byte) ((value >> 16) & 0xFF);
        valueBytes[offset + 6] = (byte) ((value >> 8) & 0xFF);
        valueBytes[offset + 7] = (byte) ((value >> 0) & 0xFF);
    }

    static BigDecimal readBigDecimal(byte[] valueBytes, int valueLength, int scale) {
        int sign = (0 == valueBytes[0]) ? -1 : 1;
        byte[] magnitude = new byte[valueLength - 1];
        for (int i = 1; i <= magnitude.length; i++)
            magnitude[magnitude.length - i] = valueBytes[i];
        return new BigDecimal(new BigInteger(sign, magnitude), scale);
    }

    /**
     * Reads a long value from byte array.
     * 
     * @param data
     *        the byte array.
     * @param nOffset
     *        the offset into byte array to start reading.
     * @return long value as read from bytes.
     */
    static long readLong(byte[] data, int nOffset) {
        return ((long) (data[nOffset + 7] & 0xff) << 56) | ((long) (data[nOffset + 6] & 0xff) << 48)
                | ((long) (data[nOffset + 5] & 0xff) << 40) | ((long) (data[nOffset + 4] & 0xff) << 32)
                | ((long) (data[nOffset + 3] & 0xff) << 24) | ((long) (data[nOffset + 2] & 0xff) << 16)
                | ((long) (data[nOffset + 1] & 0xff) << 8) | ((long) (data[nOffset] & 0xff));
    }

    /**
     * Writes a long to byte array.
     *
     * @param value
     *        long value to write.
     * @param valueBytes
     *        the byte array.
     * @param offset
     *        the offset inside byte array.
     */
    static void writeLong(long value, byte[] valueBytes, int offset) {
        valueBytes[offset++] = (byte) ((value) & 0xFF);
        valueBytes[offset++] = (byte) ((value >> 8) & 0xFF);
        valueBytes[offset++] = (byte) ((value >> 16) & 0xFF);
        valueBytes[offset++] = (byte) ((value >> 24) & 0xFF);
        valueBytes[offset++] = (byte) ((value >> 32) & 0xFF);
        valueBytes[offset++] = (byte) ((value >> 40) & 0xFF);
        valueBytes[offset++] = (byte) ((value >> 48) & 0xFF);
        valueBytes[offset] = (byte) ((value >> 56) & 0xFF);
    }

    /**
     * Parse a JDBC URL into a set of properties.
     * 
     * @param url
     *        the JDBC URL
     * @param logger
     * @return the properties
     * @throws SQLServerException
     */
    static Properties parseUrl(String url, Logger logger) throws SQLServerException {
        Properties p = new Properties();
        String tmpUrl = url;
        String sPrefix = "jdbc:sqlserver://";
        StringBuilder result = new StringBuilder();
        String name = "";
        String value = "";

        if (!tmpUrl.startsWith(sPrefix))
            return null;

        tmpUrl = tmpUrl.substring(sPrefix.length());
        int i;

        // Simple finite state machine.
        // always look at one char at a time
        final int inStart = 0;
        final int inServerName = 1;
        final int inPort = 2;
        final int inInstanceName = 3;
        final int inEscapedValueStart = 4;
        final int inEscapedValueEnd = 5;
        final int inValue = 6;
        final int inName = 7;

        int state = inStart;
        char ch;
        i = 0;
        while (i < tmpUrl.length()) {
            ch = tmpUrl.charAt(i);
            switch (state) {
                case inStart:
                    if (ch == ';') {
                        // done immediately
                        state = inName;
                    } else {
                        result.append(ch);
                        state = inServerName;
                    }
                    break;

                case inServerName:
                    if (ch == ';' || ch == ':' || ch == '\\') {
                        // non escaped trim the string
                        String property = result.toString().trim();

                        if (property.contains("=")) {
                            MessageFormat form = new MessageFormat(
                                    SQLServerException.getErrString("R_errorServerName"));
                            Object[] msgArgs = {property};
                            SQLServerException.makeFromDriverError(null, null, form.format(msgArgs), null, true);
                        }

                        if (property.length() > 0) {
                            p.put(SQLServerDriverStringProperty.SERVER_NAME.toString(), property);
                            if (logger.isLoggable(Level.FINE)) {
                                logger.fine("Property:serverName " + "Value:" + property);
                            }
                        }
                        result.setLength(0);

                        if (ch == ';')
                            state = inName;
                        else if (ch == ':')
                            state = inPort;
                        else
                            state = inInstanceName;
                    } else {
                        result.append(ch);
                        // same state
                    }
                    break;

                case inPort:
                    if (ch == ';') {
                        String property = result.toString().trim();
                        if (logger.isLoggable(Level.FINE)) {
                            logger.fine("Property:portNumber " + "Value:" + property);
                        }
                        p.put(SQLServerDriverIntProperty.PORT_NUMBER.toString(), property);
                        result.setLength(0);
                        state = inName;
                    } else {
                        result.append(ch);
                        // same state
                    }
                    break;

                case inInstanceName:
                    if (ch == ';' || ch == ':') {
                        // non escaped trim the string
                        String property = result.toString().trim();
                        if (logger.isLoggable(Level.FINE)) {
                            logger.fine("Property:instanceName " + "Value:" + property);
                        }
                        p.put(SQLServerDriverStringProperty.INSTANCE_NAME.toString(), property.toLowerCase(Locale.US));
                        result.setLength(0);

                        if (ch == ';')
                            state = inName;
                        else
                            state = inPort;
                    } else {
                        result.append(ch);
                        // same state
                    }
                    break;

                case inName:
                    if (ch == '=') {
                        // name is never escaped!
                        name = name.trim();
                        if (name.length() <= 0) {
                            SQLServerException.makeFromDriverError(null, null,
                                    SQLServerException.getErrString("R_errorConnectionString"), null, true);
                        }
                        state = inValue;
                    } else if (ch == ';') {
                        name = name.trim();
                        if (name.length() > 0) {
                            SQLServerException.makeFromDriverError(null, null,
                                    SQLServerException.getErrString("R_errorConnectionString"), null, true);
                        }
                        // same state
                    } else {
                        StringBuilder builder = new StringBuilder();
                        builder.append(name);
                        builder.append(ch);
                        name = builder.toString();
                        // same state
                    }
                    break;

                case inValue:
                    if (ch == ';') {
                        // simple value trim
                        value = value.trim();
                        name = SQLServerDriver.getNormalizedPropertyName(name, logger);
                        if (null != name) {
                            if (logger.isLoggable(Level.FINE)) {
                                if (!name.equals(SQLServerDriverStringProperty.USER.toString())) {
                                    if (!name.toLowerCase(Locale.ENGLISH).contains("password")
                                            && !name.toLowerCase(Locale.ENGLISH).contains("keystoresecret")) {
                                        logger.fine("Property:" + name + " Value:" + value);
                                    } else {
                                        logger.fine("Property:" + name);
                                    }
                                }
                            }
                            p.put(name, value);
                        }
                        name = "";
                        value = "";
                        state = inName;

                    } else if (ch == '{') {
                        state = inEscapedValueStart;
                        value = value.trim();
                        if (value.length() > 0) {
                            SQLServerException.makeFromDriverError(null, null,
                                    SQLServerException.getErrString("R_errorConnectionString"), null, true);
                        }
                    } else {
                        StringBuilder builder = new StringBuilder();
                        builder.append(value);
                        builder.append(ch);
                        value = builder.toString();
                        // same state
                    }
                    break;

                case inEscapedValueStart:
                    /*
                     * check for escaped }. when we see a }, first check to see if this is before the end of the string
                     * to avoid index out of range exception then check if the character immediately after is also a }.
                     * if it is, then we have a }}, which is not the closing of the escaped state.
                     */
                    if (ch == '}' && i + 1 < tmpUrl.length() && tmpUrl.charAt(i + 1) == '}') {
                        StringBuilder builder = new StringBuilder();
                        builder.append(value);
                        builder.append(ch);
                        value = builder.toString();
                        i++; // escaped }} into a }, so increment the counter once more
                        // same state
                    } else {
                        if (ch == '}') {
                            // no trimming use the value as it is.
                            name = SQLServerDriver.getNormalizedPropertyName(name, logger);
                            if (null != name) {
                                if (logger.isLoggable(Level.FINE)) {
                                    if (!name.equals(SQLServerDriverStringProperty.USER.toString())
                                            && !name.equals(SQLServerDriverStringProperty.PASSWORD.toString()))
                                        logger.fine("Property:" + name + " Value:" + value);
                                }
                                p.put(name, value);
                            }

                            name = "";
                            value = "";
                            // to eat the spaces until the ; potentially we could do without the state but
                            // it would not be clean
                            state = inEscapedValueEnd;
                        } else {
                            StringBuilder builder = new StringBuilder();
                            builder.append(value);
                            builder.append(ch);
                            value = builder.toString();
                            // same state
                        }
                    }
                    break;

                case inEscapedValueEnd:
                    if (ch == ';') // eat space chars till ; anything else is an error
                    {
                        state = inName;
                    } else if (ch != ' ') {
                        // error if the chars are not space
                        SQLServerException.makeFromDriverError(null, null,
                                SQLServerException.getErrString("R_errorConnectionString"), null, true);
                    }
                    break;

                default:
                    assert false : "parseURL: Invalid state " + state;
            }
            i++;
        }

        // Exit
        switch (state) {
            case inServerName:
                String property = result.toString().trim();
                if (property.length() > 0) {
                    if (logger.isLoggable(Level.FINE)) {
                        logger.fine("Property:serverName " + "Value:" + property);
                    }
                    p.put(SQLServerDriverStringProperty.SERVER_NAME.toString(), property);
                }
                break;
            case inPort:
                property = result.toString().trim();
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("Property:portNumber " + "Value:" + property);
                }
                p.put(SQLServerDriverIntProperty.PORT_NUMBER.toString(), property);
                break;
            case inInstanceName:
                property = result.toString().trim();
                if (logger.isLoggable(Level.FINE)) {
                    logger.fine("Property:instanceName " + "Value:" + property);
                }
                p.put(SQLServerDriverStringProperty.INSTANCE_NAME.toString(), property);
                break;
            case inValue:
                // simple value trim
                value = value.trim();
                name = SQLServerDriver.getNormalizedPropertyName(name, logger);
                if (null != name) {
                    if (logger.isLoggable(Level.FINE)) {
                        if (!name.equals(SQLServerDriverStringProperty.USER.toString())
                                && !name.equals(SQLServerDriverStringProperty.PASSWORD.toString())
                                && !name.equals(SQLServerDriverStringProperty.KEY_STORE_SECRET.toString()))
                            logger.fine("Property:" + name + " Value:" + value);
                    }
                    p.put(name, value);
                }

                break;
            case inEscapedValueEnd:
            case inStart:
                // do nothing!
                break;
            case inName: {
                name = name.trim();
                if (name.length() > 0) {
                    SQLServerException.makeFromDriverError(null, null,
                            SQLServerException.getErrString("R_errorConnectionString"), null, true);
                }

                break;
            }
            default:
                SQLServerException.makeFromDriverError(null, null,
                        SQLServerException.getErrString("R_errorConnectionString"), null, true);
        }
        return p;
    }

    /**
     * Accepts a SQL identifier (such as a column name or table name) and escapes the identifier using SQL Server
     * bracket escaping rules. Assumes that the incoming identifier is unescaped.
     * 
     * @inID input identifier to escape.
     * @return the escaped value.
     */
    static String escapeSQLId(String inID) {
        // SQL bracket escaping rules.
        // Given  yields -> []
        // Where  is first escaped to replace all
        // instances of "]" with "]]".
        // For example, column name "abc" -> "[abc]"
        // For example, column name "]" -> "[]]]"
        // For example, column name "]ab]cd" -> "[]]ab]]cd]"
        char ch;

        // Add 2 extra chars for open and closing brackets.
        StringBuilder outID = new StringBuilder(inID.length() + 2);

        outID.append('[');
        for (int i = 0; i < inID.length(); i++) {
            ch = inID.charAt(i);
            if (']' == ch)
                outID.append("]]");
            else
                outID.append(ch);
        }
        outID.append(']');
        return outID.toString();
    }

    /**
     * Checks if duplicate columns exists, in O(n) time.
     * 
     * @param columnName
     *        the name of the column
     * @throws SQLServerException
     *         when a duplicate column exists
     */
    static void checkDuplicateColumnName(String columnName, Set columnNames) throws SQLServerException {
        // columnList.add will return false if the same column name already exists
        if (!columnNames.add(columnName)) {
            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_TVPDuplicateColumnName"));
            Object[] msgArgs = {columnName};
            throw new SQLServerException(null, form.format(msgArgs), null, 0, false);
        }
    }

    /**
     * Reads a UNICODE string from byte buffer at offset (up to byteLength).
     * 
     * @param b
     *        the buffer containing UNICODE bytes.
     * @param offset
     *        - the offset into b where the UNICODE string starts.
     * @param byteLength
     *        - the length in bytes of the UNICODE string.
     * @param conn
     *        - the SQLServerConnection object.
     * @return new String with UNICODE data inside.
     */
    static String readUnicodeString(byte[] b, int offset, int byteLength,
            SQLServerConnection conn) throws SQLServerException {
        try {
            return new String(b, offset, byteLength, Encoding.UNICODE.charset());
        } catch (IndexOutOfBoundsException ex) {
            String txtMsg = SQLServerException
                    .checkAndAppendClientConnId(SQLServerException.getErrString("R_stringReadError"), conn);
            MessageFormat form = new MessageFormat(txtMsg);
            Object[] msgArgs = {offset};
            // Re-throw SQLServerException if conversion fails.
            throw new SQLServerException(form.format(msgArgs), null, 0, ex);
        }

    }

    // NOTE: This is for display purposes ONLY. NOT TO BE USED for data conversion.
    /**
     * Converts byte array to a string representation of hex bytes for display purposes.
     * 
     * @param b
     *        the source buffer.
     * @return "hexized" string representation of bytes.
     */
    static String byteToHexDisplayString(byte[] b) {
        if (null == b)
            return "(null)";
        int hexVal;
        StringBuilder sb = new StringBuilder(b.length * 2 + 2);
        sb.append("0x");
        for (byte aB : b) {
            hexVal = aB & 0xFF;
            sb.append(HEXCHARS[(hexVal & 0xF0) >> 4]);
            sb.append(HEXCHARS[(hexVal & 0x0F)]);
        }
        return sb.toString();
    }

    /**
     * Converts byte array to a string representation of hex bytes.
     * 
     * @param b
     *        the source buffer.
     * @return "hexized" string representation of bytes.
     */
    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();
    }

    /**
     * Looks up local hostname of client machine.
     * 
     * @exception UnknownHostException
     *            if local hostname is not found.
     * @return hostname string or ip of host if hostname cannot be resolved. If neither hostname or ip found returns ""
     *         per spec.
     */
    static String lookupHostName() {

        try {
            InetAddress localAddress = InetAddress.getLocalHost();
            if (null != localAddress) {
                String value = localAddress.getHostName();
                if (null != value && value.length() > 0)
                    return value;

                value = localAddress.getHostAddress();
                if (null != value && value.length() > 0)
                    return value;
            }
        } catch (UnknownHostException e) {
            return WSID_NOT_AVAILABLE;
        }
        // If hostname not found, return standard "" string.
        return WSID_NOT_AVAILABLE;
    }

    static final byte[] asGuidByteArray(UUID aId) {
        long msb = aId.getMostSignificantBits();
        long lsb = aId.getLeastSignificantBits();
        byte[] buffer = new byte[16];
        Util.writeLongBigEndian(msb, buffer, 0);
        Util.writeLongBigEndian(lsb, buffer, 8);

        // For the first three fields, UUID uses network byte order,
        // Guid uses native byte order. So we need to reverse
        // the first three fields before sending to server.

        byte tmpByte;

        // Reverse the first 4 bytes
        tmpByte = buffer[0];
        buffer[0] = buffer[3];
        buffer[3] = tmpByte;
        tmpByte = buffer[1];
        buffer[1] = buffer[2];
        buffer[2] = tmpByte;

        // Reverse the 5th and the 6th
        tmpByte = buffer[4];
        buffer[4] = buffer[5];
        buffer[5] = tmpByte;

        // Reverse the 7th and the 8th
        tmpByte = buffer[6];
        buffer[6] = buffer[7];
        buffer[7] = tmpByte;

        return buffer;
    }

    static final UUID readGUIDtoUUID(byte[] inputGUID) throws SQLServerException {
        if (inputGUID.length != 16) {
            throw new SQLServerException("guid length must be 16", null);
        }

        // For the first three fields, UUID uses network byte order,
        // Guid uses native byte order. So we need to reverse
        // the first three fields before creating a UUID.

        byte tmpByte;

        // Reverse the first 4 bytes
        tmpByte = inputGUID[0];
        inputGUID[0] = inputGUID[3];
        inputGUID[3] = tmpByte;
        tmpByte = inputGUID[1];
        inputGUID[1] = inputGUID[2];
        inputGUID[2] = tmpByte;

        // Reverse the 5th and the 6th
        tmpByte = inputGUID[4];
        inputGUID[4] = inputGUID[5];
        inputGUID[5] = tmpByte;

        // Reverse the 7th and the 8th
        tmpByte = inputGUID[6];
        inputGUID[6] = inputGUID[7];
        inputGUID[7] = tmpByte;

        long msb = 0L;
        for (int i = 0; i < 8; i++) {
            msb = msb << 8 | ((long) inputGUID[i] & 0xFFL);
        }
        long lsb = 0L;
        for (int i = 8; i < 16; i++) {
            lsb = lsb << 8 | ((long) inputGUID[i] & 0xFFL);
        }
        return new UUID(msb, lsb);
    }

    static final String readGUID(byte[] inputGUID) {
        String guidTemplate = "NNNNNNNN-NNNN-NNNN-NNNN-NNNNNNNNNNNN";
        byte[] guid = inputGUID;

        StringBuilder sb = new StringBuilder(guidTemplate.length());
        for (int i = 0; i < 4; i++) {
            sb.append(Util.HEXCHARS[(guid[3 - i] & 0xF0) >> 4]);
            sb.append(Util.HEXCHARS[guid[3 - i] & 0x0F]);
        }
        sb.append('-');
        for (int i = 0; i < 2; i++) {
            sb.append(Util.HEXCHARS[(guid[5 - i] & 0xF0) >> 4]);
            sb.append(Util.HEXCHARS[guid[5 - i] & 0x0F]);
        }
        sb.append('-');
        for (int i = 0; i < 2; i++) {
            sb.append(Util.HEXCHARS[(guid[7 - i] & 0xF0) >> 4]);
            sb.append(Util.HEXCHARS[guid[7 - i] & 0x0F]);
        }
        sb.append('-');
        for (int i = 0; i < 2; i++) {
            sb.append(Util.HEXCHARS[(guid[8 + i] & 0xF0) >> 4]);
            sb.append(Util.HEXCHARS[guid[8 + i] & 0x0F]);
        }
        sb.append('-');
        for (int i = 0; i < 6; i++) {
            sb.append(Util.HEXCHARS[(guid[10 + i] & 0xF0) >> 4]);
            sb.append(Util.HEXCHARS[guid[10 + i] & 0x0F]);
        }

        return sb.toString();
    }

    static boolean isActivityTraceOn() {
        LogManager lm = LogManager.getLogManager();
        String activityTrace = lm.getProperty(ACTIVITY_ID_TRACE_PROPERTY);
        return ("on".equalsIgnoreCase(activityTrace));
    }

    /**
     * Determines if a column value should be transparently decrypted (based on SQLServerStatement and the connection
     * string settings).
     * 
     * @return true if the value should be transparently decrypted, false otherwise.
     */
    static boolean shouldHonorAEForRead(SQLServerStatementColumnEncryptionSetting stmtColumnEncryptionSetting,
            SQLServerConnection connection) {
        // Command leve setting trumps all
        switch (stmtColumnEncryptionSetting) {
            case DISABLED:
                return false;
            case ENABLED:
            case RESULTSET_ONLY:
                return true;
            default:
                // Check connection level setting!
                assert SQLServerStatementColumnEncryptionSetting.USE_CONNECTION_SETTING == stmtColumnEncryptionSetting : "Unexpected value for command level override";
                return (connection != null && connection.isColumnEncryptionSettingEnabled());
        }
    }

    /**
     * Determines if parameters should be transparently encrypted (based on SQLServerStatement and the connection string
     * settings).
     * 
     * @return true if the value should be transparently encrypted, false otherwise.
     */
    static boolean shouldHonorAEForParameters(SQLServerStatementColumnEncryptionSetting stmtColumnEncryptionSetting,
            SQLServerConnection connection) {
        // Command leve setting trumps all
        switch (stmtColumnEncryptionSetting) {
            case DISABLED:
            case RESULTSET_ONLY:
                return false;
            case ENABLED:
                return true;
            default:
                // Check connection level setting!
                assert SQLServerStatementColumnEncryptionSetting.USE_CONNECTION_SETTING == stmtColumnEncryptionSetting : "Unexpected value for command level override";
                return (connection != null && connection.isColumnEncryptionSettingEnabled());
        }
    }

    static void validateMoneyRange(BigDecimal bd, JDBCType jdbcType) throws SQLServerException {
        if (null == bd)
            return;

        switch (jdbcType) {
            case MONEY:
                if (!(bd.compareTo(SSType.MAX_VALUE_MONEY) > 0 || bd.compareTo(SSType.MIN_VALUE_MONEY) < 0)) {
                    return;
                }
                break;
            case SMALLMONEY:
                if (!(bd.compareTo(SSType.MAX_VALUE_SMALLMONEY) > 0 || bd.compareTo(SSType.MIN_VALUE_SMALLMONEY) < 0)) {
                    return;
                }
                break;
            default:
                break;
        }
        MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_valueOutOfRange"));
        Object[] msgArgs = {jdbcType};
        throw new SQLServerException(form.format(msgArgs), null);
    }

    static int getValueLengthBaseOnJavaType(Object value, JavaType javaType, Integer precision, Integer scale,
            JDBCType jdbcType) throws SQLServerException {
        switch (javaType) {
            // when the value of setObject() is null, the javaType stays
            // as OBJECT. We need to get the javaType base on jdbcType
            case OBJECT:
                switch (jdbcType) {
                    case DECIMAL:
                    case NUMERIC:
                        javaType = JavaType.BIGDECIMAL;
                        break;
                    case TIME:
                        javaType = JavaType.TIME;
                        break;
                    case TIMESTAMP:
                        javaType = JavaType.TIMESTAMP;
                        break;
                    case DATETIMEOFFSET:
                        javaType = JavaType.DATETIMEOFFSET;
                        break;
                    default:
                        break;
                }
                break;
            default:
                break;
        }

        switch (javaType) {
            case STRING:
                if (JDBCType.GUID == jdbcType) {
                    String guidTemplate = "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX";
                    return ((null == value) ? 0 : guidTemplate.length());
                } else if (JDBCType.TIMESTAMP == jdbcType || JDBCType.TIME == jdbcType
                        || JDBCType.DATETIMEOFFSET == jdbcType) {
                    return ((null == scale) ? TDS.MAX_FRACTIONAL_SECONDS_SCALE : scale);
                } else if (JDBCType.BINARY == jdbcType || JDBCType.VARBINARY == jdbcType) {
                    return ((null == value) ? 0 : (ParameterUtils.hexToBin((String) value).length));
                } else if (JDBCType.GEOMETRY == jdbcType) {
                    return ((null == value) ? 0 : ((Geometry) value).serialize().length);
                } else if (JDBCType.GEOGRAPHY == jdbcType) {
                    return ((null == value) ? 0 : ((Geography) value).serialize().length);
                } else {
                    return ((null == value) ? 0 : ((String) value).length());
                }
            case BYTEARRAY:
                return ((null == value) ? 0 : ((byte[]) value).length);
            case BIGDECIMAL:
                int length;
                if (null == precision) {
                    if (null == value) {
                        length = 0;
                    } else {
                        if (0 == ((BigDecimal) value).intValue()) {
                            String s = "" + value;
                            s = s.replaceAll("\\-", "");
                            if (s.startsWith("0.")) {
                                // remove the leading zero, eg., for 0.32, the precision should be 2 and not 3
                                s = s.replaceAll("0\\.", "");
                            } else {
                                s = s.replaceAll("\\.", "");
                            }
                            length = s.length();
                        }
                        // if the value is in scientific notation format
                        else if (("" + value).contains("E")) {
                            DecimalFormat dform = new DecimalFormat("###.#####");
                            String s = dform.format(value);
                            s = s.replaceAll("\\.", "");
                            s = s.replaceAll("\\-", "");
                            length = s.length();
                        } else {
                            length = ((BigDecimal) value).precision();
                        }
                    }
                } else {
                    length = precision;
                }
                return length;
            case TIMESTAMP:
            case TIME:
            case DATETIMEOFFSET:
                return ((null == scale) ? TDS.MAX_FRACTIONAL_SECONDS_SCALE : scale);
            case CLOB:
                return ((null == value) ? 0 : (DataTypes.NTEXT_MAX_CHARS * 2));
            case NCLOB:
            case READER:
                return ((null == value) ? 0 : DataTypes.NTEXT_MAX_CHARS);
            default:
                return 0;
        }
    }

    // If the access token is expiring within next 10 minutes, lets just re-create a token for this connection attempt.
    // If the token is expiring within the next 45 mins, try to fetch a new token if there is no thread already doing
    // it.
    // If a thread is already doing the refresh, just use the existing token and proceed.
    static boolean checkIfNeedNewAccessToken(SQLServerConnection connection, Date accessTokenExpireDate) {
        LOCK.lock();
        try {
            Date now = new Date();

            // if the token's expiration is within the next 45 mins
            // 45 mins * 60 sec/min * 1000 millisec/sec
            if ((accessTokenExpireDate.getTime() - now.getTime()) < (45 * 60 * 1000)) {

                // within the next 10 mins
                if ((accessTokenExpireDate.getTime() - now.getTime()) < (10 * 60 * 1000)) {
                    return true;
                } else {
                    // check if another thread is already updating the access token
                    if (connection.attemptRefreshTokenLocked) {
                        return false;
                    } else {
                        connection.attemptRefreshTokenLocked = true;
                        return true;
                    }
                }
            }
            return false;
        } finally {
            LOCK.unlock();
        }
    }

    @SuppressWarnings("unchecked")
    static  T newInstance(Class returnType, String className, String constructorArg,
            Object[] msgArgs) throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException, ClassNotFoundException {
        Class clazz = Class.forName(className);
        if (!returnType.isAssignableFrom(clazz)) {
            MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_unassignableError"));
            throw new IllegalArgumentException(form.format(msgArgs));
        }
        if (constructorArg == null) {
            return (T) clazz.getDeclaredConstructor().newInstance();
        } else {
            return (T) clazz.getDeclaredConstructor(String.class).newInstance(constructorArg);
        }
    }

    /**
     * Escapes single quotes (') in object name to convert and pass it as String safely.
     * 
     * @param name
     *        Object name to be passed as String
     * @return Converted object name
     */
    static String escapeSingleQuotes(String name) {
        return name.replace("'", "''");
    }

    static String convertInputStreamToString(java.io.InputStream is) throws IOException {
        java.io.ByteArrayOutputStream result = new java.io.ByteArrayOutputStream();
        byte[] buffer = new byte[1024];
        int length;
        while ((length = is.read(buffer)) != -1) {
            result.write(buffer, 0, length);
        }
        return result.toString();
    }

    static String zeroOneToYesNo(int i) {
        return 0 == i ? "NO" : "YES";
    }

    static byte[] charsToBytes(char[] chars) {
        if (chars == null)
            return null;
        byte[] bytes = new byte[chars.length * 2];
        for (int i = 0; i < chars.length; i++) {
            bytes[i * 2] = (byte) (0xff & (chars[i] >> 8));
            bytes[i * 2 + 1] = (byte) (0xff & (chars[i]));
        }
        return bytes;
    }

    static char[] bytesToChars(byte[] bytes) {
        if (bytes == null)
            return null;
        char[] chars = new char[bytes.length / 2];
        for (int i = 0; i < chars.length; i++) {
            chars[i] = (char) (((0xFF & (bytes[i * 2])) << 8) | (0xFF & bytes[i * 2 + 1]));
        }
        return chars;
    }

    static String getHashedSecret(String[] secrets) throws SQLServerException {
        try {
            MessageDigest md = MessageDigest.getInstance("SHA-256");
            for (String secret : secrets) {
                if (null != secret) {
                    md.update(secret.getBytes(java.nio.charset.StandardCharsets.UTF_16LE));
                }
            }
            return new String(md.digest());
        } catch (NoSuchAlgorithmException e) {
            throw new SQLServerException(SQLServerException.getErrString("R_NoSHA256Algorithm"), e);
        }
    }
}


final class SQLIdentifier {
    // Component names default to empty string (rather than null) for consistency
    // with API behavior which returns empty string (rather than null) when the
    // particular value is not present.

    private String serverName = "";

    final String getServerName() {
        return serverName;
    }

    final void setServerName(String name) {
        serverName = name;
    }

    private String databaseName = "";

    final String getDatabaseName() {
        return databaseName;
    }

    final void setDatabaseName(String name) {
        databaseName = name;
    }

    private String schemaName = "";

    final String getSchemaName() {
        return schemaName;
    }

    final void setSchemaName(String name) {
        schemaName = name;
    }

    private String objectName = "";

    final String getObjectName() {
        return objectName;
    }

    final void setObjectName(String name) {
        objectName = name;
    }

    final String asEscapedString() {
        StringBuilder fullName = new StringBuilder(256);

        if (serverName.length() > 0)
            fullName.append("[").append(serverName).append("].");

        if (databaseName.length() > 0)
            fullName.append("[").append(databaseName).append("].");
        else
            assert 0 == serverName.length();
        if (schemaName.length() > 0)

            fullName.append("[").append(schemaName).append("].");
        else if (databaseName.length() > 0)
            fullName.append('.');

        fullName.append("[").append(objectName).append("]");

        return fullName.toString();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy