/*
 * 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 static java.nio.charset.StandardCharsets.UTF_8;
import static java.util.concurrent.TimeUnit.SECONDS;

import java.text.MessageFormat;
import java.util.List;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ThreadFactory;

import javax.xml.bind.DatatypeConverter;

class CacheClear implements Runnable {

    private String keylookupValue;
    static private java.util.logging.Logger aeLogger = java.util.logging.Logger.getLogger("com.microsoft.sqlserver.jdbc.CacheClear");

    CacheClear(String keylookupValue) {
        this.keylookupValue = keylookupValue;
    }

    @Override
    public void run() {
        // remove() is a no-op if the key is not in the map.
        // It is a concurrentHashMap, update/remove operations are thread safe.
        synchronized (SQLServerSymmetricKeyCache.lock) {
            SQLServerSymmetricKeyCache instance = SQLServerSymmetricKeyCache.getInstance();
            if (instance.getCache().containsKey(keylookupValue)) {
                instance.getCache().get(keylookupValue).zeroOutKey();
                instance.getCache().remove(keylookupValue);
                if (aeLogger.isLoggable(java.util.logging.Level.FINE)) {
                    aeLogger.fine("Removed encryption key from cache...");
                }
            }
        }
    }
}

/**
 * 
 * Cache for the Symmetric keys
 *
 */
final class SQLServerSymmetricKeyCache {
    static Object lock = new Object();
    private final ConcurrentHashMap<String, SQLServerSymmetricKey> cache;
    private static final SQLServerSymmetricKeyCache instance = new SQLServerSymmetricKeyCache();
    private static ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1, new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread t = Executors.defaultThreadFactory().newThread(r);
            t.setDaemon(true);
            return t;
        }
    });

    static final private java.util.logging.Logger aeLogger = java.util.logging.Logger
            .getLogger("com.microsoft.sqlserver.jdbc.SQLServerSymmetricKeyCache");

    private SQLServerSymmetricKeyCache() {
        cache = new ConcurrentHashMap<String, SQLServerSymmetricKey>();
    }

    static SQLServerSymmetricKeyCache getInstance() {
        return instance;
    }

    ConcurrentHashMap<String, SQLServerSymmetricKey> getCache() {
        return cache;
    }

    /**
     * Retrieves key
     * 
     * @param keyInfo
     *            contains encryption meta data information
     * @param connection
     * @return plain text key
     */
    SQLServerSymmetricKey getKey(EncryptionKeyInfo keyInfo,
            SQLServerConnection connection) throws SQLServerException {
        SQLServerSymmetricKey encryptionKey = null;
        synchronized (lock) {
            String serverName = connection.getTrustedServerNameAE();
            assert null != serverName : "serverName should not be null in getKey.";

            StringBuffer keyLookupValuebuffer = new StringBuffer(serverName);
            String keyLookupValue = null;
            keyLookupValuebuffer.append(":");

            keyLookupValuebuffer.append(DatatypeConverter.printBase64Binary((new String(keyInfo.encryptedKey, UTF_8)).getBytes()));

            keyLookupValuebuffer.append(":");
            keyLookupValuebuffer.append(keyInfo.keyStoreName);
            keyLookupValue = keyLookupValuebuffer.toString();
            keyLookupValuebuffer.setLength(0); // Get rid of the buffer, will be garbage collected.

            if (aeLogger.isLoggable(java.util.logging.Level.FINE)) {
                aeLogger.fine("Checking trusted master key path...");
            }
            Boolean[] hasEntry = new Boolean[1];
            List<String> trustedKeyPaths = SQLServerConnection.getColumnEncryptionTrustedMasterKeyPaths(serverName, hasEntry);
            if (true == hasEntry[0]) {
                if ((null == trustedKeyPaths) || (0 == trustedKeyPaths.size()) || (!trustedKeyPaths.contains(keyInfo.keyPath))) {
                    MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UntrustedKeyPath"));
                    Object[] msgArgs = {keyInfo.keyPath, serverName};
                    throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
                }
            }

            if (aeLogger.isLoggable(java.util.logging.Level.FINE)) {
                aeLogger.fine("Checking Symmetric key cache...");
            }

            // if ColumnEncryptionKeyCacheTtl is 0 no caching at all
            if (!cache.containsKey(keyLookupValue)) {

                // Check for the connection provider first.
                SQLServerColumnEncryptionKeyStoreProvider provider = connection.getSystemColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName);

                // There is no connection provider of this name, check for the global system providers.
                if (null == provider) {
                    provider = SQLServerConnection.getGlobalSystemColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName);
                }

                // There is no global system provider of this name, check for the global custom providers.
                if (null == provider) {
                    provider = SQLServerConnection.getGlobalCustomColumnEncryptionKeyStoreProvider(keyInfo.keyStoreName);
                }

                // No provider was found of this name.
                if (null == provider) {
                    String systemProviders = connection.getAllSystemColumnEncryptionKeyStoreProviders();
                    String customProviders = SQLServerConnection.getAllGlobalCustomSystemColumnEncryptionKeyStoreProviders();
                    MessageFormat form = new MessageFormat(SQLServerException.getErrString("R_UnrecognizedKeyStoreProviderName"));
                    Object[] msgArgs = {keyInfo.keyStoreName, systemProviders, customProviders};
                    throw new SQLServerException(this, form.format(msgArgs), null, 0, false);
                }

                byte[] plaintextKey;
                plaintextKey = provider.decryptColumnEncryptionKey(keyInfo.keyPath, keyInfo.algorithmName, keyInfo.encryptedKey);
                encryptionKey = new SQLServerSymmetricKey(plaintextKey);

                /*
                 * a ColumnEncryptionKeyCacheTtl value of '0' means no caching at all. The expected use case is to have the application set it once.
                 * The application could set it multiple times, in which case a key gets the TTL defined at the time of its entry into the cache.
                 */
                long columnEncryptionKeyCacheTtl = SQLServerConnection.getColumnEncryptionKeyCacheTtl();
                if (0 != columnEncryptionKeyCacheTtl) {
                    cache.putIfAbsent(keyLookupValue, encryptionKey);
                    if (aeLogger.isLoggable(java.util.logging.Level.FINE)) {
                        aeLogger.fine("Adding encryption key to cache...");
                    }
                    scheduler.schedule(new CacheClear(keyLookupValue), columnEncryptionKeyCacheTtl, SECONDS);
                }
            }
            else {
                encryptionKey = cache.get(keyLookupValue);
            }
        }
        return encryptionKey;
    }
}

@@ -4259,16 +4259,8 @@
 if (
-true == 
 hasE

fix "Change this comparison to use the equals method"