Commit 6531fac8 authored by Matt Tucker's avatar Matt Tucker Committed by matt

Fixes for searching (JM-122).


git-svn-id: http://svn.igniterealtime.org/svn/repos/messenger/trunk@1066 b35dd754-fafc-0310-a699-88a17e54d16e
parent 733ca04a
...@@ -35,7 +35,7 @@ public class LdapUserProvider implements UserProvider { ...@@ -35,7 +35,7 @@ public class LdapUserProvider implements UserProvider {
public LdapUserProvider() { public LdapUserProvider() {
manager = LdapManager.getInstance(); manager = LdapManager.getInstance();
searchFields = new HashMap<String,String>(); searchFields = new LinkedHashMap<String,String>();
String fieldList = JiveGlobals.getXMLProperty("ldap.searchFields"); String fieldList = JiveGlobals.getXMLProperty("ldap.searchFields");
// If the value isn't present, default to to username, name, and email. // If the value isn't present, default to to username, name, and email.
if (fieldList == null) { if (fieldList == null) {
...@@ -261,16 +261,18 @@ public class LdapUserProvider implements UserProvider { ...@@ -261,16 +261,18 @@ public class LdapUserProvider implements UserProvider {
throw new UnsupportedOperationException(); throw new UnsupportedOperationException();
} }
public Collection<String> getSearchFields() throws UnsupportedOperationException { public Set<String> getSearchFields() throws UnsupportedOperationException {
return Collections.unmodifiableCollection(searchFields.keySet()); return Collections.unmodifiableSet(searchFields.keySet());
} }
public Collection<User> findUsers(String field, String query) public Collection<User> findUsers(Set<String> fields, String query)
throws UnsupportedOperationException throws UnsupportedOperationException
{ {
String searchAttribute = searchFields.get(field); if (fields.isEmpty()) {
if (searchAttribute == null) { return Collections.emptyList();
throw new IllegalArgumentException("Search field " + field + " is invalid."); }
if (!searchFields.keySet().containsAll(fields)) {
throw new IllegalArgumentException("Search fields " + fields + " are not valid.");
} }
List<String> usernames = new ArrayList<String>(); List<String> usernames = new ArrayList<String>();
LdapContext ctx = null; LdapContext ctx = null;
...@@ -286,8 +288,15 @@ public class LdapUserProvider implements UserProvider { ...@@ -286,8 +288,15 @@ public class LdapUserProvider implements UserProvider {
SearchControls constraints = new SearchControls(); SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE); constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
constraints.setReturningAttributes(new String[] { manager.getUsernameField() }); constraints.setReturningAttributes(new String[] { manager.getUsernameField() });
String filter = "(" + searchAttribute + "=" + query + ")"; StringBuffer filter = new StringBuffer();
NamingEnumeration answer = ctx.search("", filter, constraints); for (String field:fields) {
String attribute = searchFields.get(field);
if (filter.length() != 0) {
filter.append(" || ");
}
filter.append("(").append(attribute).append("=").append(query).append(")");
}
NamingEnumeration answer = ctx.search("", filter.toString(), constraints);
while (answer.hasMoreElements()) { while (answer.hasMoreElements()) {
// Get the next userID. // Get the next userID.
usernames.add( usernames.add(
...@@ -316,4 +325,8 @@ public class LdapUserProvider implements UserProvider { ...@@ -316,4 +325,8 @@ public class LdapUserProvider implements UserProvider {
} }
return new UserCollection((String[])usernames.toArray(new String[usernames.size()])); return new UserCollection((String[])usernames.toArray(new String[usernames.size()]));
} }
public boolean isReadOnly() {
return true;
}
} }
\ No newline at end of file
...@@ -384,13 +384,18 @@ public class DefaultUserProvider implements UserProvider { ...@@ -384,13 +384,18 @@ public class DefaultUserProvider implements UserProvider {
} }
} }
public Collection<String> getSearchFields() throws UnsupportedOperationException { public Set<String> getSearchFields() throws UnsupportedOperationException {
return Arrays.asList("Username", "Name", "Email"); return new LinkedHashSet<String>(Arrays.asList("Username", "Name", "Email"));
} }
public Collection<User> findUsers(String field, String query) throws UnsupportedOperationException { public Collection<User> findUsers(Set<String> fields, String query)
if (!getSearchFields().contains(field)) { throws UnsupportedOperationException
throw new IllegalArgumentException("Search field " + field + " is invalid."); {
if (fields.isEmpty()) {
return Collections.emptyList();
}
if (!getSearchFields().containsAll(fields)) {
throw new IllegalArgumentException("Search fields " + fields + " are not valid.");
} }
// SQL LIKE queries don't map directly into a keyword/wildcard search like we want. // SQL LIKE queries don't map directly into a keyword/wildcard search like we want.
// Therefore, we do a best approximiation by replacing '*' with '%' and then // Therefore, we do a best approximiation by replacing '*' with '%' and then
...@@ -401,25 +406,33 @@ public class DefaultUserProvider implements UserProvider { ...@@ -401,25 +406,33 @@ public class DefaultUserProvider implements UserProvider {
query = query.substring(0, query.length()-1); query = query.substring(0, query.length()-1);
} }
List<String> usernames = new ArrayList<String>(500); List<String> usernames = new ArrayList<String>(50);
Connection con = null; Connection con = null;
PreparedStatement pstmt = null; Statement stmt = null;
try { try {
con = DbConnectionManager.getConnection(); con = DbConnectionManager.getConnection();
if (field.equals("Username")) { stmt = con.createStatement();
pstmt = con.prepareStatement("SELECT username FROM jiveUser WHERE username LIKE ?"); StringBuffer sql = new StringBuffer();
sql.append("SELECT username FROM jiveUser WHERE");
boolean first = true;
if (fields.contains("Username")) {
sql.append(" username LIKE '").append(StringUtils.escapeForSQL(query)).append("'");
first = false;
} }
else if (field.equals("Name")) { if (fields.contains("Name")) {
pstmt = con.prepareStatement("SELECT username FROM jiveUser WHERE name LIKE ?"); if (!first) {
sql.append(" AND");
} }
else { sql.append(" name LIKE '").append(StringUtils.escapeForSQL(query)).append("'");
pstmt = con.prepareStatement("SELECT username FROM jiveUser WHERE email LIKE ?"); first = false;
} }
pstmt.setString(1, query); if (fields.contains("Email")) {
ResultSet rs = pstmt.executeQuery(); if (!first) {
// Set the fetch size. This will prevent some JDBC drivers from trying sql.append(" AND");
// to load the entire result set into memory. }
DbConnectionManager.setFetchSize(rs, 500); sql.append(" email LIKE '").append(StringUtils.escapeForSQL(query)).append("'");
}
ResultSet rs = stmt.executeQuery(sql.toString());
while (rs.next()) { while (rs.next()) {
usernames.add(rs.getString(1)); usernames.add(rs.getString(1));
} }
...@@ -429,11 +442,15 @@ public class DefaultUserProvider implements UserProvider { ...@@ -429,11 +442,15 @@ public class DefaultUserProvider implements UserProvider {
Log.error(e); Log.error(e);
} }
finally { finally {
try { if (pstmt != null) { pstmt.close(); } } try { if (stmt != null) { stmt.close(); } }
catch (Exception e) { Log.error(e); } catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } } try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); } catch (Exception e) { Log.error(e); }
} }
return new UserCollection((String[])usernames.toArray(new String[usernames.size()])); return new UserCollection((String[])usernames.toArray(new String[usernames.size()]));
} }
public boolean isReadOnly() {
return false;
}
} }
\ No newline at end of file
...@@ -92,6 +92,10 @@ public class User implements Cacheable { ...@@ -92,6 +92,10 @@ public class User implements Cacheable {
* @param password the new password for the user. * @param password the new password for the user.
*/ */
public void setPassword(String password) { public void setPassword(String password) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try { try {
UserManager.getUserProvider().setPassword(username, password); UserManager.getUserProvider().setPassword(username, password);
} }
...@@ -105,6 +109,10 @@ public class User implements Cacheable { ...@@ -105,6 +109,10 @@ public class User implements Cacheable {
} }
public void setName(String name) { public void setName(String name) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try { try {
UserManager.getUserProvider().setName(username, name); UserManager.getUserProvider().setName(username, name);
this.name = name; this.name = name;
...@@ -119,6 +127,10 @@ public class User implements Cacheable { ...@@ -119,6 +127,10 @@ public class User implements Cacheable {
} }
public void setEmail(String email) { public void setEmail(String email) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try { try {
UserManager.getUserProvider().setEmail(username, email); UserManager.getUserProvider().setEmail(username, email);
this.email = email; this.email = email;
...@@ -133,6 +145,10 @@ public class User implements Cacheable { ...@@ -133,6 +145,10 @@ public class User implements Cacheable {
} }
public void setCreationDate(Date creationDate) { public void setCreationDate(Date creationDate) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try { try {
UserManager.getUserProvider().setCreationDate(username, creationDate); UserManager.getUserProvider().setCreationDate(username, creationDate);
this.creationDate = creationDate; this.creationDate = creationDate;
...@@ -147,6 +163,10 @@ public class User implements Cacheable { ...@@ -147,6 +163,10 @@ public class User implements Cacheable {
} }
public void setModificationDate(Date modificationDate) { public void setModificationDate(Date modificationDate) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try { try {
UserManager.getUserProvider().setCreationDate(username, modificationDate); UserManager.getUserProvider().setCreationDate(username, modificationDate);
this.modificationDate = modificationDate; this.modificationDate = modificationDate;
......
...@@ -20,6 +20,7 @@ import org.jivesoftware.stringprep.Stringprep; ...@@ -20,6 +20,7 @@ import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException; import org.jivesoftware.stringprep.StringprepException;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
/** /**
* Manages users, including loading, creating and deleting. * Manages users, including loading, creating and deleting.
...@@ -84,6 +85,9 @@ public class UserManager { ...@@ -84,6 +85,9 @@ public class UserManager {
public User createUser(String username, String password, String name, String email) public User createUser(String username, String password, String name, String email)
throws UserAlreadyExistsException throws UserAlreadyExistsException
{ {
if (provider.isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
// Make sure that the username is valid. // Make sure that the username is valid.
try { try {
username = Stringprep.nodeprep(username); username = Stringprep.nodeprep(username);
...@@ -102,6 +106,10 @@ public class UserManager { ...@@ -102,6 +106,10 @@ public class UserManager {
* @param user the user to delete. * @param user the user to delete.
*/ */
public void deleteUser(User user) { public void deleteUser(User user) {
if (provider.isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
String username = user.getUsername(); String username = user.getUsername();
// Make sure that the username is valid. // Make sure that the username is valid.
try { try {
...@@ -182,7 +190,7 @@ public class UserManager { ...@@ -182,7 +190,7 @@ public class UserManager {
* returned must support wild-card and keyword searching. For example, an * returned must support wild-card and keyword searching. For example, an
* implementation might send back the set {"Username", "Name", "Email"}. Any of * implementation might send back the set {"Username", "Name", "Email"}. Any of
* those three fields can then be used in a search with the * those three fields can then be used in a search with the
* {@link #findUsers(String,String)} method.<p> * {@link #findUsers(Set,String)} method.<p>
* *
* This method should throw an UnsupportedOperationException if this * This method should throw an UnsupportedOperationException if this
* operation is not supported by the backend user store. * operation is not supported by the backend user store.
...@@ -191,28 +199,28 @@ public class UserManager { ...@@ -191,28 +199,28 @@ public class UserManager {
* @throws UnsupportedOperationException if the provider does not * @throws UnsupportedOperationException if the provider does not
* support the operation (this is an optional operation). * support the operation (this is an optional operation).
*/ */
public Collection<String> getSearchFields() throws UnsupportedOperationException { public Set<String> getSearchFields() throws UnsupportedOperationException {
return provider.getSearchFields(); return provider.getSearchFields();
} }
/** /**
* Searches for users based on a field an query string. The field must be one * earches for users based on a set of fields and a query string. The fields must
* of the values returned by {@link #getSearchFields()}. The query can include * be taken from the values returned by {@link #getSearchFields()}. The query can
* wildcards. For example, a search on the field "Name" with a query of "Ma*" * include wildcards. For example, a search on the field "Name" with a query of "Ma*"
* might return user's with the name "Matt", "Martha" and "Madeline".<p> * might return user's with the name "Matt", "Martha" and "Madeline".<p>
* *
* This method should throw an UnsupportedOperationException if this * This method should throw an UnsupportedOperationException if this
* operation is not supported by the backend user store. * operation is not supported by the backend user store.
* *
* @param field the field to search on. * @param fields the fields to search on.
* @param query the query string. * @param query the query string.
* @return a Collection of users that match the search. * @return a Collection of users that match the search.
* @throws UnsupportedOperationException if the provider does not * @throws UnsupportedOperationException if the provider does not
* support the operation (this is an optional operation). * support the operation (this is an optional operation).
*/ */
public Collection<User> findUsers(String field, String query) public Collection<User> findUsers(Set<String> fields, String query)
throws UnsupportedOperationException throws UnsupportedOperationException
{ {
return provider.findUsers(field, query); return provider.findUsers(fields, query);
} }
} }
\ No newline at end of file
...@@ -13,6 +13,7 @@ package org.jivesoftware.messenger.user; ...@@ -13,6 +13,7 @@ package org.jivesoftware.messenger.user;
import java.util.Date; import java.util.Date;
import java.util.Collection; import java.util.Collection;
import java.util.Set;
/** /**
* Provider interface for the user system. * Provider interface for the user system.
...@@ -164,7 +165,7 @@ public interface UserProvider { ...@@ -164,7 +165,7 @@ public interface UserProvider {
* returned must support wild-card and keyword searching. For example, an * returned must support wild-card and keyword searching. For example, an
* implementation might send back the set {"Username", "Name", "Email"}. Any of * implementation might send back the set {"Username", "Name", "Email"}. Any of
* those three fields can then be used in a search with the * those three fields can then be used in a search with the
* {@link #findUsers(String,String)} method.<p> * {@link #findUsers(Set,String)} method.<p>
* *
* This method should throw an UnsupportedOperationException if this * This method should throw an UnsupportedOperationException if this
* operation is not supported by the backend user store. * operation is not supported by the backend user store.
...@@ -173,24 +174,31 @@ public interface UserProvider { ...@@ -173,24 +174,31 @@ public interface UserProvider {
* @throws UnsupportedOperationException if the provider does not * @throws UnsupportedOperationException if the provider does not
* support the operation (this is an optional operation). * support the operation (this is an optional operation).
*/ */
public Collection<String> getSearchFields() throws UnsupportedOperationException; public Set<String> getSearchFields() throws UnsupportedOperationException;
/** /**
* Searches for users based on a field an query string. The field must be one * Searches for users based on a set of fields and a query string. The fields must
* of the values returned by {@link #getSearchFields()}. The query can include * be taken from the values returned by {@link #getSearchFields()}. The query can
* wildcards. For example, a search on the field "Name" with a query of "Ma*" * include wildcards. For example, a search on the field "Name" with a query of "Ma*"
* might return user's with the name "Matt", "Martha" and "Madeline".<p> * might return user's with the name "Matt", "Martha" and "Madeline".<p>
* *
* This method should throw an UnsupportedOperationException if this * This method should throw an UnsupportedOperationException if this
* operation is not supported by the backend user store. * operation is not supported by the backend user store.
* *
* @param field the field to search on. * @param fields the fields to search on.
* @param query the query string. * @param query the query string.
* @return a Collection of users that match the search. * @return a Collection of users that match the search.
* @throws UnsupportedOperationException if the provider does not * @throws UnsupportedOperationException if the provider does not
* support the operation (this is an optional operation). * support the operation (this is an optional operation).
*/ */
public Collection<User> findUsers(String field, String query) public Collection<User> findUsers(Set<String> fields, String query)
throws UnsupportedOperationException; throws UnsupportedOperationException;
/**
* Returns true if the UserProvider is read-only. When read-only, that means that
* users can not be created, deleted, or modified.
*
* @return true if the user provider is read-only.
*/
public boolean isReadOnly();
} }
\ No newline at end of file
...@@ -770,6 +770,51 @@ public class StringUtils { ...@@ -770,6 +770,51 @@ public class StringUtils {
return buf.toString(); return buf.toString();
} }
/**
* Escapes all necessary characters in the String so that it can be used in SQL
*
* @param string the string to escape.
* @return the string with appropriate characters escaped.
*/
public static final String escapeForSQL(String string) {
if (string == null) {
return null;
}
else if (string.length() == 0) {
return string;
}
char ch;
char[] input = string.toCharArray();
int i = 0;
int last = 0;
int len = input.length;
StringBuffer out = null;
for (; i < len; i++) {
ch = input[i];
if (ch == '\'') {
if (out == null) {
out = new StringBuffer(len + 2);
}
if (i > last) {
out.append(input, last, i - last);
}
last = i + 1;
out.append('\'').append('\'');
}
}
if (out == null) {
return string;
}
else if (i > last) {
out.append(input, last, i - last);
}
return out.toString();
}
/** /**
* Escapes all necessary characters in the String so that it can be used * Escapes all necessary characters in the String so that it can be used
* in an XML doc. * in an XML doc.
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment