Package com.ovea.jetty.session.redis

Source Code of com.ovea.jetty.session.redis.RedisSessionManager$RedisSession

/**
* Copyright (C) 2011 Ovea <dev@ovea.com>
*
* 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.
*/
package com.ovea.jetty.session.redis;

import com.ovea.jetty.session.Serializer;
import com.ovea.jetty.session.SessionManagerSkeleton;
import com.ovea.jetty.session.serializer.XStreamSerializer;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.TransactionBlock;
import redis.clients.jedis.exceptions.JedisException;

import javax.naming.InitialContext;
import javax.servlet.http.HttpServletRequest;
import java.util.*;

import static java.lang.Integer.parseInt;
import static java.lang.Long.parseLong;

/**
* @author Mathieu Carbou (mathieu.carbou@gmail.com)
*/
public final class RedisSessionManager extends SessionManagerSkeleton<RedisSessionManager.RedisSession> {

    final static Logger LOG = Log.getLogger("com.ovea.jetty.session");
    private static final String[] FIELDS = {"id", "created", "accessed", "lastNode", "expiryTime", "lastSaved", "lastAccessed", "maxIdle", "cookieSet", "attributes"};

    private final JedisExecutor jedisExecutor;
    private final Serializer serializer;

    private long saveIntervalSec = 20; //only persist changes to session access times every 20 secs

    public RedisSessionManager(JedisPool jedisPool) {
        this(jedisPool, new XStreamSerializer());
    }

    public RedisSessionManager(String jndiName) {
        this(jndiName, new XStreamSerializer());
    }

    public RedisSessionManager(JedisPool jedisPool, Serializer serializer) {
        this.serializer = serializer;
        this.jedisExecutor = new PooledJedisExecutor(jedisPool);
    }

    public RedisSessionManager(final String jndiName, Serializer serializer) {
        this.serializer = serializer;
        this.jedisExecutor = new JedisExecutor() {
            JedisExecutor delegate;

            @Override
            public <V> V execute(JedisCallback<V> cb) {
                if (delegate == null) {
                    try {
                        InitialContext ic = new InitialContext();
                        JedisPool jedisPool = (JedisPool) ic.lookup(jndiName);
                        delegate = new PooledJedisExecutor(jedisPool);
                    } catch (Exception e) {
                        throw new IllegalStateException("Unable to find instance of " + JedisExecutor.class.getName() + " in JNDI location " + jndiName + " : " + e.getMessage(), e);
                    }
                }
                return delegate.execute(cb);
            }
        };
    }

    public void setSaveInterval(long sec) {
        saveIntervalSec = sec;
    }

    @Override
    public void doStart() throws Exception {
        serializer.start();
        super.doStart();
    }

    @Override
    public void doStop() throws Exception {
        super.doStop();
        serializer.stop();
    }

    @Override
    protected RedisSession loadSession(final String clusterId, final RedisSession current) {
        long now = System.currentTimeMillis();
        RedisSession loaded;
        if (current == null) {
            LOG.debug("[RedisSessionManager] loadSession - No session found in cache, loading id={}", clusterId);
            loaded = loadFromStore(clusterId, current);
        } else if (current.requestStarted()) {
            LOG.debug("[RedisSessionManager] loadSession - Existing session found in cache, loading id={}", clusterId);
            loaded = loadFromStore(clusterId, current);
        } else {
            loaded = current;
        }
        if (loaded == null) {
            LOG.debug("[RedisSessionManager] loadSession - No session found in Redis for id={}", clusterId);
            if (current != null)
                current.invalidate();
        } else if (loaded == current) {
            LOG.debug("[RedisSessionManager] loadSession - No change found in Redis for session id={}", clusterId);
            return loaded;
        } else if (!loaded.lastNode.equals(getSessionIdManager().getWorkerName()) || current == null) {
            //if the session in the database has not already expired
            if (loaded.expiryTime * 1000 > now) {
                //session last used on a different node, or we don't have it in memory
                loaded.changeLastNode(getSessionIdManager().getWorkerName());
            } else {
                LOG.debug("[RedisSessionManager] loadSession - Loaded session has expired, id={}", clusterId);
                loaded = null;
            }
        }
        return loaded;
    }

