Skip to content

Instantly share code, notes, and snippets.

@st-h
Created February 23, 2014 21:51
Show Gist options
  • Save st-h/9177861 to your computer and use it in GitHub Desktop.
Save st-h/9177861 to your computer and use it in GitHub Desktop.
Persister implementation for Database Session Plugin http://grails.org/plugin/database-session , which uses MongoDB as a datastore.
package org.songreel.session
import java.util.List;
import org.codehaus.groovy.grails.web.servlet.GrailsApplicationAttributes;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.util.Assert;
import com.mongodb.BasicDBObject
import com.mongodb.DB;
import com.mongodb.DBObject;
import grails.plugin.databasesession.InvalidatedSessionException;
import grails.plugin.databasesession.Persister;
import grails.plugin.databasesession.SessionProxyFilter;
class MongoSessionPersisterService implements Persister, InitializingBean {
def mongo
def grailsApplication
DB db
void afterPropertiesSet() {
def mongoConfig = grailsApplication.config?.grails?.mongo.clone()
def databaseName = mongoConfig?.remove("databaseName") ?: grailsApplication.metadata.getApplicationName()
db = mongo.getDB(databaseName)
}
@Override
public void create(String sessionId) {
log.info('create session: ' + sessionId);
long now = System.currentTimeMillis()
db.session.insert([_id: sessionId, created: now, accessed: now, maxInactive: 30])
}
@Override
public Object getAttribute(String sessionId, String name) throws InvalidatedSessionException {
log.info('get attribute: ' + name + ' from session: ' + sessionId);
if (name == null) return null
if (GrailsApplicationAttributes.FLASH_SCOPE == name) {
// special case; use request scope since a new deserialized instance is created each time it's retrieved from the session
def fs = SessionProxyFilter.request.getAttribute(GrailsApplicationAttributes.FLASH_SCOPE)
if (fs != null) {
log.info('returning FLASH_SCOPE variable: ' + fs)
return fs
}
}
def session = this.getSession(sessionId)
this.checkValid(session)
def attribute = deserializeAttributeValue(session[escape(name)]);
log.info('found: ' + attribute)
if (attribute != null && GrailsApplicationAttributes.FLASH_SCOPE == name) {
log.info('setting FLASH_SCOPE variable')
SessionProxyFilter.request.setAttribute(GrailsApplicationAttributes.FLASH_SCOPE, attribute)
}
return attribute
}
@Override
public void setAttribute(String sessionId, String name, Object value) throws InvalidatedSessionException {
log.info('set attribute: ' + name + ' to: ' + value + ' for session: ' + sessionId);
Assert.notNull name, 'name parameter cannot be null'
if (value == null) {
this.removeAttribute(sessionId, name)
return
}
// special case; use request scope and don't store in session, the filter will set it in the session at the end of the request
if (value != null && GrailsApplicationAttributes.FLASH_SCOPE == name) {
if (value != GrailsApplicationAttributes.FLASH_SCOPE) {
log.info('setting FLASH_SCOPE: ' + value)
SessionProxyFilter.request.setAttribute(GrailsApplicationAttributes.FLASH_SCOPE, value)
return
}
// the filter set the value as the key, so retrieve it from the request
value = SessionProxyFilter.request.getAttribute(GrailsApplicationAttributes.FLASH_SCOPE)
log.info('updated value for name: ' + name + ' to: ' + value)
}
def update = [accessed: System.currentTimeMillis()]
update[escape(name)] = serializeAttributeValue(value)
def session = db.session.findAndModify(
[
_id: sessionId,
invalid: [$exists: false]
],
[
$set: update
]
)
this.checkValid(session)
}
@Override
public void removeAttribute(String sessionId, String name) throws InvalidatedSessionException {
log.info('remove attribute: ' + name + ' from session: ' + sessionId);
if (name == null) return
def pull = [:]
pull[escape(name)] = 1
// only update valid sessions.
def session = db.session.findAndModify(
[
_id: sessionId,
invalid: [$exists: false]
],
[
$set: [accessed: System.currentTimeMillis()],
$pull: pull
]
)
this.checkValid(session)
}
@Override
public List<String> getAttributeNames(String sessionId) throws InvalidatedSessionException {
log.info("get attribute names for session: " + sessionId);
def session = this.getSession(sessionId)
this.checkValid(session)
List<String> attributeNames = new ArrayList<String>()
for (String attribute : session.keySet()) {
if (!'_id'.equals(attribute)) {
attributeNames.add(unescape(attribute))
}
}
return attributeNames
}
@Override
public void invalidate(String sessionId) {
log.info('invalidate session: ' + sessionId);
def conf = grailsApplication.config.grails.plugin.databasesession
def deleteInvalidSessions = conf.deleteInvalidSessions ?: false
if (deleteInvalidSessions) {
db.session.remove([_id: sessionId])
}
else {
db.session.update([_id: sessionId], [$set: [invalid: true]])
}
}
@Override
public long getLastAccessedTime(String sessionId) throws InvalidatedSessionException {
log.info('get last accessed time for session: ' + sessionId)
def session = this.getSession(sessionId);
this.checkValid(session)
return session.accessed
}
@Override
public void setMaxInactiveInterval(String sessionId, int interval) throws InvalidatedSessionException {
log.info('setting max inactive interval for session: ' + sessionId + ' to: ' + interval)
if (interval == 0) {
this.invalidate(sessionId)
}
def session = db.session.findAndModify(
[
_id: sessionId,
invalid: [$exists: false]
],
[
maxInactive: interval
]
)
this.checkValid(session)
}
@Override
public int getMaxInactiveInterval(String sessionId) throws InvalidatedSessionException {
log.info('get max inactive interval for session: ' + sessionId)
def session = this.getSession(sessionId)
this.checkValid(session)
return session.maxInactive
}
@Override
public boolean isValid(String sessionId) {
def session = this.getSession(sessionId)
if (session == null || session.invalid
|| session.accessed < (System.currentTimeMillis() - session.maxInactive * 1000 * 60)) {
log.info('session: ' + sessionId + ' is invalid')
return false
}
log.info('session: ' + sessionId + ' is valid')
return true;
}
protected void checkValid(def session) {
if (session == null || session.invalid != null) {
log.warn('session is invalid!')
throw new InvalidatedSessionException()
}
}
private String escape(String string) {
return string.replaceAll('\\.', '\uff0E').replaceAll('\\\$', '\uff04')
}
private String unescape(String string) {
return string.replaceAll('\uff0E', '\\.').replaceAll('\uff04', '\\\$')
}
private def getSession(String sessionId) {
db.session.findAndModify(
[
_id: sessionId
],
[
$set: [accessed: System.currentTimeMillis()]
]
)
}
private def deserializeAttributeValue(byte[] serialized) {
if (!serialized) {
return null
}
// might throw IOException - let the caller handle it
new ObjectInputStream(new ByteArrayInputStream(serialized)) {
@Override
protected Class<?> resolveClass(ObjectStreamClass objectStreamClass) throws IOException, ClassNotFoundException {
Class.forName objectStreamClass.name, true, Thread.currentThread().contextClassLoader
}
}.readObject()
}
private byte[] serializeAttributeValue(value) {
if (value == null) {
return null
}
ByteArrayOutputStream baos = new ByteArrayOutputStream()
// might throw IOException - let the caller handle it
new ObjectOutputStream(baos).writeObject value
baos.toByteArray()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment