FileDocCategorySizeDatePackage
JDBCBayesianAnalyzer.javaAPI DocApache James 2.3.113962Fri Jan 12 12:56:34 GMT 2007org.apache.james.util

JDBCBayesianAnalyzer.java

/****************************************************************
 * 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.james.util;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import java.io.File;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.DatabaseMetaData;

/**
 * Manages the persistence of the spam bayesian analysis corpus using a JDBC database.
 *
 * <p>This class is abstract to allow implementations to 
 * take advantage of different logging capabilities/interfaces in
 * different parts of the code.</p>

 * @version CVS $Revision: $ $Date: $
 * @since 2.3.0
 */

abstract public class JDBCBayesianAnalyzer
extends BayesianAnalyzer {
    
    /**
     *Public object representing a lock on database activity.
     */
    public final static String DATABASE_LOCK = "database lock";
    
    /**
     * An abstract method which child classes override to handle logging of
     * errors in their particular environments.
     *
     * @param errorString the error message generated
     */
    abstract protected void delegatedLog(String errorString);

    /**
     * The JDBCUtil helper class
     */
    private final JDBCUtil theJDBCUtil = new JDBCUtil() {
        protected void delegatedLog(String logString) {
            this.delegatedLog(logString);
        }
    };
    
    /**
     * Contains all of the sql strings for this component.
     */
    private SqlResources sqlQueries = new SqlResources();

    /**
     * Holds value of property sqlFileName.
     */
    private String sqlFileName;
    
    private File sqlFile;

    /**
     * Holds value of property sqlParameters.
     */
    private Map sqlParameters = new HashMap();

    /**
     * Holds value of property lastDatabaseUpdateTime.
     */
    private static long lastDatabaseUpdateTime;
    
    /**
     * Getter for property sqlFileName.
     * @return Value of property sqlFileName.
     */
    public String getSqlFileName() {

        return this.sqlFileName;
    }

    /**
     * Setter for property sqlFileName.
     * @param sqlFileName New value of property sqlFileName.
     */
    public void setSqlFileName(String sqlFileName) {

        this.sqlFileName = sqlFileName;
    }

    /**
     * Getter for property sqlParameters.
     * @return Value of property sqlParameters.
     */
    public Map getSqlParameters() {

        return this.sqlParameters;
    }

    /**
     * Setter for property sqlParameters.
     * @param sqlParameters New value of property sqlParameters.
     */
    public void setSqlParameters(Map sqlParameters) {

        this.sqlParameters = sqlParameters;
    }

    /**
     * Getter for static lastDatabaseUpdateTime.
     * @return Value of property lastDatabaseUpdateTime.
     */
    public static long getLastDatabaseUpdateTime() {

        return lastDatabaseUpdateTime;
    }

    /**
     * Sets static lastDatabaseUpdateTime to System.currentTimeMillis().
     */
    public static void touchLastDatabaseUpdateTime() {

        lastDatabaseUpdateTime = System.currentTimeMillis();
    }

    /**
     * Default constructor.
     */
    public JDBCBayesianAnalyzer() {
    }
        
    /**
     * Loads the token frequencies from the database.
     * @param conn The connection for accessing the database
     * @throws SQLException If a database error occurs
     */
    public void loadHamNSpam(Connection conn)
    throws java.sql.SQLException {
        PreparedStatement pstmt = null;
        ResultSet rs = null;
        
        try {
            pstmt = conn.prepareStatement(sqlQueries.getSqlString("selectHamTokens", true));
            rs = pstmt.executeQuery();
            
            Map ham = getHamTokenCounts();
            while (rs.next()) {
                String token = rs.getString(1);
                int count = rs.getInt(2);
                // to reduce memory, use the token only if the count is > 1
                if (count > 1) {
                    ham.put(token, new Integer(count));
                }
            }
            //Verbose.
            delegatedLog("Ham tokens count: " + ham.size());
            
            rs.close();
            pstmt.close();
                        
            //Get the spam tokens/counts.
            pstmt = conn.prepareStatement(sqlQueries.getSqlString("selectSpamTokens", true));
            rs = pstmt.executeQuery();
            
            Map spam = getSpamTokenCounts();
            while (rs.next()) {
                String token = rs.getString(1);
                int count = rs.getInt(2);
                // to reduce memory, use the token only if the count is > 1
                if (count > 1) {
                    spam.put(token, new Integer(count));
                }
            }
            
            //Verbose.
            delegatedLog("Spam tokens count: " + spam.size());
            
            rs.close();
            pstmt.close();
                        
            //Get the ham/spam message counts.
            pstmt = conn.prepareStatement(sqlQueries.getSqlString("selectMessageCounts", true));
            rs = pstmt.executeQuery();
            if (rs.next()) {
                setHamMessageCount(rs.getInt(1));
                setSpamMessageCount(rs.getInt(2));
            }
            
            rs.close();
            pstmt.close();
            
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (java.sql.SQLException se) {
                }
                
                rs = null;
            }
            
            if (pstmt != null) {
                try {
                    pstmt.close();
                } catch (java.sql.SQLException se) {
                }
                
                pstmt = null;
            }
        }
    }
    
    /**
     * Updates the database with new "ham" token frequencies.
     * @param conn The connection for accessing the database
     * @throws SQLException If a database error occurs
     */
    public void updateHamTokens(Connection conn)
    throws java.sql.SQLException {
        updateTokens(conn, getHamTokenCounts(),
                sqlQueries.getSqlString("insertHamToken", true),
                sqlQueries.getSqlString("updateHamToken", true));
        
        setMessageCount(conn, sqlQueries.getSqlString("updateHamMessageCounts", true), getHamMessageCount());
    }
    
    /**
     * Updates the database with new "spam" token frequencies.
     * @param conn The connection for accessing the database
     * @throws SQLException If a database error occurs
     */
    public void updateSpamTokens(Connection conn)
    throws java.sql.SQLException {
         updateTokens(conn, getSpamTokenCounts(),
                sqlQueries.getSqlString("insertSpamToken", true),
                sqlQueries.getSqlString("updateSpamToken", true));
       
        setMessageCount(conn, sqlQueries.getSqlString("updateSpamMessageCounts", true), getSpamMessageCount());
    }
    
    private void setMessageCount(Connection conn, String sqlStatement, int count)
    throws java.sql.SQLException {
        PreparedStatement init = null;
        PreparedStatement update = null;
        
        try {
            //set the ham/spam message counts.
            init = conn.prepareStatement(sqlQueries.getSqlString("initializeMessageCounts", true));
            update = conn.prepareStatement(sqlStatement);
            
            update.setInt(1, count);
            
            if (update.executeUpdate() == 0) {
                init.executeUpdate();
                update.executeUpdate();
            }

        } finally {
            if (init != null) {
                try {
                    init.close();
                } catch (java.sql.SQLException ignore) {
                }
            }
            if (update != null) {
                try {
                    update.close();
                } catch (java.sql.SQLException ignore) {
                }
            }
        }
    }
    
    private void updateTokens(Connection conn, Map tokens, String insertSqlStatement, String updateSqlStatement)
    throws java.sql.SQLException {
        PreparedStatement insert = null;
        PreparedStatement update = null;
        
        try {
            //Used to insert new token entries.
            insert = conn.prepareStatement(insertSqlStatement);
            
            //Used to update existing token entries.
            update = conn.prepareStatement(updateSqlStatement);
            
            Iterator i = tokens.keySet().iterator();
            while (i.hasNext()) {
                String key = (String) i.next();
                int value = ((Integer) tokens.get(key)).intValue();
                
                update.setInt(1, value);
                update.setString(2, key);
                
                //If the update affected 0 (zero) rows, then the token hasn't been
                //encountered before, and we need to add it to the corpus.
                if (update.executeUpdate() == 0) {
                    insert.setString(1, key);
                    insert.setInt(2, value);
                    
                    insert.executeUpdate();
                }
            }
        } finally {
            if (insert != null) {
                try {
                    insert.close();
                } catch (java.sql.SQLException ignore) {
                }
                
                insert = null;
            }
            
            if (update != null) {
                try {
                    update.close();
                } catch (java.sql.SQLException ignore) {
                }
                
                update = null;
            }
        }
    }
    
    /**
     * Initializes the sql query environment from the SqlResources file.
     * Will look for conf/sqlResources.xml.
     * @param conn The connection for accessing the database
     * @param mailetContext The current mailet context,
     * for finding the conf/sqlResources.xml file
     * @throws Exception If any error occurs
     */
    public void initSqlQueries(Connection conn, org.apache.mailet.MailetContext mailetContext) throws Exception {
        try {
            if (conn.getAutoCommit()) {
                conn.setAutoCommit(false);
            }
            
            this.sqlFile = new File((String) mailetContext.getAttribute("confDir"), "sqlResources.xml").getCanonicalFile();
            sqlQueries.init(this.sqlFile, JDBCBayesianAnalyzer.class.getName() , conn, getSqlParameters());
            
            checkTables(conn);
        } finally {
            theJDBCUtil.closeJDBCConnection(conn);
        }
    }
    
    private void checkTables(Connection conn) throws SQLException {
        DatabaseMetaData dbMetaData = conn.getMetaData();
        // Need to ask in the case that identifiers are stored, ask the DatabaseMetaInfo.
        // Try UPPER, lower, and MixedCase, to see if the table is there.
        
        boolean dbUpdated = false;
        
        dbUpdated = createTable(conn, "hamTableName", "createHamTable");
        
        dbUpdated = createTable(conn, "spamTableName", "createSpamTable");
        
        dbUpdated = createTable(conn, "messageCountsTableName", "createMessageCountsTable");
        
        //Commit our changes if necessary.
        if (conn != null && dbUpdated && !conn.getAutoCommit()) {
            conn.commit();
            dbUpdated = false;
        }
            
    }
    
    private boolean createTable(Connection conn, String tableNameSqlStringName, String createSqlStringName) throws SQLException {
        String tableName = sqlQueries.getSqlString(tableNameSqlStringName, true);
        
        DatabaseMetaData dbMetaData = conn.getMetaData();

        // Try UPPER, lower, and MixedCase, to see if the table is there.
        if (theJDBCUtil.tableExists(dbMetaData, tableName)) {
            return false;
        }
        
        PreparedStatement createStatement = null;
        
        try {
            createStatement =
                    conn.prepareStatement(sqlQueries.getSqlString(createSqlStringName, true));
            createStatement.execute();
            
            StringBuffer logBuffer = null;
            logBuffer =
                    new StringBuffer(64)
                    .append("Created table '")
                    .append(tableName)
                    .append("' using sqlResources string '")
                    .append(createSqlStringName)
                    .append("'.");
            delegatedLog(logBuffer.toString());
            
        } finally {
            theJDBCUtil.closeJDBCStatement(createStatement);
        }
        
        return true;
    }
    
}