001/*
002 * (C) Copyright 2017 Nuxeo (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Florent Guillaume
018 */
019package org.nuxeo.runtime.kv;
020
021import static java.nio.charset.StandardCharsets.UTF_8;
022
023import java.nio.ByteBuffer;
024import java.nio.charset.CharacterCodingException;
025import java.nio.charset.CharsetDecoder;
026import java.nio.charset.CodingErrorAction;
027import java.util.Collection;
028import java.util.HashMap;
029import java.util.Map;
030
031/**
032 * Key/Value Store common methods.
033 *
034 * @since 9.3
035 */
036public abstract class AbstractKeyValueStoreProvider implements KeyValueStoreProvider {
037
038    protected static final ThreadLocal<CharsetDecoder> UTF_8_DECODERS = ThreadLocal.withInitial(
039            () -> UTF_8.newDecoder().onMalformedInput(CodingErrorAction.REPORT).onUnmappableCharacter(
040                    CodingErrorAction.REPORT));
041
042    /**
043     * Converts UTF-8 bytes to a String, or throws if malformed.
044     *
045     * @throws CharacterCodingException
046     */
047    protected static String bytesToString(byte[] bytes) throws CharacterCodingException {
048        return bytes == null ? null : UTF_8_DECODERS.get().decode(ByteBuffer.wrap(bytes)).toString();
049    }
050
051    /**
052     * Converts a String to UTF-8 bytes.
053     */
054    protected static byte[] stringToBytes(String string) {
055        return string == null ? null : string.getBytes(UTF_8);
056    }
057
058    @Override
059    public void put(String key, byte[] value) {
060        put(key, value, 0);
061    }
062
063    @Override
064    public void put(String key, String value) {
065        put(key, stringToBytes(value), 0);
066    }
067
068    @Override
069    public void put(String key, String value, long ttl) {
070        put(key, stringToBytes(value), ttl);
071    }
072
073    @Override
074    public String getString(String key) {
075        byte[] bytes = get(key);
076        try {
077            return bytesToString(bytes);
078        } catch (CharacterCodingException e) {
079            throw new IllegalArgumentException("Value is not a String for key: " + key);
080        }
081    }
082
083    /*
084     * This default implementation is uninteresting. It is expected that underlying storage implementations
085     * will leverage bulk fetching to deliver significant optimizations over this simple loop.
086     */
087    @Override
088    public Map<String, byte[]> get(Collection<String> keys) {
089        Map<String, byte[]> map = new HashMap<>(keys.size());
090        for (String key : keys) {
091            byte[] value = get(key);
092            if (value != null) {
093                map.put(key, value);
094            }
095        }
096        return map;
097    }
098
099    /*
100     * This default implementation is uninteresting. It is expected that underlying storage implementations
101     * will leverage bulk fetching to deliver significant optimizations over this simple loop.
102     */
103    @Override
104    public Map<String, String> getStrings(Collection<String> keys) {
105        Map<String, String> map = new HashMap<>(keys.size());
106        for (String key : keys) {
107            String value = getString(key);
108            if (value != null) {
109                map.put(key, value);
110            }
111        }
112        return map;
113    }
114
115    @Override
116    public boolean compareAndSet(String key, byte[] expected, byte[] value) {
117        return compareAndSet(key, expected, value, 0);
118    }
119
120    @Override
121    public boolean compareAndSet(String key, String expected, String value) {
122        return compareAndSet(key, expected, value, 0);
123    }
124
125    @Override
126    public boolean compareAndSet(String key, String expected, String value, long ttl) {
127        return compareAndSet(key, stringToBytes(expected), stringToBytes(value), ttl);
128    }
129
130}