ru.zinin.redis.session.RedisManager.java Source code

Java tutorial

Introduction

Here is the source code for ru.zinin.redis.session.RedisManager.java

Source

/*
 * Copyright 2011 Alexander V. Zinin
 *
 * 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 ru.zinin.redis.session;

import org.apache.catalina.*;
import org.apache.catalina.mbeans.MBeanUtils;
import org.apache.catalina.session.Constants;
import org.apache.catalina.util.LifecycleMBeanBase;
import org.apache.catalina.util.SessionIdGenerator;
import org.apache.commons.pool.impl.GenericObjectPool;
import org.apache.juli.logging.Log;
import org.apache.juli.logging.LogFactory;
import org.apache.tomcat.util.res.StringManager;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.Protocol;

import javax.naming.InitialContext;
import javax.naming.NamingException;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.IOException;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.Executors;

/**
 * Date: 28.10.11 22:16
 *
 * @author Alexander V. Zinin (mail@zinin.ru)
 */
public class RedisManager extends LifecycleMBeanBase implements Manager, PropertyChangeListener {
    private final Log log = LogFactory.getLog(RedisManager.class);

    private static final String info = "RedisManager/1.0";
    private static final StringManager sm = StringManager.getManager(Constants.Package);

    private Container container;

    private int maxInactiveInterval = 30 * 60;
    private int sessionIdLength = 32;
    private int maxActiveSessions = -1;

    private String secureRandomClass = null;
    private String secureRandomAlgorithm = "SHA1PRNG";
    private String secureRandomProvider = null;
    private SessionIdGenerator sessionIdGenerator = null;

    private String jedisJndiName = "pool/jedis";

    private String redisHostname = "localhost";
    private int redisPort = Protocol.DEFAULT_PORT;
    private int redisTimeout = Protocol.DEFAULT_TIMEOUT;
    private String redisPassword;
    private JedisPool pool;

    private boolean disableListeners = false;
    private int dbIndex = 0;

    private PropertyChangeSupport support = new PropertyChangeSupport(this);

    private final RedisEventListenerThread eventListenerThread = new RedisEventListenerThread(this);

    @Override
    public Container getContainer() {
        return container;
    }

    @Override
    public void setContainer(Container container) {
        log.trace(String.format("EXEC setContainer(%s);", container));

        // De-register from the old Container (if any)
        if ((this.container != null) && (this.container instanceof Context)) {
            this.container.removePropertyChangeListener(this);
        }

        Container oldContainer = this.container;
        this.container = container;
        support.firePropertyChange("container", oldContainer, this.container);

        // Register with the new Container (if any)
        if ((this.container != null) && (this.container instanceof Context)) {
            setMaxInactiveInterval(((Context) this.container).getSessionTimeout() * 60);
            this.container.addPropertyChangeListener(this);
        }
    }

    @Override
    public boolean getDistributable() {
        log.trace("EXEC getDistributable();");

        return true;
    }

    @Override
    public void setDistributable(boolean distributable) {
        log.trace(String.format("EXEC setDistributable(%s);", distributable));

        if (!distributable) {
            log.error("Only distributable web applications supported.");
        }
    }

    @Override
    public String getInfo() {
        log.trace("EXEC getInfo();");

        return info;
    }

    @Override
    public int getMaxInactiveInterval() {
        log.trace("EXEC getMaxInactiveInterval();");

        return maxInactiveInterval;
    }

    @Override
    public void setMaxInactiveInterval(int interval) {
        log.trace(String.format("EXEC setMaxInactiveInterval(%d);", interval));

        int oldMaxInactiveInterval = this.maxInactiveInterval;
        this.maxInactiveInterval = interval;
        support.firePropertyChange("maxInactiveInterval", Integer.valueOf(oldMaxInactiveInterval),
                Integer.valueOf(this.maxInactiveInterval));
    }

    @Override
    public int getSessionIdLength() {
        log.trace("EXEC getSessionIdLength();");

        return sessionIdLength;
    }

    @Override
    public void setSessionIdLength(int idLength) {
        log.trace(String.format("EXEC setSessionIdLength(%d);", idLength));

        int oldSessionIdLength = this.sessionIdLength;
        this.sessionIdLength = idLength;
        support.firePropertyChange("sessionIdLength", Integer.valueOf(oldSessionIdLength),
                Integer.valueOf(this.sessionIdLength));
    }

