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

com.bazaarvoice.dropwizard.caching.memcached.KeyUtils Maven / Gradle / Ivy

/*
 * Copyright 2014 Bazaarvoice, Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package com.bazaarvoice.dropwizard.caching.memcached;

import com.google.common.base.Charsets;
import com.google.common.base.Throwables;
import com.sun.jersey.core.util.Base64;
import net.spy.memcached.MemcachedClientIF;

import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

/**
 * Memcached key utility functions.
 */
public class KeyUtils {
    /**
     * Length of a base 64 encoded SHA-1 hash in characters.
     */
    private static final int HASH_LENGTH = 28;
    /**
     * Last char in a base 64 encoded SHA-1 hash.
     */
    private static final char LAST_HASH_CHAR = '=';
    /**
     * Maximum allowed length of a memcached key, in bytes.
     */
    private static final int MAX_KEY_BYTES = MemcachedClientIF.MAX_KEY_LENGTH;
    /**
     * Number of bytes to truncate the key at to accommodate they hash as a suffix.
     */
    private static final int KEY_TRUNCATE_BYTES = MAX_KEY_BYTES - HASH_LENGTH;

    /**
     * This method truncates keys that exceed the limit for memcached while attempting to keep them
     * unique. The key is truncated and a hash of the original key is appended.
     */
    public static String truncateKey(String key) {
        byte[] keyBytes = key.getBytes(Charsets.UTF_8);

        if (keyBytes.length < MAX_KEY_BYTES || (keyBytes.length == MAX_KEY_BYTES && keyBytes[keyBytes.length - 1] != LAST_HASH_CHAR)) {
            // Key is short enough
            // If the key ends in equal sign, it could conflict with a truncated/hashed key, so
            // truncate and hash it
            return key;
        }

        // Generate hash of original, full key
        byte[] keyHash = generateKeyHash(keyBytes);
        assert keyHash.length == HASH_LENGTH;
        assert keyHash[keyHash.length - 1] == LAST_HASH_CHAR;

        // Overwrite ending partial character with single byte chars
        // This is not strictly required, but keeps the key valid UTF-8
        int lastCharIndex = KEY_TRUNCATE_BYTES - 1;

        while ((keyBytes[lastCharIndex] & 0xC0) == 0x80) {
            lastCharIndex -= 1;
        }

        if (utf8CharLen(keyBytes[lastCharIndex]) > (KEY_TRUNCATE_BYTES - lastCharIndex)) {
            while (lastCharIndex < KEY_TRUNCATE_BYTES) {
                keyBytes[lastCharIndex++] = '+';
            }
        }

        // Append key hash
        System.arraycopy(keyHash, 0, keyBytes, KEY_TRUNCATE_BYTES, keyHash.length);
        return new String(keyBytes, 0, MAX_KEY_BYTES, Charsets.UTF_8);
    }

    private static byte[] generateKeyHash(byte[] keyBytes) {
        try {
            MessageDigest digest = MessageDigest.getInstance("SHA-1");
            byte[] hashBytes = digest.digest(keyBytes);
            return Base64.encode(hashBytes);
        } catch (NoSuchAlgorithmException ex) {
            // SHA-1 is included with every java distro, so this exception should not occur
            throw Throwables.propagate(ex);
        }
    }

    private static int utf8CharLen(byte byte1) {
        if ((byte1 & 0x80) == 0) {
            return 1;
        } else if ((byte1 & 0xE0) == 0xC0) {
            return 2;
        } else if ((byte1 & 0xF0) == 0xE0) {
            return 3;
        } else if ((byte1 & 0xF8) == 0xF0) {
            return 4;
        } else if ((byte1 & 0xFC) == 0xF8) {
            return 5;
        } else {
            return 6;
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy