blob: 5153c0271c644be6df12b4d71fd103d0539fdd9a [file] [log] [blame]
//
// ========================================================================
// Copyright (c) 1995-2017 Mort Bay Consulting Pty. Ltd.
// ------------------------------------------------------------------------
// All rights reserved. This program and the accompanying materials
// are made available under the terms of the Eclipse Public License v1.0
// and Apache License v2.0 which accompanies this distribution.
//
// The Eclipse Public License is available at
// http://www.eclipse.org/legal/epl-v10.html
//
// The Apache License v2.0 is available at
// http://www.opensource.org/licenses/apache2.0.php
//
// You may elect to redistribute this code under either of these licenses.
// ========================================================================
//
package org.eclipse.jetty.gcloud.memcached.session;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.Serializable;
import java.util.Map;
import org.eclipse.jetty.gcloud.session.GCloudSessionManager;
import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
import org.eclipse.jetty.util.StringUtil;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import com.google.cloud.datastore.Key;
import net.rubyeye.xmemcached.MemcachedClient;
import net.rubyeye.xmemcached.XMemcachedClient;
import net.rubyeye.xmemcached.XMemcachedClientBuilder;
import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
/**
* GCloudMemcachedSessionManager
*
* Use memcached in front of GCloudDataStore
*
*/
public class GCloudMemcachedSessionManager extends GCloudSessionManager
{
private final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
protected String _host;
protected String _port;
protected MemcachedClient _client;
protected int _expirySec = 0;
private boolean _heartbeats = true;
/**
* ContextClassloaderSerializingTranscoder
*
* A xmemcached transcoder that will use the thread context classloader to
* resolve classes during object deserialization: necessary for Servlet Spec
* classloading order of context classloader first.
*
*/
public class ContextClassloaderSerializingTranscoder extends SerializingTranscoder
{
@Override
protected Object deserialize(byte[] in)
{
if (in == null)
return null;
Object rv = null;
try (ByteArrayInputStream bis = new ByteArrayInputStream(in);ObjectInputStream is = new ClassLoadingObjectInputStream(bis);)
{
rv = is.readObject();
}
catch (IOException e)
{
LOG.warn("Caught IOException decoding " + in.length + " bytes of data", e);
}
catch (ClassNotFoundException e)
{
LOG.warn("Caught CNFE decoding " + in.length + " bytes of data", e);
}
return rv;
}
}
/**
* MemcacheSession
*
* Needed to make a constructor public.
*/
public class MemcacheSession extends GCloudSessionManager.Session
{
public MemcacheSession(String sessionId, long created, long accessed, long maxInterval)
{
super(sessionId, created, accessed, maxInterval);
}
}
/**
* Every time a Session is put into the cache one of these objects
* is created to copy the data out of the in-memory session, and
* every time an object is read from the cache one of these objects
* a fresh Session object is created based on the data held by this
* object.
*/
public class SerializableSessionData implements Serializable
{
/**
*
*/
private static final long serialVersionUID = -7779120106058533486L;
String clusterId;
String contextPath;
String vhost;
long accessed;
long lastAccessed;
long createTime;
long cookieSetTime;
String lastNode;
long expiry;
long maxInactive;
Map<String, Object> attributes;
public SerializableSessionData()
{}
public SerializableSessionData(Session s)
{
clusterId = s.getClusterId();
contextPath = s.getContextPath();
vhost = s.getVHost();
accessed = s.getAccessed();
lastAccessed = s.getLastAccessedTime();
createTime = s.getCreationTime();
cookieSetTime = s.getCookieSetTime();
lastNode = s.getLastNode();
expiry = s.getExpiry();
maxInactive = s.getMaxInactiveInterval();
attributes = s.getAttributeMap();
}
private void writeObject(java.io.ObjectOutputStream out) throws IOException
{
out.writeUTF(clusterId); //session id
out.writeUTF(contextPath); //context path
out.writeUTF(vhost); //first vhost
out.writeLong(accessed);//accessTime
out.writeLong(lastAccessed); //lastAccessTime
out.writeLong(createTime); //time created
out.writeLong(cookieSetTime);//time cookie was set
out.writeUTF(lastNode); //name of last node managing
out.writeLong(expiry);
out.writeLong(maxInactive);
out.writeObject(attributes);
}
private void readObject(java.io.ObjectInputStream ois) throws IOException, ClassNotFoundException
{
clusterId = ois.readUTF();
contextPath = ois.readUTF();
vhost = ois.readUTF();
accessed = ois.readLong();//accessTime
lastAccessed = ois.readLong(); //lastAccessTime
createTime = ois.readLong(); //time created
cookieSetTime = ois.readLong();//time cookie was set
lastNode = ois.readUTF(); //last managing node
expiry = ois.readLong();
maxInactive = ois.readLong();
Object o = ois.readObject();
attributes = ((Map<String,Object>)o);
}
}
/**
* @return the expiry setting for memcached
*/
public int getExpirySec()
{
return _expirySec;
}
/**
* @param expirySec the time in seconds for an item to remain in memcached
*/
public void setExpirySec(int expirySec)
{
_expirySec = expirySec;
}
/**
* @param heartbeats if true memcached heartbeats are enabled. Default is true.
*/
public void setHeartbeats (boolean heartbeats)
{
_heartbeats = heartbeats;
}
@Override
public void doStart() throws Exception
{
if (StringUtil.isBlank(_host) || StringUtil.isBlank(_port))
throw new IllegalStateException("Memcached host and/or port not configured");
LOG.info("Memcached host {} port {}", _host, _port);
XMemcachedClientBuilder builder = new XMemcachedClientBuilder(_host+":"+_port);
_client = builder.build();
_client.setEnableHeartBeat(_heartbeats);
_client.setTranscoder(new ContextClassloaderSerializingTranscoder());
super.doStart();
}
@Override
public void doStop() throws Exception
{
super.doStop();
_client.shutdown();
_client = null;
}
@Override
protected Session load(Key key) throws Exception
{
//first try the memcache cache
if (LOG.isDebugEnabled()) LOG.debug("Loading key {} from memcached ", key.name());
Session session = loadFromMemcached(key.name());
if (session != null)
return session;
//then try gcloudatastore
return super.load(key);
}
/**
* @param key the key for the memcache item
* @return the Session inflated from memcache
* @throws Exception
*/
protected Session loadFromMemcached(String key) throws Exception
{
SerializableSessionData sd = _client.get(key);
if (sd == null)
return null;
Session session = new MemcacheSession (sd.clusterId, sd.createTime, sd.accessed, sd.maxInactive);
session.setLastNode(sd.lastNode);
session.setContextPath(sd.contextPath);
session.setVHost(sd.vhost);
session.setCookieSetTime(sd.cookieSetTime);
session.setLastAccessedTime(sd.lastAccessed);
session.setLastNode(sd.lastNode);
session.setExpiry(sd.expiry);
session.addAttributes(sd.attributes);
return session;
}
@Override
protected void save(Session session) throws Exception
{
//save to gcloud and then memcache
super.save(session);
saveToMemcached(session);
}
@Override
protected void delete (GCloudSessionManager.Session session)
{
Exception memcacheException = null;
try
{
deleteFromMemcached(session);
}
catch (Exception e)
{
memcacheException = e;
}
super.delete(session);
if (memcacheException != null)
throw new RuntimeException(memcacheException);
}
protected void deleteFromMemcached(Session session) throws Exception
{
Key gcloudKey = makeKey(session, _context);
_client.delete(gcloudKey.name());
}
/**
* Store the session into memcached
* @param session the Session to be serialized
* @throws Exception
*/
protected void saveToMemcached(Session session) throws Exception
{
Key gcloudKey = makeKey(session, _context);
_client.set(gcloudKey.name(), getExpirySec(), new SerializableSessionData(session));
}
/**
* @return the host address of the memcached server
*/
public String getHost()
{
return _host;
}
/**
* @param host the host address of the memcached server
*/
public void setHost(String host)
{
_host = host;
}
/**
* @return the port of the memcached server
*/
public String getPort()
{
return _port;
}
/**
* @param port the port of the memcached server
*/
public void setPort(String port)
{
_port = port;
}
}