/* * Tigase Jabber/XMPP Server * Copyright (C) 2004-2012 "Artur Hefczyc" <artur.hefczyc@tigase.org> * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, either version 3 of the License. * * This program 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 Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. Look for COPYING file in the top folder. * If not, see http://www.gnu.org/licenses/. * * $Rev$ * Last modified by $Author$ * $Date$ */ package tigase.db.jdbc; //~--- non-JDK imports -------------------------------------------------------- import tigase.db.AuthRepository; import tigase.db.AuthRepositoryImpl; import tigase.db.AuthorizationException; import tigase.db.DBInitException; import tigase.db.DataRepository; import tigase.db.RepositoryFactory; import tigase.db.TigaseDBException; import tigase.db.UserExistsException; import tigase.db.UserNotFoundException; import tigase.db.UserRepository; import tigase.util.SimpleCache; import tigase.xmpp.BareJID; //~--- JDK imports ------------------------------------------------------------ import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Types; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.StringTokenizer; import java.util.logging.Level; import java.util.logging.Logger; //~--- classes ---------------------------------------------------------------- /** * Not synchronized implementation! Musn't be used by more than one thread at * the same time. * <p> * Thanks to Daniele for better unique IDs handling. Created: Thu Oct 26 * 11:48:53 2006 * </p> * * @author <a href="mailto:artur.hefczyc@tigase.org">Artur Hefczyc</a> * @author <a href="mailto:piras@tiscali.com">Daniele</a> * @version $Rev$ */ public class JDBCRepository implements AuthRepository, UserRepository { private static final Logger log = Logger.getLogger(JDBCRepository.class.getName()); /** Field description */ public static final String DEF_USERS_TBL = "tig_users"; /** Field description */ public static final String DEF_NODES_TBL = "tig_nodes"; /** Field description */ public static final String DEF_PAIRS_TBL = "tig_pairs"; /** Field description */ public static final String DEF_MAXIDS_TBL = "tig_max_ids"; /** Field description */ public static final String DEF_ROOT_NODE = "root"; private static final String USER_STR = "User: "; private static final String GET_USER_DB_UID_QUERY = "{ call TigGetUserDBUid(?) }"; private static final String GET_USERS_COUNT_QUERY = "{ call TigAllUsersCount() }"; private static final String DEF_GET_USERS_QUERY = "{ call TigAllUsers() }"; private static final String PGSQL_GET_USERS_QUERY = "select TigAllUsers()"; private static final String ADD_USER_PLAIN_PW_QUERY = "{ call TigAddUserPlainPw(?, ?) }"; private static final String REMOVE_USER_QUERY = "{ call TigRemoveUser(?) }"; private static final String ADD_NODE_QUERY = "{ call TigAddNode(?, ?, ?) }"; private static final String COUNT_USERS_FOR_DOMAIN_QUERY = "select count(*) from tig_users where user_id like ?"; private static final String DATA_FOR_NODE_QUERY = "select pval from " + DEF_PAIRS_TBL + " where (nid = ?) AND (pkey = ?)"; private static final String KEYS_FOR_NODE_QUERY = "select pkey from " + DEF_PAIRS_TBL + " where (nid = ?)"; private static final String NODES_FOR_NODE_QUERY = "select nid, node from " + DEF_NODES_TBL + " where parent_nid = ?"; private static final String INSERT_KEY_VAL_QUERY = "insert into " + DEF_PAIRS_TBL + " (nid, uid, pkey, pval) " + " values (?, ?, ?, ?)"; private static final String REMOVE_KEY_DATA_QUERY = "delete from " + DEF_PAIRS_TBL + " where (nid = ?) AND (pkey = ?)"; private static final String UPDATE_PAIRS_QUERY = "{ call TigUpdatePairs(?, ?, ?, ?) }"; public static final String CURRENT_DB_SCHEMA_VER = "5.1"; public static final String SCHEMA_UPGRADE_LINK = "http://www.tigase.org/content/tigase-51-database-schema-upgrade"; /** Field description */ public static final String DERBY_GETSCHEMAVER_QUERY = "values TigGetDBProperty('schema-version')"; /** Field description */ public static final String JDBC_GETSCHEMAVER_QUERY = "select TigGetDBProperty('schema-version')"; // ~--- fields --------------------------------------------------------------- private AuthRepository auth = null; // Cache moved to connection pool private Map<String, Object> cache = null; private DataRepository data_repo = null; private String get_users_query = null; private boolean derby_mode = false; private boolean autoCreateUser = false; // ~--- methods -------------------------------------------------------------- private void addDataList(DataRepository repo, BareJID user_id, final String subnode, final String key, final String[] list) throws UserNotFoundException, SQLException, UserNotFoundException { long uid = -2; long nid = -2; try { // OK uid = getUserUID(repo, user_id, autoCreateUser); // OK nid = getNodeNID(repo, uid, subnode); if ( log.isLoggable( Level.FINEST ) ){ log.log( Level.FINEST, "Saving data adding data list, user_id: {0}, subnode: {1}, key: {2}, uid: {3}, nid: {4}, list: {5}", new Object[] { user_id, subnode, key, uid, nid, Arrays.toString( list ) } ); } if (nid < 0) { try { // OK nid = createNodePath(repo, user_id, subnode); } catch (SQLException e) { // This may happen in cluster node, when 2 nodes at the same // time write data to the same location, like offline messages.... // Let's try to get the nid again. // OK nid = getNodeNID(repo, uid, subnode); } } PreparedStatement insert_key_val_st = null; if (repo == null) { insert_key_val_st = data_repo.getPreparedStatement(user_id, INSERT_KEY_VAL_QUERY); } else { insert_key_val_st = repo.getPreparedStatement(user_id, INSERT_KEY_VAL_QUERY); } synchronized (insert_key_val_st) { insert_key_val_st.setLong(1, nid); insert_key_val_st.setLong(2, uid); insert_key_val_st.setString(3, key); for (String val : list) { insert_key_val_st.setString(4, val); insert_key_val_st.executeUpdate(); } // end of for (String val: list) } } catch (SQLException e) { log.log(Level.WARNING, "Error adding data list, user_id: " + user_id + ", subnode: " + subnode + ", key: " + key + ", uid: " + uid + ", nid: " + nid + ", list: " + Arrays.toString(list), e); throw e; } // cache.put(user_id+"/"+subnode+"/"+key, list); } /** * Describe <code>addDataList</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @param key * a <code>String</code> value * @param list * a <code>String[]</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public void addDataList(BareJID user_id, final String subnode, final String key, final String[] list) throws UserNotFoundException, TigaseDBException { try { addDataList(null, user_id, subnode, key, list); } catch (SQLException ex) { throw new TigaseDBException("Problem adding data list to repository", ex); } } /** * Describe <code>addUser</code> method here. * * @param user_id * a <code>String</code> value * @exception UserExistsException * if an error occurs * @throws TigaseDBException */ @Override public void addUser(BareJID user_id) throws UserExistsException, TigaseDBException { try { addUserRepo(null, user_id); } catch (SQLException e) { throw new UserExistsException("Error adding user to repository: ", e); } } /** * Describe <code>addUser</code> method here. * * @param user * a <code>String</code> value * @param password * a <code>String</code> value * @exception UserExistsException * if an error occurs * @exception TigaseDBException * if an error occurs */ @Override public void addUser(BareJID user, final String password) throws UserExistsException, TigaseDBException { auth.addUser(user, password); } /** * Describe <code>digestAuth</code> method here. * * @param user * a <code>String</code> value * @param digest * a <code>String</code> value * @param id * a <code>String</code> value * @param alg * a <code>String</code> value * @return a <code>boolean</code> value * * @throws AuthorizationException * @exception UserNotFoundException * if an error occurs * @exception TigaseDBException * if an error occurs */ @Override @Deprecated public boolean digestAuth(BareJID user, final String digest, final String id, final String alg) throws UserNotFoundException, TigaseDBException, AuthorizationException { return auth.digestAuth(user, digest, id, alg); } // ~--- get methods ---------------------------------------------------------- /** * Describe <code>getData</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @param key * a <code>String</code> value * @param def * a <code>String</code> value * @return a <code>String</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public String getData(BareJID user_id, final String subnode, final String key, final String def) throws UserNotFoundException, TigaseDBException { // String[] cache_res = (String[])cache.get(user_id+"/"+subnode+"/"+key); // if (cache_res != null) { // return cache_res[0]; // } // end of if (result != null) ResultSet rs = null; try { long nid = getNodeNID(null, user_id, subnode); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Loading data for key: {0}, user: {1}, node: {2}, def: {3}, found nid: {4}", new Object[] { key, user_id, subnode, def, nid }); } PreparedStatement data_for_node_st = data_repo.getPreparedStatement(user_id, DATA_FOR_NODE_QUERY); synchronized (data_for_node_st) { if (nid > 0) { String result = def; data_for_node_st.setLong(1, nid); data_for_node_st.setString(2, key); rs = data_for_node_st.executeQuery(); if (rs.next()) { result = rs.getString(1); if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Found data: {0}", result); } } // cache.put(user_id+"/"+subnode+"/"+key, new String[] {result}); return result; } else { return def; } // end of if (nid > 0) else } } catch (SQLException e) { throw new TigaseDBException("Error getting user data for: " + user_id + "/" + subnode + "/" + key, e); } finally { data_repo.release(null, rs); } } /** * Describe <code>getData</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @param key * a <code>String</code> value * @return a <code>String</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public String getData(BareJID user_id, final String subnode, final String key) throws UserNotFoundException, TigaseDBException { return getData(user_id, subnode, key, null); } /** * Describe <code>getData</code> method here. * * @param user_id * a <code>String</code> value * @param key * a <code>String</code> value * @return a <code>String</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public String getData(BareJID user_id, final String key) throws UserNotFoundException, TigaseDBException { return getData(user_id, null, key, null); } /** * Describe <code>getDataList</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @param key * a <code>String</code> value * @return a <code>String[]</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public String[] getDataList(BareJID user_id, final String subnode, final String key) throws UserNotFoundException, TigaseDBException { // String[] cache_res = (String[])cache.get(user_id+"/"+subnode+"/"+key); // if (cache_res != null) { // return cache_res; // } // end of if (result != null) ResultSet rs = null; try { long nid = getNodeNID(null, user_id, subnode); if ( log.isLoggable( Level.FINEST ) ){ log.log( Level.FINEST, "Loading data for key: {0}, user: {1}, node: {2}, found nid: {3}", new Object[] { key, user_id, subnode, nid } ); } PreparedStatement data_for_node_st = data_repo.getPreparedStatement(user_id, DATA_FOR_NODE_QUERY); synchronized (data_for_node_st) { if (nid > 0) { List<String> results = new ArrayList<String>(); data_for_node_st.setLong(1, nid); data_for_node_st.setString(2, key); rs = data_for_node_st.executeQuery(); while (rs.next()) { results.add(rs.getString(1)); if ( log.isLoggable( Level.FINEST ) ){ log.log( Level.FINEST, "Found data: {0}", rs.getString(1) ); } } String[] result = (results.size() == 0) ? null : results.toArray(new String[results.size()]); // cache.put(user_id+"/"+subnode+"/"+key, result); return result; } else { return null; } // end of if (nid > 0) else } } catch (SQLException e) { throw new TigaseDBException("Error getting data list for: " + user_id + "/" + subnode + "/" + key, e); } finally { data_repo.release(null, rs); } } /** * Describe <code>getKeys</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @return a <code>String[]</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public String[] getKeys(BareJID user_id, final String subnode) throws UserNotFoundException, TigaseDBException { ResultSet rs = null; try { long nid = getNodeNID(null, user_id, subnode); if (nid > 0) { List<String> results = new ArrayList<String>(); PreparedStatement keys_for_node_st = data_repo.getPreparedStatement(user_id, KEYS_FOR_NODE_QUERY); synchronized (keys_for_node_st) { keys_for_node_st.setLong(1, nid); rs = keys_for_node_st.executeQuery(); while (rs.next()) { results.add(rs.getString(1)); } return (results.size() == 0) ? null : results .toArray(new String[results.size()]); } } else { return null; } // end of if (nid > 0) else } catch (SQLException e) { throw new TigaseDBException("Error getting subnodes list.", e); } finally { data_repo.release(null, rs); } } /** * Describe <code>getKeys</code> method here. * * @param user_id * a <code>String</code> value * @return a <code>String[]</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public String[] getKeys(BareJID user_id) throws UserNotFoundException, TigaseDBException { return getKeys(user_id, null); } /** * Method description * * * @return */ @Override public String getResourceUri() { return data_repo.getResourceUri(); } /** * Describe <code>getSubnodes</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @return a <code>String[]</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public String[] getSubnodes(BareJID user_id, final String subnode) throws UserNotFoundException, TigaseDBException { ResultSet rs = null; try { long nid = getNodeNID(null, user_id, subnode); PreparedStatement nodes_for_node_st = data_repo.getPreparedStatement(user_id, NODES_FOR_NODE_QUERY); synchronized (nodes_for_node_st) { if (nid > 0) { List<String> results = new ArrayList<String>(); nodes_for_node_st.setLong(1, nid); rs = nodes_for_node_st.executeQuery(); while (rs.next()) { results.add(rs.getString(2)); } return (results.size() == 0) ? null : results .toArray(new String[results.size()]); } else { return null; } // end of if (nid > 0) else } } catch (SQLException e) { throw new TigaseDBException("Error getting subnodes list.", e); } finally { data_repo.release(null, rs); } } /** * Describe <code>getSubnodes</code> method here. * * @param user_id * a <code>String</code> value * @return a <code>String[]</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public String[] getSubnodes(BareJID user_id) throws UserNotFoundException, TigaseDBException { return getSubnodes(user_id, null); } /** * Method description * * * @param user_id * * @return * * @throws TigaseDBException */ @Override public long getUserUID(BareJID user_id) throws TigaseDBException { Long cache_res = (Long) cache.get(user_id.toString()); if (cache_res != null) { return cache_res.longValue(); } // end of if (result != null) long result = -1; try { result = getUserUID(null, user_id); } catch (SQLException e) { throw new TigaseDBException("Error retrieving user UID from repository: ", e); } cache.put(user_id.toString(), Long.valueOf(result)); return result; } public long getUserUID(DataRepository repo, BareJID user_id) throws SQLException { ResultSet rs = null; long result = -1; try { PreparedStatement uid_sp = null; if (repo == null) { uid_sp = data_repo.getPreparedStatement(user_id, GET_USER_DB_UID_QUERY); } else { uid_sp = repo.getPreparedStatement(user_id, GET_USER_DB_UID_QUERY); } synchronized (uid_sp) { uid_sp.setString(1, user_id.toString()); rs = uid_sp.executeQuery(); if (rs.next()) { result = rs.getLong(1); } else { result = -1; } } } finally { data_repo.release(null, rs); } return result; } /** * <code>getUsers</code> method is thread safe. * * @return a <code>List</code> of user IDs from database. * * @throws TigaseDBException */ @Override public List<BareJID> getUsers() throws TigaseDBException { ResultSet rs = null; List<BareJID> users = null; try { PreparedStatement all_users_sp = data_repo.getPreparedStatement(null, get_users_query); synchronized (all_users_sp) { // Load all user ids from database rs = all_users_sp.executeQuery(); users = new ArrayList<BareJID>(1000); while (rs.next()) { users.add(BareJID.bareJIDInstanceNS(rs.getString(1))); } // end of while (rs.next()) } } catch (SQLException e) { throw new TigaseDBException("Problem loading user list from repository", e); } finally { data_repo.release(null, rs); rs = null; } return users; } /** * <code>getUsersCount</code> method is thread safe. It uses local variable * for storing <code>Statement</code>. * * @return a <code>long</code> number of user accounts in database. */ @Override public long getUsersCount() { ResultSet rs = null; try { long users = -1; PreparedStatement users_count_sp = data_repo.getPreparedStatement(null, GET_USERS_COUNT_QUERY); synchronized (users_count_sp) { // Load all user count from database rs = users_count_sp.executeQuery(); if (rs.next()) { users = rs.getLong(1); } // end of while (rs.next()) } return users; } catch (SQLException e) { return -1; // throw new // TigaseDBException("Problem loading user list from repository", e); } finally { data_repo.release(null, rs); rs = null; } } /** * Method description * * * @param domain * * @return */ @Override public long getUsersCount(String domain) { ResultSet rs = null; try { long users = -1; PreparedStatement users_domain_count_st = data_repo.getPreparedStatement(null, COUNT_USERS_FOR_DOMAIN_QUERY); synchronized (users_domain_count_st) { // Load all user count from database users_domain_count_st.setString(1, "%@" + domain); rs = users_domain_count_st.executeQuery(); if (rs.next()) { users = rs.getLong(1); } // end of while (rs.next()) } return users; } catch (SQLException e) { return -1; // throw new // TigaseDBException("Problem loading user list from repository", e); } finally { data_repo.release(null, rs); rs = null; } } // ~--- methods -------------------------------------------------------------- /** * Describe <code>initRepository</code> method here. * * @param connection_str * a <code>String</code> value * @param params * * @throws DBInitException */ @Override public void initRepository(final String connection_str, Map<String, String> params) throws DBInitException { try { derby_mode = connection_str.startsWith("jdbc:derby"); data_repo = RepositoryFactory.getDataRepository(null, connection_str, params); checkDBSchema(); if (connection_str.contains("autoCreateUser=true")) { autoCreateUser = true; } // end of if (db_conn.contains()) if (connection_str.contains("cacheRepo=off")) { log.fine("Disabling cache."); cache = Collections.synchronizedMap(new RepoCache(0, -1000)); } else { cache = Collections.synchronizedMap(new RepoCache(10000, 60 * 1000)); } data_repo.initPreparedStatement(GET_USER_DB_UID_QUERY, GET_USER_DB_UID_QUERY); data_repo.initPreparedStatement(GET_USERS_COUNT_QUERY, GET_USERS_COUNT_QUERY); if (connection_str.startsWith("jdbc:postgresql")) { get_users_query = PGSQL_GET_USERS_QUERY; } else { get_users_query = DEF_GET_USERS_QUERY; } data_repo.initPreparedStatement(get_users_query, get_users_query); data_repo.initPreparedStatement(ADD_USER_PLAIN_PW_QUERY, ADD_USER_PLAIN_PW_QUERY); data_repo.initPreparedStatement(REMOVE_USER_QUERY, REMOVE_USER_QUERY); data_repo.initPreparedStatement(ADD_NODE_QUERY, ADD_NODE_QUERY); data_repo.initPreparedStatement(COUNT_USERS_FOR_DOMAIN_QUERY, COUNT_USERS_FOR_DOMAIN_QUERY); data_repo.initPreparedStatement(DATA_FOR_NODE_QUERY, DATA_FOR_NODE_QUERY); data_repo.initPreparedStatement(KEYS_FOR_NODE_QUERY, KEYS_FOR_NODE_QUERY); data_repo.initPreparedStatement(NODES_FOR_NODE_QUERY, NODES_FOR_NODE_QUERY); data_repo.initPreparedStatement(INSERT_KEY_VAL_QUERY, INSERT_KEY_VAL_QUERY); data_repo.initPreparedStatement(REMOVE_KEY_DATA_QUERY, REMOVE_KEY_DATA_QUERY); data_repo.initPreparedStatement(UPDATE_PAIRS_QUERY, UPDATE_PAIRS_QUERY); auth = new AuthRepositoryImpl(this); // initRepo(); log.log(Level.INFO, "Initialized database connection: {0}", connection_str); } catch (Exception e) { data_repo = null; throw new DBInitException( "Problem initializing jdbc connection: " + connection_str, e); } } /** * Method description * * * @param user * * @throws TigaseDBException * @throws UserNotFoundException */ @Override public void logout(BareJID user) throws UserNotFoundException, TigaseDBException { auth.logout(user); } /** * Describe <code>otherAuth</code> method here. * * @param props * a <code>Map</code> value * @return a <code>boolean</code> value * @exception UserNotFoundException * if an error occurs * @exception TigaseDBException * if an error occurs * @exception AuthorizationException * if an error occurs */ @Override public boolean otherAuth(final Map<String, Object> props) throws UserNotFoundException, TigaseDBException, AuthorizationException { return auth.otherAuth(props); } // Implementation of tigase.db.AuthRepository /** * Describe <code>plainAuth</code> method here. * * @param user * a <code>String</code> value * @param password * a <code>String</code> value * @return a <code>boolean</code> value * * @throws AuthorizationException * @exception UserNotFoundException * if an error occurs * @exception TigaseDBException * if an error occurs */ @Override @Deprecated public boolean plainAuth(BareJID user, final String password) throws UserNotFoundException, TigaseDBException, AuthorizationException { return auth.plainAuth(user, password); } /** * Method description * * * @param authProps */ @Override public void queryAuth(Map<String, Object> authProps) { auth.queryAuth(authProps); } /** * Describe <code>removeData</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @param key * a <code>String</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public void removeData(BareJID user_id, final String subnode, final String key) throws UserNotFoundException, TigaseDBException { removeData(null, user_id, subnode, key); } private void removeData(DataRepository repo, BareJID user_id, final String subnode, final String key) throws UserNotFoundException, TigaseDBException { // cache.remove(user_id+"/"+subnode+"/"+key); try { long nid = getNodeNID(repo, user_id, subnode); if ( log.isLoggable( Level.FINEST ) ){ log.log( Level.FINEST, "Removing data, user_id: {0}, subnode: {1}, key: {2}, nid: {3}", new Object[] { user_id, subnode, key, nid } ); } PreparedStatement remove_key_data_st = null; if (repo == null) { remove_key_data_st = data_repo.getPreparedStatement(user_id, REMOVE_KEY_DATA_QUERY); } else { remove_key_data_st = repo.getPreparedStatement(user_id, REMOVE_KEY_DATA_QUERY); } synchronized (remove_key_data_st) { if (nid > 0) { remove_key_data_st.setLong(1, nid); remove_key_data_st.setString(2, key); remove_key_data_st.executeUpdate(); } } } catch (SQLException e) { throw new TigaseDBException("Error getting subnodes list.", e); } } /** * Describe <code>removeData</code> method here. * * @param user_id * a <code>String</code> value * @param key * a <code>String</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public void removeData(BareJID user_id, final String key) throws UserNotFoundException, TigaseDBException { removeData(user_id, null, key); } /** * Describe <code>removeSubnode</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public void removeSubnode(BareJID user_id, final String subnode) throws UserNotFoundException, TigaseDBException { if (subnode == null) { return; } // end of if (subnode == null) try { long nid = getNodeNID(null, user_id, subnode); if (nid > 0) { deleteSubnode(null, nid); cache.remove(user_id + "/" + subnode); } } catch (SQLException e) { throw new TigaseDBException("Error getting subnodes list.", e); } } /** * <code>removeUser</code> method is thread safe. It uses local variable for * storing <code>Statement</code>. * * @param user_id * a <code>String</code> value the user Jabber ID. * * @throws TigaseDBException * @exception UserNotFoundException * if an error occurs */ @Override public void removeUser(BareJID user_id) throws UserNotFoundException, TigaseDBException { Statement stmt = null; ResultSet rs = null; String query = null; if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Removing user: {0}", user_id); } try { stmt = data_repo.createStatement(user_id); // Get user account uid long uid = getUserUID(null, user_id, autoCreateUser); // Remove all user enrties from pairs table query = "delete from " + DEF_PAIRS_TBL + " where uid = " + uid; stmt.executeUpdate(query); // Remove all user entries from nodes table query = "delete from " + DEF_NODES_TBL + " where uid = " + uid; stmt.executeUpdate(query); PreparedStatement user_del_sp = data_repo.getPreparedStatement(user_id, REMOVE_USER_QUERY); // Remove user account from users table synchronized (user_del_sp) { user_del_sp.setString(1, user_id.toString()); user_del_sp.executeUpdate(); } } catch (SQLException e) { throw new TigaseDBException("Error removing user from repository: " + query, e); } finally { data_repo.release(stmt, rs); stmt = null; cache.remove(user_id.toString()); // cache.clear(); } } // ~--- set methods ---------------------------------------------------------- /** * Describe <code>setData</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @param key * a <code>String</code> value * @param value * a <code>String</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public void setData(BareJID user_id, final String subnode, final String key, final String value) throws UserNotFoundException, TigaseDBException { long uid = -2; long nid = -2; DataRepository repo = data_repo.takeRepoHandle(user_id); synchronized (repo) { try { uid = getUserUID(repo, user_id, autoCreateUser); nid = getNodeNID(repo, uid, subnode); if ( log.isLoggable( Level.FINEST ) ){ log.log( Level.FINEST, "Saving data setting data, user_id: {0}, subnode: {1}, key: {2}, uid: {3}, nid: {4}, value: {5}", new Object[] { user_id, subnode, key, uid, nid, value } ); } if (nid < 0) { try { // OK nid = createNodePath(repo, user_id, subnode); } catch (SQLException e) { // This may happen in cluster node, when 2 nodes at the same // time write data to the same location, like offline messages.... // Let's try to get the nid again. // OK nid = getNodeNID(repo, uid, subnode); } } PreparedStatement update_pairs_sp = repo.getPreparedStatement(user_id, UPDATE_PAIRS_QUERY); update_pairs_sp.setLong(1, nid); update_pairs_sp.setLong(2, uid); update_pairs_sp.setString(3, key); update_pairs_sp.setString(4, value); update_pairs_sp.executeUpdate(); } catch (SQLException e) { log.log(Level.WARNING, "Error setting data , user_id: " + user_id + ", subnode: " + subnode + ", key: " + key + ", uid: " + uid + ", nid: " + nid + ", value: " + value, e); } } } /** * Describe <code>setData</code> method here. * * @param user_id * a <code>String</code> value * @param key * a <code>String</code> value * @param value * a <code>String</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public void setData(BareJID user_id, final String key, final String value) throws UserNotFoundException, TigaseDBException { setData(user_id, null, key, value); } /** * Describe <code>setDataList</code> method here. * * @param user_id * a <code>String</code> value * @param subnode * a <code>String</code> value * @param key * a <code>String</code> value * @param list * a <code>String[]</code> value * @exception UserNotFoundException * if an error occurs * @throws TigaseDBException */ @Override public void setDataList(BareJID user_id, final String subnode, final String key, final String[] list) throws UserNotFoundException, TigaseDBException { // Transactions may not yet work properly but at least let's make sure // both calls below are executed exclusively on the same DB connection DataRepository repo = data_repo.takeRepoHandle(user_id); synchronized (repo) { try { removeData(repo, user_id, subnode, key); try { addDataList(repo, user_id, subnode, key, list); } catch (SQLException ex) { throw new TigaseDBException("Problem adding data to DB, user_id: " + user_id + ", subnode: " + subnode + ", key: " + key + ", list: " + Arrays.toString(list), ex); } } finally { data_repo.releaseRepoHandle(repo); } } // int counter = 0; // boolean success = false; // DataRepository repo = data_repo.takeRepoHandle(); // try { // while (!success && ++counter < 4) { // try { // repo.startTransaction(); // removeData(repo, user_id, subnode, key); // addDataList(repo, user_id, subnode, key, list); // repo.commit(); // repo.endTransaction(); // success = true; // } catch (SQLException sqlex) { // try { // repo.rollback(); // repo.endTransaction(); // } catch (SQLException e) { // log.log(Level.WARNING, "Problem rolling-back transaction: ", e); // } // try { // Thread.sleep(10); // } catch (InterruptedException ex) { // } // } // } // } finally { // data_repo.releaseRepoHandle(repo); // } // if (!success) { // log.log(Level.WARNING, // "Unsuccessful dataList set, user_id: " + user_id + ", subnode: " + // subnode // + ", key: " + key + ", list: " + Arrays.toString(list)); // } } /** * Method description * * * @param user * @param password * * @throws TigaseDBException */ @Override public void updatePassword(BareJID user, final String password) throws TigaseDBException { auth.updatePassword(user, password); } /** * Method description * * * @param user * * @return */ @Override public boolean userExists(BareJID user) { try { getUserUID(null, user, false); return true; } catch (Exception e) { return false; } } private long addNode(DataRepository repo, long uid, long parent_nid, String node_name) throws SQLException { ResultSet rs = null; PreparedStatement node_add_sp = null; if (repo == null) { node_add_sp = data_repo.getPreparedStatement(null, ADD_NODE_QUERY); } else { node_add_sp = repo.getPreparedStatement(null, ADD_NODE_QUERY); } synchronized (node_add_sp) { try { if (parent_nid < 0) { node_add_sp.setNull(1, Types.BIGINT); } else { node_add_sp.setLong(1, parent_nid); } // end of else node_add_sp.setLong(2, uid); node_add_sp.setString(3, node_name); rs = node_add_sp.executeQuery(); if (rs.next()) { return rs.getLong(1); } else { log.warning("Missing NID after adding new node..."); return -1; // throw new TigaseDBException("Propeblem adding new node. " // + "The SP should return nid or fail"); } // end of if (isnext) else } finally { data_repo.release(null, rs); } } // return new_nid; } /** * <code>addUserRepo</code> method is thread safe. It uses local variable for * storing <code>Statement</code>. * * @param user_id * a <code>String</code> value of the user ID. * @return a <code>long</code> value of <code>uid</code> database user ID. * @exception SQLException * if an error occurs */ private long addUserRepo(DataRepository repo, BareJID user_id) throws SQLException { ResultSet rs = null; long uid = -1; PreparedStatement user_add_sp = null; if (repo == null) { user_add_sp = data_repo.getPreparedStatement(user_id, ADD_USER_PLAIN_PW_QUERY); } else { user_add_sp = repo.getPreparedStatement(user_id, ADD_USER_PLAIN_PW_QUERY); } synchronized (user_add_sp) { try { user_add_sp.setString(1, user_id.toString()); user_add_sp.setNull(2, Types.VARCHAR); rs = user_add_sp.executeQuery(); if (rs.next()) { uid = rs.getLong(1); // addNode(uid, -1, root_node); } else { log.warning("Missing UID after adding new user..."); // throw new // TigaseDBException("Propeblem adding new user to repository. " // + "The SP should return uid or fail"); } // end of if (isnext) else } finally { data_repo.release(null, rs); } } cache.put(user_id.toString(), Long.valueOf(uid)); return uid; } private String buildNodeQuery(long uid, String node_path) { String query = "select nid as nid1 from " + DEF_NODES_TBL + " where (uid = " + uid + ")" + " AND (parent_nid is null)" + " AND (node = '" + DEF_ROOT_NODE + "')"; if (node_path == null) { return query; } else { StringTokenizer strtok = new StringTokenizer(node_path, "/", false); int cnt = 1; String subquery = query; while (strtok.hasMoreTokens()) { String token = strtok.nextToken(); ++cnt; subquery = "select nid as nid" + cnt + ", node as node" + cnt + " from " + DEF_NODES_TBL + ", (" + subquery + ") nodes" + (cnt - 1) + " where (parent_nid = nid" + (cnt - 1) + ")" + " AND (node = '" + token + "')"; } // end of while (strtok.hasMoreTokens()) return subquery; } // end of else } // Implementation of tigase.db.UserRepository private void checkDBSchema() throws SQLException { String schema_version = "1.0"; String query = (derby_mode ? DERBY_GETSCHEMAVER_QUERY : JDBC_GETSCHEMAVER_QUERY); Statement stmt = data_repo.createStatement(null); ResultSet rs = stmt.executeQuery(query); try { if (rs.next()) { schema_version = rs.getString(1); if (false == CURRENT_DB_SCHEMA_VER.equals(schema_version)) { System.err.println("\n\nPlease upgrade database schema now."); System.err.println("Current scheme version is: " + schema_version + ", expected: " + CURRENT_DB_SCHEMA_VER); System.err.println("Check the schema upgrade guide at the address:"); System.err.println(SCHEMA_UPGRADE_LINK); System.err.println("----"); System.err.println("If you have upgraded your schema and you are still"); System.err.println("experiencing this problem please contact support at"); System.err.println("e-mail address: support@tigase.org"); // e.printStackTrace(); System.exit(100); } } } finally { data_repo.release(stmt, rs); } // end of try-catch } private long createNodePath(DataRepository repo, BareJID user_id, String node_path) throws SQLException, UserNotFoundException { if (node_path == null) { // Or should I throw NullPointerException? // OK return getNodeNID(repo, user_id, null); } // end of if (node_path == null) // OK long uid = getUserUID(repo, user_id, autoCreateUser); // OK long nid = getNodeNID(repo, uid, null); StringTokenizer strtok = new StringTokenizer(node_path, "/", false); StringBuilder built_path = new StringBuilder(); while (strtok.hasMoreTokens()) { String token = strtok.nextToken(); built_path.append("/").append(token); // OK long cur_nid = getNodeNID(repo, uid, built_path.toString()); if (cur_nid > 0) { nid = cur_nid; } else { // OK nid = addNode(repo, uid, nid, token); } // end of if (cur_nid > 0) else } // end of while (strtok.hasMoreTokens()) return nid; } private void deleteSubnode(DataRepository repo, long nid) throws SQLException { Statement stmt = null; ResultSet rs = null; String query = null; try { if (repo == null) { stmt = data_repo.createStatement(null); } else { stmt = repo.createStatement(null); } query = "delete from " + DEF_PAIRS_TBL + " where nid = " + nid; stmt.executeUpdate(query); query = "delete from " + DEF_NODES_TBL + " where nid = " + nid; stmt.executeUpdate(query); } finally { data_repo.release(stmt, rs); } } // ~--- get methods ---------------------------------------------------------- private long getNodeNID(DataRepository repo, long uid, String node_path) throws SQLException, UserNotFoundException { String query = buildNodeQuery(uid, node_path); if (log.isLoggable(Level.FINEST)) { log.finest(query); } Statement stmt = null; ResultSet rs = null; long nid = -1; try { if (repo == null) { stmt = data_repo.createStatement(null); } else { stmt = repo.createStatement(null); } rs = stmt.executeQuery(query); if (rs.next()) { nid = rs.getLong(1); } else { nid = -1; } // end of if (isnext) else if (nid <= 0) { if (node_path == null) { log.info("Missing root node, database upgrade or bug in the code? Adding missing " + "root node now."); // OK nid = addNode(repo, uid, -1, "root"); } else { if (log.isLoggable(Level.FINEST)) { log.log(Level.FINEST, "Missing nid for node path: {0} and uid: {1}", new Object[] { node_path, uid }); } } } return nid; } finally { data_repo.release(stmt, rs); stmt = null; rs = null; } } private long getNodeNID(DataRepository repo, BareJID user_id, String node_path) throws SQLException, UserNotFoundException { Long cache_res = (Long) cache.get(user_id + "/" + node_path); if (cache_res != null) { return cache_res.longValue(); } // end of if (result != null) // OK long uid = getUserUID(repo, user_id, autoCreateUser); // OK long result = getNodeNID(repo, uid, node_path); if (result > 0) { cache.put(user_id + "/" + node_path, Long.valueOf(result)); } // end of if (result > 0) return result; } private long getUserUID(DataRepository repo, BareJID user_id, boolean autoCreate) throws SQLException, UserNotFoundException { // OK long result = getUserUID(repo, user_id); if (result <= 0) { if (autoCreate) { // OK result = addUserRepo(repo, user_id); } else { throw new UserNotFoundException("User does not exist: " + user_id); } // end of if (autoCreate) else } // end of if (isnext) else return result; } // ~--- inner classes -------------------------------------------------------- private class RepoCache extends SimpleCache<String, Object> { /** * Constructs ... * * * @param maxsize * @param cache_time */ public RepoCache(int maxsize, long cache_time) { super(maxsize, cache_time); } // ~--- methods ------------------------------------------------------------ /** * Method description * * * @param key * * @return */ @Override public Object remove(Object key) { if (cache_off) { return null; } Object val = super.remove(key); String strk = key.toString(); Iterator<String> ks = keySet().iterator(); while (ks.hasNext()) { String k = ks.next().toString(); if (k.startsWith(strk)) { ks.remove(); } // end of if (k.startsWith(strk)) } // end of while (ks.hasNext()) return val; } } } // JDBCRepository // ~ Formatted in Sun Code Convention // ~ Formatted by Jindent --- http://www.jindent.com