001/*
002 * (C) Copyright 2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * All rights reserved. This program and the accompanying materials
005 * are made available under the terms of the GNU Lesser General Public License
006 * (LGPL) version 2.1 which accompanies this distribution, and is available at
007 * http://www.gnu.org/licenses/lgpl-2.1.html
008 *
009 * This library is distributed in the hope that it will be useful,
010 * but WITHOUT ANY WARRANTY; without even the implied warranty of
011 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012 * Lesser General Public License for more details.
013 *
014 * Contributors:
015 *     Maxime Hilaire
016 */
017
018package org.nuxeo.ecm.core.redis.contribs;
019
020import java.io.ByteArrayInputStream;
021import java.io.ByteArrayOutputStream;
022import java.io.IOException;
023import java.io.InputStream;
024import java.io.ObjectInputStream;
025import java.io.ObjectOutputStream;
026import java.io.Serializable;
027
028import org.apache.commons.logging.Log;
029import org.apache.commons.logging.LogFactory;
030import org.nuxeo.ecm.core.api.NuxeoException;
031import org.nuxeo.ecm.core.cache.AbstractCache;
032import org.nuxeo.ecm.core.cache.CacheDescriptor;
033import org.nuxeo.ecm.core.redis.RedisAdmin;
034import org.nuxeo.ecm.core.redis.RedisCallable;
035import org.nuxeo.ecm.core.redis.RedisExecutor;
036import org.nuxeo.runtime.api.Framework;
037
038import redis.clients.jedis.Jedis;
039
040/**
041 * Cache implementation on top of Redis
042 *
043 * @since 6.0
044 */
045public class RedisCache extends AbstractCache {
046
047    protected static final String UTF_8 = "UTF-8";
048
049    protected static final Log log = LogFactory.getLog(RedisCache.class);
050
051    protected final RedisExecutor executor;
052
053    protected final String namespace;
054
055    public RedisCache(CacheDescriptor desc) {
056        super(desc);
057        executor = Framework.getService(RedisExecutor.class);
058        namespace = Framework.getService(RedisAdmin.class).namespace("cache", name);
059    }
060
061    protected String formatKey(String key) {
062        return namespace.concat(key);
063    }
064
065    protected Serializable deserializeValue(byte[] workBytes) throws IOException {
066        if (workBytes == null) {
067            return null;
068        }
069
070        InputStream bain = new ByteArrayInputStream(workBytes);
071        ObjectInputStream in = new ObjectInputStream(bain);
072        try {
073            return (Serializable) in.readObject();
074        } catch (ClassNotFoundException e) {
075            throw new NuxeoException(e);
076        }
077    }
078
079    protected static byte[] bytes(String string) {
080        try {
081            return string.getBytes(UTF_8);
082        } catch (IOException e) {
083            // cannot happen for UTF-8
084            throw new NuxeoException(e);
085        }
086    }
087
088    @Override
089    public Serializable get(final String key) {
090        return executor.execute(new RedisCallable<Serializable>() {
091            @Override
092            public Serializable call(Jedis jedis) {
093                try {
094                    return deserializeValue(jedis.get(bytes(formatKey(key))));
095                } catch (IOException e) {
096                    log.error(e);
097                    return null;
098                }
099            }
100        });
101
102    }
103
104    protected byte[] serializeValue(Serializable value) throws IOException {
105        ByteArrayOutputStream baout = new ByteArrayOutputStream();
106        ObjectOutputStream out = new ObjectOutputStream(baout);
107        out.writeObject(value);
108        out.flush();
109        out.close();
110        return baout.toByteArray();
111    }
112
113    @Override
114    public void invalidate(final String key) {
115        executor.execute(new RedisCallable<Void>() {
116            @Override
117            public Void call(Jedis jedis) {
118                jedis.del(new String[] { formatKey(key) });
119                return null;
120            }
121        });
122    }
123
124    @Override
125    public void invalidateAll() {
126        Framework.getService(RedisAdmin.class).clear(formatKey("*"));
127    }
128
129    @Override
130    public void put(final String key, final Serializable value) {
131        executor.execute(new RedisCallable<Void>() {
132            @Override
133            public Void call(Jedis jedis) {
134                try {
135                    byte[] bkey = bytes(formatKey(key));
136                    jedis.set(bkey, serializeValue(value));
137                    // Redis set in second ttl but descriptor set as mn
138                    int ttlKey = ttl * 60;
139                    jedis.expire(bkey, ttlKey);
140                    return null;
141                } catch (IOException e) {
142                    throw new NuxeoException(e);
143                }
144            }
145        });
146    }
147
148    @Override
149    public boolean hasEntry(final String key) {
150        return (Boolean) executor.execute(new RedisCallable<Serializable>() {
151            @Override
152            public Serializable call(Jedis jedis) {
153                return jedis.exists(bytes(formatKey(key)));
154            }
155        });
156    }
157
158}