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 {
public LdapUserProvider() {
manager = LdapManager.getInstance();
searchFields = new HashMap<String,String>();
searchFields = new LinkedHashMap<String,String>();
String fieldList = JiveGlobals.getXMLProperty("ldap.searchFields");
// If the value isn't present, default to to username, name, and email.
if (fieldList == null) {
......@@ -261,16 +261,18 @@ public class LdapUserProvider implements UserProvider {
throw new UnsupportedOperationException();
}
public Collection<String> getSearchFields() throws UnsupportedOperationException {
return Collections.unmodifiableCollection(searchFields.keySet());
public Set<String> getSearchFields() throws UnsupportedOperationException {
return Collections.unmodifiableSet(searchFields.keySet());
}
public Collection<User> findUsers(String field, String query)
public Collection<User> findUsers(Set<String> fields, String query)
throws UnsupportedOperationException
{
String searchAttribute = searchFields.get(field);
if (searchAttribute == null) {
throw new IllegalArgumentException("Search field " + field + " is invalid.");
if (fields.isEmpty()) {
return Collections.emptyList();
}
if (!searchFields.keySet().containsAll(fields)) {
throw new IllegalArgumentException("Search fields " + fields + " are not valid.");
}
List<String> usernames = new ArrayList<String>();
LdapContext ctx = null;
......@@ -286,8 +288,15 @@ public class LdapUserProvider implements UserProvider {
SearchControls constraints = new SearchControls();
constraints.setSearchScope(SearchControls.SUBTREE_SCOPE);
constraints.setReturningAttributes(new String[] { manager.getUsernameField() });
String filter = "(" + searchAttribute + "=" + query + ")";
NamingEnumeration answer = ctx.search("", filter, constraints);
StringBuffer filter = new StringBuffer();
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()) {
// Get the next userID.
usernames.add(
......@@ -316,4 +325,8 @@ public class LdapUserProvider implements UserProvider {
}
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 {
}
}
public Collection<String> getSearchFields() throws UnsupportedOperationException {
return Arrays.asList("Username", "Name", "Email");
public Set<String> getSearchFields() throws UnsupportedOperationException {
return new LinkedHashSet<String>(Arrays.asList("Username", "Name", "Email"));
}
public Collection<User> findUsers(String field, String query) throws UnsupportedOperationException {
if (!getSearchFields().contains(field)) {
throw new IllegalArgumentException("Search field " + field + " is invalid.");
public Collection<User> findUsers(Set<String> fields, String query)
throws UnsupportedOperationException
{
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.
// Therefore, we do a best approximiation by replacing '*' with '%' and then
......@@ -401,25 +406,33 @@ public class DefaultUserProvider implements UserProvider {
query = query.substring(0, query.length()-1);
}
List<String> usernames = new ArrayList<String>(500);
List<String> usernames = new ArrayList<String>(50);
Connection con = null;
PreparedStatement pstmt = null;
Statement stmt = null;
try {
con = DbConnectionManager.getConnection();
if (field.equals("Username")) {
pstmt = con.prepareStatement("SELECT username FROM jiveUser WHERE username LIKE ?");
stmt = con.createStatement();
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")) {
pstmt = con.prepareStatement("SELECT username FROM jiveUser WHERE name LIKE ?");
if (fields.contains("Name")) {
if (!first) {
sql.append(" AND");
}
sql.append(" name LIKE '").append(StringUtils.escapeForSQL(query)).append("'");
first = false;
}
else {
pstmt = con.prepareStatement("SELECT username FROM jiveUser WHERE email LIKE ?");
if (fields.contains("Email")) {
if (!first) {
sql.append(" AND");
}
sql.append(" email LIKE '").append(StringUtils.escapeForSQL(query)).append("'");
}
pstmt.setString(1, query);
ResultSet rs = pstmt.executeQuery();
// Set the fetch size. This will prevent some JDBC drivers from trying
// to load the entire result set into memory.
DbConnectionManager.setFetchSize(rs, 500);
ResultSet rs = stmt.executeQuery(sql.toString());
while (rs.next()) {
usernames.add(rs.getString(1));
}
......@@ -429,11 +442,15 @@ public class DefaultUserProvider implements UserProvider {
Log.error(e);
}
finally {
try { if (pstmt != null) { pstmt.close(); } }
try { if (stmt != null) { stmt.close(); } }
catch (Exception e) { Log.error(e); }
try { if (con != null) { con.close(); } }
catch (Exception e) { Log.error(e); }
}
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 {
* @param password the new password for the user.
*/
public void setPassword(String password) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try {
UserManager.getUserProvider().setPassword(username, password);
}
......@@ -105,6 +109,10 @@ public class User implements Cacheable {
}
public void setName(String name) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try {
UserManager.getUserProvider().setName(username, name);
this.name = name;
......@@ -119,6 +127,10 @@ public class User implements Cacheable {
}
public void setEmail(String email) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try {
UserManager.getUserProvider().setEmail(username, email);
this.email = email;
......@@ -133,6 +145,10 @@ public class User implements Cacheable {
}
public void setCreationDate(Date creationDate) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try {
UserManager.getUserProvider().setCreationDate(username, creationDate);
this.creationDate = creationDate;
......@@ -147,6 +163,10 @@ public class User implements Cacheable {
}
public void setModificationDate(Date modificationDate) {
if (UserManager.getUserProvider().isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
try {
UserManager.getUserProvider().setCreationDate(username, modificationDate);
this.modificationDate = modificationDate;
......
......@@ -20,6 +20,7 @@ import org.jivesoftware.stringprep.Stringprep;
import org.jivesoftware.stringprep.StringprepException;
import java.util.Collection;
import java.util.Set;
/**
* Manages users, including loading, creating and deleting.
......@@ -84,6 +85,9 @@ public class UserManager {
public User createUser(String username, String password, String name, String email)
throws UserAlreadyExistsException
{
if (provider.isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
// Make sure that the username is valid.
try {
username = Stringprep.nodeprep(username);
......@@ -102,6 +106,10 @@ public class UserManager {
* @param user the user to delete.
*/
public void deleteUser(User user) {
if (provider.isReadOnly()) {
throw new UnsupportedOperationException("User provider is read-only.");
}
String username = user.getUsername();
// Make sure that the username is valid.
try {
......@@ -182,7 +190,7 @@ public class UserManager {
* returned must support wild-card and keyword searching. For example, an
* implementation might send back the set {"Username", "Name", "Email"}. Any of
* 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
* operation is not supported by the backend user store.
......@@ -191,28 +199,28 @@ public class UserManager {
* @throws UnsupportedOperationException if the provider does not
* support the operation (this is an optional operation).
*/
public Collection<String> getSearchFields() throws UnsupportedOperationException {
public Set<String> getSearchFields() throws UnsupportedOperationException {
return provider.getSearchFields();
}
/**
* Searches for users based on a field an query string. The field must be one
* of the values returned by {@link #getSearchFields()}. The query can include
* wildcards. For example, a search on the field "Name" with a query of "Ma*"
* earches for users based on a set of fields and a query string. The fields must
* be taken from the values returned by {@link #getSearchFields()}. The query can
* 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>
*
* This method should throw an UnsupportedOperationException if this
* 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.
* @return a Collection of users that match the search.
* @throws UnsupportedOperationException if the provider does not
* 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
{
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;
import java.util.Date;
import java.util.Collection;
import java.util.Set;
/**
* Provider interface for the user system.
......@@ -164,7 +165,7 @@ public interface UserProvider {
* returned must support wild-card and keyword searching. For example, an
* implementation might send back the set {"Username", "Name", "Email"}. Any of
* 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
* operation is not supported by the backend user store.
......@@ -173,24 +174,31 @@ public interface UserProvider {
* @throws UnsupportedOperationException if the provider does not
* 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
* of the values returned by {@link #getSearchFields()}. The query can include
* wildcards. For example, a search on the field "Name" with a query of "Ma*"
* Searches for users based on a set of fields and a query string. The fields must
* be taken from the values returned by {@link #getSearchFields()}. The query can
* 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>
*
* This method should throw an UnsupportedOperationException if this
* 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.
* @return a Collection of users that match the search.
* @throws UnsupportedOperationException if the provider does not
* 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;
/**
* 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 {
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
* 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