    private RedisSession loadFromStore(final String clusterId, final RedisSession current) {
        List<String> redisData = jedisExecutor.execute(new JedisCallback<List<String>>() {
            @Override
            public List<String> execute(Jedis jedis) {
                final String key = RedisSessionIdManager.REDIS_SESSION_KEY + clusterId;
                if (current == null) {
                    return jedis.exists(key) ? jedis.hmget(key, FIELDS) : null;
                } else {
                    String val = jedis.hget(key, "lastSaved");
                    if (val == null) {
                        // no session in store
                        return Collections.emptyList();
                    }
                    if (current.lastSaved != Long.parseLong(val)) {
                        // session has changed - reload
                        return jedis.hmget(key, FIELDS);
                    } else {
                        // session dit not changed in cache since last save
                        return null;
                    }
                }
            }
        });
        if (redisData == null) {
            // case where session has not been modified
            return current;
        }
        if (redisData.isEmpty() || redisData.get(0) == null) {
            // no session found in redis (no data)
            return null;
        }
        Map<String, String> data = new HashMap<String, String>();
        for (int i = 0; i < FIELDS.length; i++)
            data.put(FIELDS[i], redisData.get(i));
        String attrs = data.get("attributes");
        //noinspection unchecked
        return new RedisSession(data, attrs == null ? new HashMap<String, Object>() : serializer.deserialize(attrs, Map.class));
    }

    @Override
    protected void storeSession(final RedisSession session) {
        if (!session.redisMap.isEmpty()) {
            final Map<String, String> toStore = session.redisMap.containsKey("attributes") ?
                session.redisMap :
                new TreeMap<String, String>(session.redisMap);
            if (toStore.containsKey("attributes"))
                toStore.put("attributes", serializer.serialize(session.getSessionAttributes()));
            LOG.debug("[RedisSessionManager] storeSession - Storing session id={}", session.getClusterId());
            jedisExecutor.execute(new JedisCallback<Object>() {
                @Override
                public Object execute(Jedis jedis) {
                    session.lastSaved = System.currentTimeMillis();
                    toStore.put("lastSaved", "" + session.lastSaved);
                    return jedis.multi(new TransactionBlock() {
                        @Override
                        public void execute() throws JedisException {
                            final String key = RedisSessionIdManager.REDIS_SESSION_KEY + session.getClusterId();
                            super.hmset(key, toStore);
                            int ttl = session.getMaxInactiveInterval();
                            if (ttl > 0) {
                                super.expire(key, ttl);
                            }
                        }
                    });
                }
            });
            session.redisMap.clear();
        }
    }

    @Override
    protected RedisSession newSession(HttpServletRequest request) {
        return new RedisSession(request);
    }

    @Override
    protected void deleteSession(final RedisSession session) {
        LOG.debug("[RedisSessionManager] deleteSession - Deleting from Redis session id={}", session.getClusterId());
        jedisExecutor.execute(new JedisCallback<Object>() {
            @Override
            public Object execute(Jedis jedis) {
                return jedis.del(RedisSessionIdManager.REDIS_SESSION_KEY + session.getClusterId());
            }
        });
    }

    final class RedisSession extends SessionManagerSkeleton.SessionSkeleton {

        private final Map<String, String> redisMap = new TreeMap<String, String>();

        private long expiryTime;
        private long lastSaved;
        private String lastNode;
        private final ThreadLocal<Boolean> firstAccess = new ThreadLocal<Boolean>() {
            @Override
            protected Boolean initialValue() {
                return true;
            }
        };

        private RedisSession(HttpServletRequest request) {
            super(request);
            lastNode = getSessionIdManager().getWorkerName();
            long ttl = getMaxInactiveInterval();
            expiryTime = ttl <= 0 ? 0 : System.currentTimeMillis() / 1000 + ttl;
            // new session so prepare redis map accordingly
            redisMap.put("id", getClusterId());
            redisMap.put("context", getCanonicalizedContext());
            redisMap.put("virtualHost", getVirtualHost());
            redisMap.put("created", "" + getCreationTime());
            redisMap.put("lastNode", lastNode);
            redisMap.put("lastAccessed", "" + getLastAccessedTime());
            redisMap.put("accessed", "" + getAccessed());
            redisMap.put("expiryTime", "" + expiryTime);
            redisMap.put("maxIdle", "" + ttl);
            redisMap.put("cookieSet", "" + getCookieSetTime());
            redisMap.put("attributes", "");
        }

        RedisSession(Map<String, String> redisData, Map<String, Object> attributes) {
            super(parseLong(redisData.get("created")), parseLong(redisData.get("accessed")), redisData.get("id"));
            lastNode = redisData.get("lastNode");
            expiryTime = parseLong(redisData.get("expiryTime"));
            lastSaved = parseLong(redisData.get("lastSaved"));
            super.setMaxInactiveInterval(parseInt(redisData.get("maxIdle")));
            setCookieSetTime(parseLong(redisData.get("cookieSet")));
            for (Map.Entry<String, Object> entry : attributes.entrySet()) {
                super.doPutOrRemove(entry.getKey(), entry.getValue());
            }
            super.access(parseLong(redisData.get("lastAccessed")));
        }

        public void changeLastNode(String lastNode) {
            this.lastNode = lastNode;
            redisMap.put("lastNode", lastNode);
        }

        @Override
        public void setAttribute(String name, Object value) {
            super.setAttribute(name, value);
            redisMap.put("attributes", "");
        }

        @Override
        public void removeAttribute(String name) {
            super.removeAttribute(name);
            redisMap.put("attributes", "");
        }

        public final Map<String, Object> getSessionAttributes() {
            Map<String, Object> attrs = new LinkedHashMap<String, Object>();
            for (String key : super.getNames()) {
                attrs.put(key, super.doGet(key));
            }
            return attrs;
        }

        @Override
        protected boolean access(long time) {
            boolean ret = super.access(time);
            firstAccess.remove();
            int ttl = getMaxInactiveInterval();
            expiryTime = ttl <= 0 ? 0 : time / 1000 + ttl;
            // prepare serialization
            redisMap.put("lastAccessed", "" + getLastAccessedTime());
            redisMap.put("accessed", "" + getAccessed());
            redisMap.put("expiryTime", "" + expiryTime);
            return ret;
        }

        @Override
        public void setMaxInactiveInterval(int secs) {
            super.setMaxInactiveInterval(secs);
            // prepare serialization
            redisMap.put("maxIdle", "" + secs);
        }

        @Override
        protected void cookieSet() {
            super.cookieSet();
            // prepare serialization
            redisMap.put("cookieSet", "" + getCookieSetTime());
        }

        @Override
        protected void complete() {
            super.complete();
            if (!redisMap.isEmpty()
                && (redisMap.size() != 3
                || !redisMap.containsKey("lastAccessed")
                || !redisMap.containsKey("accessed")
                || !redisMap.containsKey("expiryTime")
                || getAccessed() - lastSaved >= saveIntervalSec * 1000)) {
                try {
                    willPassivate();
                    storeSession(this);
                    didActivate();
                } catch (Exception e) {
                    LOG.warn("[RedisSessionManager] complete - Problem persisting changed session data id=" + getId(), e);
                } finally {
                    redisMap.clear();
                }
            }
        }

        public boolean requestStarted() {
            boolean first = firstAccess.get();
            if (first)
                firstAccess.set(false);
            return first;
        }
    }
}
TOP

Related Classes of com.ovea.jetty.session.redis.RedisSessionManager$RedisSession

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.