    @Override
    public long getSessionCounter() {
        log.trace("EXEC getSessionCounter();");

        return 0;
    }

    @Override
    public void setSessionCounter(long sessionCounter) {
        log.trace(String.format("EXEC setSessionCounter(%d);", sessionCounter));
    }

    @Override
    public int getMaxActive() {
        log.trace("EXEC getMaxActive();");

        return 0;
    }

    @Override
    public void setMaxActive(int maxActive) {
        log.trace(String.format("EXEC setMaxActive(%d);", maxActive));
    }

    public int getMaxActiveSessions() {
        log.trace("EXEC getMaxActiveSessions();");

        return this.maxActiveSessions;
    }

    public void setMaxActiveSessions(int max) {
        log.trace(String.format("EXEC setMaxActiveSessions(%d);", max));

        int oldMaxActiveSessions = this.maxActiveSessions;
        this.maxActiveSessions = max;
        support.firePropertyChange("maxActiveSessions", Integer.valueOf(oldMaxActiveSessions),
                Integer.valueOf(this.maxActiveSessions));
    }

    @Override
    public int getActiveSessions() {
        log.trace("EXEC getActiveSessions();");

        String key = RedisSessionKeys.getSessionsKey();

        Long result;
        Jedis jedis = pool.getResource();
        try {
            jedis.select(dbIndex);
            result = jedis.zcard(key);
            pool.returnResource(jedis);
        } catch (Throwable e) {
            pool.returnBrokenResource(jedis);
            throw new RuntimeException(e);
        }

        return result.intValue();
    }

    @Override
    public long getExpiredSessions() {
        log.trace("EXEC getExpiredSessions();");

        return 0;
    }

    @Override
    public void setExpiredSessions(long expiredSessions) {
        log.trace(String.format("EXEC setExpiredSessions(%d);", expiredSessions));
    }

    @Override
    public int getRejectedSessions() {
        log.trace("EXEC getRejectedSessions();");

        return 0;
    }

    @Override
    public int getSessionMaxAliveTime() {
        log.trace("EXEC getSessionMaxAliveTime();");

        return 0;
    }

    @Override
    public void setSessionMaxAliveTime(int sessionMaxAliveTime) {
        log.trace(String.format("EXEC setSessionMaxAliveTime(%d);", sessionMaxAliveTime));
    }

    @Override
    public int getSessionAverageAliveTime() {
        log.trace("EXEC getSessionAverageAliveTime();");

        return 0;
    }

    @Override
    public int getSessionCreateRate() {
        log.trace("EXEC getSessionCreateRate();");

        return 0;
    }

    @Override
    public int getSessionExpireRate() {
        log.trace("EXEC getSessionExpireRate();");

        return 0;
    }

    @Override
    public void add(Session session) {
        log.trace(String.format("EXEC add(%s);", session));
    }

    @Override
    public void addPropertyChangeListener(PropertyChangeListener listener) {
        log.trace(String.format("EXEC addPropertyChangeListener(%s);", listener));

        support.addPropertyChangeListener(listener);
    }

    @Override
    public void changeSessionId(Session session) {
        log.trace(String.format("EXEC changeSessionId(%s);", session));

        String oldId = session.getIdInternal();
        session.setId(generateSessionId(), false);
        String newId = session.getIdInternal();

        container.fireContainerEvent(Context.CHANGE_SESSION_ID_EVENT, new String[] { oldId, newId });
    }

    @Override
    public Session createEmptySession() {
        log.trace("EXEC createEmptySession();");

        throw new UnsupportedOperationException("Cannot create empty session.");
    }

    @Override
    public Session createSession(String sessionId) {
        log.trace(String.format("EXEC createSession(%s);", sessionId));

        if ((maxActiveSessions >= 0) && (getActiveSessions() >= maxActiveSessions)) {
            throw new IllegalStateException(sm.getString("managerBase.createSession.ise"));
        }

        String id = sessionId;
        if (id == null) {
            id = generateSessionId();
        }

        return new RedisHttpSession(id, this, maxInactiveInterval);
    }

    @Override
    public Session findSession(String id) throws IOException {
        log.trace(String.format("EXEC findSession(%s);", id));

        if (id == null) {
            return null;
        }

        String key = RedisSessionKeys.getLastAccessTimeKey(id);

        String lastAccessTime;
        Jedis jedis = pool.getResource();
        try {
            jedis.select(dbIndex);
            lastAccessTime = jedis.get(key);
            pool.returnResource(jedis);
        } catch (Throwable e) {
            pool.returnBrokenResource(jedis);
            throw new RuntimeException(e);
        }

        if (lastAccessTime == null) {
            return null;
        }

        return new RedisHttpSession(id, this);
    }

