/*
* Licensed to the Apache Software Foundation ( ASF ) under one or more
* contributor license agreements . See the NOTICE file distributed with
* this work for additional information regarding copyright ownership .
* The ASF licenses this file to You under the Apache License , Version 2 . 0
* ( the " License " ) ; you may not use this file except in compliance with
* the License . You may obtain a copy of the License at
*
* http : //www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing , software
* distributed under the License is distributed on an " AS IS " BASIS ,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND , either express or implied .
* See the License for the specific language governing permissions and
* limitations under the License .
*/
package org.apache.catalina.session;
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.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.NamingException;
import javax.sql.DataSource;
import org.apache.catalina.Container;
import org.apache.catalina.Globals;
import org.apache.catalina.Session;
import org.apache.juli.logging.Log;
/**
* Implementation of the { @ link org . apache . catalina . Store Store } 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
*/
public class DataSourceStore extends StoreBase {
/**
* Context name associated with this Store
*/
private String name = null ;
/**
* Name to register for this Store , used for logging .
*/
protected static final String storeName = "dataSourceStore" ;
/**
* name of the JNDI resource
*/
protected String dataSourceName = null ;
/**
* Context local datasource .
*/
private boolean localDataSource = false ;
/**
* DataSource to use
*/
protected DataSource dataSource = null ;
// ------------------------------------------------------------ Table & cols
/**
* Table to use .
*/
protected String sessionTable = "tomcat$sessions" ;
/**
* Column to use for / Engine / Host / Context name
*/
protected String sessionAppCol = "app" ;
/**
* Id column to use .
*/
protected String sessionIdCol = "id" ;
/**
* Data column to use .
*/
protected String sessionDataCol = "data" ;
/**
* { @ code Is Valid } column to use .
*/
protected String sessionValidCol = "valid" ;
/**
* Max Inactive column to use .
*/
protected String sessionMaxInactiveCol = "maxinactive" ;
/**
* Last Accessed column to use .
*/
protected String sessionLastAccessedCol = "lastaccess" ;
// -------------------------------------------------------------- Properties
/**
* @ return the name for this instance ( built from container name )
*/
public String getName() {
if (name == null ) {
Container container = manager.getContext();
String contextName = container.getName();
if (!contextName.startsWith("/" )) {
contextName = "/" + contextName;
}
String hostName = "" ;
String engineName = "" ;
if (container.getParent() != null ) {
Container host = container.getParent();
hostName = host.getName();
if (host.getParent() != null ) {
engineName = host.getParent().getName();
}
}
name = "/" + engineName + "/" + hostName + contextName;
}
return name;
}
/**
* @ return the name for this Store , used for logging .
*/
@Override
public String getStoreName() {
return storeName;
}
/**
* 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 sessionTable;
}
/**
* Set the App column for the table .
*
* @ param sessionAppCol the column name
*/
public void setSessionAppCol(String sessionAppCol) {
String oldSessionAppCol = this .sessionAppCol;
this .sessionAppCol = sessionAppCol;
support.firePropertyChange("sessionAppCol" , oldSessionAppCol, this .sessionAppCol);
}
/**
* @ return the web application name column for the table .
*/
public String getSessionAppCol() {
return this .sessionAppCol;
}
/**
* Set the Id column for the table .
*
* @ param sessionIdCol the column name
*/
public void setSessionIdCol(String sessionIdCol) {
String oldSessionIdCol = this .sessionIdCol;
this .sessionIdCol = sessionIdCol;
support.firePropertyChange("sessionIdCol" , oldSessionIdCol, this .sessionIdCol);
}
/**
* @ return the Id column for the table .
*/
public String getSessionIdCol() {
return this .sessionIdCol;
}
/**
* Set the Data column for the table
*
* @ param sessionDataCol the column name
*/
public void setSessionDataCol(String sessionDataCol) {
String oldSessionDataCol = this .sessionDataCol;
this .sessionDataCol = sessionDataCol;
support.firePropertyChange("sessionDataCol" , oldSessionDataCol, this .sessionDataCol);
}
/**
* @ return the data column for the table
*/
public String getSessionDataCol() {
return this .sessionDataCol;
}
/**
* Set the { @ code Is Valid } column for the table
*
* @ param sessionValidCol The column name
*/
public void setSessionValidCol(String sessionValidCol) {
String oldSessionValidCol = this .sessionValidCol;
this .sessionValidCol = sessionValidCol;
support.firePropertyChange("sessionValidCol" , oldSessionValidCol, this .sessionValidCol);
}
/**
* @ return the { @ code Is Valid } column
*/
public String getSessionValidCol() {
return this .sessionValidCol;
}
/**
* Set the { @ code Max Inactive } column for the table
*
* @ param sessionMaxInactiveCol The column name
*/
public void setSessionMaxInactiveCol(String sessionMaxInactiveCol) {
String oldSessionMaxInactiveCol = this .sessionMaxInactiveCol;
this .sessionMaxInactiveCol = sessionMaxInactiveCol;
support.firePropertyChange("sessionMaxInactiveCol" , oldSessionMaxInactiveCol, this .sessionMaxInactiveCol);
}
/**
* @ return the { @ code Max Inactive } column
*/
public String getSessionMaxInactiveCol() {
return this .sessionMaxInactiveCol;
}
/**
* Set the { @ code Last Accessed } column for the table
*
* @ param sessionLastAccessedCol The column name
*/
public void setSessionLastAccessedCol(String sessionLastAccessedCol) {
String oldSessionLastAccessedCol = this .sessionLastAccessedCol;
this .sessionLastAccessedCol = sessionLastAccessedCol;
support.firePropertyChange("sessionLastAccessedCol" , oldSessionLastAccessedCol, this .sessionLastAccessedCol);
}
/**
* @ return the { @ code Last Accessed } column
*/
public String getSessionLastAccessedCol() {
return this .sessionLastAccessedCol;
}
/**
* Set the JNDI name of a DataSource - factory to use for db access
*
* @ param dataSourceName The JNDI name of the DataSource - factory
*/
public void setDataSourceName(String dataSourceName) {
if (dataSourceName == null || dataSourceName.trim().isEmpty()) {
manager.getContext().getLogger().warn(sm.getString(getStoreName() + ".missingDataSourceName" ));
return ;
}
this .dataSourceName = dataSourceName;
}
/**
* @ return the name of the JNDI DataSource - factory
*/
public String getDataSourceName() {
return this .dataSourceName;
}
/**
* @ return if the datasource will be looked up in the webapp JNDI Context .
*/
public boolean getLocalDataSource() {
return localDataSource;
}
/**
* Set to { @ code true } to cause the datasource to be looked up in the webapp JNDI Context .
*
* @ param localDataSource the new flag value
*/
public void setLocalDataSource(boolean localDataSource) {
this .localDataSource = localDataSource;
}
// --------------------------------------------------------- Public Methods
@Override
public String[] expiredKeys() throws IOException {
return keys(true );
}
@Override
public String[] keys() throws IOException {
return keys(false );
}
/**
* 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 .
*
* @ param expiredOnly flag , whether only keys of expired sessions should be returned
*
* @ return array containing the list of session IDs
*
* @ exception IOException if an input / output error occurred
*/
private String[] keys(boolean expiredOnly) throws IOException {
String keys[] = null ;
int numberOfTries = 2 ;
while (numberOfTries > 0 ) {
Connection _conn = getConnection();
if (_conn == null ) {
return new String[0 ];
}
try {
String keysSql =
"SELECT " + sessionIdCol + " FROM " + sessionTable + " WHERE " + sessionAppCol + " = ?" ;
if (expiredOnly) {
keysSql += " AND (" + sessionLastAccessedCol + " + " + sessionMaxInactiveCol + " * 1000 < ?)" ;
}
try (PreparedStatement preparedKeysSql = _conn.prepareStatement(keysSql)) {
preparedKeysSql.setString(1 , getName());
if (expiredOnly) {
preparedKeysSql.setLong(2 , System.currentTimeMillis());
}
try (ResultSet rst = preparedKeysSql.executeQuery()) {
List<String> tmpkeys = new ArrayList<>();
if (rst != null ) {
while (rst.next()) {
tmpkeys.add(rst.getString(1 ));
}
}
keys = tmpkeys.toArray(new String[0 ]);
// Break out after the finally block
numberOfTries = 0 ;
}
}
} catch (SQLException e) {
manager.getContext().getLogger().error(sm.getString(getStoreName() + ".SQLException" , e));
keys = new String[0 ];
// Close the connection so that it gets reopened next time
} finally {
release(_conn);
}
numberOfTries--;
}
return keys;
}
/**
* Return an integer containing a count of all Sessions currently saved in this Store . If there are no Sessions ,
* < code > 0 < / code > is returned .
*
* @ return the count of all sessions currently saved in this Store
*
* @ exception IOException if an input / output error occurred
*/
@Override
public int getSize() throws IOException {
int size = 0 ;
String sizeSql = "SELECT COUNT(" + sessionIdCol + ") FROM " + sessionTable + " WHERE " + sessionAppCol + " = ?" ;
int numberOfTries = 2 ;
while (numberOfTries > 0 ) {
Connection _conn = getConnection();
if (_conn == null ) {
return size;
}
try (PreparedStatement preparedSizeSql = _conn.prepareStatement(sizeSql)) {
preparedSizeSql.setString(1 , getName());
try (ResultSet rst = preparedSizeSql.executeQuery()) {
if (rst.next()) {
size = rst.getInt(1 );
}
// Break out after the finally block
numberOfTries = 0 ;
}
} catch (SQLException e) {
manager.getContext().getLogger().error(sm.getString(getStoreName() + ".SQLException" , e));
} finally {
release(_conn);
}
numberOfTries--;
}
return size;
}
/**
* Load the Session associated with the id < code > id < / code > . If no such session is found < code > null < / code > is
* returned .
*
* @ param id a value of type < code > String < / code >
*
* @ return the stored < code > Session < / code >
*
* @ exception ClassNotFoundException if an error occurs
* @ exception IOException if an input / output error occurred
*/
@Override
public Session load(String id) throws ClassNotFoundException, IOException {
StandardSession _session = null ;
org.apache.catalina.Context context = getManager().getContext();
Log contextLog = context.getLogger();
int numberOfTries = 2 ;
String loadSql = "SELECT " + sessionIdCol + ", " + sessionDataCol + " FROM " + sessionTable + " WHERE " +
sessionIdCol + " = ? AND " + sessionAppCol + " = ?" ;
while (numberOfTries > 0 ) {
Connection _conn = getConnection();
if (_conn == null ) {
return null ;
}
ClassLoader oldThreadContextCL = context.bind(Globals.IS_SECURITY_ENABLED, null );
try (PreparedStatement preparedLoadSql = _conn.prepareStatement(loadSql)) {
preparedLoadSql.setString(1 , id);
preparedLoadSql.setString(2 , getName());
try (ResultSet rst = preparedLoadSql.executeQuery()) {
if (rst.next()) {
try (ObjectInputStream ois = getObjectInputStream(rst.getBinaryStream(2 ))) {
if (contextLog.isDebugEnabled()) {
contextLog.debug(sm.getString(getStoreName() + ".loading" , id, sessionTable));
}
_session = (StandardSession) manager.createEmptySession();
_session.readObjectData(ois);
_session.setManager(manager);
}
} else if (context.getLogger().isDebugEnabled()) {
contextLog.debug(getStoreName() + ": No persisted data object found" );
}
// Break out after the finally block
numberOfTries = 0 ;
}
} catch (SQLException e) {
contextLog.error(sm.getString(getStoreName() + ".SQLException" , e));
} finally {
context.unbind(Globals.IS_SECURITY_ENABLED, oldThreadContextCL);
release(_conn);
}
numberOfTries--;
}
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
*/
@Override
public void remove(String id) throws IOException {
int numberOfTries = 2 ;
while (numberOfTries > 0 ) {
Connection _conn = getConnection();
if (_conn == null ) {
return ;
}
try {
remove(id, _conn);
// Break out after the finally block
numberOfTries = 0 ;
} catch (SQLException e) {
manager.getContext().getLogger().error(sm.getString(getStoreName() + ".SQLException" , e));
} finally {
release(_conn);
}
numberOfTries--;
}
if (manager.getContext().getLogger().isDebugEnabled()) {
manager.getContext().getLogger().debug(sm.getString(getStoreName() + ".removing" , id, sessionTable));
}
}
/**
* 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
* @ param _ conn open connection to be used
*
* @ throws SQLException if an error occurs while talking to the database
*/
private void remove(String id, Connection _conn) throws SQLException {
String removeSql =
"DELETE FROM " + sessionTable + " WHERE " + sessionIdCol + " = ? AND " + sessionAppCol + " = ?" ;
try (PreparedStatement preparedRemoveSql = _conn.prepareStatement(removeSql)) {
preparedRemoveSql.setString(1 , id);
preparedRemoveSql.setString(2 , getName());
preparedRemoveSql.execute();
}
}
/**
* Remove all of the Sessions in this Store .
*
* @ exception IOException if an input / output error occurs
*/
@Override
public void clear() throws IOException {
String clearSql = "DELETE FROM " + sessionTable + " WHERE " + sessionAppCol + " = ?" ;
int numberOfTries = 2 ;
while (numberOfTries > 0 ) {
Connection _conn = getConnection();
if (_conn == null ) {
return ;
}
try (PreparedStatement preparedClearSql = _conn.prepareStatement(clearSql)) {
preparedClearSql.setString(1 , getName());
preparedClearSql.execute();
// Break out after the finally block
numberOfTries = 0 ;
} catch (SQLException e) {
manager.getContext().getLogger().error(sm.getString(getStoreName() + ".SQLException" , e));
} finally {
release(_conn);
}
numberOfTries--;
}
}
/**
* Save a session to the Store .
*
* @ param session the session to be stored
*
* @ exception IOException if an input / output error occurs
*/
@Override
public void save(Session session) throws IOException {
ByteArrayOutputStream bos = null ;
String saveSql = "INSERT INTO " + sessionTable + " (" + sessionIdCol + ", " + sessionAppCol + ", " +
sessionDataCol + ", " + sessionValidCol + ", " + sessionMaxInactiveCol + ", " + sessionLastAccessedCol +
") VALUES (?, ?, ?, ?, ?, ?)" ;
synchronized (session) {
int numberOfTries = 2 ;
while (numberOfTries > 0 ) {
Connection _conn = getConnection();
if (_conn == null ) {
return ;
}
try {
// If sessions already exist in DB, remove and insert again.
// TODO:
// * Check if ID exists in database and if so use UPDATE.
remove(session.getIdInternal(), _conn);
bos = new ByteArrayOutputStream();
try (ObjectOutputStream oos = new ObjectOutputStream(new BufferedOutputStream(bos))) {
((StandardSession) session).writeObjectData(oos);
}
byte [] obs = bos.toByteArray();
int size = obs.length;
try (ByteArrayInputStream bis = new ByteArrayInputStream(obs, 0 , size);
InputStream in = new BufferedInputStream(bis, size);
PreparedStatement preparedSaveSql = _conn.prepareStatement(saveSql)) {
preparedSaveSql.setString(1 , session.getIdInternal());
preparedSaveSql.setString(2 , getName());
preparedSaveSql.setBinaryStream(3 , in, size);
preparedSaveSql.setString(4 , session.isValid() ? "1" : "0" );
preparedSaveSql.setInt(5 , session.getMaxInactiveInterval());
preparedSaveSql.setLong(6 , session.getLastAccessedTime());
preparedSaveSql.execute();
// Break out after the finally block
numberOfTries = 0 ;
}
} catch (SQLException e) {
manager.getContext().getLogger().error(sm.getString(getStoreName() + ".SQLException" , e));
} catch (IOException e) {
// Ignore
} finally {
release(_conn);
}
numberOfTries--;
}
}
if (manager.getContext().getLogger().isDebugEnabled()) {
manager.getContext().getLogger()
.debug(sm.getString(getStoreName() + ".saving" , session.getIdInternal(), sessionTable));
}
}
// --------------------------------------------------------- Protected Methods
/**
* Check the connection associated with this store , if it ' s < code > null < / code > or closed try to reopen it . Returns
* < code > null < / code > if the connection could not be established .
*
* @ return < code > Connection < / code > if the connection succeeded
*/
protected Connection getConnection() {
Connection conn = null ;
try {
conn = open();
if (conn == null || conn.isClosed()) {
manager.getContext().getLogger().info(sm.getString(getStoreName() + ".checkConnectionDBClosed" ));
conn = open();
if (conn == null || conn.isClosed()) {
manager.getContext().getLogger()
.info(sm.getString(getStoreName() + ".checkConnectionDBReOpenFail" ));
}
}
} catch (SQLException ex) {
manager.getContext().getLogger()
.error(sm.getString(getStoreName() + ".checkConnectionSQLException" , ex.toString()));
}
return conn;
}
/**
* Open ( if necessary ) and return a database connection for use by this Store .
*
* @ return database connection ready to use
*
* @ exception SQLException if a database error occurs
*/
protected Connection open() throws SQLException {
if (dataSourceName != null && dataSource == null ) {
org.apache.catalina.Context context = getManager().getContext();
ClassLoader oldThreadContextCL = null ;
if (getLocalDataSource()) {
oldThreadContextCL = context.bind(Globals.IS_SECURITY_ENABLED, null );
}
Context initCtx;
try {
initCtx = new InitialContext();
Context envCtx = (Context) initCtx.lookup("java:comp/env" );
this .dataSource = (DataSource) envCtx.lookup(this .dataSourceName);
} catch (NamingException e) {
context.getLogger().error(sm.getString(getStoreName() + ".wrongDataSource" , this .dataSourceName), e);
} finally {
if (getLocalDataSource()) {
context.unbind(Globals.IS_SECURITY_ENABLED, oldThreadContextCL);
}
}
}
if (dataSource != null ) {
return dataSource.getConnection();
} else {
throw new IllegalStateException(sm.getString(getStoreName() + ".missingDataSource" ));
}
}
/**
* Close the specified database connection .
*
* @ param dbConnection The connection to be closed
*/
protected void close(Connection dbConnection) {
// Do nothing if the database connection is already closed
if (dbConnection == null ) {
return ;
}
// Commit if autoCommit is false
try {
if (!dbConnection.getAutoCommit()) {
dbConnection.commit();
}
} catch (SQLException e) {
manager.getContext().getLogger().error(sm.getString(getStoreName() + ".commitSQLException" ), e);
}
// Close this database connection, and log any errors
try {
dbConnection.close();
} catch (SQLException e) {
manager.getContext().getLogger().error(sm.getString(getStoreName() + ".close" , e.toString())); // Just log
// it here
}
}
/**
* Release the connection , if it is associated with a connection pool .
*
* @ param conn The connection to be released
*/
protected void release(Connection conn) {
if (dataSource != null ) {
close(conn);
}
}
}
Messung V0.5 in Prozent C=93 H=90 G=91
¤ Dauer der Verarbeitung: 0.13 Sekunden
¤
*© Formatika GbR, Deutschland