001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.util;
019:
020: import java.util.HashMap;
021: import java.util.Iterator;
022: import java.util.Map;
023:
024: import java.io.File;
025:
026: import java.sql.Connection;
027: import java.sql.PreparedStatement;
028: import java.sql.ResultSet;
029: import java.sql.SQLException;
030: import java.sql.DatabaseMetaData;
031:
032: /**
033: * Manages the persistence of the spam bayesian analysis corpus using a JDBC database.
034: *
035: * <p>This class is abstract to allow implementations to
036: * take advantage of different logging capabilities/interfaces in
037: * different parts of the code.</p>
038:
039: * @version CVS $Revision: $ $Date: $
040: * @since 2.3.0
041: */
042:
043: abstract public class JDBCBayesianAnalyzer extends BayesianAnalyzer {
044:
045: /**
046: *Public object representing a lock on database activity.
047: */
048: public final static String DATABASE_LOCK = "database lock";
049:
050: /**
051: * An abstract method which child classes override to handle logging of
052: * errors in their particular environments.
053: *
054: * @param errorString the error message generated
055: */
056: abstract protected void delegatedLog(String errorString);
057:
058: /**
059: * The JDBCUtil helper class
060: */
061: private final JDBCUtil theJDBCUtil = new JDBCUtil() {
062: protected void delegatedLog(String logString) {
063: this .delegatedLog(logString);
064: }
065: };
066:
067: /**
068: * Contains all of the sql strings for this component.
069: */
070: private SqlResources sqlQueries = new SqlResources();
071:
072: /**
073: * Holds value of property sqlFileName.
074: */
075: private String sqlFileName;
076:
077: private File sqlFile;
078:
079: /**
080: * Holds value of property sqlParameters.
081: */
082: private Map sqlParameters = new HashMap();
083:
084: /**
085: * Holds value of property lastDatabaseUpdateTime.
086: */
087: private static long lastDatabaseUpdateTime;
088:
089: /**
090: * Getter for property sqlFileName.
091: * @return Value of property sqlFileName.
092: */
093: public String getSqlFileName() {
094:
095: return this .sqlFileName;
096: }
097:
098: /**
099: * Setter for property sqlFileName.
100: * @param sqlFileName New value of property sqlFileName.
101: */
102: public void setSqlFileName(String sqlFileName) {
103:
104: this .sqlFileName = sqlFileName;
105: }
106:
107: /**
108: * Getter for property sqlParameters.
109: * @return Value of property sqlParameters.
110: */
111: public Map getSqlParameters() {
112:
113: return this .sqlParameters;
114: }
115:
116: /**
117: * Setter for property sqlParameters.
118: * @param sqlParameters New value of property sqlParameters.
119: */
120: public void setSqlParameters(Map sqlParameters) {
121:
122: this .sqlParameters = sqlParameters;
123: }
124:
125: /**
126: * Getter for static lastDatabaseUpdateTime.
127: * @return Value of property lastDatabaseUpdateTime.
128: */
129: public static long getLastDatabaseUpdateTime() {
130:
131: return lastDatabaseUpdateTime;
132: }
133:
134: /**
135: * Sets static lastDatabaseUpdateTime to System.currentTimeMillis().
136: */
137: public static void touchLastDatabaseUpdateTime() {
138:
139: lastDatabaseUpdateTime = System.currentTimeMillis();
140: }
141:
142: /**
143: * Default constructor.
144: */
145: public JDBCBayesianAnalyzer() {
146: }
147:
148: /**
149: * Loads the token frequencies from the database.
150: * @param conn The connection for accessing the database
151: * @throws SQLException If a database error occurs
152: */
153: public void loadHamNSpam(Connection conn)
154: throws java.sql.SQLException {
155: PreparedStatement pstmt = null;
156: ResultSet rs = null;
157:
158: try {
159: pstmt = conn.prepareStatement(sqlQueries.getSqlString(
160: "selectHamTokens", true));
161: rs = pstmt.executeQuery();
162:
163: Map ham = getHamTokenCounts();
164: while (rs.next()) {
165: String token = rs.getString(1);
166: int count = rs.getInt(2);
167: // to reduce memory, use the token only if the count is > 1
168: if (count > 1) {
169: ham.put(token, new Integer(count));
170: }
171: }
172: //Verbose.
173: delegatedLog("Ham tokens count: " + ham.size());
174:
175: rs.close();
176: pstmt.close();
177:
178: //Get the spam tokens/counts.
179: pstmt = conn.prepareStatement(sqlQueries.getSqlString(
180: "selectSpamTokens", true));
181: rs = pstmt.executeQuery();
182:
183: Map spam = getSpamTokenCounts();
184: while (rs.next()) {
185: String token = rs.getString(1);
186: int count = rs.getInt(2);
187: // to reduce memory, use the token only if the count is > 1
188: if (count > 1) {
189: spam.put(token, new Integer(count));
190: }
191: }
192:
193: //Verbose.
194: delegatedLog("Spam tokens count: " + spam.size());
195:
196: rs.close();
197: pstmt.close();
198:
199: //Get the ham/spam message counts.
200: pstmt = conn.prepareStatement(sqlQueries.getSqlString(
201: "selectMessageCounts", true));
202: rs = pstmt.executeQuery();
203: if (rs.next()) {
204: setHamMessageCount(rs.getInt(1));
205: setSpamMessageCount(rs.getInt(2));
206: }
207:
208: rs.close();
209: pstmt.close();
210:
211: } finally {
212: if (rs != null) {
213: try {
214: rs.close();
215: } catch (java.sql.SQLException se) {
216: }
217:
218: rs = null;
219: }
220:
221: if (pstmt != null) {
222: try {
223: pstmt.close();
224: } catch (java.sql.SQLException se) {
225: }
226:
227: pstmt = null;
228: }
229: }
230: }
231:
232: /**
233: * Updates the database with new "ham" token frequencies.
234: * @param conn The connection for accessing the database
235: * @throws SQLException If a database error occurs
236: */
237: public void updateHamTokens(Connection conn)
238: throws java.sql.SQLException {
239: updateTokens(conn, getHamTokenCounts(), sqlQueries
240: .getSqlString("insertHamToken", true), sqlQueries
241: .getSqlString("updateHamToken", true));
242:
243: setMessageCount(conn, sqlQueries.getSqlString(
244: "updateHamMessageCounts", true), getHamMessageCount());
245: }
246:
247: /**
248: * Updates the database with new "spam" token frequencies.
249: * @param conn The connection for accessing the database
250: * @throws SQLException If a database error occurs
251: */
252: public void updateSpamTokens(Connection conn)
253: throws java.sql.SQLException {
254: updateTokens(conn, getSpamTokenCounts(), sqlQueries
255: .getSqlString("insertSpamToken", true), sqlQueries
256: .getSqlString("updateSpamToken", true));
257:
258: setMessageCount(conn, sqlQueries.getSqlString(
259: "updateSpamMessageCounts", true), getSpamMessageCount());
260: }
261:
262: private void setMessageCount(Connection conn, String sqlStatement,
263: int count) throws java.sql.SQLException {
264: PreparedStatement init = null;
265: PreparedStatement update = null;
266:
267: try {
268: //set the ham/spam message counts.
269: init = conn.prepareStatement(sqlQueries.getSqlString(
270: "initializeMessageCounts", true));
271: update = conn.prepareStatement(sqlStatement);
272:
273: update.setInt(1, count);
274:
275: if (update.executeUpdate() == 0) {
276: init.executeUpdate();
277: update.executeUpdate();
278: }
279:
280: } finally {
281: if (init != null) {
282: try {
283: init.close();
284: } catch (java.sql.SQLException ignore) {
285: }
286: }
287: if (update != null) {
288: try {
289: update.close();
290: } catch (java.sql.SQLException ignore) {
291: }
292: }
293: }
294: }
295:
296: private void updateTokens(Connection conn, Map tokens,
297: String insertSqlStatement, String updateSqlStatement)
298: throws java.sql.SQLException {
299: PreparedStatement insert = null;
300: PreparedStatement update = null;
301:
302: try {
303: //Used to insert new token entries.
304: insert = conn.prepareStatement(insertSqlStatement);
305:
306: //Used to update existing token entries.
307: update = conn.prepareStatement(updateSqlStatement);
308:
309: Iterator i = tokens.keySet().iterator();
310: while (i.hasNext()) {
311: String key = (String) i.next();
312: int value = ((Integer) tokens.get(key)).intValue();
313:
314: update.setInt(1, value);
315: update.setString(2, key);
316:
317: //If the update affected 0 (zero) rows, then the token hasn't been
318: //encountered before, and we need to add it to the corpus.
319: if (update.executeUpdate() == 0) {
320: insert.setString(1, key);
321: insert.setInt(2, value);
322:
323: insert.executeUpdate();
324: }
325: }
326: } finally {
327: if (insert != null) {
328: try {
329: insert.close();
330: } catch (java.sql.SQLException ignore) {
331: }
332:
333: insert = null;
334: }
335:
336: if (update != null) {
337: try {
338: update.close();
339: } catch (java.sql.SQLException ignore) {
340: }
341:
342: update = null;
343: }
344: }
345: }
346:
347: /**
348: * Initializes the sql query environment from the SqlResources file.
349: * Will look for conf/sqlResources.xml.
350: * @param conn The connection for accessing the database
351: * @param mailetContext The current mailet context,
352: * for finding the conf/sqlResources.xml file
353: * @throws Exception If any error occurs
354: */
355: public void initSqlQueries(Connection conn,
356: org.apache.mailet.MailetContext mailetContext)
357: throws Exception {
358: try {
359: if (conn.getAutoCommit()) {
360: conn.setAutoCommit(false);
361: }
362:
363: this .sqlFile = new File((String) mailetContext
364: .getAttribute("confDir"), "sqlResources.xml")
365: .getCanonicalFile();
366: sqlQueries.init(this .sqlFile, JDBCBayesianAnalyzer.class
367: .getName(), conn, getSqlParameters());
368:
369: checkTables(conn);
370: } finally {
371: theJDBCUtil.closeJDBCConnection(conn);
372: }
373: }
374:
375: private void checkTables(Connection conn) throws SQLException {
376: DatabaseMetaData dbMetaData = conn.getMetaData();
377: // Need to ask in the case that identifiers are stored, ask the DatabaseMetaInfo.
378: // Try UPPER, lower, and MixedCase, to see if the table is there.
379:
380: boolean dbUpdated = false;
381:
382: dbUpdated = createTable(conn, "hamTableName", "createHamTable");
383:
384: dbUpdated = createTable(conn, "spamTableName",
385: "createSpamTable");
386:
387: dbUpdated = createTable(conn, "messageCountsTableName",
388: "createMessageCountsTable");
389:
390: //Commit our changes if necessary.
391: if (conn != null && dbUpdated && !conn.getAutoCommit()) {
392: conn.commit();
393: dbUpdated = false;
394: }
395:
396: }
397:
398: private boolean createTable(Connection conn,
399: String tableNameSqlStringName, String createSqlStringName)
400: throws SQLException {
401: String tableName = sqlQueries.getSqlString(
402: tableNameSqlStringName, true);
403:
404: DatabaseMetaData dbMetaData = conn.getMetaData();
405:
406: // Try UPPER, lower, and MixedCase, to see if the table is there.
407: if (theJDBCUtil.tableExists(dbMetaData, tableName)) {
408: return false;
409: }
410:
411: PreparedStatement createStatement = null;
412:
413: try {
414: createStatement = conn.prepareStatement(sqlQueries
415: .getSqlString(createSqlStringName, true));
416: createStatement.execute();
417:
418: StringBuffer logBuffer = null;
419: logBuffer = new StringBuffer(64).append("Created table '")
420: .append(tableName).append(
421: "' using sqlResources string '").append(
422: createSqlStringName).append("'.");
423: delegatedLog(logBuffer.toString());
424:
425: } finally {
426: theJDBCUtil.closeJDBCStatement(createStatement);
427: }
428:
429: return true;
430: }
431:
432: }
|