Extracted ooo-user bits into separate library.

This commit is contained in:
Michael Bayne
2011-10-05 09:37:10 -07:00
commit 1d78bbe27b
84 changed files with 12903 additions and 0 deletions
@@ -0,0 +1,450 @@
//
// $Id$
package com.threerings.user;
import static com.threerings.user.Log.log;
import java.sql.Connection;
import java.sql.Date;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.List;
import com.samskivert.io.PersistenceException;
import com.samskivert.jdbc.ConnectionProvider;
import com.samskivert.jdbc.DatabaseLiaison;
import com.samskivert.jdbc.JDBCUtil;
import com.samskivert.jdbc.JORARepository;
import com.samskivert.jdbc.jora.Cursor;
import com.samskivert.jdbc.jora.FieldMask;
import com.samskivert.jdbc.jora.Table;
/**
* Maintains persistent reward information.
*/
public class RewardRepository extends JORARepository
{
/**
* Creates the repository with the specified connection provider.
*
* @exception PersistenceException thrown if an error occurs communicating with the underlying
* persistence facilities.
*/
public RewardRepository (ConnectionProvider provider)
throws PersistenceException
{
super(provider, OOOUserRepository.USER_REPOSITORY_IDENT);
}
/**
* Creates a new RewardInfo record in the database. <code>info</code> should have the
* <code>description</code>, <code>data</code> and <code>expiration</code> filled in.
*/
public void createReward (RewardInfo info)
throws PersistenceException
{
info.maxEligibleId = getMaxUserId();
store(_infoTable, info);
}
/**
* Immediately expires a reward by setting the expiration to the current date then making a
* call to <code>purgeExpiredRewards</code>.
*/
public void expireReward (int rewardId)
throws PersistenceException
{
RewardInfo info = new RewardInfo();
info.rewardId = rewardId;
info.expiration = new Date(System.currentTimeMillis());
updateField(_infoTable, info, "expiration");
purgeExpiredRewards();
}
/**
* Attempt to activate the reward for an account. If this account has not already activated
* the reward, a new RewardRecord will be created. Returns true if a new RewardRecord is
* created, false otherwise.
*/
public boolean activateReward (int rewardId, String account)
throws PersistenceException
{
return activateReward(rewardId, account, null);
}
public boolean activateReward (int rewardId, String account, String param)
throws PersistenceException
{
final RewardRecord rr = new RewardRecord();
rr.rewardId = rewardId;
rr.account = account;
rr.param = param == null ? "" : param;
// try to insert it; catching duplicate row exceptions and reporting that the reward is
// already activated
return executeUpdate(new Operation<Boolean>() {
public Boolean invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
try {
_rewardsTable.insert(conn, rr);
return true;
} catch (SQLException sqe) {
if (liaison.isDuplicateRowException(sqe)) {
return false;
} else {
throw sqe;
}
}
}
});
}
/**
* Activates monthly rewards of the specified ID for the account between start and end. If they
* already have monthly rewards for this ID, will add new ones only at appropriate monthly
* intervals from the existing ones.
*/
public boolean activateMonthlyRewards (final int rewardId, final String account,
final java.util.Date start, final java.util.Date end)
throws PersistenceException
{
// Setup an example to find relevant rewards.
final RewardRecord example = new RewardRecord();
example.rewardId = rewardId;
example.account = account;
final FieldMask mask = _rewardsTable.getFieldMask();
mask.setModified("rewardId");
mask.setModified("account");
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
return executeUpdate(new Operation<Boolean>() {
public Boolean invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
// Find the most recent reward this account already has of this type.
java.util.Date latest = null;
Cursor<RewardRecord> matches =
_rewardsTable.queryByExample(conn, example, mask);
RewardRecord rec;
while ((rec = matches.next()) != null) {
if (rec.param == null) {
log.warning("Missing monthly reward param: ", "rewardId", rewardId,
"account", account);
continue;
}
try {
java.util.Date thisDate = dateFormat.parse(rec.param);
if (latest == null || thisDate.after(latest)) {
latest = thisDate;
}
} catch (ParseException pe) {
log.warning("Bogus reward param: ", "rewardId", rewardId,
"account", account, "param", rec.param);
continue;
}
}
// We use the maximum of our provided start time and a month-after-latest
// as the first time we'll input a reward. Note that this can result
// in no rewards being given.
Calendar startCal = Calendar.getInstance();
if (latest != null) {
startCal.setTime(latest);
startCal.add(Calendar.MONTH, 1);
if (startCal.getTime().before(start)) {
startCal.setTime(start);
}
} else {
startCal.setTime(start);
}
// Add in any appropriate rewards between the modified start and end.
while (startCal.getTime().before(end)) {
final RewardRecord rr = new RewardRecord();
rr.rewardId = rewardId;
rr.account = account;
rr.param = dateFormat.format(startCal.getTime());
_rewardsTable.insert(conn, rr);
startCal.add(Calendar.MONTH, 1);
}
return true;
}
});
}
/**
* Deactivates all rewards of the specified ID and account during the time window.
* Returns the number of rewards deleted.
*/
public int deactivateMonthlyRewards (final int rewardId, final String account,
final java.util.Date start, final java.util.Date end)
throws PersistenceException
{
return executeUpdate(new Operation<Integer>() {
public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
String delQuery = "delete from REWARD_RECORDS where REWARD_ID = ? and ACCOUNT = ?" +
" and PARAM >= ? and PARAM <= ? and REDEEMER_IDENT is null";
final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
String startStr = dateFormat.format(start);
String endStr = dateFormat.format(end);
PreparedStatement stmt = null;
try {
stmt = conn.prepareStatement(delQuery);
stmt.setInt(1, rewardId);
stmt.setString(2, account);
stmt.setString(3, startStr);
stmt.setString(4, endStr);
return stmt.executeUpdate();
} finally {
JDBCUtil.close(stmt);
}
}
});
}
/**
* Returns all currently active rewards.
*/
public List<RewardInfo> loadActiveRewards ()
throws PersistenceException
{
return loadAll(_infoTable, "where EXPIRATION > NOW()");
}
/**
* Returns all rewards.
*/
public List<RewardInfo> loadRewards ()
throws PersistenceException
{
return loadAll(_infoTable, "order by REWARD_ID desc");
}
/**
* Returns all <code>RewardRecord</code>s that match the given account name and whose eligible
* date is in the past. The returned list will be sorted by <code>rewardId</code>.
*/
public List<RewardRecord> loadActivatedRewards (String account)
throws PersistenceException
{
String condition = "where ACCOUNT = " + JDBCUtil.escape(account) +
" order by REWARD_ID";
return loadAll(_rewardsTable, condition);
}
/**
* Returns all <code>RewardRecord</code>s that match either the account or redeemer identifier.
* The returned list will be sorted by <code>rewardId</code>.
*/
public List<RewardRecord> loadActivatedRewards (String account, String redeemerIdent)
throws PersistenceException
{
String condition = "where ACCOUNT = " + JDBCUtil.escape(account) +
" or REDEEMER_IDENT = " + JDBCUtil.escape(redeemerIdent) +
" order by REWARD_ID, PARAM";
return loadAll(_rewardsTable, condition);
}
/**
* Returns all <code>RewardRecord</code>s for a specified <code>rewardId</code> that match
* either the account or redeemer identifier.
*/
public List<RewardRecord> loadActivatedReward (
String account, String redeemerIdent, int rewardId)
throws PersistenceException
{
String condition = "where REWARD_ID = " + rewardId +
" and (ACCOUNT = " + JDBCUtil.escape(account) +
" or REDEEMER_IDENT = " + JDBCUtil.escape(redeemerIdent) + ")";
return loadAll(_rewardsTable, condition);
}
/**
* Returns all <code>RewardRecord</code>s for a specified <code>rewardId</code> and
* <code>param</code> that match either the account or redeemer identifier.
*/
public List<RewardRecord> loadActivatedReward (
String account, String redeemerIdent, int rewardId, String param)
throws PersistenceException
{
String paramStr = (param == null) ? "PARAM is NULL" : "PARAM = " + JDBCUtil.escape(param);
String condition = "where REWARD_ID = " + rewardId +
" and " + paramStr +
" and (ACCOUNT = " + JDBCUtil.escape(account) +
" or REDEEMER_IDENT = " + JDBCUtil.escape(redeemerIdent) + ")";
return loadAll(_rewardsTable, condition);
}
/**
* Updates the supplied RewardRecord with the indicated <code>redeemerIdent</code> and store it
* to the database.
*/
public boolean redeemReward (RewardRecord record, String redeemerIdent)
throws PersistenceException
{
String update = "update " + _rewardsTable.getName() + " set REDEEMER_IDENT = " +
JDBCUtil.escape(redeemerIdent) + " where REDEEMER_IDENT is NULL and REWARD_ID = " +
record.rewardId + " and ACCOUNT = " + JDBCUtil.escape(record.account) +
" and PARAM = " + JDBCUtil.escape(record.param);
return update(update) > 0;
}
/**
* Summarizes the reward data and purges expired rewards.
*/
public void purgeExpiredRewards ()
throws PersistenceException
{
executeUpdate(new Operation<Object> () {
public Object invoke (Connection conn, DatabaseLiaison liaison)
throws PersistenceException, SQLException
{
summarizeAndUpdate(conn, "", "ACTIVATIONS");
summarizeAndUpdate(conn, "and REDEEMER_IDENT is NOT NULL", "REDEMPTIONS");
return null;
}
});
update("delete from REWARD_RECORDS using REWARD_RECORDS, REWARD_INFO " +
"where REWARD_INFO.REWARD_ID = REWARD_RECORDS.REWARD_ID " +
"and REWARD_INFO.EXPIRATION <= NOW()");
}
/**
* Summarizes the reward data and stores it in the reward info records.
*/
protected void summarizeAndUpdate (Connection conn, String condition, String column)
throws PersistenceException, SQLException
{
String activatedQuery = "select count(*), REWARD_INFO.REWARD_ID " +
"from REWARD_RECORDS, REWARD_INFO " +
"where REWARD_INFO.REWARD_ID = REWARD_RECORDS.REWARD_ID " + condition +
" group by REWARD_ID";
String activatedUpdate = "update REWARD_INFO set " + column + " = ? where REWARD_ID = ?";
PreparedStatement ustmt = null;
Statement stmt = null;
try {
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(activatedQuery);
ustmt = conn.prepareStatement(activatedUpdate);
while (rs.next()) {
ustmt.setInt(1, rs.getInt(1));
ustmt.setInt(2, rs.getInt(2));
ustmt.executeUpdate();
}
} finally {
JDBCUtil.close(ustmt);
JDBCUtil.close(stmt);
}
}
/**
* Returns the max userid currently in use.
*/
protected int getMaxUserId ()
throws PersistenceException
{
return execute(new Operation<Integer> () {
public Integer invoke (Connection conn, DatabaseLiaison liaison)
throws PersistenceException, SQLException
{
String query = "select MAX(userId) from users";
Statement stmt = null;
int maxUserId = 0;
try {
stmt = conn.createStatement();
ResultSet rs = stmt.executeQuery(query);
if (rs.next()) {
maxUserId = rs.getInt(1);
} else {
log.warning("No users found!?");
}
} finally {
JDBCUtil.close(stmt);
}
return maxUserId;
}
});
}
@Override
protected void migrateSchema (Connection conn, DatabaseLiaison liaison)
throws SQLException, PersistenceException
{
String[] rewardInfoTable = {
"REWARD_ID INTEGER NOT NULL AUTO_INCREMENT PRIMARY KEY",
"DESCRIPTION VARCHAR(255) NOT NULL",
"COUPON_CODE VARCHAR(255)",
"DATA VARCHAR(255) NOT NULL",
"EXPIRATION DATE NOT NULL",
"MAX_ELIGIBLE_ID INTEGER NOT NULL",
"ACTIVATIONS INTEGER NOT NULL",
"REDEMPTIONS INTEGER NOT NULL"
};
JDBCUtil.createTableIfMissing(conn, "REWARD_INFO", rewardInfoTable, "");
// TEMP: (2006-12-22) add COUPON_CODE column
JDBCUtil.addColumn(conn, "REWARD_INFO", "COUPON_CODE", "VARCHAR(255)", "DESCRIPTION");
// END TEMP
String[] rewardsTable = {
"REWARD_ID INTEGER NOT NULL",
"ACCOUNT VARCHAR(255)",
"REDEEMER_IDENT VARCHAR(64)",
"PARAM VARCHAR(255)",
"PRIMARY KEY (REWARD_ID, ACCOUNT, PARAM)",
"INDEX (ACCOUNT)",
"INDEX (REDEEMER_IDENT)"
};
JDBCUtil.createTableIfMissing(conn, "REWARD_RECORDS", rewardsTable, "");
// Add a parameter column. This allows a particular reward to be granted more than once, and
// pass specific details to the reward handler.
if (!JDBCUtil.tableContainsColumn(conn, "REWARD_RECORDS", "PARAM")) {
// unfortunately, we need to recreate the primary key
JDBCUtil.dropPrimaryKey(conn, "REWARD_RECORDS");
// add generic parameter column, and include
JDBCUtil.addColumn(conn, "REWARD_RECORDS", "PARAM", "VARCHAR(255), " +
"ADD PRIMARY KEY (REWARD_ID, ACCOUNT, PARAM)", null);
}
}
@Override
protected void createTables ()
{
_infoTable = new Table<RewardInfo>(
RewardInfo.class, "REWARD_INFO", REWARD_INFO_PRIMARY_KEY, true);
_rewardsTable = new Table<RewardRecord>(
RewardRecord.class, "REWARD_RECORDS", REWARD_RECORDS_PRIMARY_KEYS, true);
}
/** The primary key for the reward info table. */
protected static final String REWARD_INFO_PRIMARY_KEY = "REWARD_ID";
/** The primary keys for the rewards table. */
protected static final String[] REWARD_RECORDS_PRIMARY_KEYS = {
"REWARD_ID", "ACCOUNT", "PARAM"
};
/** The table used to store reward information. */
protected Table<RewardInfo> _infoTable;
/** The table used to store activated rewards. */
protected Table<RewardRecord> _rewardsTable;
}