    @Override
    public Session[] findSessions() {
        log.trace("EXEC findSessions();");

        Set<String> sessionIds;

        Jedis jedis = pool.getResource();
        try {
            jedis.select(dbIndex);
            sessionIds = jedis.zrangeByScore(RedisSessionKeys.getSessionsKey(), 0, Double.MAX_VALUE);

            pool.returnResource(jedis);
        } catch (Throwable e) {
            pool.returnBrokenResource(jedis);
            throw new RuntimeException(e);
        }

        Set<RedisHttpSession> result = new HashSet<RedisHttpSession>(sessionIds.size());
        for (String sessionId : sessionIds) {
            RedisHttpSession session = new RedisHttpSession(sessionId, this);
            if (session.isValid()) {
                result.add(session);
            }
        }

        //noinspection ToArrayCallWithZeroLengthArrayArgument
        return result.toArray(new Session[] {});
    }

    @Override
    public void load() throws ClassNotFoundException, IOException {
        log.trace("EXEC load();");
    }

    @Override
    public void remove(Session session) {
        log.trace(String.format("EXEC remove(%s);", session));

        remove(session, false);
    }

    @Override
    public void remove(Session session, boolean update) {
        log.trace(String.format("EXEC remove(%s, %s);", session, update));
    }

    @Override
    public void removePropertyChangeListener(PropertyChangeListener listener) {
        log.trace(String.format("EXEC removePropertyChangeListener(%s);", listener));

        support.removePropertyChangeListener(listener);
    }

    @Override
    public void unload() throws IOException {
        log.trace("EXEC unload();");
    }

    @Override
    public void backgroundProcess() {
        log.trace("EXEC backgroundProcess();");

        long max = System.currentTimeMillis() - (maxInactiveInterval * 1000);
        Set<String> sessionIds;

        Jedis jedis = pool.getResource();
        try {
            jedis.select(dbIndex);
            sessionIds = jedis.zrangeByScore(RedisSessionKeys.getSessionsKey(), 0, max);

            pool.returnResource(jedis);
        } catch (Throwable e) {
            pool.returnBrokenResource(jedis);
            throw new RuntimeException(e);
        }

        log.debug(sessionIds.size() + " sessions expired.");

        Set<String> removedIds = new HashSet<String>();
        if (!sessionIds.isEmpty()) {
            jedis = pool.getResource();
            try {
                for (String sessionId : sessionIds) {
                    if (jedis.zrem(RedisSessionKeys.getSessionsKey(), sessionId) > 0) {
                        removedIds.add(sessionId);
                    }
                }

                pool.returnResource(jedis);
            } catch (Throwable e) {
                pool.returnBrokenResource(jedis);
                throw new RuntimeException(e);
            }

            for (String sessionId : removedIds) {
                RedisHttpSession session = new RedisHttpSession(sessionId, this);

                session.expire();
            }
        }
    }

    @Override
    protected String getDomainInternal() {
        log.trace("EXEC getDomainInternal();");

        return MBeanUtils.getDomain(container);
    }

    @Override
    protected String getObjectNameKeyProperties() {
        log.trace("EXEC getObjectNameKeyProperties();");

        StringBuilder name = new StringBuilder("type=Manager");

        if (container instanceof Context) {
            name.append(",context=");
            String contextName = container.getName();
            if (!contextName.startsWith("/")) {
                name.append('/');
            }
            name.append(contextName);

            Context context = (Context) container;
            name.append(",host=");
            name.append(context.getParent().getName());
        } else {
            // Unlikely / impossible? Handle it to be safe
            name.append(",container=");
            name.append(container.getName());
        }

        return name.toString();
    }

    public String getJedisJndiName() {
        return jedisJndiName;
    }

    public void setJedisJndiName(String jedisJndiName) {
        this.jedisJndiName = jedisJndiName;
    }

    public String getRedisHostname() {
        return redisHostname;
    }

    public void setRedisHostname(String redisHostname) {
        this.redisHostname = redisHostname;
    }

    public int getRedisPort() {
        return redisPort;
    }

    public void setRedisPort(int redisPort) {
        this.redisPort = redisPort;
    }

    public int getRedisTimeout() {
        return redisTimeout;
    }

