/*
* (C) Copyright 2014 Nuxeo SA (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:
* Maxime Hilaire
*/
package org.nuxeo.ecm.core.redis.contribs;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.util.Set;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.nuxeo.ecm.core.api.NuxeoException;
import org.nuxeo.ecm.core.cache.AbstractCache;
import org.nuxeo.ecm.core.cache.CacheDescriptor;
import org.nuxeo.ecm.core.redis.RedisAdmin;
import org.nuxeo.ecm.core.redis.RedisCallable;
import org.nuxeo.ecm.core.redis.RedisExecutor;
import org.nuxeo.runtime.api.Framework;
import redis.clients.jedis.Jedis;
/**
* Cache implementation on top of Redis
*
* @since 6.0
*/
public class RedisCache extends AbstractCache {
protected static final String UTF_8 = "UTF-8";
protected static final Log log = LogFactory.getLog(RedisCache.class);
protected final RedisExecutor executor;
protected final String namespace;
public RedisCache(CacheDescriptor desc) {
super(desc);
executor = Framework.getService(RedisExecutor.class);
namespace = Framework.getService(RedisAdmin.class).namespace("cache", name);
}
protected String formatKey(String key) {
return namespace.concat(key);
}
protected Serializable deserializeValue(byte[] workBytes) throws IOException {
if (workBytes == null) {
return null;
}
InputStream bain = new ByteArrayInputStream(workBytes);
ObjectInputStream in = new ObjectInputStream(bain);
try {
return (Serializable) in.readObject();
} catch (ClassNotFoundException e) {
throw new NuxeoException(e);
}
}
protected static byte[] bytes(String string) {
try {
return string.getBytes(UTF_8);
} catch (IOException e) {
// cannot happen for UTF-8
throw new NuxeoException(e);
}
}
@Override
public Serializable get(final String key) {
return executor.execute(new RedisCallable<Serializable>() {
@Override
public Serializable call(Jedis jedis) {
try {
return deserializeValue(jedis.get(bytes(formatKey(key))));
} catch (IOException e) {
log.error(e);
return null;
}
}
});
}
@Override
public Set<String> keySet() {
return executor.execute(new RedisCallable<Set<String>>() {
@Override
public Set<String> call(Jedis jedis) {
int offset = namespace.length();
return jedis.keys(formatKey("*"))
.stream()
.map(key -> key.substring(offset))
.collect(Collectors.toSet());
}
});
}
protected byte[] serializeValue(Serializable value) throws IOException {
ByteArrayOutputStream baout = new ByteArrayOutputStream();
try (ObjectOutputStream out = new ObjectOutputStream(baout)) {
out.writeObject(value);
out.flush();
}
return baout.toByteArray();
}
@Override
public void invalidate(final String key) {
executor.execute(new RedisCallable<Void>() {
@Override
public Void call(Jedis jedis) {
jedis.del(new String[] { formatKey(key) });
return null;
}
});
}
@Override
public void invalidateAll() {
Framework.getService(RedisAdmin.class).clear(formatKey("*"));
}
@Override
public void put(final String key, final Serializable value) {
executor.execute(new RedisCallable<Void>() {
@Override
public Void call(Jedis jedis) {
try {
byte[] bkey = bytes(formatKey(key));
jedis.set(bkey, serializeValue(value));
// Redis set in second ttl but descriptor set as mn
int ttlKey = ttl * 60;
jedis.expire(bkey, ttlKey);
return null;
} catch (IOException e) {
throw new NuxeoException(e);
}
}
});
}
@Override
public boolean hasEntry(final String key) {
return executor.<Boolean>execute(new RedisCallable<Boolean>() {
@Override
public Boolean call(Jedis jedis) {
return jedis.exists(bytes(formatKey(key)));
}
}).booleanValue();
}
/**
* Too expensive to evaluate the # keys redis side, should monitor redis itself
* @return -1L
*/
@Override
public long getSize() {
return -1L;
}
}