Skip to content

Commit

Permalink
Add user search methods by email/name fragment (#5051)
Browse files Browse the repository at this point in the history
  • Loading branch information
akorneta committed May 15, 2017
1 parent 69842e6 commit a15f3db
Show file tree
Hide file tree
Showing 7 changed files with 271 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
*
* @author Max Shaposhnik (mshaposhnik@codenvy.com)
* @author Yevhenii Voevodin
* @author Anton Korneta
*/
@Singleton
public class UserManager {
Expand Down Expand Up @@ -220,6 +221,58 @@ public Page<UserImpl> getAll(int maxItems, long skipCount) throws ServerExceptio
return userDao.getAll(maxItems, skipCount);
}

/**
* Returns all users whose email address contains specified {@code emailPart}.
*
* @param emailPart
* fragment of user's email
* @param maxItems
* the maximum number of users to return
* @param skipCount
* the number of users to skip
* @return list of matched users
* @throws NullPointerException
* when {@code emailPart} is null
* @throws IllegalArgumentException
* when {@code maxItems} or {@code skipCount} is negative or
* when {@code skipCount} more than {@value Integer#MAX_VALUE}
* @throws ServerException
* when any other error occurs
*/
public Page<? extends User> getByEmailPart(String emailPart, int maxItems, long skipCount) throws ServerException {
requireNonNull(emailPart, "Required non-null email part");
checkArgument(maxItems >= 0, "The number of items to return can't be negative");
checkArgument(skipCount >= 0 && skipCount <= Integer.MAX_VALUE,
"The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
return userDao.getByEmailPart(emailPart, maxItems, skipCount);
}

/**
* Returns all users whose name contains specified {@code namePart}.
*
* @param namePart
* fragment of user's name
* @param maxItems
* the maximum number of users to return
* @param skipCount
* the number of users to skip
* @return list of matched users
* @throws NullPointerException
* when {@code namePart} is null
* @throws IllegalArgumentException
* when {@code maxItems} or {@code skipCount} is negative or
* when {@code skipCount} more than {@value Integer#MAX_VALUE}
* @throws ServerException
* when any other error occurs
*/
public Page<? extends User> getByNamePart(String namePart, int maxItems, long skipCount) throws ServerException {
requireNonNull(namePart, "Required non-null name part");
checkArgument(maxItems >= 0, "The number of items to return can't be negative");
checkArgument(skipCount >= 0 && skipCount <= Integer.MAX_VALUE,
"The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
return userDao.getByNamePart(namePart, maxItems, skipCount);
}

/**
* Gets total count of all users
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,60 @@ public Page<UserImpl> getAll(int maxItems, long skipCount) throws ServerExceptio
}
}

@Override
@Transactional
public Page<UserImpl> getByNamePart(String namePart, int maxItems, long skipCount) throws ServerException {
requireNonNull(namePart, "Required non-null name part");
checkArgument(maxItems >= 0, "The number of items to return can't be negative");
checkArgument(skipCount >= 0 && skipCount <= Integer.MAX_VALUE,
"The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
try {
final List<UserImpl> list = managerProvider.get()
.createNamedQuery("User.getByNamePart", UserImpl.class)
.setParameter("name", namePart.toLowerCase())
.setMaxResults(maxItems)
.setFirstResult((int)skipCount)
.getResultList()
.stream()
.map(JpaUserDao::erasePassword)
.collect(toList());
final long count = managerProvider.get()
.createNamedQuery("User.getByNamePartCount", Long.class)
.setParameter("name", namePart.toLowerCase())
.getSingleResult();
return new Page<>(list, skipCount, maxItems, count);
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}

@Override
@Transactional
public Page<UserImpl> getByEmailPart(String emailPart, int maxItems, long skipCount) throws ServerException {
requireNonNull(emailPart, "Required non-null email part");
checkArgument(maxItems >= 0, "The number of items to return can't be negative");
checkArgument(skipCount >= 0 && skipCount <= Integer.MAX_VALUE,
"The number of items to skip can't be negative or greater than " + Integer.MAX_VALUE);
try {
final List<UserImpl> list = managerProvider.get()
.createNamedQuery("User.getByEmailPart", UserImpl.class)
.setParameter("email", emailPart.toLowerCase())
.setMaxResults(maxItems)
.setFirstResult((int)skipCount)
.getResultList()
.stream()
.map(JpaUserDao::erasePassword)
.collect(toList());
final long count = managerProvider.get()
.createNamedQuery("User.getByEmailPartCount", Long.class)
.setParameter("email", emailPart.toLowerCase())
.getSingleResult();
return new Page<>(list, skipCount, maxItems, count);
} catch (RuntimeException x) {
throw new ServerException(x.getLocalizedMessage(), x);
}
}

@Override
@Transactional
public long getTotalCount() throws ServerException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,8 +49,15 @@
@NamedQuery(name = "User.getAll",
query = "SELECT u FROM Usr u"),
@NamedQuery(name = "User.getTotalCount",
query = "SELECT COUNT(u) FROM Usr u")

query = "SELECT COUNT(u) FROM Usr u"),
@NamedQuery(name = "User.getByEmailPart",
query = "SELECT u FROM Usr u WHERE LOWER(u.email) LIKE CONCAT('%', :email, '%')"),
@NamedQuery(name = "User.getByEmailPartCount",
query = "SELECT COUNT(u) FROM Usr u WHERE LOWER(u.email) LIKE CONCAT('%', :email, '%')"),
@NamedQuery(name = "User.getByNamePart",
query = "SELECT u FROM Usr u WHERE LOWER(u.name) LIKE CONCAT('%', :name, '%')"),
@NamedQuery(name = "User.getByNamePartCount",
query = "SELECT COUNT(u) FROM Usr u WHERE LOWER(u.name) LIKE CONCAT('%', :name, '%')")
}
)
@Table(name = "usr")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
* such kind of inconsistencies are expected by design and may be treated further.
*
* @author Yevhenii Voevodin
* @author Anton Korneta
*/
public interface UserDao {

Expand Down Expand Up @@ -176,6 +177,56 @@ public interface UserDao {
*/
Page<UserImpl> getAll(int maxItems, long skipCount) throws ServerException;

/**
* Returns all users whose name contains(case insensitively) specified {@code namePart}.
*
* @param namePart
* fragment of user's name
* @param maxItems
* the maximum number of users to return
* @param skipCount
* the number of users to skip
* @return list of matched users
* @throws NullPointerException
* when {@code namePart} is null
* @throws IllegalArgumentException
* when {@code maxItems} or {@code skipCount} is negative or
* when {@code skipCount} more than {@value Integer#MAX_VALUE}
* @throws ServerException
* when any other error occurs
*/
Page<UserImpl> getByNamePart(String namePart, int maxItems, long skipCount) throws ServerException;

/**
* Returns all users whose email address contains(case insensitively) specified {@code emailPart}.
*
* <p>For example if email fragment would be 'CHE' then result of search will include the following:
* <pre>
* | emails | result |
* | Cherkassy@example.com | + |
* | preacher@example.com | + |
* | user@ukr.che | + |
* | johny@example.com | - |
* | CoachEddie@example.com | + |
* </pre>
*
* @param emailPart
* fragment of user's email
* @param maxItems
* the maximum number of users to return
* @param skipCount
* the number of users to skip
* @return list of matched users
* @throws NullPointerException
* when {@code emailPart} is null
* @throws IllegalArgumentException
* when {@code maxItems} or {@code skipCount} is negative or
* when {@code skipCount} more than {@value Integer#MAX_VALUE}
* @throws ServerException
* when any other error occurs
*/
Page<UserImpl> getByEmailPart(String emailPart, int maxItems, long skipCount) throws ServerException;

/**
* Get count of all users from persistent layer.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,11 @@
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import static java.util.Arrays.asList;
import static java.util.Arrays.stream;
import static java.util.stream.Collectors.toSet;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.doCallRealMethod;
import static org.mockito.Mockito.mock;
Expand All @@ -56,6 +59,8 @@ public class UserDaoTest {

private static final int COUNT_OF_USERS = 5;

private static final String NAME_PREFIX = "user_name-";

private UserImpl[] users;

@Inject
Expand All @@ -73,7 +78,7 @@ public void setUp() throws TckRepositoryException {

for (int i = 0; i < users.length; i++) {
final String id = NameGenerator.generate("user", Constants.ID_LENGTH);
final String name = "user_name-" + i;
final String name = NAME_PREFIX + i;
final String email = name + "@eclipse.org";
final String password = NameGenerator.generate("", Constants.PASSWORD_LENGTH);
final List<String> aliases = new ArrayList<>(asList("google:" + name, "github:" + name));
Expand Down Expand Up @@ -418,6 +423,72 @@ public void shouldReturnUserWithNullPasswordWhenGetAllUser() throws Exception {
.count(), users.length);
}

@Test(expectedExceptions = NullPointerException.class)
public void throwsNpeWhenGettingByNamePartWithNullEmailPart() throws Exception {
userDao.getByNamePart(null, 0, 0);
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void throwsIllegalArgExceptionWhenGettingByNamePartWithNegativeMaxItems() throws Exception {
userDao.getByNamePart(NAME_PREFIX, -1, 0);
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void throwsIllegalArgExceptionWhenGettingByNamePartWithNegativeSkipCount() throws Exception {
userDao.getByNamePart(NAME_PREFIX, 10, -1);
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void throwsIllegalArgExceptionWhenGettingByNamePartWithSkipCountThatGreaterThanLimit() throws Exception {
userDao.getByNamePart(NAME_PREFIX, 10, 0xffffffffL);
}

@Test
public void getsUsersByNamePart() throws Exception {
Set<UserImpl> actual = stream(users).map(u -> new UserImpl(u.getId(),
u.getEmail(),
u.getName(),
null,
u.getAliases())).collect(toSet());

Set<UserImpl> expect = new HashSet<>(userDao.getByNamePart(NAME_PREFIX, users.length, 0).getItems());

assertEquals(actual, expect);
}

@Test(expectedExceptions = NullPointerException.class)
public void throwsNpeWhenGettingByEmailPartWithNullEmailPart() throws Exception {
userDao.getByEmailPart(null, 0, 0);
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void throwsIllegalArgExceptionWhenGettingByEmailPartWithNegativeMaxItems() throws Exception {
userDao.getByEmailPart(NAME_PREFIX, -1, 0);
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void throwsIllegalArgExceptionWhenGettingByEmailPartWithNegativeSkipCount() throws Exception {
userDao.getByEmailPart(NAME_PREFIX, 10, -1);
}

@Test(expectedExceptions = IllegalArgumentException.class)
public void throwsIllegalArgExceptionWhenGettingByEmailPartWithSkipCountThatGreaterThanLimit() throws Exception {
userDao.getByEmailPart(NAME_PREFIX, 10, 0xffffffffL);
}

@Test
public void getsUsersByEmailPart() throws Exception {
Set<UserImpl> actual = stream(users).map(u -> new UserImpl(u.getId(),
u.getEmail(),
u.getName(),
null,
u.getAliases())).collect(toSet());

Set<UserImpl> expect = new HashSet<>(userDao.getByEmailPart(NAME_PREFIX, users.length, 0).getItems());

assertEquals(actual, expect);
}

private static void assertEqualsNoPassword(User actual, User expected) {
assertNotNull(actual, "Expected not-null user");
assertEquals(actual.getId(), expected.getId());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--
-- [2012] - [2017] Codenvy, S.A.
-- All Rights Reserved.
--
-- NOTICE: All information contained herein is, and remains
-- the property of Codenvy S.A. and its suppliers,
-- if any. The intellectual and technical concepts contained
-- herein are proprietary to Codenvy S.A.
-- and its suppliers and may be covered by U.S. and Foreign Patents,
-- patents in process, and are protected by trade secret or copyright law.
-- Dissemination of this information or reproduction of this material
-- is strictly forbidden unless prior written permission is obtained
-- from Codenvy S.A..
--

Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--
-- [2012] - [2017] Codenvy, S.A.
-- All Rights Reserved.
--
-- NOTICE: All information contained herein is, and remains
-- the property of Codenvy S.A. and its suppliers,
-- if any. The intellectual and technical concepts contained
-- herein are proprietary to Codenvy S.A.
-- and its suppliers and may be covered by U.S. and Foreign Patents,
-- patents in process, and are protected by trade secret or copyright law.
-- Dissemination of this information or reproduction of this material
-- is strictly forbidden unless prior written permission is obtained
-- from Codenvy S.A..
--
CREATE EXTENSION IF NOT EXISTS pg_trgm;
CREATE INDEX index_user_lower_email ON usr USING GIN (LOWER(email) gin_trgm_ops);
CREATE INDEX index_user_lower_name ON usr USING GIN (LOWER(name) gin_trgm_ops);

0 comments on commit a15f3db

Please sign in to comment.