comparison src/org/eclipse/jetty/server/session/JDBCSessionManager.java @ 802:3428c60d7cfc

replace jetty jars with source
author Franklin Schmidt <fschmidt@gmail.com>
date Wed, 07 Sep 2016 21:15:48 -0600
parents
children
comparison
equal deleted inserted replaced
801:6a21393191c1 802:3428c60d7cfc
1 //
2 // ========================================================================
3 // Copyright (c) 1995-2014 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
20 package org.eclipse.jetty.server.session;
21
22 import java.io.ByteArrayInputStream;
23 import java.io.ByteArrayOutputStream;
24 import java.io.IOException;
25 import java.io.InputStream;
26 import java.io.ObjectInputStream;
27 import java.io.ObjectOutputStream;
28 import java.sql.Connection;
29 import java.sql.PreparedStatement;
30 import java.sql.ResultSet;
31 import java.sql.SQLException;
32 import java.sql.Statement;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.ListIterator;
36 import java.util.Map;
37 import java.util.Set;
38 import java.util.concurrent.ConcurrentHashMap;
39 import java.util.concurrent.atomic.AtomicReference;
40
41 import javax.servlet.SessionTrackingMode;
42 import javax.servlet.http.HttpServletRequest;
43 import javax.servlet.http.HttpSessionEvent;
44 import javax.servlet.http.HttpSessionListener;
45
46 import org.eclipse.jetty.server.SessionIdManager;
47 import org.eclipse.jetty.server.handler.ContextHandler;
48 import org.eclipse.jetty.util.log.Log;
49 import org.eclipse.jetty.util.log.Logger;
50
51 /**
52 * JDBCSessionManager
53 *
54 * SessionManager that persists sessions to a database to enable clustering.
55 *
56 * Session data is persisted to the JettySessions table:
57 *
58 * rowId (unique in cluster: webapp name/path + virtualhost + sessionId)
59 * contextPath (of the context owning the session)
60 * sessionId (unique in a context)
61 * lastNode (name of node last handled session)
62 * accessTime (time in milliseconds session was accessed)
63 * lastAccessTime (previous time in milliseconds session was accessed)
64 * createTime (time in milliseconds session created)
65 * cookieTime (time in milliseconds session cookie created)
66 * lastSavedTime (last time in milliseconds session access times were saved)
67 * expiryTime (time in milliseconds that the session is due to expire)
68 * map (attribute map)
69 *
70 * As an optimization, to prevent thrashing the database, we do not persist
71 * the accessTime and lastAccessTime every time the session is accessed. Rather,
72 * we write it out every so often. The frequency is controlled by the saveIntervalSec
73 * field.
74 */
75 public class JDBCSessionManager extends AbstractSessionManager
76 {
77 private static final Logger LOG = Log.getLogger(JDBCSessionManager.class);
78
79 private ConcurrentHashMap<String, AbstractSession> _sessions;
80 protected JDBCSessionIdManager _jdbcSessionIdMgr = null;
81 protected long _saveIntervalSec = 60; //only persist changes to session access times every 60 secs
82
83
84
85
86 /**
87 * Session
88 *
89 * Session instance.
90 */
91 public class Session extends AbstractSession
92 {
93 private static final long serialVersionUID = 5208464051134226143L;
94
95 /**
96 * If dirty, session needs to be (re)persisted
97 */
98 private boolean _dirty=false;
99
100
101 /**
102 * Time in msec since the epoch that a session cookie was set for this session
103 */
104 private long _cookieSet;
105
106
107 /**
108 * Time in msec since the epoch that the session will expire
109 */
110 private long _expiryTime;
111
112
113 /**
114 * Time in msec since the epoch that the session was last persisted
115 */
116 private long _lastSaved;
117
118
119 /**
120 * Unique identifier of the last node to host the session
121 */
122 private String _lastNode;
123
124
125 /**
126 * Virtual host for context (used to help distinguish 2 sessions with same id on different contexts)
127 */
128 private String _virtualHost;
129
130
131 /**
132 * Unique row in db for session
133 */
134 private String _rowId;
135
136
137 /**
138 * Mangled context name (used to help distinguish 2 sessions with same id on different contexts)
139 */
140 private String _canonicalContext;
141
142
143 /**
144 * Session from a request.
145 *
146 * @param request
147 */
148 protected Session (HttpServletRequest request)
149 {
150 super(JDBCSessionManager.this,request);
151 int maxInterval=getMaxInactiveInterval();
152 _expiryTime = (maxInterval <= 0 ? 0 : (System.currentTimeMillis() + maxInterval*1000L));
153 _virtualHost = JDBCSessionManager.getVirtualHost(_context);
154 _canonicalContext = canonicalize(_context.getContextPath());
155 _lastNode = getSessionIdManager().getWorkerName();
156 }
157
158
159 /**
160 * Session restored from database
161 * @param sessionId
162 * @param rowId
163 * @param created
164 * @param accessed
165 */
166 protected Session (String sessionId, String rowId, long created, long accessed)
167 {
168 super(JDBCSessionManager.this, created, accessed, sessionId);
169 _rowId = rowId;
170 }
171
172
173 protected synchronized String getRowId()
174 {
175 return _rowId;
176 }
177
178 protected synchronized void setRowId(String rowId)
179 {
180 _rowId = rowId;
181 }
182
183 public synchronized void setVirtualHost (String vhost)
184 {
185 _virtualHost=vhost;
186 }
187
188 public synchronized String getVirtualHost ()
189 {
190 return _virtualHost;
191 }
192
193 public synchronized long getLastSaved ()
194 {
195 return _lastSaved;
196 }
197
198 public synchronized void setLastSaved (long time)
199 {
200 _lastSaved=time;
201 }
202
203 public synchronized void setExpiryTime (long time)
204 {
205 _expiryTime=time;
206 }
207
208 public synchronized long getExpiryTime ()
209 {
210 return _expiryTime;
211 }
212
213
214 public synchronized void setCanonicalContext(String str)
215 {
216 _canonicalContext=str;
217 }
218
219 public synchronized String getCanonicalContext ()
220 {
221 return _canonicalContext;
222 }
223
224 public void setCookieSet (long ms)
225 {
226 _cookieSet = ms;
227 }
228
229 public synchronized long getCookieSet ()
230 {
231 return _cookieSet;
232 }
233
234 public synchronized void setLastNode (String node)
235 {
236 _lastNode=node;
237 }
238
239 public synchronized String getLastNode ()
240 {
241 return _lastNode;
242 }
243
244 @Override
245 public void setAttribute (String name, Object value)
246 {
247 super.setAttribute(name, value);
248 _dirty=true;
249 }
250
251 @Override
252 public void removeAttribute (String name)
253 {
254 super.removeAttribute(name);
255 _dirty=true;
256 }
257
258 @Override
259 protected void cookieSet()
260 {
261 _cookieSet = getAccessed();
262 }
263
264 /**
265 * Entry to session.
266 * Called by SessionHandler on inbound request and the session already exists in this node's memory.
267 *
268 * @see org.eclipse.jetty.server.session.AbstractSession#access(long)
269 */
270 @Override
271 protected boolean access(long time)
272 {
273 synchronized (this)
274 {
275 if (super.access(time))
276 {
277 int maxInterval=getMaxInactiveInterval();
278 _expiryTime = (maxInterval <= 0 ? 0 : (time + maxInterval*1000L));
279 return true;
280 }
281 return false;
282 }
283 }
284
285
286
287 /**
288 * Exit from session
289 * @see org.eclipse.jetty.server.session.AbstractSession#complete()
290 */
291 @Override
292 protected void complete()
293 {
294 synchronized (this)
295 {
296 super.complete();
297 try
298 {
299 if (isValid())
300 {
301 if (_dirty)
302 {
303 //The session attributes have changed, write to the db, ensuring
304 //http passivation/activation listeners called
305 willPassivate();
306 updateSession(this);
307 didActivate();
308 }
309 else if ((getAccessed() - _lastSaved) >= (getSaveInterval() * 1000L))
310 {
311 updateSessionAccessTime(this);
312 }
313 }
314 }
315 catch (Exception e)
316 {
317 LOG.warn("Problem persisting changed session data id="+getId(), e);
318 }
319 finally
320 {
321 _dirty=false;
322 }
323 }
324 }
325
326 @Override
327 protected void timeout() throws IllegalStateException
328 {
329 if (LOG.isDebugEnabled())
330 LOG.debug("Timing out session id="+getClusterId());
331 super.timeout();
332 }
333
334 @Override
335 public String toString ()
336 {
337 return "Session rowId="+_rowId+",id="+getId()+",lastNode="+_lastNode+
338 ",created="+getCreationTime()+",accessed="+getAccessed()+
339 ",lastAccessed="+getLastAccessedTime()+",cookieSet="+_cookieSet+
340 ",lastSaved="+_lastSaved+",expiry="+_expiryTime;
341 }
342 }
343
344
345
346
347 /**
348 * ClassLoadingObjectInputStream
349 *
350 * Used to persist the session attribute map
351 */
352 protected class ClassLoadingObjectInputStream extends ObjectInputStream
353 {
354 public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException
355 {
356 super(in);
357 }
358
359 public ClassLoadingObjectInputStream () throws IOException
360 {
361 super();
362 }
363
364 @Override
365 public Class<?> resolveClass (java.io.ObjectStreamClass cl) throws IOException, ClassNotFoundException
366 {
367 try
368 {
369 return Class.forName(cl.getName(), false, Thread.currentThread().getContextClassLoader());
370 }
371 catch (ClassNotFoundException e)
372 {
373 return super.resolveClass(cl);
374 }
375 }
376 }
377
378
379 /**
380 * Set the time in seconds which is the interval between
381 * saving the session access time to the database.
382 *
383 * This is an optimization that prevents the database from
384 * being overloaded when a session is accessed very frequently.
385 *
386 * On session exit, if the session attributes have NOT changed,
387 * the time at which we last saved the accessed
388 * time is compared to the current accessed time. If the interval
389 * is at least saveIntervalSecs, then the access time will be
390 * persisted to the database.
391 *
392 * If any session attribute does change, then the attributes and
393 * the accessed time are persisted.
394 *
395 * @param sec
396 */
397 public void setSaveInterval (long sec)
398 {
399 _saveIntervalSec=sec;
400 }
401
402 public long getSaveInterval ()
403 {
404 return _saveIntervalSec;
405 }
406
407
408
409 /**
410 * A method that can be implemented in subclasses to support
411 * distributed caching of sessions. This method will be
412 * called whenever the session is written to the database
413 * because the session data has changed.
414 *
415 * This could be used eg with a JMS backplane to notify nodes
416 * that the session has changed and to delete the session from
417 * the node's cache, and re-read it from the database.
418 * @param session
419 */
420 public void cacheInvalidate (Session session)
421 {
422
423 }
424
425
426 /**
427 * A session has been requested by its id on this node.
428 *
429 * Load the session by id AND context path from the database.
430 * Multiple contexts may share the same session id (due to dispatching)
431 * but they CANNOT share the same contents.
432 *
433 * Check if last node id is my node id, if so, then the session we have
434 * in memory cannot be stale. If another node used the session last, then
435 * we need to refresh from the db.
436 *
437 * NOTE: this method will go to the database, so if you only want to check
438 * for the existence of a Session in memory, use _sessions.get(id) instead.
439 *
440 * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSession(java.lang.String)
441 */
442 @Override
443 public Session getSession(String idInCluster)
444 {
445 Session session = null;
446 Session memSession = (Session)_sessions.get(idInCluster);
447
448 synchronized (this)
449 {
450 //check if we need to reload the session -
451 //as an optimization, don't reload on every access
452 //to reduce the load on the database. This introduces a window of
453 //possibility that the node may decide that the session is local to it,
454 //when the session has actually been live on another node, and then
455 //re-migrated to this node. This should be an extremely rare occurrence,
456 //as load-balancers are generally well-behaved and consistently send
457 //sessions to the same node, changing only iff that node fails.
458 //Session data = null;
459 long now = System.currentTimeMillis();
460 if (LOG.isDebugEnabled())
461 {
462 if (memSession==null)
463 LOG.debug("getSession("+idInCluster+"): not in session map,"+
464 " now="+now+
465 " lastSaved="+(memSession==null?0:memSession._lastSaved)+
466 " interval="+(_saveIntervalSec * 1000L));
467 else
468 LOG.debug("getSession("+idInCluster+"): in session map, "+
469 " now="+now+
470 " lastSaved="+(memSession==null?0:memSession._lastSaved)+
471 " interval="+(_saveIntervalSec * 1000L)+
472 " lastNode="+memSession._lastNode+
473 " thisNode="+getSessionIdManager().getWorkerName()+
474 " difference="+(now - memSession._lastSaved));
475 }
476
477 try
478 {
479 if (memSession==null)
480 {
481 LOG.debug("getSession("+idInCluster+"): no session in session map. Reloading session data from db.");
482 session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
483 }
484 else if ((now - memSession._lastSaved) >= (_saveIntervalSec * 1000L))
485 {
486 LOG.debug("getSession("+idInCluster+"): stale session. Reloading session data from db.");
487 session = loadSession(idInCluster, canonicalize(_context.getContextPath()), getVirtualHost(_context));
488 }
489 else
490 {
491 LOG.debug("getSession("+idInCluster+"): session in session map");
492 session = memSession;
493 }
494 }
495 catch (Exception e)
496 {
497 LOG.warn("Unable to load session "+idInCluster, e);
498 return null;
499 }
500
501
502 //If we have a session
503 if (session != null)
504 {
505 //If the session was last used on a different node, or session doesn't exist on this node
506 if (!session.getLastNode().equals(getSessionIdManager().getWorkerName()) || memSession==null)
507 {
508 //if session doesn't expire, or has not already expired, update it and put it in this nodes' memory
509 if (session._expiryTime <= 0 || session._expiryTime > now)
510 {
511 if (LOG.isDebugEnabled())
512 LOG.debug("getSession("+idInCluster+"): lastNode="+session.getLastNode()+" thisNode="+getSessionIdManager().getWorkerName());
513
514 session.setLastNode(getSessionIdManager().getWorkerName());
515 _sessions.put(idInCluster, session);
516
517 //update in db: if unable to update, session will be scavenged later
518 try
519 {
520 updateSessionNode(session);
521 session.didActivate();
522 }
523 catch (Exception e)
524 {
525 LOG.warn("Unable to update freshly loaded session "+idInCluster, e);
526 return null;
527 }
528 }
529 else
530 {
531 LOG.debug("getSession ({}): Session has expired", idInCluster);
532 session=null;
533 }
534
535 }
536 else
537 {
538 //the session loaded from the db and the one in memory are the same, so keep using the one in memory
539 session = memSession;
540 LOG.debug("getSession({}): Session not stale {}", idInCluster,session);
541 }
542 }
543 else
544 {
545 //No session in db with matching id and context path.
546 LOG.debug("getSession({}): No session in database matching id={}",idInCluster,idInCluster);
547 }
548
549 return session;
550 }
551 }
552
553 /**
554 * Get the number of sessions.
555 *
556 * @see org.eclipse.jetty.server.session.AbstractSessionManager#getSessions()
557 */
558 @Override
559 public int getSessions()
560 {
561 int size = 0;
562 synchronized (this)
563 {
564 size = _sessions.size();
565 }
566 return size;
567 }
568
569
570 /**
571 * Start the session manager.
572 *
573 * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStart()
574 */
575 @Override
576 public void doStart() throws Exception
577 {
578 if (_sessionIdManager==null)
579 throw new IllegalStateException("No session id manager defined");
580
581 _jdbcSessionIdMgr = (JDBCSessionIdManager)_sessionIdManager;
582
583 _sessions = new ConcurrentHashMap<String, AbstractSession>();
584
585 super.doStart();
586 }
587
588
589 /**
590 * Stop the session manager.
591 *
592 * @see org.eclipse.jetty.server.session.AbstractSessionManager#doStop()
593 */
594 @Override
595 public void doStop() throws Exception
596 {
597 _sessions.clear();
598 _sessions = null;
599
600 super.doStop();
601 }
602
603 @Override
604 protected void invalidateSessions()
605 {
606 //Do nothing - we don't want to remove and
607 //invalidate all the sessions because this
608 //method is called from doStop(), and just
609 //because this context is stopping does not
610 //mean that we should remove the session from
611 //any other nodes
612 }
613
614
615 /**
616 * Invalidate a session.
617 *
618 * @param idInCluster
619 */
620 protected void invalidateSession (String idInCluster)
621 {
622 Session session = null;
623 synchronized (this)
624 {
625 session = (Session)_sessions.get(idInCluster);
626 }
627
628 if (session != null)
629 {
630 session.invalidate();
631 }
632 }
633
634 /**
635 * Delete an existing session, both from the in-memory map and
636 * the database.
637 *
638 * @see org.eclipse.jetty.server.session.AbstractSessionManager#removeSession(java.lang.String)
639 */
640 @Override
641 protected boolean removeSession(String idInCluster)
642 {
643 synchronized (this)
644 {
645 Session session = (Session)_sessions.remove(idInCluster);
646 try
647 {
648 if (session != null)
649 deleteSession(session);
650 }
651 catch (Exception e)
652 {
653 LOG.warn("Problem deleting session id="+idInCluster, e);
654 }
655 return session!=null;
656 }
657 }
658
659
660 /**
661 * Add a newly created session to our in-memory list for this node and persist it.
662 *
663 * @see org.eclipse.jetty.server.session.AbstractSessionManager#addSession(org.eclipse.jetty.server.session.AbstractSession)
664 */
665 @Override
666 protected void addSession(AbstractSession session)
667 {
668 if (session==null)
669 return;
670
671 synchronized (this)
672 {
673 _sessions.put(session.getClusterId(), session);
674 }
675
676 //TODO or delay the store until exit out of session? If we crash before we store it
677 //then session data will be lost.
678 try
679 {
680 synchronized (session)
681 {
682 session.willPassivate();
683 storeSession(((JDBCSessionManager.Session)session));
684 session.didActivate();
685 }
686 }
687 catch (Exception e)
688 {
689 LOG.warn("Unable to store new session id="+session.getId() , e);
690 }
691 }
692
693
694 /**
695 * Make a new Session.
696 *
697 * @see org.eclipse.jetty.server.session.AbstractSessionManager#newSession(javax.servlet.http.HttpServletRequest)
698 */
699 @Override
700 protected AbstractSession newSession(HttpServletRequest request)
701 {
702 return new Session(request);
703 }
704
705 /* ------------------------------------------------------------ */
706 /** Remove session from manager
707 * @param session The session to remove
708 * @param invalidate True if {@link HttpSessionListener#sessionDestroyed(HttpSessionEvent)} and
709 * {@link SessionIdManager#invalidateAll(String)} should be called.
710 */
711 @Override
712 public void removeSession(AbstractSession session, boolean invalidate)
713 {
714 // Remove session from context and global maps
715 boolean removed = false;
716
717 synchronized (this)
718 {
719 //take this session out of the map of sessions for this context
720 if (getSession(session.getClusterId()) != null)
721 {
722 removed = true;
723 removeSession(session.getClusterId());
724 }
725 }
726
727 if (removed)
728 {
729 // Remove session from all context and global id maps
730 _sessionIdManager.removeSession(session);
731
732 if (invalidate)
733 _sessionIdManager.invalidateAll(session.getClusterId());
734
735 if (invalidate && !_sessionListeners.isEmpty())
736 {
737 HttpSessionEvent event=new HttpSessionEvent(session);
738 for (HttpSessionListener l : _sessionListeners)
739 l.sessionDestroyed(event);
740 }
741 if (!invalidate)
742 {
743 session.willPassivate();
744 }
745 }
746 }
747
748
749 /**
750 * Expire any Sessions we have in memory matching the list of
751 * expired Session ids.
752 *
753 * @param sessionIds
754 */
755 protected void expire (List<?> sessionIds)
756 {
757 //don't attempt to scavenge if we are shutting down
758 if (isStopping() || isStopped())
759 return;
760
761 //Remove any sessions we already have in memory that match the ids
762 Thread thread=Thread.currentThread();
763 ClassLoader old_loader=thread.getContextClassLoader();
764 ListIterator<?> itor = sessionIds.listIterator();
765
766 try
767 {
768 while (itor.hasNext())
769 {
770 String sessionId = (String)itor.next();
771 if (LOG.isDebugEnabled())
772 LOG.debug("Expiring session id "+sessionId);
773
774 Session session = (Session)_sessions.get(sessionId);
775 if (session != null)
776 {
777 session.timeout();
778 itor.remove();
779 }
780 else
781 {
782 if (LOG.isDebugEnabled())
783 LOG.debug("Unrecognized session id="+sessionId);
784 }
785 }
786 }
787 catch (Throwable t)
788 {
789 LOG.warn("Problem expiring sessions", t);
790 }
791 finally
792 {
793 thread.setContextClassLoader(old_loader);
794 }
795 }
796
797
798 /**
799 * Load a session from the database
800 * @param id
801 * @return the session data that was loaded
802 * @throws Exception
803 */
804 protected Session loadSession (final String id, final String canonicalContextPath, final String vhost)
805 throws Exception
806 {
807 final AtomicReference<Session> _reference = new AtomicReference<Session>();
808 final AtomicReference<Exception> _exception = new AtomicReference<Exception>();
809 Runnable load = new Runnable()
810 {
811 @SuppressWarnings("unchecked")
812 public void run()
813 {
814 Session session = null;
815 Connection connection=null;
816 PreparedStatement statement = null;
817 try
818 {
819 connection = getConnection();
820 statement = _jdbcSessionIdMgr._dbAdaptor.getLoadStatement(connection, id, canonicalContextPath, vhost);
821 ResultSet result = statement.executeQuery();
822 if (result.next())
823 {
824 session = new Session(id, result.getString(_jdbcSessionIdMgr._sessionTableRowId), result.getLong("createTime"), result.getLong("accessTime"));
825 session.setCookieSet(result.getLong("cookieTime"));
826 session.setLastAccessedTime(result.getLong("lastAccessTime"));
827 session.setLastNode(result.getString("lastNode"));
828 session.setLastSaved(result.getLong("lastSavedTime"));
829 session.setExpiryTime(result.getLong("expiryTime"));
830 session.setCanonicalContext(result.getString("contextPath"));
831 session.setVirtualHost(result.getString("virtualHost"));
832
833 InputStream is = ((JDBCSessionIdManager)getSessionIdManager())._dbAdaptor.getBlobInputStream(result, "map");
834 ClassLoadingObjectInputStream ois = new ClassLoadingObjectInputStream (is);
835 Object o = ois.readObject();
836 session.addAttributes((Map<String,Object>)o);
837 ois.close();
838
839 if (LOG.isDebugEnabled())
840 LOG.debug("LOADED session "+session);
841 }
842 _reference.set(session);
843 }
844 catch (Exception e)
845 {
846 _exception.set(e);
847 }
848 finally
849 {
850 if (statement!=null)
851 {
852 try { statement.close(); }
853 catch(Exception e) { LOG.warn(e); }
854 }
855
856 if (connection!=null)
857 {
858 try { connection.close();}
859 catch(Exception e) { LOG.warn(e); }
860 }
861 }
862 }
863 };
864
865 if (_context==null)
866 load.run();
867 else
868 _context.getContextHandler().handle(load);
869
870 if (_exception.get()!=null)
871 {
872 //if the session could not be restored, take its id out of the pool of currently-in-use
873 //session ids
874 _jdbcSessionIdMgr.removeSession(id);
875 throw _exception.get();
876 }
877
878 return _reference.get();
879 }
880
881 /**
882 * Insert a session into the database.
883 *
884 * @param data
885 * @throws Exception
886 */
887 protected void storeSession (Session session)
888 throws Exception
889 {
890 if (session==null)
891 return;
892
893 //put into the database
894 Connection connection = getConnection();
895 PreparedStatement statement = null;
896 try
897 {
898 String rowId = calculateRowId(session);
899
900 long now = System.currentTimeMillis();
901 connection.setAutoCommit(true);
902 statement = connection.prepareStatement(_jdbcSessionIdMgr._insertSession);
903 statement.setString(1, rowId); //rowId
904 statement.setString(2, session.getId()); //session id
905 statement.setString(3, session.getCanonicalContext()); //context path
906 statement.setString(4, session.getVirtualHost()); //first vhost
907 statement.setString(5, getSessionIdManager().getWorkerName());//my node id
908 statement.setLong(6, session.getAccessed());//accessTime
909 statement.setLong(7, session.getLastAccessedTime()); //lastAccessTime
910 statement.setLong(8, session.getCreationTime()); //time created
911 statement.setLong(9, session.getCookieSet());//time cookie was set
912 statement.setLong(10, now); //last saved time
913 statement.setLong(11, session.getExpiryTime());
914
915 ByteArrayOutputStream baos = new ByteArrayOutputStream();
916 ObjectOutputStream oos = new ObjectOutputStream(baos);
917 oos.writeObject(session.getAttributeMap());
918 byte[] bytes = baos.toByteArray();
919
920 ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
921 statement.setBinaryStream(12, bais, bytes.length);//attribute map as blob
922
923 statement.executeUpdate();
924 session.setRowId(rowId); //set it on the in-memory data as well as in db
925 session.setLastSaved(now);
926
927
928 if (LOG.isDebugEnabled())
929 LOG.debug("Stored session "+session);
930 }
931 finally
932 {
933 if (statement!=null)
934 {
935 try { statement.close(); }
936 catch(Exception e) { LOG.warn(e); }
937 }
938
939 if (connection!=null)
940 connection.close();
941 }
942 }
943
944
945 /**
946 * Update data on an existing persisted session.
947 *
948 * @param data the session
949 * @throws Exception
950 */
951 protected void updateSession (Session data)
952 throws Exception
953 {
954 if (data==null)
955 return;
956
957 Connection connection = getConnection();
958 PreparedStatement statement = null;
959 try
960 {
961 long now = System.currentTimeMillis();
962 connection.setAutoCommit(true);
963 statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSession);
964 statement.setString(1, getSessionIdManager().getWorkerName());//my node id
965 statement.setLong(2, data.getAccessed());//accessTime
966 statement.setLong(3, data.getLastAccessedTime()); //lastAccessTime
967 statement.setLong(4, now); //last saved time
968 statement.setLong(5, data.getExpiryTime());
969
970 ByteArrayOutputStream baos = new ByteArrayOutputStream();
971 ObjectOutputStream oos = new ObjectOutputStream(baos);
972 oos.writeObject(data.getAttributeMap());
973 byte[] bytes = baos.toByteArray();
974 ByteArrayInputStream bais = new ByteArrayInputStream(bytes);
975
976 statement.setBinaryStream(6, bais, bytes.length);//attribute map as blob
977 statement.setString(7, data.getRowId()); //rowId
978 statement.executeUpdate();
979
980 data.setLastSaved(now);
981 if (LOG.isDebugEnabled())
982 LOG.debug("Updated session "+data);
983 }
984 finally
985 {
986 if (statement!=null)
987 {
988 try { statement.close(); }
989 catch(Exception e) { LOG.warn(e); }
990 }
991
992 if (connection!=null)
993 connection.close();
994 }
995 }
996
997
998 /**
999 * Update the node on which the session was last seen to be my node.
1000 *
1001 * @param data the session
1002 * @throws Exception
1003 */
1004 protected void updateSessionNode (Session data)
1005 throws Exception
1006 {
1007 String nodeId = getSessionIdManager().getWorkerName();
1008 Connection connection = getConnection();
1009 PreparedStatement statement = null;
1010 try
1011 {
1012 connection.setAutoCommit(true);
1013 statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionNode);
1014 statement.setString(1, nodeId);
1015 statement.setString(2, data.getRowId());
1016 statement.executeUpdate();
1017 statement.close();
1018 if (LOG.isDebugEnabled())
1019 LOG.debug("Updated last node for session id="+data.getId()+", lastNode = "+nodeId);
1020 }
1021 finally
1022 {
1023 if (statement!=null)
1024 {
1025 try { statement.close(); }
1026 catch(Exception e) { LOG.warn(e); }
1027 }
1028
1029 if (connection!=null)
1030 connection.close();
1031 }
1032 }
1033
1034 /**
1035 * Persist the time the session was last accessed.
1036 *
1037 * @param data the session
1038 * @throws Exception
1039 */
1040 private void updateSessionAccessTime (Session data)
1041 throws Exception
1042 {
1043 Connection connection = getConnection();
1044 PreparedStatement statement = null;
1045 try
1046 {
1047 long now = System.currentTimeMillis();
1048 connection.setAutoCommit(true);
1049 statement = connection.prepareStatement(_jdbcSessionIdMgr._updateSessionAccessTime);
1050 statement.setString(1, getSessionIdManager().getWorkerName());
1051 statement.setLong(2, data.getAccessed());
1052 statement.setLong(3, data.getLastAccessedTime());
1053 statement.setLong(4, now);
1054 statement.setLong(5, data.getExpiryTime());
1055 statement.setString(6, data.getRowId());
1056 statement.executeUpdate();
1057 data.setLastSaved(now);
1058 statement.close();
1059 if (LOG.isDebugEnabled())
1060 LOG.debug("Updated access time session id="+data.getId());
1061 }
1062 finally
1063 {
1064 if (statement!=null)
1065 {
1066 try { statement.close(); }
1067 catch(Exception e) { LOG.warn(e); }
1068 }
1069
1070 if (connection!=null)
1071 connection.close();
1072 }
1073 }
1074
1075
1076
1077
1078 /**
1079 * Delete a session from the database. Should only be called
1080 * when the session has been invalidated.
1081 *
1082 * @param data
1083 * @throws Exception
1084 */
1085 protected void deleteSession (Session data)
1086 throws Exception
1087 {
1088 Connection connection = getConnection();
1089 PreparedStatement statement = null;
1090 try
1091 {
1092 connection.setAutoCommit(true);
1093 statement = connection.prepareStatement(_jdbcSessionIdMgr._deleteSession);
1094 statement.setString(1, data.getRowId());
1095 statement.executeUpdate();
1096 if (LOG.isDebugEnabled())
1097 LOG.debug("Deleted Session "+data);
1098 }
1099 finally
1100 {
1101 if (statement!=null)
1102 {
1103 try { statement.close(); }
1104 catch(Exception e) { LOG.warn(e); }
1105 }
1106
1107 if (connection!=null)
1108 connection.close();
1109 }
1110 }
1111
1112
1113
1114 /**
1115 * Get a connection from the driver.
1116 * @return
1117 * @throws SQLException
1118 */
1119 private Connection getConnection ()
1120 throws SQLException
1121 {
1122 return ((JDBCSessionIdManager)getSessionIdManager()).getConnection();
1123 }
1124
1125 /**
1126 * Calculate a unique id for this session across the cluster.
1127 *
1128 * Unique id is composed of: contextpath_virtualhost0_sessionid
1129 * @param data
1130 * @return
1131 */
1132 private String calculateRowId (Session data)
1133 {
1134 String rowId = canonicalize(_context.getContextPath());
1135 rowId = rowId + "_" + getVirtualHost(_context);
1136 rowId = rowId+"_"+data.getId();
1137 return rowId;
1138 }
1139
1140 /**
1141 * Get the first virtual host for the context.
1142 *
1143 * Used to help identify the exact session/contextPath.
1144 *
1145 * @return 0.0.0.0 if no virtual host is defined
1146 */
1147 private static String getVirtualHost (ContextHandler.Context context)
1148 {
1149 String vhost = "0.0.0.0";
1150
1151 if (context==null)
1152 return vhost;
1153
1154 String [] vhosts = context.getContextHandler().getVirtualHosts();
1155 if (vhosts==null || vhosts.length==0 || vhosts[0]==null)
1156 return vhost;
1157
1158 return vhosts[0];
1159 }
1160
1161 /**
1162 * Make an acceptable file name from a context path.
1163 *
1164 * @param path
1165 * @return
1166 */
1167 private static String canonicalize (String path)
1168 {
1169 if (path==null)
1170 return "";
1171
1172 return path.replace('/', '_').replace('.','_').replace('\\','_');
1173 }
1174 }