001: /*
002: * JDBCStore.java
003: * $Header: /home/cvs/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/JDBCStore.java,v 1.5 2002/04/17 17:07:53 remm Exp $
004: * $Revision: 1.5 $
005: * $Date: 2002/04/17 17:07:53 $
006: *
007: * ====================================================================
008: *
009: * The Apache Software License, Version 1.1
010: *
011: * Copyright (c) 1999 The Apache Software Foundation. All rights
012: * reserved.
013: *
014: * Redistribution and use in source and binary forms, with or without
015: * modification, are permitted provided that the following conditions
016: * are met:
017: *
018: * 1. Redistributions of source code must retain the above copyright
019: * notice, this list of conditions and the following disclaimer.
020: *
021: * 2. Redistributions in binary form must reproduce the above copyright
022: * notice, this list of conditions and the following disclaimer in
023: * the documentation and/or other materials provided with the
024: * distribution.
025: *
026: * 3. The end-user documentation included with the redistribution, if
027: * any, must include the following acknowlegement:
028: * "This product includes software developed by the
029: * Apache Software Foundation (http://www.apache.org/)."
030: * Alternately, this acknowlegement may appear in the software itself,
031: * if and wherever such third-party acknowlegements normally appear.
032: *
033: * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
034: * Foundation" must not be used to endorse or promote products derived
035: * from this software without prior written permission. For written
036: * permission, please contact apache@apache.org.
037: *
038: * 5. Products derived from this software may not be called "Apache"
039: * nor may "Apache" appear in their names without prior written
040: * permission of the Apache Group.
041: *
042: * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
043: * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
044: * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
045: * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
046: * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
047: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
048: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
049: * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
050: * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
051: * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
052: * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
053: * SUCH DAMAGE.
054: * ====================================================================
055: *
056: * This software consists of voluntary contributions made by many
057: * individuals on behalf of the Apache Software Foundation. For more
058: * information on the Apache Software Foundation, please see
059: * <http://www.apache.org/>.
060: *
061: * [Additional notices, if required by prior licensing conditions]
062: *
063: */
064:
065: package org.apache.catalina.session;
066:
067: import java.io.InputStream;
068: import java.io.OutputStream;
069: import java.io.BufferedInputStream;
070: import java.io.BufferedOutputStream;
071: import java.io.ByteArrayInputStream;
072: import java.io.ByteArrayOutputStream;
073: import java.io.IOException;
074: import java.io.InputStream;
075: import java.io.ObjectInputStream;
076: import java.io.ObjectOutputStream;
077: import java.io.ObjectStreamClass;
078: import java.sql.Connection;
079: import java.sql.DriverManager;
080: import java.sql.PreparedStatement;
081: import java.sql.ResultSet;
082: import java.sql.SQLException;
083: import org.apache.catalina.Container;
084: import org.apache.catalina.LifecycleException;
085: import org.apache.catalina.Loader;
086: import org.apache.catalina.Session;
087: import org.apache.catalina.Store;
088: import org.apache.catalina.util.CustomObjectInputStream;
089:
090: /**
091: * Implementation of the <code>Store</code> interface that stores
092: * serialized session objects in a database. Sessions that are
093: * saved are still subject to being expired based on inactivity.
094: *
095: * @author Bip Thelin
096: * @version $Revision: 1.5 $, $Date: 2002/04/17 17:07:53 $
097: */
098:
099: public class JDBCStore extends StoreBase implements Store {
100:
101: /**
102: * The descriptive information about this implementation.
103: */
104: protected static String info = "JDBCStore/1.0";
105:
106: /**
107: * Name to register for this Store, used for logging.
108: */
109: protected static String storeName = "JDBCStore";
110:
111: /**
112: * Name to register for the background thread.
113: */
114: protected String threadName = "JDBCStore";
115:
116: /**
117: * Connection string to use when connecting to the DB.
118: */
119: protected String connString = null;
120:
121: /**
122: * The database connection.
123: */
124: private Connection conn = null;
125:
126: /**
127: * Driver to use.
128: */
129: protected String driverName = null;
130:
131: // ------------------------------------------------------------- Table & cols
132:
133: /**
134: * Table to use.
135: */
136: protected String sessionTable = "tomcat$sessions";
137:
138: /**
139: * Id column to use.
140: */
141: protected String sessionIdCol = "id";
142:
143: /**
144: * Data column to use.
145: */
146: protected String sessionDataCol = "data";
147:
148: /**
149: * Is Valid column to use.
150: */
151: protected String sessionValidCol = "valid";
152:
153: /**
154: * Max Inactive column to use.
155: */
156: protected String sessionMaxInactiveCol = "maxinactive";
157:
158: /**
159: * Last Accessed column to use.
160: */
161: protected String sessionLastAccessedCol = "lastaccess";
162:
163: // ------------------------------------------------------------- SQL Variables
164:
165: /**
166: * Variable to hold the <code>getSize()</code> prepared statement.
167: */
168: protected PreparedStatement preparedSizeSql = null;
169:
170: /**
171: * Variable to hold the <code>keys()</code> prepared statement.
172: */
173: protected PreparedStatement preparedKeysSql = null;
174:
175: /**
176: * Variable to hold the <code>save()</code> prepared statement.
177: */
178: protected PreparedStatement preparedSaveSql = null;
179:
180: /**
181: * Variable to hold the <code>clear()</code> prepared statement.
182: */
183: protected PreparedStatement preparedClearSql = null;
184:
185: /**
186: * Variable to hold the <code>remove()</code> prepared statement.
187: */
188: protected PreparedStatement preparedRemoveSql = null;
189:
190: /**
191: * Variable to hold the <code>load()</code> prepared statement.
192: */
193: protected PreparedStatement preparedLoadSql = null;
194:
195: // ------------------------------------------------------------- Properties
196:
197: /**
198: * Return the info for this Store.
199: */
200: public String getInfo() {
201: return (info);
202: }
203:
204: /**
205: * Return the thread name for this Store.
206: */
207: public String getThreadName() {
208: return (threadName);
209: }
210:
211: /**
212: * Return the name for this Store, used for logging.
213: */
214: public String getStoreName() {
215: return (storeName);
216: }
217:
218: /**
219: * Set the driver for this Store.
220: *
221: * @param driverName The new driver
222: */
223: public void setDriverName(String driverName) {
224: String oldDriverName = this .driverName;
225: this .driverName = driverName;
226: support.firePropertyChange("driverName", oldDriverName,
227: this .driverName);
228: this .driverName = driverName;
229: }
230:
231: /**
232: * Return the driver for this Store.
233: */
234: public String getDriverName() {
235: return (this .driverName);
236: }
237:
238: /**
239: * Set the Connection URL for this Store.
240: *
241: * @param connectionURL The new Connection URL
242: */
243: public void setConnectionURL(String connectionURL) {
244: String oldConnString = this .connString;
245: this .connString = connectionURL;
246: support.firePropertyChange("connString", oldConnString,
247: this .connString);
248: }
249:
250: /**
251: * Return the Connection URL for this Store.
252: */
253: public String getConnectionURL() {
254: return (this .connString);
255: }
256:
257: /**
258: * Set the table for this Store.
259: *
260: * @param sessionTable The new table
261: */
262: public void setSessionTable(String sessionTable) {
263: String oldSessionTable = this .sessionTable;
264: this .sessionTable = sessionTable;
265: support.firePropertyChange("sessionTable", oldSessionTable,
266: this .sessionTable);
267: }
268:
269: /**
270: * Return the table for this Store.
271: */
272: public String getSessionTable() {
273: return (this .sessionTable);
274: }
275:
276: /**
277: * Set the Id column for the table.
278: *
279: * @param sessionIdCol the column name
280: */
281: public void setSessionIdCol(String sessionIdCol) {
282: String oldSessionIdCol = this .sessionIdCol;
283: this .sessionIdCol = sessionIdCol;
284: support.firePropertyChange("sessionIdCol", oldSessionIdCol,
285: this .sessionIdCol);
286: }
287:
288: /**
289: * Return the Id column for the table.
290: */
291: public String getSessionIdCol() {
292: return (this .sessionIdCol);
293: }
294:
295: /**
296: * Set the Data column for the table
297: *
298: * @param sessionDataCol the column name
299: */
300: public void setSessionDataCol(String sessionDataCol) {
301: String oldSessionDataCol = this .sessionDataCol;
302: this .sessionDataCol = sessionDataCol;
303: support.firePropertyChange("sessionDataCol", oldSessionDataCol,
304: this .sessionDataCol);
305: }
306:
307: /**
308: * Return the data column for the table
309: */
310: public String getSessionDataCol() {
311: return (this .sessionDataCol);
312: }
313:
314: /**
315: * Set the Is Valid column for the table
316: *
317: * @param sessionValidCol The column name
318: */
319: public void setSessionValidCol(String sessionValidCol) {
320: String oldSessionValidCol = this .sessionValidCol;
321: this .sessionValidCol = sessionValidCol;
322: support.firePropertyChange("sessionValidCol",
323: oldSessionValidCol, this .sessionValidCol);
324: }
325:
326: /**
327: * Return the Is Valid column
328: */
329: public String getSessionValidCol() {
330: return (this .sessionValidCol);
331: }
332:
333: /**
334: * Set the Max Inactive column for the table
335: *
336: * @param sessionMaxInactiveCol The column name
337: */
338: public void setSessionMaxInactiveCol(String sessionMaxInactiveCol) {
339: String oldSessionMaxInactiveCol = this .sessionMaxInactiveCol;
340: this .sessionMaxInactiveCol = sessionMaxInactiveCol;
341: support.firePropertyChange("sessionMaxInactiveCol",
342: oldSessionMaxInactiveCol, this .sessionMaxInactiveCol);
343: }
344:
345: /**
346: * Return the Max Inactive column
347: */
348: public String getSessionMaxInactiveCol() {
349: return (this .sessionMaxInactiveCol);
350: }
351:
352: /**
353: * Set the Last Accessed column for the table
354: *
355: * @param sessionLastAccessedCol The column name
356: */
357: public void setSessionLastAccessedCol(String sessionLastAccessedCol) {
358: String oldSessionLastAccessedCol = this .sessionLastAccessedCol;
359: this .sessionLastAccessedCol = sessionLastAccessedCol;
360: support.firePropertyChange("sessionLastAccessedCol",
361: oldSessionLastAccessedCol, this .sessionLastAccessedCol);
362: }
363:
364: /**
365: * Return the Last Accessed column
366: */
367: public String getSessionLastAccessedCol() {
368: return (this .sessionLastAccessedCol);
369: }
370:
371: // --------------------------------------------------------- Public Methods
372:
373: /**
374: * Return an array containing the session identifiers of all Sessions
375: * currently saved in this Store. If there are no such Sessions, a
376: * zero-length array is returned.
377: *
378: * @exception IOException if an input/output error occurred
379: */
380: public String[] keys() throws IOException {
381: String keysSql = "SELECT COUNT(s." + sessionIdCol + "), c."
382: + sessionIdCol + " FROM " + sessionTable + " s, "
383: + sessionTable + " c" + " GROUP BY c." + sessionIdCol;
384:
385: Connection _conn = getConnection();
386: ResultSet rst = null;
387: String keys[] = null;
388: int i;
389:
390: if (_conn == null)
391: return (new String[0]);
392:
393: try {
394: if (preparedKeysSql == null)
395: preparedKeysSql = _conn.prepareStatement(keysSql);
396:
397: rst = preparedKeysSql.executeQuery();
398: if (rst != null && rst.next()) {
399: keys = new String[rst.getInt(1)];
400: keys[0] = rst.getString(2);
401: i = 1;
402:
403: while (rst.next())
404: keys[i++] = rst.getString(2);
405: } else {
406: keys = new String[0];
407: }
408: } catch (SQLException e) {
409: log(sm.getString(getStoreName() + ".SQLException", e));
410: } finally {
411: try {
412: if (rst != null)
413: rst.close();
414: } catch (SQLException e) {
415: ;
416: }
417:
418: release(_conn);
419: _conn = null;
420: }
421:
422: return (keys);
423: }
424:
425: /**
426: * Return an integer containing a count of all Sessions
427: * currently saved in this Store. If there are no Sessions,
428: * <code>0</code> is returned.
429: *
430: * @exception IOException if an input/output error occurred
431: */
432: public int getSize() throws IOException {
433: int size = 0;
434: String sizeSql = "SELECT COUNT(" + sessionIdCol
435: + ") FROM ".concat(sessionTable);
436: Connection _conn = getConnection();
437: ResultSet rst = null;
438:
439: if (_conn == null)
440: return (size);
441:
442: try {
443: if (preparedSizeSql == null)
444: preparedSizeSql = _conn.prepareStatement(sizeSql);
445:
446: rst = preparedSizeSql.executeQuery();
447: if (rst.next())
448: size = rst.getInt(1);
449: } catch (SQLException e) {
450: log(sm.getString(getStoreName() + ".SQLException", e));
451: } finally {
452: try {
453: if (rst != null)
454: rst.close();
455: } catch (SQLException e) {
456: ;
457: }
458:
459: release(_conn);
460: _conn = null;
461: }
462:
463: return (size);
464: }
465:
466: /**
467: * Load the Session associated with the id <code>id</code>.
468: * If no such session is found <code>null</code> is returned.
469: *
470: * @param id a value of type <code>String</code>
471: * @return the stored <code>Session</code>
472: * @exception ClassNotFoundException if an error occurs
473: * @exception IOException if an input/output error occurred
474: */
475: public Session load(String id) throws ClassNotFoundException,
476: IOException {
477: ResultSet rst = null;
478: Connection _conn = getConnection();
479: StandardSession _session = null;
480: Loader loader = null;
481: ClassLoader classLoader = null;
482: ObjectInputStream ois = null;
483: BufferedInputStream bis = null;
484: Container container = manager.getContainer();
485: String loadSql = "SELECT " + sessionIdCol + ", "
486: + sessionDataCol + " FROM " + sessionTable + " WHERE "
487: + sessionIdCol + " = ?";
488:
489: if (_conn == null)
490: return (null);
491:
492: try {
493: if (preparedLoadSql == null)
494: preparedLoadSql = _conn.prepareStatement(loadSql);
495:
496: preparedLoadSql.setString(1, id);
497: rst = preparedLoadSql.executeQuery();
498: if (rst.next()) {
499: bis = new BufferedInputStream(rst.getBinaryStream(2));
500:
501: if (container != null)
502: loader = container.getLoader();
503:
504: if (loader != null)
505: classLoader = loader.getClassLoader();
506:
507: if (classLoader != null)
508: ois = new CustomObjectInputStream(bis, classLoader);
509: else
510: ois = new ObjectInputStream(bis);
511: } else if (debug > 0) {
512: log(getStoreName() + ": No persisted data object found");
513: }
514: } catch (SQLException e) {
515: log(sm.getString(getStoreName() + ".SQLException", e));
516: } finally {
517: try {
518: if (rst != null)
519: rst.close();
520: } catch (SQLException e) {
521: ;
522: }
523:
524: release(_conn);
525: _conn = null;
526: }
527:
528: if (ois != null) {
529: try {
530: _session = (StandardSession) manager.createSession();
531: _session.readObjectData(ois);
532: _session.setManager(manager);
533: } finally {
534: if (ois != null) {
535: try {
536: ois.close();
537: bis = null;
538: } catch (IOException e) {
539: ;
540: }
541: }
542: }
543:
544: if (debug > 0)
545: log(sm.getString(getStoreName() + ".loading", id,
546: sessionTable));
547: }
548:
549: return (_session);
550: }
551:
552: /**
553: * Remove the Session with the specified session identifier from
554: * this Store, if present. If no such Session is present, this method
555: * takes no action.
556: *
557: * @param id Session identifier of the Session to be removed
558: *
559: * @exception IOException if an input/output error occurs
560: */
561: public void remove(String id) throws IOException {
562: Connection _conn = getConnection();
563: String removeSql = "DELETE FROM " + sessionTable + " WHERE "
564: + sessionIdCol + " = ?";
565:
566: if (_conn == null)
567: return;
568:
569: try {
570: if (preparedRemoveSql == null)
571: preparedRemoveSql = _conn.prepareStatement(removeSql);
572:
573: preparedRemoveSql.setString(1, id);
574: preparedRemoveSql.execute();
575: } catch (SQLException e) {
576: log(sm.getString(getStoreName() + ".SQLException", e));
577: } finally {
578: release(_conn);
579: _conn = null;
580: }
581:
582: if (debug > 0)
583: log(sm.getString(getStoreName() + ".removing", id,
584: sessionTable));
585: }
586:
587: /**
588: * Remove all of the Sessions in this Store.
589: *
590: * @exception IOException if an input/output error occurs
591: */
592: public void clear() throws IOException {
593: Connection _conn = getConnection();
594: String clearSql = "DELETE FROM ".concat(sessionTable);
595:
596: if (_conn == null)
597: return;
598:
599: try {
600: if (preparedClearSql == null)
601: preparedClearSql = _conn.prepareStatement(clearSql);
602:
603: preparedClearSql.execute();
604: } catch (SQLException e) {
605: log(sm.getString(getStoreName() + ".SQLException", e));
606: } finally {
607: release(_conn);
608: _conn = null;
609: }
610: }
611:
612: /**
613: * Save a session to the Store.
614: *
615: * @param session the session to be stored
616: * @exception IOException if an input/output error occurs
617: */
618: public void save(Session session) throws IOException {
619: String saveSql = "INSERT INTO " + sessionTable + " ("
620: + sessionIdCol + ", " + sessionDataCol + ", "
621: + sessionValidCol + ", " + sessionMaxInactiveCol + ", "
622: + sessionLastAccessedCol + ") VALUES (?, ?, ?, ?, ?)";
623: Connection _conn = getConnection();
624: ObjectOutputStream oos = null;
625: ByteArrayOutputStream bos = null;
626: ByteArrayInputStream bis = null;
627: InputStream in = null;
628:
629: if (_conn == null)
630: return;
631:
632: // If sessions already exist in DB, remove and insert again.
633: // TODO:
634: // * Check if ID exists in database and if so use UPDATE.
635: remove(session.getId());
636:
637: try {
638: bos = new ByteArrayOutputStream();
639: oos = new ObjectOutputStream(new BufferedOutputStream(bos));
640:
641: ((StandardSession) session).writeObjectData(oos);
642: oos.close();
643:
644: byte[] obs = bos.toByteArray();
645: int size = obs.length;
646: bis = new ByteArrayInputStream(obs, 0, size);
647: in = new BufferedInputStream(bis, size);
648:
649: if (preparedSaveSql == null)
650: preparedSaveSql = _conn.prepareStatement(saveSql);
651:
652: preparedSaveSql.setString(1, session.getId());
653: preparedSaveSql.setBinaryStream(2, in, size);
654: preparedSaveSql.setString(3, session.isValid() ? "1" : "0");
655: preparedSaveSql.setInt(4, session.getMaxInactiveInterval());
656: preparedSaveSql.setLong(5, session.getLastAccessedTime());
657: preparedSaveSql.execute();
658: } catch (SQLException e) {
659: log(sm.getString(getStoreName() + ".SQLException", e));
660: } catch (IOException e) {
661: ;
662: } finally {
663: if (bis != null)
664: bis.close();
665:
666: if (in != null)
667: in.close();
668:
669: bis = null;
670: bos = null;
671: oos = null;
672: in = null;
673:
674: release(_conn);
675: _conn = null;
676: }
677: if (debug > 0)
678: log(sm.getString(getStoreName() + ".saving", session
679: .getId(), sessionTable));
680: }
681:
682: // --------------------------------------------------------- Protected Methods
683:
684: /**
685: * Check the connection associated with this store, if it's
686: * <code>null</code> or closed try to reopen it.
687: * Returns <code>null</code> if the connection could not be established.
688: *
689: * @return <code>Connection</code> if the connection suceeded
690: */
691: protected Connection getConnection() {
692: try {
693: if (conn == null || conn.isClosed()) {
694: Class.forName(driverName);
695: log(sm.getString(getStoreName()
696: + ".checkConnectionDBClosed"));
697: conn = DriverManager.getConnection(connString);
698: conn.setAutoCommit(true);
699:
700: if (conn == null || conn.isClosed())
701: log(sm.getString(getStoreName()
702: + ".checkConnectionDBReOpenFail"));
703: }
704: } catch (SQLException ex) {
705: log(sm.getString(getStoreName()
706: + ".checkConnectionSQLException", ex.toString()));
707: } catch (ClassNotFoundException ex) {
708: log(sm.getString(getStoreName()
709: + ".checkConnectionClassNotFoundException", ex
710: .toString()));
711: }
712:
713: return conn;
714: }
715:
716: /**
717: * Release the connection, not needed here since the
718: * connection is not associated with a connection pool.
719: *
720: * @param conn The connection to be released
721: */
722: protected void release(Connection conn) {
723: ;
724: }
725:
726: /**
727: * Called once when this Store is first started.
728: */
729: public void start() throws LifecycleException {
730: super .start();
731:
732: // Open connection to the database
733: this .conn = getConnection();
734: }
735:
736: /**
737: * Gracefully terminate everything associated with our db.
738: * Called once when this Store is stoping.
739: *
740: */
741: public void stop() throws LifecycleException {
742: super .stop();
743:
744: // Close and release everything associated with our db.
745: if (conn != null) {
746: try {
747: conn.commit();
748: } catch (SQLException e) {
749: ;
750: }
751:
752: try {
753: preparedSizeSql.close();
754: } catch (SQLException e) {
755: ;
756: }
757:
758: try {
759: preparedKeysSql.close();
760: } catch (SQLException e) {
761: ;
762: }
763:
764: try {
765: preparedSaveSql.close();
766: } catch (SQLException e) {
767: ;
768: }
769:
770: try {
771: preparedClearSql.close();
772: } catch (SQLException e) {
773: ;
774: }
775:
776: try {
777: preparedRemoveSql.close();
778: } catch (SQLException e) {
779: ;
780: }
781:
782: try {
783: preparedLoadSql.close();
784: } catch (SQLException e) {
785: ;
786: }
787:
788: try {
789: conn.close();
790: } catch (SQLException e) {
791: ;
792: }
793:
794: this.preparedSizeSql = null;
795: this.preparedKeysSql = null;
796: this.preparedSaveSql = null;
797: this.preparedClearSql = null;
798: this.preparedRemoveSql = null;
799: this.preparedLoadSql = null;
800: this.conn = null;
801: }
802: }
803: }
|