    public void setRedisTimeout(int redisTimeout) {
        this.redisTimeout = redisTimeout;
    }

    public String getRedisPassword() {
        return redisPassword;
    }

    public void setRedisPassword(String redisPassword) {
        this.redisPassword = redisPassword;
    }

    public JedisPool getPool() {
        return pool;
    }

    public boolean isDisableListeners() {
        return disableListeners;
    }

    public void setDisableListeners(boolean disableListeners) {
        this.disableListeners = disableListeners;
    }

    public int getDbIndex() {
        return dbIndex;
    }

    public void setDbIndex(int dbIndex) {
        this.dbIndex = dbIndex;
    }

    @Override
    protected void startInternal() throws LifecycleException {
        log.trace("EXEC startInternal();");

        sessionIdGenerator = new SessionIdGenerator();
        sessionIdGenerator.setJvmRoute(getJvmRoute());
        sessionIdGenerator.setSecureRandomAlgorithm(getSecureRandomAlgorithm());
        sessionIdGenerator.setSecureRandomClass(getSecureRandomClass());
        sessionIdGenerator.setSecureRandomProvider(getSecureRandomProvider());
        sessionIdGenerator.setSessionIdLength(getSessionIdLength());

        // Force initialization of the random number generator
        if (log.isDebugEnabled()) {
            log.debug("Force random number initialization starting");
        }
        sessionIdGenerator.generateSessionId();
        if (log.isDebugEnabled()) {
            log.debug("Force random number initialization completed");
        }

        log.debug("Trying to get jedis pool from JNDI...");
        InitialContext initialContext = null;
        try {
            initialContext = new InitialContext();
            pool = (JedisPool) initialContext.lookup("java:/comp/env/" + jedisJndiName);
        } catch (NamingException e) {
            log.warn("JedisPool not found in JNDI");
        }

        log.debug("Pool from JNDI: " + pool);

        if (pool == null) {
            GenericObjectPool.Config config = new GenericObjectPool.Config();
            config.testOnBorrow = true;
            pool = new JedisPool(config, redisHostname, redisPort, redisTimeout, redisPassword);
        }

        if (!disableListeners) {
            Executors.newSingleThreadExecutor().execute(eventListenerThread);
        }

        setState(LifecycleState.STARTING);
    }

    public Engine getEngine() {
        Engine e = null;
        for (Container c = getContainer(); e == null && c != null; c = c.getParent()) {
            if (c instanceof Engine) {
                e = (Engine) c;
            }
        }
        return e;
    }

    public String getJvmRoute() {
        Engine e = getEngine();
        return e == null ? null : e.getJvmRoute();
    }

    public String getSecureRandomClass() {
        return this.secureRandomClass;
    }

    public void setSecureRandomClass(String secureRandomClass) {
        String oldSecureRandomClass = this.secureRandomClass;
        this.secureRandomClass = secureRandomClass;
        support.firePropertyChange("secureRandomClass", oldSecureRandomClass, this.secureRandomClass);
    }

    public String getSecureRandomAlgorithm() {
        return secureRandomAlgorithm;
    }

    public void setSecureRandomAlgorithm(String secureRandomAlgorithm) {
        this.secureRandomAlgorithm = secureRandomAlgorithm;
    }

    public String getSecureRandomProvider() {
        return secureRandomProvider;
    }

    public void setSecureRandomProvider(String secureRandomProvider) {
        this.secureRandomProvider = secureRandomProvider;
    }

    @Override
    protected void stopInternal() throws LifecycleException {
        log.trace("EXEC stopInternal();");

        setState(LifecycleState.STOPPING);

        this.sessionIdGenerator = null;

        if (!disableListeners) {
            eventListenerThread.stop();
        }

        pool.destroy();
    }

    protected String generateSessionId() {
        String result;

        try {
            do {
                result = sessionIdGenerator.generateSessionId();
            } while (findSession(result) != null);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        return result;
    }

    @Override
    public void propertyChange(PropertyChangeEvent event) {
        log.trace(String.format("EXEC propertyChange(%s);", event));

        if (!(event.getSource() instanceof Context)) {
            return;
        }

        if (event.getPropertyName().equals("sessionTimeout")) {
            try {
                setMaxInactiveInterval((Integer) event.getNewValue() * 60);
            } catch (NumberFormatException e) {
                log.error(sm.getString("managerBase.sessionTimeout", event.getNewValue()));
            }
        }
    }
}