tomcat-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Bip Thelin <...@razorfish.com>
Subject JDBCStore package for Tomcat 4.x
Date Sun, 08 Apr 2001 02:05:28 GMT
Here's the JDBCStore implementation. To use it change your server.xml to something like:

<Manager
	className="org.apache.catalina.session.PersistentManager"
	debug="0" saveOnRestart="true" maxActiveSessions="1"
	minIdleSwap="-1" maxIdleSwap="1" maxIdleBackup="-1">
	<Store className="org.apache.catalina.session.JDBCStore" 				driverName="com.merant.datadirect.jdbc.sqlserver.SQLServerDriver"
		sessionTable="tomcat$sessions"
		connectionURL="jdbc:mysql://localhost/authority?user=test;password=test"
		debug="99"/>
</Manager>

You also have to create a table that has the fields id, session. And where id is a varchar
field
and session is a binary field, i.e. Blob. Sort of like:
CREATE TABLE [dbo].[tomcat$sessions] (
	[id] [varchar] (50) NOT NULL ,
	[session] [binary] (1000) NULL 
)

However the SQL command varies from different RDBMS.

	..bip

-----------------------------------------------------------------------------------------
Index: LocalStrings.properties
===================================================================
RCS file: /home/cvspublic/jakarta-tomcat-4.0/catalina/src/share/org/apache/catalina/session/LocalStrings.properties,v
retrieving revision 1.4
diff -u -r1.4 LocalStrings.properties
--- LocalStrings.properties     2001/02/03 20:36:20     1.4
+++ LocalStrings.properties     2001/04/08 01:57:06
@@ -5,6 +5,16 @@
 fileStore.saving=Saving Session {0} to file {1}
 fileStore.loading=Loading Session {0} from file {1}
 fileStore.removing=Removing Session {0} at file {1}
+JDBCStore.alreadyStarted=JDBC Store has already been started
+JDBCStore.notStarted=JDBC Store has not yet been started
+JDBCStore.saving=Saving Session {0} to database {1}
+JDBCStore.loading=Loading Session {0} from database {1}
+JDBCStore.removing=Removing Session {0} at database {1}
+JDBCStore.SQLException=SQL Error {0}
+JDBCStore.checkConnectionDBClosed=The database connection is null or was found
to be closed. Trying to re-open it.
+JDBCStore.checkConnectionDBReOpenFail=The re-open on the database failed. The d
atabase could be down.
+JDBCStore.checkConnectionSQLException=A SQL exception occured {0}
+JDBCStore.checkConnectionClassNotFoundException=JDBC driver class not found {0}

 managerBase.complete=Seeding of random number generator has been completed
 managerBase.getting=Getting message digest component for algorithm {0}
 managerBase.gotten=Completed getting message digest component




/*
 * JDBCStore.java
 * $Header$
 * $Revision$
 * $Date$
 *
 * ====================================================================
 *
 * The Apache Software License, Version 1.1
 *
 * Copyright (c) 1999 The Apache Software Foundation.  All rights
 * reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 *
 * 3. The end-user documentation included with the redistribution, if
 *    any, must include the following acknowlegement:
 *       "This product includes software developed by the
 *        Apache Software Foundation (http://www.apache.org/)."
 *    Alternately, this acknowlegement may appear in the software itself,
 *    if and wherever such third-party acknowlegements normally appear.
 *
 * 4. The names "The Jakarta Project", "Tomcat", and "Apache Software
 *    Foundation" must not be used to endorse or promote products derived
 *    from this software without prior written permission. For written
 *    permission, please contact apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
 * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
 * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
 * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
 * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
 * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
 * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Software Foundation.  For more
 * information on the Apache Software Foundation, please see
 * <http://www.apache.org/>.
 *
 * [Additional notices, if required by prior licensing conditions]
 *
 */

package org.apache.catalina.session;

import java.beans.PropertyChangeListener;
import java.beans.PropertyChangeSupport;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.ObjectStreamClass;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.apache.catalina.Container;
import org.apache.catalina.Lifecycle;
import org.apache.catalina.LifecycleEvent;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleException;
import org.apache.catalina.LifecycleListener;
import org.apache.catalina.Loader;
import org.apache.catalina.Logger;
import org.apache.catalina.Manager;
import org.apache.catalina.Session;
import org.apache.catalina.Store;
import org.apache.catalina.util.CustomObjectInputStream;
import org.apache.catalina.util.LifecycleSupport;
import org.apache.catalina.util.StringManager;

