Java tutorial
/* * Copyright 2012 david gonzalez. * * 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.activecq.experiments.redis.impl; import com.activecq.experiments.datastores.SessionRequest; import com.activecq.experiments.datastores.SessionUtil; import com.activecq.experiments.redis.RedisConnectionPool; import com.activecq.experiments.redis.RedisSession; import org.apache.commons.lang.StringUtils; import org.apache.felix.scr.annotations.*; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.adapter.AdapterFactory; import org.apache.sling.commons.osgi.PropertiesUtil; import org.osgi.framework.Constants; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import redis.clients.jedis.Jedis; import redis.clients.jedis.Pipeline; import javax.servlet.*; import javax.servlet.http.Cookie; import javax.servlet.http.HttpSession; import java.io.*; import java.util.HashSet; import java.util.Map; import java.util.Set; /** * User: david */ @Component(label = "Experiments - Redis Session Util", description = "Redis session manager.") @Properties({ @Property(label = "Vendor", name = Constants.SERVICE_VENDOR, value = "ActiveCQ", propertyPrivate = true), @Property(label = "Adaptables", description = "Adaptables list the 'From' objects the adapter supports", name = AdapterFactory.ADAPTABLE_CLASSES, value = { "org.apache.sling.api.SlingHttpServletRequest" }, propertyPrivate = true), @Property(label = "Adapters", description = "Adapters list the 'To' objects the adapter supports", name = AdapterFactory.ADAPTER_CLASSES, value = { "com.activecq.experiments.redis.RedisSession" }, propertyPrivate = true), @Property(label = "Sling Filter Order", description = "Order in which this Sling Filter is invoked", name = "sling.filter.order", intValue = 0, propertyPrivate = true), @Property(label = "Sling Filter Scope", description = "Only invoke filter on requests entering CQ", name = "sling.filter.scope", value = "REQUEST", propertyPrivate = true) }) @Service(value = { SessionUtil.class, AdapterFactory.class, Filter.class }) public class RedisSessionUtilImpl implements SessionUtil, AdapterFactory, Filter { private final Logger log = LoggerFactory.getLogger(this.getClass()); /* Session Cookie Name */ private static final String DEFAULT_SESSION_COOKIE_NAME = "CQSESSION"; private String sessionCookieName = DEFAULT_SESSION_COOKIE_NAME; @Property(label = "Session Cookie Name", description = "Name of HTTP Cookie that stores the Redis DB Session UID for the user. Defaults to: CQSESSION", value = DEFAULT_SESSION_COOKIE_NAME) private static final String PROP_SESSION_COOKIE_NAME = "prop.session.cookie.name"; /* Session Expiry */ private static final int DEFAULT_SESSION_EXPIRY = 60; private int sessionExpiry = DEFAULT_SESSION_EXPIRY; @Property(label = "Session Expiry", description = "In mins. Length of time a Redis-based session can persist after its last access.", intValue = DEFAULT_SESSION_EXPIRY) private static final String PROP_SESSION_EXPIRY = "prop.session.expiry"; /* Session Cookie - Secure */ private static final boolean DEFAULT_SECURE_COOKIE = false; private boolean secureCookie = DEFAULT_SECURE_COOKIE; @Property(label = "Secure Session Cookie", description = "Set session cookie to only be sent over Secure (HTTPS) connections.", boolValue = DEFAULT_SECURE_COOKIE) private static final String PROP_SECURE_COOKIE = "prop.session.cookie.secure"; public static final String REDIS_KEY_PREFIX_SESSION = "cq:session:attr:"; public static final String REDIS_KEY_PREFIX_INDEX = "cq:session:index:"; public static final String REDIS_KEY_PREFIX_CREATED = "cq:session:created:"; public static final String REDIS_KEY_PREFIX_LAST_ACCESSED = "cq:session:lastaccessed:"; public static final String REDIS_KEY_PREFIX_DELIMITER = ":"; @Reference(target = "(service.uid=session)") private RedisConnectionPool pool; @Override public boolean hasSessionCookie(final SlingHttpServletRequest request) { final Cookie cookie = request.getCookie(this.getSessionCookieName()); final boolean hasCookie = cookie != null; if (!hasCookie) { log.debug("Redis Session Cookie cannot be found. Use of Redis-based sessions will silently fail."); } return hasCookie; } public String getSessionCookieName() { return sessionCookieName; } @Override public String getId(final SlingHttpServletRequest request) { final Cookie cookie = request.getCookie(this.getSessionCookieName()); if (cookie != null) { return StringUtils.stripToNull(cookie.getValue()); } return null; } @Override public Object getAttribute(final SlingHttpServletRequest request, final String name) throws IOException, ClassNotFoundException { if (!this.hasSessionCookie(request)) { return null; } final String sessionId = this.getId(request); return this.getAttribute(sessionId, name); } protected Object getAttribute(final String sessionId, String name) throws IOException, ClassNotFoundException { if (StringUtils.isBlank(sessionId) || StringUtils.isBlank(name)) { return null; } final String attrKey = this.getAttrKey(sessionId, name); final String lastAccessedAtKey = this.getLastAccessedAtKey(sessionId); final Jedis jedis = this.getJedis(); try { if (!jedis.exists(attrKey)) { return null; } final String timestamp = String.valueOf(System.currentTimeMillis()); final Object val = this.deserialize(jedis.get(attrKey.getBytes())); jedis.set(lastAccessedAtKey, timestamp); this.updateExpiration(sessionId); return val; } finally { this.returnJedis(jedis); } } @Override public void setAttribute(final SlingHttpServletRequest request, final String name, final Object value) throws IOException { if (!this.hasSessionCookie(request)) { return; } final String sessionId = this.getId(request); this.setAttribute(sessionId, name, value); } protected void setAttribute(final String sessionId, String name, final Object value) throws IOException { final String attrKey = this.getAttrKey(sessionId, name); final String createdAtKey = this.getCreatedAtKey(sessionId); final String lastAccessedAtKey = this.getLastAccessedAtKey(sessionId); final String indexKey = this.getIndexKey(sessionId); final Jedis jedis = this.getJedis(); try { final Pipeline pipeline = jedis.pipelined(); final String timestamp = String.valueOf(System.currentTimeMillis()); pipeline.set(attrKey.getBytes(), this.serialize(value)); pipeline.sadd(indexKey, attrKey); pipeline.setnx(createdAtKey, timestamp); pipeline.set(lastAccessedAtKey, timestamp); pipeline.sync(); this.updateExpiration(sessionId); } finally { this.returnJedis(jedis); } } @Override public void removeAttribute(final SlingHttpServletRequest request, final String name) { if (!this.hasSessionCookie(request)) { return; } final String sessionId = this.getId(request); this.removeAttribute(sessionId, name); } protected void removeAttribute(final String sessionId, final String name) { final String attrKey = this.getAttrKey(sessionId, name); final String lastAccessedAtKey = this.getLastAccessedAtKey(sessionId); final String indexKey = this.getIndexKey(sessionId); final Jedis jedis = this.getJedis(); try { final Pipeline pipeline = jedis.pipelined(); final String timestamp = String.valueOf(System.currentTimeMillis()); pipeline.del(attrKey.getBytes()); pipeline.srem(indexKey, attrKey); pipeline.set(lastAccessedAtKey, timestamp); pipeline.sync(); this.updateExpiration(sessionId); } finally { this.returnJedis(jedis); } } @Override public long getLastAccessTime(final SlingHttpServletRequest request) { if (!this.hasSessionCookie(request)) { return 0; } final String sessionId = this.getId(request); return this.getLastAccessTime(sessionId); } protected long getLastAccessTime(final String sessionId) { final String lastAccessedAtKey = this.getLastAccessedAtKey(sessionId); final Jedis jedis = this.getJedis(); try { final String lastAccessedAt = jedis.get(lastAccessedAtKey); if (lastAccessedAt == null) { return 0; } return Long.parseLong(lastAccessedAt); } finally { this.returnJedis(jedis); } } @Override public long getCreationTime(final SlingHttpServletRequest request) { if (!this.hasSessionCookie(request)) { return 0; } final String sessionId = this.getId(request); return this.getCreationTime(sessionId); } protected long getCreationTime(final String sessionId) { if (StringUtils.isBlank(sessionId)) { return 0; } final String createdAtKey = this.getCreatedAtKey(sessionId); final Jedis jedis = this.getJedis(); try { final String createdAt = jedis.get(createdAtKey); if (createdAt == null) { return 0; } return Long.parseLong(createdAt); } finally { this.returnJedis(jedis); } } @Override public Set<String> getAttributeNames(final SlingHttpServletRequest request) { if (!this.hasSessionCookie(request)) { return new HashSet<String>(); } final String sessionId = this.getId(request); return this.getAttributeNames(sessionId); } protected Set<String> getAttributeNames(final String sessionId) { if (StringUtils.isBlank(sessionId)) { return new HashSet<String>(); } final String indexKey = this.getIndexKey(sessionId); final Jedis jedis = this.getJedis(); try { final Set<String> members = jedis.smembers(indexKey); this.updateExpiration(sessionId); final Set<String> attributes = new HashSet<String>(); final String remove = this.getAttrKeyPrefix(sessionId); for (final String member : members) { final String tmp = StringUtils.removeStart(member, remove); attributes.add(tmp); } return attributes; } finally { this.returnJedis(jedis); } } @Override public void invalidate(final SlingHttpServletRequest request) { if (!this.hasSessionCookie(request)) { return; } final String sessionId = this.getId(request); this.invalidate(sessionId); } protected void invalidate(final String sessionId) { if (StringUtils.isBlank(sessionId)) { return; } final String indexKey = this.getIndexKey(sessionId); final String createdAtKey = this.getCreatedAtKey(sessionId); final String lastAccessedAtKey = this.getLastAccessedAtKey(sessionId); final Jedis jedis = this.getJedis(); try { final Set<String> keysToDelete = jedis.smembers(indexKey); keysToDelete.add(indexKey); keysToDelete.add(createdAtKey); keysToDelete.add(lastAccessedAtKey); jedis.del(keysToDelete.toArray(new String[keysToDelete.size()])); } finally { this.returnJedis(jedis); } } @Override public Cookie createSessionCookie() { final Cookie cookie = new Cookie(this.getSessionCookieName(), java.util.UUID.randomUUID().toString()); cookie.setPath("/"); cookie.setSecure(this.secureCookie); // Expire with browser session cookie.setMaxAge(-1); return cookie; } @Override public String createSession(final SlingHttpServletRequest request, final SlingHttpServletResponse response) { if (this.hasValidSession(request)) { return this.getId(request); } final Cookie cookie = this.createSessionCookie(); final String sessionId = cookie.getValue(); this.initSession(sessionId); response.addCookie(cookie); return sessionId; } @Override public HttpSession getSession(final SlingHttpServletRequest request) { return new RedisSession(request, this); } @Override public int getMaxInactiveInterval() { return this.sessionExpiry; } @Override public boolean hasValidSession(final SlingHttpServletRequest request) { if (!this.hasSessionCookie(request)) { return false; } final String sessionId = this.getId(request); if (sessionId == null) { return false; } if (this.getCreationTime(sessionId) < 1) { return false; } if (this.getLastAccessTime(sessionId) < 1) { return false; } return true; } protected Jedis getJedis() { return this.pool.getJedis(); } protected void returnJedis(final Jedis jedis) { this.pool.returnJedis(jedis); } protected long initSession(final String sessionId) { final String createdAtKey = this.getCreatedAtKey(sessionId); final String lastAccessedAtKey = this.getLastAccessedAtKey(sessionId); final Jedis jedis = this.getJedis(); try { final Pipeline pipeline = jedis.pipelined(); final long timeInMillis = System.currentTimeMillis(); final String timestamp = String.valueOf(timeInMillis); pipeline.setnx(createdAtKey, timestamp); pipeline.set(lastAccessedAtKey, timestamp); pipeline.sync(); this.updateExpiration(sessionId); return timeInMillis; } finally { this.returnJedis(jedis); } } protected void updateExpiration(String sessionId) { final String indexKey = this.getIndexKey(sessionId); final String createdAtKey = this.getCreatedAtKey(sessionId); final String lastAccessedAtKey = this.getLastAccessedAtKey(sessionId); final Jedis jedis = this.getJedis(); try { final Set<String> members = jedis.smembers(indexKey); final Pipeline pipeline = jedis.pipelined(); for (final String member : members) { pipeline.expire(member.getBytes(), sessionExpiry); } pipeline.expire(indexKey, sessionExpiry); pipeline.expire(createdAtKey, sessionExpiry); pipeline.expire(lastAccessedAtKey, sessionExpiry); pipeline.sync(); } finally { this.returnJedis(jedis); } } protected String getAttrKey(String sessionId, String name) { if (StringUtils.isBlank(sessionId) || StringUtils.isBlank(name)) { return null; } return REDIS_KEY_PREFIX_SESSION + sessionId + REDIS_KEY_PREFIX_DELIMITER + name; } protected String getAttrKeyPrefix(String sessionId) { if (StringUtils.isBlank(sessionId)) { return null; } return REDIS_KEY_PREFIX_SESSION + sessionId + REDIS_KEY_PREFIX_DELIMITER; } protected String getIndexKey(String sessionId) { if (sessionId == null) { return null; } return REDIS_KEY_PREFIX_INDEX + sessionId; } protected String getCreatedAtKey(String sessionId) { if (sessionId == null) { return null; } return REDIS_KEY_PREFIX_CREATED + sessionId; } protected String getLastAccessedAtKey(String sessionId) { if (sessionId == null) { return null; } return REDIS_KEY_PREFIX_LAST_ACCESSED + sessionId; } protected byte[] serialize(final Object obj) throws IOException { final ByteArrayOutputStream out = new ByteArrayOutputStream(); final ObjectOutputStream os = new ObjectOutputStream(out); try { os.writeObject(obj); return out.toByteArray(); } finally { out.close(); os.close(); } } protected Object deserialize(final byte[] bytes) throws IOException, ClassNotFoundException { if (bytes == null) { return null; } ByteArrayInputStream in = new ByteArrayInputStream(bytes); ObjectInputStream is = new ObjectInputStream(in); try { return is.readObject(); } finally { in.close(); is.close(); } } /************************************************************************** * * Sling Adapter Factory * *************************************************************************/ @Override public <AdapterType> AdapterType getAdapter(Object adaptable, Class<AdapterType> type) { // Ensure the adaptable object is of an appropriate type if (adaptable == null || !(adaptable instanceof SlingHttpServletRequest)) { log.warn("Object {} could not be adapted to {}", adaptable, type); return null; } final SlingHttpServletRequest request = ((SlingHttpServletRequest) adaptable); final RedisSession redisSession = new RedisSession(request, this); return (AdapterType) redisSession; } /************************************************************************** * * Sling Filter * *************************************************************************/ @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { if (!(request instanceof SlingHttpServletRequest) || !(response instanceof SlingHttpServletResponse)) { // Not a SlingHttpServletRequest/Response, so ignore. chain.doFilter(request, response); return; } final SlingHttpServletRequest slingRequest = (SlingHttpServletRequest) request; final SlingHttpServletResponse slingResponse = (SlingHttpServletResponse) response; if (this.hasValidSession(slingRequest)) { chain.doFilter(request, response); } else { // Add the Session Cookie to the current request so this request can access the SessionId // This SessionId is the same as the one added to the Response Cookie // This case should only happen once per session (the first time the session is instantiated) // Add Cookie to slingResponse so future requests can make use of it final String sessionId = this.createSession(slingRequest, slingResponse); final Cookie sessionCookie = new Cookie(this.getSessionCookieName(), sessionId); // Add sessionCookie to this request so it (this request) can make use of it final SessionRequest sessionRequest = new SessionRequest(slingRequest, sessionCookie); chain.doFilter(sessionRequest, slingResponse); } } @Override public void destroy() { } /************************************************************************** * * Sling Filter * *************************************************************************/ @Activate protected void activate(final Map<String, Object> properties) throws Exception { this.sessionCookieName = PropertiesUtil.toString(properties.get(PROP_SESSION_COOKIE_NAME), DEFAULT_SESSION_COOKIE_NAME); this.secureCookie = PropertiesUtil.toBoolean(properties.get(PROP_SECURE_COOKIE), DEFAULT_SECURE_COOKIE); // Convert to Seconds (60 * N) this.sessionExpiry = 60 * PropertiesUtil.toInteger(PROP_SESSION_EXPIRY, DEFAULT_SESSION_EXPIRY); } @Deactivate protected void deactivate(final Map<String, Object> properties) throws Exception { this.sessionCookieName = ""; this.sessionExpiry = 0; } }