/*
* Copyright (C) 2000 - 2011 TagServlet Ltd
*
* This file is part of Open BlueDragon (OpenBD) CFML Server Engine.
*
* OpenBD is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* Free Software Foundation,version 3.
*
* OpenBD is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with OpenBD. If not, see http://www.gnu.org/licenses/
*
* Additional permission under GNU GPL version 3 section 7
*
* If you modify this Program, or any covered work, by linking or combining
* it with any of the JARS listed in the README.txt (or a modified version of
* (that library), containing parts covered by the terms of that JAR, the
* licensors of this Program grant you additional permission to convey the
* resulting work.
* README.txt @ http://www.openbluedragon.org/license/README.txt
*
* http://www.openbluedragon.org/
* $Id: $
*/
package com.naryx.tagfusion.cfm.application;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.aw20.util.SystemClockEvent;
import com.naryx.tagfusion.cfm.engine.cfEngine;
import com.naryx.tagfusion.cfm.engine.cfmRunTimeException;
import com.naryx.tagfusion.cfm.sql.cfDataSource;
import com.naryx.tagfusion.xmlConfig.xmlCFML;
/**
* Responsible for purging client data that is x days old from any datasources where this is enabled
*/
public class cfClientDataManager implements SystemClockEvent {
private final long DEFAULT_CLIENT_EXPIRY = 1;
private String SQL_SELECT;
private String SQL_DELETE_GLOBAL;
private String SQL_DELETE_APPDATA;
private String DATA_TABLE_NAME;
private String SQL_DELETE_ALL; // a multi statement string to DELETE all expired entries from both tables
public cfClientDataManager() {
String GLOBAL_TABLE_NAME;
// Also, set up different table names for ColdFusion compatibility
if (cfApplicationManager.cf5ClientData) {
DATA_TABLE_NAME = "CDATA";
GLOBAL_TABLE_NAME = "CGLOBAL";
} else {
DATA_TABLE_NAME = "BDDATA";
GLOBAL_TABLE_NAME = "BDGLOBAL";
}
SQL_SELECT = "SELECT CFID FROM " + GLOBAL_TABLE_NAME + " WHERE LVISIT < ?";
SQL_DELETE_GLOBAL = "DELETE FROM " + GLOBAL_TABLE_NAME + " WHERE LVISIT < ?";
SQL_DELETE_APPDATA = "DELETE FROM " + DATA_TABLE_NAME + " WHERE CFID IN ( SELECT CFID FROM " + GLOBAL_TABLE_NAME + " WHERE LVISIT < ? )";
SQL_DELETE_ALL = SQL_DELETE_APPDATA + ";" + SQL_DELETE_GLOBAL;
cfEngine.thisPlatform.timerSetListenerHourly(this);
}
public void clockEvent(int type) {
// loop thru the datasources
xmlCFML config = cfEngine.getConfig();
String clientstorage = config.getString("server.cfapplication.clientstorage", "cookie").toLowerCase();
if (clientstorage == null || clientstorage.equals("cookie")) { // can't purge cookie based client data
return;
}
// is purge enabled?
try {
// default this to false since if this data isn't present it's unlikely the clientexpiry is either
boolean purgeEnabled = config.getBoolean("server.cfapplication.clientpurgeenabled", false);
if (purgeEnabled) { // what's the expiry period
long period = config.getLong("server.cfapplication.clientexpiry", DEFAULT_CLIENT_EXPIRY);
period = period * 1000 * 60 * 60 * 24; // convert from no of days to milliseconds
purgeDBData(clientstorage, period);
}
} catch (SQLException e) {
cfEngine.log("Failed to delete client data from database: " + e.getMessage());
} catch (cfmRunTimeException e) {
cfEngine.log("Failed to delete client data: " + e.getMessage());
}
}
private void purgeDBData(String _datasourcename, long _period) throws SQLException, cfmRunTimeException {
java.sql.Date expiredDate = new java.sql.Date(System.currentTimeMillis() - _period);
cfDataSource datasource = new cfDataSource(_datasourcename);
Connection connection = null;
try {
// get cfid's where lvisit is l
connection = datasource.getConnection();
connection.setAutoCommit(false);
try {
// -- first try the most efficient 1 statement method (some db's don't support this)
PreparedStatement delStmtGlob = connection.prepareStatement(SQL_DELETE_ALL);
try {
delStmtGlob.setDate(1, expiredDate);
delStmtGlob.setDate(2, expiredDate);
} catch (SQLException sqlExc) {
// When setDate() is called the ODBC SQL Server Driver will throw an exception stating
// "Optional feature not implemented". To work around this we catch the exception and
// call setString instead.
String expiredDateStr = expiredDate.toString();
delStmtGlob.setString(1, expiredDateStr);
delStmtGlob.setString(2, expiredDateStr);
}
delStmtGlob.execute();
delStmtGlob.close();
connection.commit();
} catch (SQLException e) {
try {
// -- next try the 2 statement method
PreparedStatement delStmtGlob = connection.prepareStatement(SQL_DELETE_APPDATA);
delStmtGlob.setDate(1, expiredDate);
delStmtGlob.execute();
delStmtGlob.close();
PreparedStatement delStmtApp = connection.prepareStatement(SQL_DELETE_GLOBAL);
delStmtApp.setDate(1, expiredDate);
delStmtApp.execute();
delStmtApp.close();
connection.commit();
} catch (SQLException e2) {
// -- the first 2 methods failed so try this method
connection.setAutoCommit(true); // restore this for the SELECT
List<String> cfids = getCFIDtoDelete(connection, expiredDate);
if (cfids.size() > 0) {
int cfidsCount = cfids.size();
int batchSize = 100;
connection.setAutoCommit(false);
// -- delete in batches of 100
for (int i = 0; i < cfidsCount; i += batchSize) {
StringBuilder sb = new StringBuilder("DELETE FROM " + DATA_TABLE_NAME + " WHERE CFID IN (");
int start = i;
int end = i + batchSize;
for (int j = start; j < end && j < cfidsCount; j++) {
sb.append("'");
sb.append(cfids.get(j));
sb.append("',");
}
sb = sb.deleteCharAt(sb.length() - 1);
sb.append(")");
PreparedStatement delStmtApp = connection.prepareStatement(sb.toString());
delStmtApp.execute();
delStmtApp.close();
}
// -- DELETE BDGLOBAL entries
PreparedStatement delStmtApp = connection.prepareStatement(SQL_DELETE_GLOBAL);
delStmtApp.setDate(1, expiredDate);
delStmtApp.execute();
delStmtApp.close();
connection.commit();
}
}
}
} finally {
if (connection != null) {
try {
connection.setAutoCommit(true);
} catch (SQLException ignore) {
}
connection.close();
}
}
}
private List<String> getCFIDtoDelete(Connection _connection, Date _expiredDate) throws SQLException {
ResultSet result = null;
try {
List<String> cfids = new ArrayList<String>();
PreparedStatement stmt = _connection.prepareStatement(SQL_SELECT);
stmt.setDate(1, _expiredDate);
result = stmt.executeQuery();
while (result.next()) {
cfids.add(result.getString(1));
}
stmt.close();
return cfids;
} finally {
try {
if (result != null)
result.close();
} catch (SQLException ignore1) {
}
}
}
}