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 */ Session(HttpServletRequest request)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 */ Session(String sessionId, String rowId, long created, long accessed)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 getRowId()173 protected synchronized String getRowId() 174 { 175 return _rowId; 176 } 177 setRowId(String rowId)178 protected synchronized void setRowId(String rowId) 179 { 180 _rowId = rowId; 181 } 182 setVirtualHost(String vhost)183 public synchronized void setVirtualHost (String vhost) 184 { 185 _virtualHost=vhost; 186 } 187 getVirtualHost()188 public synchronized String getVirtualHost () 189 { 190 return _virtualHost; 191 } 192 getLastSaved()193 public synchronized long getLastSaved () 194 { 195 return _lastSaved; 196 } 197 setLastSaved(long time)198 public synchronized void setLastSaved (long time) 199 { 200 _lastSaved=time; 201 } 202 setExpiryTime(long time)203 public synchronized void setExpiryTime (long time) 204 { 205 _expiryTime=time; 206 } 207 getExpiryTime()208 public synchronized long getExpiryTime () 209 { 210 return _expiryTime; 211 } 212 213 setCanonicalContext(String str)214 public synchronized void setCanonicalContext(String str) 215 { 216 _canonicalContext=str; 217 } 218 getCanonicalContext()219 public synchronized String getCanonicalContext () 220 { 221 return _canonicalContext; 222 } 223 setCookieSet(long ms)224 public void setCookieSet (long ms) 225 { 226 _cookieSet = ms; 227 } 228 getCookieSet()229 public synchronized long getCookieSet () 230 { 231 return _cookieSet; 232 } 233 setLastNode(String node)234 public synchronized void setLastNode (String node) 235 { 236 _lastNode=node; 237 } 238 getLastNode()239 public synchronized String getLastNode () 240 { 241 return _lastNode; 242 } 243 244 @Override setAttribute(String name, Object value)245 public void setAttribute (String name, Object value) 246 { 247 super.setAttribute(name, value); 248 _dirty=true; 249 } 250 251 @Override removeAttribute(String name)252 public void removeAttribute (String name) 253 { 254 super.removeAttribute(name); 255 _dirty=true; 256 } 257 258 @Override cookieSet()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 access(long time)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 complete()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 timeout()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 toString()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 { ClassLoadingObjectInputStream(java.io.InputStream in)354 public ClassLoadingObjectInputStream(java.io.InputStream in) throws IOException 355 { 356 super(in); 357 } 358 ClassLoadingObjectInputStream()359 public ClassLoadingObjectInputStream () throws IOException 360 { 361 super(); 362 } 363 364 @Override resolveClass(java.io.ObjectStreamClass cl)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 */ setSaveInterval(long sec)397 public void setSaveInterval (long sec) 398 { 399 _saveIntervalSec=sec; 400 } 401 getSaveInterval()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 */ cacheInvalidate(Session session)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 getSession(String idInCluster)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 getSessions()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 doStart()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 doStop()595 public void doStop() throws Exception 596 { 597 _sessions.clear(); 598 _sessions = null; 599 600 super.doStop(); 601 } 602 603 @Override invalidateSessions()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 */ invalidateSession(String idInCluster)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 removeSession(String idInCluster)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 addSession(AbstractSession session)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 newSession(HttpServletRequest request)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 removeSession(AbstractSession session, boolean invalidate)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 */ expire(List<?> sessionIds)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 */ loadSession(final String id, final String canonicalContextPath, final String vhost)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 */ storeSession(Session session)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 */ updateSession(Session data)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 */ updateSessionNode(Session data)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 */ updateSessionAccessTime(Session data)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 */ deleteSession(Session data)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 */ getConnection()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 */ calculateRowId(Session data)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 */ getVirtualHost(ContextHandler.Context context)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 */ canonicalize(String path)1167 private static String canonicalize (String path) 1168 { 1169 if (path==null) 1170 return ""; 1171 1172 return path.replace('/', '_').replace('.','_').replace('\\','_'); 1173 } 1174 } 1175