View Javadoc

1   //
2   //  ========================================================================
3   //  Copyright (c) 1995-2016 Mort Bay Consulting Pty. Ltd.
4   //  ------------------------------------------------------------------------
5   //  All rights reserved. This program and the accompanying materials
6   //  are made available under the terms of the Eclipse Public License v1.0
7   //  and Apache License v2.0 which accompanies this distribution.
8   //
9   //      The Eclipse Public License is available at
10  //      http://www.eclipse.org/legal/epl-v10.html
11  //
12  //      The Apache License v2.0 is available at
13  //      http://www.opensource.org/licenses/apache2.0.php
14  //
15  //  You may elect to redistribute this code under either of these licenses.
16  //  ========================================================================
17  //
18  
19  package org.eclipse.jetty.gcloud.memcached.session;
20  
21  
22  import java.io.ByteArrayInputStream;
23  import java.io.IOException;
24  import java.io.ObjectInputStream;
25  import java.io.Serializable;
26  import java.util.Map;
27  
28  import org.eclipse.jetty.gcloud.session.GCloudSessionManager;
29  import org.eclipse.jetty.util.ClassLoadingObjectInputStream;
30  import org.eclipse.jetty.util.StringUtil;
31  import org.eclipse.jetty.util.log.Log;
32  import org.eclipse.jetty.util.log.Logger;
33  
34  import com.google.cloud.datastore.Key;
35  
36  import net.rubyeye.xmemcached.MemcachedClient;
37  import net.rubyeye.xmemcached.XMemcachedClient;
38  import net.rubyeye.xmemcached.XMemcachedClientBuilder;
39  import net.rubyeye.xmemcached.transcoders.SerializingTranscoder;
40  
41  /**
42   * GCloudMemcachedSessionManager
43   *
44   * Use memcached in front of GCloudDataStore
45   *
46   */
47  public class GCloudMemcachedSessionManager extends GCloudSessionManager
48  {
49      private  final static Logger LOG = Log.getLogger("org.eclipse.jetty.server.session");
50      
51      protected String _host;
52      protected String _port;
53      protected MemcachedClient _client;
54      protected int _expirySec = 0;
55      private boolean _heartbeats = true;
56  
57      
58  
59      /**
60       * ContextClassloaderSerializingTranscoder
61       *
62       * A xmemcached transcoder that will use the thread context classloader to
63       * resolve classes during object deserialization: necessary for Servlet Spec
64       * classloading order of context classloader first.
65       *  
66       */
67      public class ContextClassloaderSerializingTranscoder extends SerializingTranscoder
68      {
69  
70          @Override
71          protected Object deserialize(byte[] in)
72          {
73  
74              if (in == null)
75                  return null;
76  
77              Object rv = null;
78              try (ByteArrayInputStream bis = new ByteArrayInputStream(in);ObjectInputStream is = new ClassLoadingObjectInputStream(bis);)
79              {
80  
81                  rv = is.readObject();
82              }
83              catch (IOException e)
84              {
85                  LOG.warn("Caught IOException decoding " + in.length + " bytes of data", e);
86              }
87              catch (ClassNotFoundException e)
88              {
89                  LOG.warn("Caught CNFE decoding " + in.length + " bytes of data", e);
90              }
91              
92              return rv;
93  
94          }
95      }
96  
97      
98      
99      /**
100      * MemcacheSession
101      *
102      * Needed to make a constructor public.
103      */
104     public class MemcacheSession extends GCloudSessionManager.Session
105     {
106 
107         public MemcacheSession(String sessionId, long created, long accessed, long maxInterval)
108         {
109             super(sessionId, created, accessed, maxInterval);
110         }  
111     }
112     
113     /**
114      * Every time a Session is put into the cache one of these objects
115      * is created to copy the data out of the in-memory session, and 
116      * every time an object is read from the cache one of these objects
117      * a fresh Session object is created based on the data held by this
118      * object.
119      */
120     public class SerializableSessionData implements Serializable
121     {
122         /**
123          * 
124          */
125         private static final long serialVersionUID = -7779120106058533486L;
126         String clusterId;
127         String contextPath;
128         String vhost;
129         long accessed;
130         long lastAccessed;
131         long createTime;
132         long cookieSetTime;
133         String lastNode;
134         long expiry;
135         long maxInactive;
136         Map<String, Object> attributes;
137 
138 
139 
140         public SerializableSessionData()
141         {}
142 
143 
144         public SerializableSessionData(Session s)
145         {
146             clusterId = s.getClusterId();
147             contextPath = s.getContextPath();
148             vhost = s.getVHost();
149             accessed = s.getAccessed();
150             lastAccessed = s.getLastAccessedTime();
151             createTime = s.getCreationTime();
152             cookieSetTime = s.getCookieSetTime();
153             lastNode = s.getLastNode();
154             expiry = s.getExpiry();
155             maxInactive = s.getMaxInactiveInterval();
156             attributes = s.getAttributeMap();
157        }
158        
159         
160         private void writeObject(java.io.ObjectOutputStream out) throws IOException
161         { 
162             out.writeUTF(clusterId); //session id
163             out.writeUTF(contextPath); //context path
164             out.writeUTF(vhost); //first vhost
165 
166             out.writeLong(accessed);//accessTime
167             out.writeLong(lastAccessed); //lastAccessTime
168             out.writeLong(createTime); //time created
169             out.writeLong(cookieSetTime);//time cookie was set
170             out.writeUTF(lastNode); //name of last node managing
171 
172             out.writeLong(expiry); 
173             out.writeLong(maxInactive);
174             out.writeObject(attributes);
175         }
176 
177         private void readObject(java.io.ObjectInputStream ois) throws IOException, ClassNotFoundException
178         {
179             clusterId = ois.readUTF();
180             contextPath = ois.readUTF();
181             vhost = ois.readUTF();
182             accessed = ois.readLong();//accessTime
183             lastAccessed = ois.readLong(); //lastAccessTime
184             createTime = ois.readLong(); //time created
185             cookieSetTime = ois.readLong();//time cookie was set
186             lastNode = ois.readUTF(); //last managing node
187             expiry = ois.readLong(); 
188             maxInactive = ois.readLong();
189             Object o = ois.readObject();
190             attributes = ((Map<String,Object>)o);
191         }
192     }
193 
194 
195     
196     
197     
198     
199     /**
200      * @return the expiry setting for memcached
201      */
202     public int getExpirySec()
203     {
204         return _expirySec;
205     }
206 
207     /**
208      * @param expirySec the time in seconds for an item to remain in memcached
209      */
210     public void setExpirySec(int expirySec)
211     {
212         _expirySec = expirySec;
213     }
214     
215     
216     /**
217      * @param heartbeats if true memcached heartbeats are enabled. Default is true.
218      */
219     public void setHeartbeats (boolean heartbeats)
220     {
221         _heartbeats  = heartbeats;
222     }
223 
224     
225     @Override
226     public void doStart() throws Exception
227     {
228         if (StringUtil.isBlank(_host) || StringUtil.isBlank(_port))
229             throw new IllegalStateException("Memcached host and/or port not configured");
230 
231         LOG.info("Memcached host {} port {}", _host, _port);
232         
233         XMemcachedClientBuilder builder = new XMemcachedClientBuilder(_host+":"+_port);
234         _client = builder.build();
235         _client.setEnableHeartBeat(_heartbeats);
236         
237 
238         _client.setTranscoder(new ContextClassloaderSerializingTranscoder());
239         super.doStart();
240     }
241 
242     @Override
243     public void doStop() throws Exception
244     {
245         super.doStop();
246         _client.shutdown();
247         _client = null;
248     }
249 
250     @Override
251     protected Session load(Key key) throws Exception
252     {
253         //first try the memcache cache
254         if (LOG.isDebugEnabled()) LOG.debug("Loading key {} from memcached ", key.name());
255         Session session =  loadFromMemcached(key.name());
256         if (session != null)
257             return session;
258 
259         //then try gcloudatastore
260         return super.load(key);
261     }
262 
263     /**
264      * @param key the key for the memcache item
265      * @return the Session inflated from memcache
266      * @throws Exception
267      */
268     protected Session loadFromMemcached(String key) throws Exception
269     {
270         SerializableSessionData sd = _client.get(key);
271 
272         if (sd == null)
273             return null;
274 
275         Session session = new MemcacheSession (sd.clusterId, sd.createTime, sd.accessed, sd.maxInactive);
276         session.setLastNode(sd.lastNode);
277         session.setContextPath(sd.contextPath);
278         session.setVHost(sd.vhost);
279         session.setCookieSetTime(sd.cookieSetTime);
280         session.setLastAccessedTime(sd.lastAccessed);
281         session.setLastNode(sd.lastNode);
282         session.setExpiry(sd.expiry);
283         session.addAttributes(sd.attributes);
284         return session;
285     }
286 
287 
288     @Override
289     protected void save(Session session) throws Exception
290     {
291         //save to gcloud and then memcache
292         super.save(session);        
293         saveToMemcached(session);
294     }
295 
296     
297     
298     @Override
299     protected void delete (GCloudSessionManager.Session session)
300     {  
301         Exception memcacheException = null;
302         try
303         {
304             deleteFromMemcached(session);
305         }
306         catch (Exception e)
307         {
308             memcacheException = e;
309         }
310         
311         super.delete(session);
312         if (memcacheException != null)
313             throw new RuntimeException(memcacheException);
314     }
315 
316     
317     protected void deleteFromMemcached(Session session) throws Exception
318     {
319         Key gcloudKey = makeKey(session, _context);
320         _client.delete(gcloudKey.name());
321     }
322 
323     /**
324      * Store the session into memcached
325      * @param session the Session to be serialized
326      * @throws Exception
327      */
328     protected void saveToMemcached(Session session) throws Exception
329     {
330         Key gcloudKey = makeKey(session, _context);
331         _client.set(gcloudKey.name(), getExpirySec(), new SerializableSessionData(session));
332     }
333 
334     /**
335      * @return the host address of the memcached server
336      */
337     public String getHost()
338     {
339         return _host;
340     }
341 
342     /**
343      * @param host the host address of the memcached server
344      */
345     public void setHost(String host)
346     {
347         _host = host;
348     }
349 
350     /**
351      * @return the port of the memcached server
352      */
353     public String getPort()
354     {
355         return _port;
356     }
357     
358    
359 
360     /**
361      * @param port the port of the memcached server
362      */
363     public void setPort(String port)
364     {
365         _port = port;
366     }
367 }