001/*
002 * (C) Copyright 2014 Nuxeo SA (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 *     Maxime Hilaire
018 */
019
020package org.nuxeo.ecm.core.redis.contribs;
021
022import java.io.ByteArrayInputStream;
023import java.io.ByteArrayOutputStream;
024import java.io.IOException;
025import java.io.InputStream;
026import java.io.ObjectInputStream;
027import java.io.ObjectOutputStream;
028import java.io.Serializable;
029
030import org.apache.commons.logging.Log;
031import org.apache.commons.logging.LogFactory;
032import org.nuxeo.ecm.core.api.NuxeoException;
033import org.nuxeo.ecm.core.cache.AbstractCache;
034import org.nuxeo.ecm.core.cache.CacheDescriptor;
035import org.nuxeo.ecm.core.redis.RedisAdmin;
036import org.nuxeo.ecm.core.redis.RedisCallable;
037import org.nuxeo.ecm.core.redis.RedisExecutor;
038import org.nuxeo.runtime.api.Framework;
039
040import redis.clients.jedis.Jedis;
041
042/**
043 * Cache implementation on top of Redis
044 *
045 * @since 6.0
046 */
047public class RedisCache extends AbstractCache {
048
049    protected static final String UTF_8 = "UTF-8";
050
051    protected static final Log log = LogFactory.getLog(RedisCache.class);
052
053    protected final RedisExecutor executor;
054
055    protected final String namespace;
056
057    public RedisCache(CacheDescriptor desc) {
058        super(desc);
059        executor = Framework.getService(RedisExecutor.class);
060        namespace = Framework.getService(RedisAdmin.class).namespace("cache", name);
061    }
062
063    protected String formatKey(String key) {
064        return namespace.concat(key);
065    }
066
067    protected Serializable deserializeValue(byte[] workBytes) throws IOException {
068        if (workBytes == null) {
069            return null;
070        }
071
072        InputStream bain = new ByteArrayInputStream(workBytes);
073        ObjectInputStream in = new ObjectInputStream(bain);
074        try {
075            return (Serializable) in.readObject();
076        } catch (ClassNotFoundException e) {
077            throw new NuxeoException(e);
078        }
079    }
080
081    protected static byte[] bytes(String string) {
082        try {
083            return string.getBytes(UTF_8);
084        } catch (IOException e) {
085            // cannot happen for UTF-8
086            throw new NuxeoException(e);
087        }
088    }
089
090    @Override
091    public Serializable get(final String key) {
092        return executor.execute(new RedisCallable<Serializable>() {
093            @Override
094            public Serializable call(Jedis jedis) {
095                try {
096                    return deserializeValue(jedis.get(bytes(formatKey(key))));
097                } catch (IOException e) {
098                    log.error(e);
099                    return null;
100                }
101            }
102        });
103
104    }
105
106    protected byte[] serializeValue(Serializable value) throws IOException {
107        ByteArrayOutputStream baout = new ByteArrayOutputStream();
108        ObjectOutputStream out = new ObjectOutputStream(baout);
109        out.writeObject(value);
110        out.flush();
111        out.close();
112        return baout.toByteArray();
113    }
114
115    @Override
116    public void invalidate(final String key) {
117        executor.execute(new RedisCallable<Void>() {
118            @Override
119            public Void call(Jedis jedis) {
120                jedis.del(new String[] { formatKey(key) });
121                return null;
122            }
123        });
124    }
125
126    @Override
127    public void invalidateAll() {
128        Framework.getService(RedisAdmin.class).clear(formatKey("*"));
129    }
130
131    @Override
132    public void put(final String key, final Serializable value) {
133        executor.execute(new RedisCallable<Void>() {
134            @Override
135            public Void call(Jedis jedis) {
136                try {
137                    byte[] bkey = bytes(formatKey(key));
138                    jedis.set(bkey, serializeValue(value));
139                    // Redis set in second ttl but descriptor set as mn
140                    int ttlKey = ttl * 60;
141                    jedis.expire(bkey, ttlKey);
142                    return null;
143                } catch (IOException e) {
144                    throw new NuxeoException(e);
145                }
146            }
147        });
148    }
149
150    @Override
151    public boolean hasEntry(final String key) {
152        return (Boolean) executor.execute(new RedisCallable<Serializable>() {
153            @Override
154            public Serializable call(Jedis jedis) {
155                return jedis.exists(bytes(formatKey(key)));
156            }
157        });
158    }
159
160}