Created
February 23, 2014 21:51
-
-
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.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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