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.ecm.core.redis.contribs; 020 021import static java.nio.charset.StandardCharsets.UTF_8; 022 023import java.io.IOException; 024import java.util.Arrays; 025import java.util.Collections; 026import java.util.List; 027import java.util.Map; 028 029import org.apache.commons.logging.Log; 030import org.apache.commons.logging.LogFactory; 031import org.nuxeo.ecm.core.api.NuxeoException; 032import org.nuxeo.ecm.core.redis.RedisAdmin; 033import org.nuxeo.ecm.core.redis.RedisExecutor; 034import org.nuxeo.ecm.core.storage.kv.KeyValueStoreProvider; 035import org.nuxeo.runtime.api.Framework; 036 037/** 038 * Redis implementation of a Key/Value Store Provider. 039 * <p> 040 * The following configuration properties are available: 041 * <ul> 042 * <li>namespace: the Redis namespace to use for keys (in addition to the global Redis namespace configured in the Redis 043 * service). 044 * </ul> 045 * 046 * @since 9.1 047 */ 048public class RedisKeyValueStore implements KeyValueStoreProvider { 049 050 private static final Log log = LogFactory.getLog(RedisKeyValueStore.class); 051 052 public static final String NAMESPACE_PROP = "namespace"; 053 054 protected static final Long ONE = Long.valueOf(1); 055 056 protected String namespace; 057 058 protected byte[] compareAndSetSHA; 059 060 protected byte[] compareAndDelSHA; 061 062 protected byte[] compareNullAndSetSHA; 063 064 protected static byte[] getBytes(String key) { 065 return key.getBytes(UTF_8); 066 } 067 068 @Override 069 public void initialize(Map<String, String> properties) { 070 log.debug("Initializing"); 071 String name = properties.get(NAMESPACE_PROP); 072 RedisAdmin redisAdmin = Framework.getService(RedisAdmin.class); 073 namespace = redisAdmin.namespace(name == null ? new String[0] : new String[] { name }); 074 try { 075 compareAndSetSHA = getBytes(redisAdmin.load("org.nuxeo.ecm.core.redis", "compare-and-set")); 076 compareAndDelSHA = getBytes(redisAdmin.load("org.nuxeo.ecm.core.redis", "compare-and-del")); 077 compareNullAndSetSHA = getBytes(redisAdmin.load("org.nuxeo.ecm.core.redis", "compare-null-and-set")); 078 } catch (IOException e) { 079 throw new NuxeoException("Cannot load Redis script", e); 080 } 081 } 082 083 @Override 084 public void close() { 085 log.debug("Closed"); 086 } 087 088 @Override 089 public void clear() { 090 RedisAdmin redisAdmin = Framework.getService(RedisAdmin.class); 091 redisAdmin.clear(namespace + "*"); 092 } 093 094 @Override 095 public void put(String key, byte[] value) { 096 RedisExecutor redisExecutor = Framework.getService(RedisExecutor.class); 097 redisExecutor.execute(jedis -> { 098 byte[] keyb = getBytes(namespace + key); 099 if (value == null) { 100 jedis.del(keyb); 101 } else { 102 jedis.set(keyb, value); 103 } 104 return null; 105 }); 106 } 107 108 @Override 109 public byte[] get(String key) { 110 RedisExecutor redisExecutor = Framework.getService(RedisExecutor.class); 111 return redisExecutor.execute(jedis -> jedis.get(getBytes(namespace + key))); 112 } 113 114 @Override 115 public boolean compareAndSet(String key, byte[] expected, byte[] value) { 116 if (expected == null && value == null) { 117 return get(key) == null; 118 } else { 119 byte[] sha; 120 List<byte[]> keys = Collections.singletonList(getBytes(namespace + key)); 121 List<byte[]> args; 122 if (expected == null) { 123 sha = compareNullAndSetSHA; 124 args = Collections.singletonList(value); 125 } else if (value == null) { 126 sha = compareAndDelSHA; 127 args = Collections.singletonList(expected); 128 } else { 129 sha = compareAndSetSHA; 130 args = Arrays.asList(expected, value); 131 } 132 RedisExecutor redisExecutor = Framework.getService(RedisExecutor.class); 133 Object result = redisExecutor.evalsha(sha, keys, args); 134 return ONE.equals(result); 135 } 136 } 137 138}