/*
* (C) Copyright 2017 Nuxeo (http://nuxeo.com/) and others.
*
* 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.
*
* Contributors:
* Florent Guillaume
*/
package org.nuxeo.ecm.core.redis.contribs;
import static java.nio.charset.StandardCharsets.UTF_8;
import java.io.IOException;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.ecm.core.redis.RedisAdmin;
import org.nuxeo.ecm.core.redis.RedisExecutor;
import org.nuxeo.ecm.core.storage.kv.KeyValueStoreProvider;
import org.nuxeo.runtime.api.Framework;
/**
* Redis implementation of a Key/Value Store Provider.
* <p>
* The following configuration properties are available:
* <ul>
* <li>namespace: the Redis namespace to use for keys (in addition to the global Redis namespace configured in the Redis
* service).
* </ul>
*
* @since 9.1
*/
public class RedisKeyValueStore implements KeyValueStoreProvider {
private static final Log log = LogFactory.getLog(RedisKeyValueStore.class);
public static final String NAMESPACE_PROP = "namespace";
protected static final Long ONE = Long.valueOf(1);
protected String namespace;
protected byte[] compareAndSetSHA;
protected byte[] compareAndDelSHA;
protected byte[] compareNullAndSetSHA;
protected static byte[] getBytes(String key) {
return key.getBytes(UTF_8);
}
@Override
public void initialize(Map<String, String> properties) {
log.debug("Initializing");
String name = properties.get(NAMESPACE_PROP);
RedisAdmin redisAdmin = Framework.getService(RedisAdmin.class);
namespace = redisAdmin.namespace(name == null ? new String[0] : new String[] { name });
try {
compareAndSetSHA = getBytes(redisAdmin.load("org.nuxeo.ecm.core.redis", "compare-and-set"));
compareAndDelSHA = getBytes(redisAdmin.load("org.nuxeo.ecm.core.redis", "compare-and-del"));
compareNullAndSetSHA = getBytes(redisAdmin.load("org.nuxeo.ecm.core.redis", "compare-null-and-set"));
} catch (IOException e) {
throw new NuxeoException("Cannot load Redis script", e);
}
}
@Override
public void close() {
log.debug("Closed");
}
@Override
public void clear() {
RedisAdmin redisAdmin = Framework.getService(RedisAdmin.class);
redisAdmin.clear(namespace + "*");
}
@Override
public void put(String key, byte[] value) {
RedisExecutor redisExecutor = Framework.getService(RedisExecutor.class);
redisExecutor.execute(jedis -> {
byte[] keyb = getBytes(namespace + key);
if (value == null) {
jedis.del(keyb);
} else {
jedis.set(keyb, value);
}
return null;
});
}
@Override
public byte[] get(String key) {
RedisExecutor redisExecutor = Framework.getService(RedisExecutor.class);
return redisExecutor.execute(jedis -> jedis.get(getBytes(namespace + key)));
}
@Override
public boolean compareAndSet(String key, byte[] expected, byte[] value) {
if (expected == null && value == null) {
return get(key) == null;
} else {
byte[] sha;
List<byte[]> keys = Collections.singletonList(getBytes(namespace + key));
List<byte[]> args;
if (expected == null) {
sha = compareNullAndSetSHA;
args = Collections.singletonList(value);
} else if (value == null) {
sha = compareAndDelSHA;
args = Collections.singletonList(expected);
} else {
sha = compareAndSetSHA;
args = Arrays.asList(expected, value);
}
RedisExecutor redisExecutor = Framework.getService(RedisExecutor.class);
Object result = redisExecutor.evalsha(sha, keys, args);
return ONE.equals(result);
}
}
}