/* * 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(); } } }