/**
 * Concrete implementation of the <code>Store</code> interface that stores
 * serialized session objects in a database.  Sessions that are
 * saved are still subject to being expired based on inactivity.
 *
 * @author Bip Thelin
 * @version $Revision$, $Date$
 */

public final class JDBCStore
    implements Lifecycle, Runnable, Store {

    // ----------------------------------------------------- Instance Variables

    /**
     * The interval (in seconds) between checks for expired sessions.
     */
    private int checkInterval = 60;

    /**
     * The descriptive information about this implementation.
     */
    private static final String info = "JDBCStore/1.0";

    /**
     * The lifecycle event support for this component.
     */
    protected LifecycleSupport lifecycle = new LifecycleSupport(this);

    /**
     * Has this component been started yet?
     */
    private boolean started = false;

    /**
     * The property change support for this component.
     */
    private PropertyChangeSupport support = new PropertyChangeSupport(this);

    /**
     * The string manager for this package.
     */
    private StringManager sm = StringManager.getManager(Constants.Package);

    /**
     * The background thread.
     */
    private Thread thread = null;

    /**
     * The background thread completion semaphore.
     */
    private boolean threadDone = false;

    /**
     * The Manager with which this FileStore is associated.
     */
    protected Manager manager;

    /**
     * The debugging detail level for this component.
     */
    protected int debug = 0;

    /**
     * Name to register for the background thread.
     */
    private String threadName = "JDBCStore";

    /**
     * Connection string to use when connecting to the DB.
     */
    private String connString = null;

    /**
     * Table to use.
     */
    private String sessionTable = null;

    /**
     * The database connection.
     */
    private Connection conn = null;

    /**
     * Driver to use.
     */
    private String driverName = null;

    // ------------------------------------------------------------- SQL Variables

    private PreparedStatement preparedSizeSql = null;

    private PreparedStatement preparedKeysSql = null;

    private PreparedStatement preparedSaveSql = null;

    private PreparedStatement preparedClearSql = null;

    private PreparedStatement preparedRemoveSql = null;

    private PreparedStatement preparedLoadSql = null;

    // ------------------------------------------------------------- Properties

    /**
     * Return the info for this Store.
     */
    public String getInfo() {
	return(info);
    }

    /**
     * Set the debugging detail level for this Store.
     *
     * @param debug The new debugging detail level
     */
    public void setDebug(int debug) {
	this.debug = debug;
    }

    /**
     * Return the debugging detail level for this Store.
     */
    public int getDebug() {
	return(this.debug);
    }

    /**
     * Set the driver for this Store.
     *
     * @param driverName The new driver
     */
    public void setDriverName(String driverName) {
	String oldDriverName = this.driverName;
	this.driverName = driverName;
	support.firePropertyChange("driverName",
				   oldDriverName,
				   this.driverName);
	this.driverName = driverName;
    }

    /**
     * Return the driver for this Store.
     */
    public String getDriverName() {
	return(this.driverName);
    }

    /**
     * Set the Connection URL for this Store.
     *
     * @param connectionURL The new Connection URL
     */
    public void setConnectionURL(String connectionURL) {
	String oldConnString = this.connString;
	this.connString = connectionURL;
	support.firePropertyChange("connString",
				   oldConnString,
				   this.connString);
    }

    /**
     * Return the Connection URL for this Store.
     */
    public String getConnectionURL() {
	return(this.connString);
    }

    /**
     * Set the table for this Store.
     *
     * @param sessionTable The new table
     */
    public void setSessionTable(String sessionTable) {
	String oldSessionTable = this.sessionTable;
	this.sessionTable = sessionTable;
	support.firePropertyChange("sessionTable",
				   oldSessionTable,
				   this.sessionTable);
    }

    /**
     * Return the table for this Store.
     */
    public String getSessionTable() {
	return(this.driverName);
    }

    /**
     * Set the check interval (in seconds) for this Store.
     *
     * @param checkInterval The new check interval
     */
    public void setCheckInterval(int checkInterval) {
	int oldCheckInterval = this.checkInterval;
	this.checkInterval = checkInterval;
	support.firePropertyChange("checkInterval",
				   new Integer(oldCheckInterval),
				   new Integer(this.checkInterval));
    }

    /**
     * Return the check interval (in seconds) for this Store.
     */
    public int getCheckInterval() {
	return(this.checkInterval);
    }

    /**
     * Set the Manager with which this JDBCStore is associated.
     *
     * @param manager The newly associated Manager
     */
    public void setManager(Manager manager) {
	Manager oldManager = this.manager;
	this.manager = manager;
	support.firePropertyChange("manager", oldManager, this.manager);
    }

    /**
     * Return the Manager with which the JDBCStore is associated.
     */
    public Manager getManager() {
    	return(this.manager);
    }

    // --------------------------------------------------------- Public Methods

    /**
     * Add a lifecycle event listener to this component.
     *
     * @param listener The listener to add
     */
    public void addLifecycleListener(LifecycleListener listener) {
	lifecycle.addLifecycleListener(listener);
    }

    /**
     * Remove a lifecycle event listener from this component.
     *
     * @param listener The listener to add
     */
    public void removeLifecycleListener(LifecycleListener listener) {
	lifecycle.removeLifecycleListener(listener);
    }

    /**
     * Add a property change listener to this component.
     *
     * @param listener a value of type 'PropertyChangeListener'
     */
    public void addPropertyChangeListener(PropertyChangeListener listener) {
	support.addPropertyChangeListener(listener);
    }

    /**
     * Remove a property change listener from this component.
     *
     * @param listener The listener to remove
     */
    public void removePropertyChangeListener(PropertyChangeListener listener) {
	support.removePropertyChangeListener(listener);
    }

    /**
     * Return an array containing the session identifiers of all Sessions
     * currently saved in this Store.  If there are no such Sessions, a
     * zero-length array is returned.
     *
     * @exception IOException if an input/output error occurred
     */
    public String[] keys() throws IOException {
	String keysSql =
	    "SELECT c.size, s.id FROM "+sessionTable+" s, "+
	    "(SELECT COUNT(id) AS size FROM "+sessionTable+") c";
	ResultSet rst = null;
	String keys[] = null;
	int i;

	if(!checkConnection())
	    return(new String[0]);

	try {
	    if(preparedKeysSql == null)
		preparedKeysSql = conn.prepareStatement(keysSql);

	    rst = preparedKeysSql.executeQuery();
	    if (rst != null && rst.next()) {
		keys = new String[rst.getInt(1)];
		keys[0] = rst.getString(2);
		i=1;

		while(rst.next())
		    keys[i++] = rst.getString(2);
	    } else {
		keys = new String[0];
	    }
	} catch(SQLException e) {
	    log(sm.getString("JDBCStore.SQLException", e));
	} finally {
	    try {
		if(rst != null)
		    rst.close();
	    } catch(SQLException e) {
		;
	    }
	}	

	return(keys);
    }

    public int getSize() throws IOException {
	int size = 0;
	String sizeSql = "SELECT COUNT(id) FROM ".concat(sessionTable);
	ResultSet rst = null;

	if(!checkConnection())
	    return(size);

	try {
	    if(preparedSizeSql == null)
		preparedSizeSql = conn.prepareStatement(sizeSql);

	    rst = preparedSizeSql.executeQuery();
	    if (rst.next())
		size = rst.getInt(1);
	} catch(SQLException e) {
	    log(sm.getString("JDBCStore.SQLException", e));
	} finally {
	    try {
		if(rst != null)
		    rst.close();
	    } catch(SQLException e) {
		;
	    }
	}

	return(size);
    }

    public Session load(String id)
	throws ClassNotFoundException, IOException {
	ResultSet rst = null;
	StandardSession _session = null;
	Loader loader = null;
	ClassLoader classLoader = null;
	ObjectInputStream ois = null;
	BufferedInputStream bis = null;
	Container container = manager.getContainer();
	String loadSql =
	    ("SELECT id, session FROM ".concat(sessionTable)).concat(" WHERE id = ?");

	if(!checkConnection())
	    return(null);

	try {
	    if(preparedLoadSql == null)
		preparedLoadSql = conn.prepareStatement(loadSql);

	    preparedLoadSql.setString(1, id);
	    rst = preparedLoadSql.executeQuery();
	    if (rst.next()) {
		bis = new BufferedInputStream(rst.getBinaryStream(2));

		if (container != null)
		    loader = container.getLoader();

		if (loader != null)
		    classLoader = loader.getClassLoader();

		if (classLoader != null)
		    ois = new CustomObjectInputStream(bis,
						      classLoader);
		else
		    ois = new ObjectInputStream(bis);
	    } else if (debug > 0) {
		    log("JDBCStore: No persisted data object found");
	    }
	} catch(SQLException e) {
	    log(sm.getString("JDBCStore.SQLException", e));
	} finally {
	    try {
		if(rst != null)
		    rst.close();
	    } catch(SQLException e) {
		;
	    }
	}

	try {
	    _session = (StandardSession) manager.createSession();
            _session.readObjectData(ois);
	    _session.setManager(manager);
	} finally {
	    if (ois != null) {
		try {
		    ois.close();
		    bis = null;
		} catch (IOException e) {
		    ;
		}
	    }
	}

	if (debug > 0)
	    log(sm.getString("JDBCStore.loading",
			     id, sessionTable));
	return(_session);
    }

    /**
     * Remove the Session with the specified session identifier from
     * this Store, if present.  If no such Session is present, this method
     * takes no action.
     *
     * @param id Session identifier of the Session to be removed
     *
     * @exception IOException if an input/output error occurs
     */
    public void remove(String id) throws IOException {
	String removeSql =
	    ("DELETE FROM ".concat(sessionTable)).concat(" WHERE id = ?");

	if(!checkConnection())
	    return;

	try {
	    if(preparedRemoveSql == null)
		preparedRemoveSql = conn.prepareStatement(removeSql);

	    preparedRemoveSql.setString(1, id);
	    preparedRemoveSql.execute();
	} catch(SQLException e) {
	    log(sm.getString("JDBCStore.SQLException", e));
	}

	if (debug > 0)
	    log(sm.getString("JDBCStore.removing", id, sessionTable));
    }

    /**
     * Remove all of the Sessions in this Store.
     *
     * @exception IOException if an input/output error occurs
     */
    public void clear() throws IOException {
	String clearSql = "DELETE FROM ".concat(sessionTable);

	if(!checkConnection())
	    return;

	try {
	    if(preparedClearSql == null)
		preparedClearSql = conn.prepareStatement(clearSql);

	    preparedClearSql.execute();
	} catch(SQLException e) {
	    log(sm.getString("JDBCStore.SQLException", e));
	}
    }

    public void save(Session session) throws IOException {
	String saveSql =
	    ("INSERT INTO ".concat(sessionTable)).concat(" VALUES (?, ?)");
	ObjectOutputStream oos = null;
	ByteArrayOutputStream bos = null;
	ByteArrayInputStream bis = null;
	InputStream in = null;

	if(!checkConnection())
	    return;

	// If sessions already exist in DB, remove and insert again.
	// TODO:
	// * Check if ID exists in database and if so use UPDATE.
	remove(session.getId());

	try {
	    bos = new ByteArrayOutputStream();
	    oos = new ObjectOutputStream(new BufferedOutputStream(bos));

	    ((StandardSession)session).writeObjectData(oos);
	    oos.close();

	    byte[] obs = bos.toByteArray();
	    int size = obs.length;
	    bis = new ByteArrayInputStream(obs, 0, size);
	    in = new BufferedInputStream(bis, size);
	    
	    if(preparedSaveSql == null)
		preparedSaveSql = conn.prepareStatement(saveSql);

	    preparedSaveSql.setString(1, session.getId());
	    preparedSaveSql.setBinaryStream(2, in, size);
	    preparedSaveSql.execute();
	} catch(SQLException e) {
	    log(sm.getString("JDBCStore.SQLException", e));
	} catch (IOException e) {
	    ;
	} finally {
	    if(bis != null)
		bis.close();

	    if(in != null)
		in.close();

	    bis = null;
	    bos = null;
	    oos = null;
	    in = null;
	}
	if (debug > 0)
	    log(sm.getString("JDBCStore.saving",
			     session.getId(), sessionTable));
    }

    // --------------------------------------------------------- Private Methods

    private void processExpires() {
	long timeNow = System.currentTimeMillis();
	String[] keys = null;

    	if(!started)
    	    return;
	
	try {
	    keys = keys();
	} catch (IOException e) {
	    log (e.toString());
	    e.printStackTrace();
	    return;
	}
	
	for (int i = 0; i < keys.length; i++) {
	    try {
		StandardSession session = (StandardSession) load(keys[i]);
		if (!session.isValid())
		    continue;
		int maxInactiveInterval = session.getMaxInactiveInterval();
		if (maxInactiveInterval < 0)
		    continue;
		int timeIdle = // Truncate, do not round up
		(int) ((timeNow - session.getLastAccessedTime()) / 1000L);
		if (timeIdle >= maxInactiveInterval) {
		    session.expire();
		    remove(session.getId());
		}
	    } catch (IOException e) {
	    	log (e.toString());
	    	e.printStackTrace();
	    } catch (ClassNotFoundException e) {
	    	log (e.toString());
	    	e.printStackTrace();
	    }
	}
    }

    private boolean checkConnection(){
	boolean state = false;

        try {
            if(conn == null || conn.isClosed()) {
                Class.forName(driverName);
                log(sm.getString("JDBCStore.checkConnectionDBClosed"));
		conn = DriverManager.getConnection(connString);
		conn.setAutoCommit(true);

                if(conn == null || conn.isClosed()) {
                  log(sm.getString("JDBCStore.checkConnectionDBReOpenFail"));
                  state = false;
                } else {
		    state = true;
		}
            } else {
		state = true;
	    }
        } catch (SQLException ex){
            log(sm.getString("JDBCStore.checkConnectionSQLException",
			     ex.toString()));
        } catch (ClassNotFoundException ex) {
            log(sm.getString("JDBCStore.checkConnectionClassNotFoundException",
			     ex.toString()));
        }

	return state;
    }

    /**
     * Log a message on the Logger associated with our Container (if any).
     *
     * @param message Message to be logged
     */
    private void log(String message) {
	Logger logger = null;
	Container container = manager.getContainer();

	if (container != null)
	    logger = container.getLogger();

	if (logger != null) {
	    logger.log("Manager[" + container.getName() + "]: "
		       + message);
	} else {
	    String containerName = null;
	    if (container != null)
		containerName = container.getName();
	    System.out.println("Manager[" + containerName
			       + "]: " + message);
	}
    }

    // --------------------------------------------------------- Thread Methods

    /**
     * The background thread that checks for session timeouts and shutdown.
     */
    public void run() {
	// Loop until the termination semaphore is set
	while (!threadDone) {
	    threadSleep();
	    processExpires();
	}
    }

    /**
     * Prepare for the beginning of active use of the public methods of this
     * component.  This method should be called after <code>configure()</code>,
     * and before any of the public methods of the component are utilized.
     *
     * @exception IllegalStateException if this component has already been
     *  started
     * @exception LifecycleException if this component detects a fatal error
     *  that prevents this component from being used
     */
    public void start() throws LifecycleException {
	// Validate and update our current component state
	if (started)
	    throw new LifecycleException
		(sm.getString("JDBCStore.alreadyStarted"));
	lifecycle.fireLifecycleEvent(START_EVENT, null);
	started = true;

	// Start the background reaper thread
	threadStart();

	// Open connection to the database
	checkConnection();
    }

    /**
     * Gracefully terminate the active use of the public methods of this
     * component.  This method should be the last one called on a given
     * instance of this component.
     *
     * @exception IllegalStateException if this component has not been started
     * @exception LifecycleException if this component detects a fatal error
     *  that needs to be reported
     */
    public void stop() throws LifecycleException {
	// Validate and update our current component state
	if (!started)
	    throw new LifecycleException
		(sm.getString("JDBCStore.notStarted"));
	lifecycle.fireLifecycleEvent(STOP_EVENT, null);
	started = false;

	// Stop the background reaper thread
	threadStop();

	// Close the connection to the database.
	if(conn != null) {
	    try {
		conn.commit();
		conn.close();
	    } catch (SQLException e) {
		;
	    }
	}
    }

    /**
     * Start the background thread that will periodically check for
     * session timeouts.
     */
    private void threadStart() {
	if (thread != null)
	    return;

	threadDone = false;
	thread = new Thread(this, threadName);
	thread.setDaemon(true);
	thread.start();
    }

    /**
     * Sleep for the duration specified by the <code>checkInterval</code>
     * property.
     */
    private void threadSleep() {
	try {
	    Thread.sleep(checkInterval * 1000L);
	} catch (InterruptedException e) {
	    ;
	}
    }

    /**
     * Stop the background thread that is periodically checking for
     * session timeouts.
     */
    private void threadStop() {
	if (thread == null)
	    return;

	threadDone = true;
	thread.interrupt();
	try {
	    thread.join();
	} catch (InterruptedException e) {
	    ;
	}

	thread = null;
    }
}

Mime
View raw message