/*
* Copyright 2006-2010 Daniel Henninger. All rights reserved.
*
* This software is published under the terms of the GNU Public License (GPL),
* a copy of which is included in this distribution.
*/
package net.sf.kraken.registration;
import net.sf.kraken.type.TransportType;
import org.apache.log4j.Logger;
import org.jivesoftware.database.DbConnectionManager;
import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.util.NotFoundException;
import org.jivesoftware.util.cache.Cache;
import org.jivesoftware.util.cache.CacheFactory;
import org.xmpp.packet.JID;
import java.sql.*;
import java.util.*;
/**
* Manages registration data for transports. Individual transports use the registration data
* and then create sessions used to exchange messages and presence data.
*
* @author Matt Tucker
*/
public class RegistrationManager {
private static RegistrationManager instance = null;
static Logger Log = Logger.getLogger(RegistrationManager.class);
private static final String DELETE_REGISTRATION =
"DELETE FROM ofGatewayRegistration WHERE registrationID=?";
private static final String ALL_REGISTRATION_COUNT =
"SELECT count(*) FROM ofGatewayRegistration";
private static final String ALL_REGISTRATIONS =
"SELECT registrationID FROM ofGatewayRegistration ORDER BY jid,transportType";
private static final String LOAD_REGISTRATION =
"SELECT registrationID FROM ofGatewayRegistration WHERE jid=? AND transportType=? AND username=?";
private static final String ALL_USER_REGISTRATIONS =
"SELECT registrationID FROM ofGatewayRegistration WHERE jid=?";
private static final String ALL_GATEWAY_REGISTRATIONS =
"SELECT registrationID FROM ofGatewayRegistration WHERE transportType=?";
private static final String USER_GATEWAY_REGISTRATIONS =
"SELECT registrationID FROM ofGatewayRegistration WHERE jid=? AND transportType=?";
private static final String DELETE_PSEUDO_ROSTER =
"DELETE FROM ofGatewayPseudoRoster WHERE registrationID=?";
private static final String ALL_JIDS_REGISTERED =
"SELECT jid FROM ofGatewayRegistration WHERE transportType=?";
private static final String UPDATE_REGISTRATION =
"UPDATE ofGatewayRegistration SET jid=?,transportType=?,username=?,password=?,nickname=?,registrationDate=?,lastLogin=? WHERE registrationID=?";
public static final String GATEWAYREGISTRATIONS_CACHE_NAME = "Kraken Registration Cache";
/* Cached known registrations. */
/**
* Cache (unlimited, never expire) that holds the locations of a transport session.
* Key: transport type (aim, icq, etc) + bare JID, Value: nodeID
* We store the key like BareJID@transportType so... user@example.org@msn
*/
private Cache<String,ArrayList<String>> registeredCache = CacheFactory.createCache(GATEWAYREGISTRATIONS_CACHE_NAME);
private RegistrationManager() {
}
/**
* Retrieve the instance of the registration manager.
*
* @return Current instance of RegistrationManager.
*/
public static RegistrationManager getInstance() {
if (instance == null) {
instance = new RegistrationManager();
}
return instance;
}
/**
* Shuts down the registration manager.
*/
public void shutdown() {
if (instance != null) {
instance = null;
}
}
/**
* Creates a new registration.
*
* @param jid the JID of the user making the registration.
* @param transportType the type of the transport.
* @param username the username on the transport service.
* @param password the password on the transport service.
* @param nickname the nickname on the transport service.
* @return a new registration.
*/
public Registration createRegistration(JID jid, TransportType transportType, String username,
String password, String nickname)
{
ArrayList<String> regList;
if (!registeredCache.containsKey(transportType.toString())) {
regList = new ArrayList<String>();
}
else {
regList = registeredCache.get(transportType.toString());
}
regList.add(jid.toBareJID());
registeredCache.put(transportType.toString(), regList);
return new Registration(jid, transportType, username, password, nickname);
}
/**
* Deletes a registration.
*
* @param registration the registration to delete.
*/
public void deleteRegistration(Registration registration) {
Connection con = null;
PreparedStatement pstmt = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(DELETE_REGISTRATION);
pstmt.setLong(1, registration.getRegistrationID());
pstmt.executeUpdate();
pstmt = con.prepareStatement(DELETE_PSEUDO_ROSTER);
pstmt.setLong(1, registration.getRegistrationID());
pstmt.executeUpdate();
ArrayList<String> regList = registeredCache.get(registration.getTransportType().toString());
regList.remove(registration.getJID().toBareJID());
registeredCache.put(registration.getTransportType().toString(), regList);
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(pstmt, con);
}
}
/**
* Returns all registrations for a particular type of transport.
*
* @param transportType the transport type.
* @return all registrations for the transport type.
*/
public Collection<Registration> getRegistrations(TransportType transportType) {
List<Long> registrationIDs = new ArrayList<Long>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ALL_GATEWAY_REGISTRATIONS);
pstmt.setString(1, transportType.name());
rs = pstmt.executeQuery();
while (rs.next()) {
registrationIDs.add(rs.getLong(1));
}
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
if (registrationIDs.isEmpty()) {
return Collections.emptyList();
}
else {
return new RegistrationCollection(registrationIDs);
}
}
/**
* Returns all registrations for a particular JID.
*
* @param jid the JID of the user.
* @return all registrations for the JID.
*/
public Collection<Registration> getRegistrations(JID jid) {
List<Long> registrationIDs = new ArrayList<Long>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ALL_USER_REGISTRATIONS);
// Use the bare JID of the user.
pstmt.setString(1, jid.toBareJID());
rs = pstmt.executeQuery();
while (rs.next()) {
registrationIDs.add(rs.getLong(1));
}
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
if (registrationIDs.isEmpty()) {
return Collections.emptyList();
}
else {
return new RegistrationCollection(registrationIDs);
}
}
/**
* Returns all registrations that a JID has on a particular transport type.
* In the typical case, a JID has a single registration with a particular transport
* type. However, it's also possible to maintain multiple registrations. For example,
* the user "joe_smith@example.com" might have have two user accounts on the AIM
* transport service: "jsmith" and "joesmith".
*
* @param jid the JID of the user.
* @param transportType the type of the transport.
* @return all registrations for the JID of a particular transport type.
*/
public Collection<Registration> getRegistrations(JID jid, TransportType transportType) {
List<Long> registrationIDs = new ArrayList<Long>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(USER_GATEWAY_REGISTRATIONS);
// Use the bare JID of the user.
pstmt.setString(1, jid.toBareJID());
pstmt.setString(2, transportType.name());
rs = pstmt.executeQuery();
while (rs.next()) {
registrationIDs.add(rs.getLong(1));
}
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
if (registrationIDs.isEmpty()) {
return Collections.emptyList();
}
else {
return new RegistrationCollection(registrationIDs);
}
}
/**
* Returns a registration given a JID, transport type, and username.
*
* @param jid the JID of the user.
* @param transportType the transport type.
* @param username the username on the transport service.
* @return the registration.
* @throws NotFoundException if the registration could not be found.
*/
public Registration getRegistration(JID jid, TransportType transportType, String username)
throws NotFoundException
{
long registrationID = -1;
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(LOAD_REGISTRATION);
pstmt.setString(1, jid.toBareJID());
pstmt.setString(2, transportType.name());
pstmt.setString(3, username);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new NotFoundException("Could not load registration with ID " + registrationID);
}
registrationID = rs.getLong(1);
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return new Registration(registrationID);
}
/**
* Returns the count of all registrations.
*
* @return the total count of registrations.
*/
public int getRegistrationCount() {
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ALL_REGISTRATION_COUNT);
rs = pstmt.executeQuery();
rs.next();
return rs.getInt(1);
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
return 0;
}
/**
* Returns true or false if the user has a current registration.
*
* This needs to be -very- fast, so it operates strictly on a cached list.
*
* @param jid JID to check if they are registered.
* @param transportType Transport type to check for registrations.
* @return True or false if the user is registered.
*/
public boolean isRegistered(JID jid, TransportType transportType) {
cacheIfNotCached(transportType);
try {
return registeredCache.get(transportType.toString()).contains(jid.toBareJID());
}
catch (Exception e) {
return false;
}
}
/**
* Caches the list of users registered with this transport if not already cached.
*
* @param transportType Transport type to be cached.
*/
public void cacheIfNotCached(TransportType transportType) {
if (registeredCache.containsKey(transportType.toString())) {
// Already cached, no problem.
return;
}
ArrayList<String> registrations = new ArrayList<String>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ALL_JIDS_REGISTERED);
pstmt.setString(1, transportType.name());
rs = pstmt.executeQuery();
while (rs.next()) {
registrations.add(rs.getString(1));
}
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
registeredCache.put(transportType.toString(), registrations);
}
}
/**
* Returns all registrations.
*
* @return all registrations.
*/
public Collection<Registration> getRegistrations() {
List<Long> registrationIDs = new ArrayList<Long>();
Connection con = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
try {
con = DbConnectionManager.getConnection();
pstmt = con.prepareStatement(ALL_REGISTRATIONS);
rs = pstmt.executeQuery();
while (rs.next()) {
registrationIDs.add(rs.getLong(1));
}
}
catch (SQLException sqle) {
Log.error(sqle);
}
finally {
DbConnectionManager.closeConnection(rs, pstmt, con);
}
if (registrationIDs.isEmpty()) {
return Collections.emptyList();
}
else {
return new RegistrationCollection(registrationIDs);
}
}
/**
* Overwrites an existing registration with updated information
*
* Note: Registration must be based on an existing registration for this to work.
*
* @param curReg Registration we are overwriting.
* @param newReg New registration info that will overwrite old.
* @throws SQLException is there was an error interacting with the database.
*/
public void overwriteExistingRegistration(Registration curReg, Registration newReg) throws SQLException {
Connection con = null;
PreparedStatement pstmt = null;
boolean abortTransaction = false;
try {
con = DbConnectionManager.getTransactionConnection();
pstmt = con.prepareStatement(UPDATE_REGISTRATION);
pstmt.setString(1, newReg.getJID().toString());
pstmt.setString(2, newReg.getTransportType().name());
pstmt.setString(3, newReg.getUsername());
if (newReg.getPassword() != null) {
// The password is stored in encrypted form for improved security.
String encryptedPassword = AuthFactory.encryptPassword(newReg.getPassword());
pstmt.setString(4, encryptedPassword);
}
else {
pstmt.setNull(4, Types.VARCHAR);
}
if (newReg.getNickname() != null) {
pstmt.setString(5, newReg.getNickname());
}
else {
pstmt.setNull(5, Types.VARCHAR);
}
pstmt.setLong(6, newReg.getRegistrationDate().getTime());
if (newReg.getLastLogin() != null) {
pstmt.setLong(7, newReg.getLastLogin().getTime());
}
else {
pstmt.setNull(7, Types.INTEGER);
}
pstmt.setLong(8, curReg.getRegistrationID());
pstmt.executeUpdate();
}
catch (SQLException sqle) {
abortTransaction = true;
throw sqle;
}
finally {
DbConnectionManager.closeTransactionConnection(pstmt, con, abortTransaction);
}
}
/**
* Converts a list of registration IDs into a Collection of Registrations.
*/
@SuppressWarnings("unchecked")
private static class RegistrationCollection extends AbstractCollection<Registration> {
private final List<Long> registrationIDs;
/**
* Constructs a new query results object.
*
* @param registrationIDs the list of registration IDs.
*/
public RegistrationCollection(List<Long> registrationIDs) {
this.registrationIDs = registrationIDs;
}
@Override
public Iterator<Registration> iterator() {
final Iterator<Long> regIterator = registrationIDs.iterator();
return new Iterator() {
private Registration nextElement = null;
public boolean hasNext() {
if (nextElement == null) {
nextElement = getNextElement();
if (nextElement == null) {
return false;
}
}
return true;
}
public Registration next() {
Registration element;
if (nextElement != null) {
element = nextElement;
nextElement = null;
}
else {
element = getNextElement();
if (element == null) {
throw new NoSuchElementException();
}
}
return element;
}
public void remove() {
throw new UnsupportedOperationException();
}
private Registration getNextElement() {
if (!regIterator.hasNext()) {
return null;
}
while (regIterator.hasNext()) {
try {
long registrationID = regIterator.next();
return new Registration(registrationID);
}
catch (Exception e) {
Log.error(e);
}
}
return null;
}
};
}
@Override
public int size() {
return registrationIDs.size();
}
}
}