Improving user search REST API v2
This commit is contained in:
parent
f56bd76d9f
commit
e141b7632d
|
@ -20,6 +20,7 @@ package org.apache.archiva.redback.rest.api.services.v2;
|
|||
*/
|
||||
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.headers.Header;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Content;
|
||||
|
@ -54,6 +55,7 @@ import javax.ws.rs.PathParam;
|
|||
import javax.ws.rs.Produces;
|
||||
import javax.ws.rs.QueryParam;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import static javax.ws.rs.core.MediaType.APPLICATION_JSON;
|
||||
import static org.apache.archiva.redback.rest.api.Constants.DEFAULT_PAGE_LIMIT;
|
||||
|
@ -97,6 +99,13 @@ public interface UserService
|
|||
@Produces( { APPLICATION_JSON } )
|
||||
@RedbackAuthorization( permissions = RedbackRoleConstants.USER_MANAGEMENT_USER_LIST_OPERATION )
|
||||
@Operation( summary = "Returns all users defined. The result is paged.",
|
||||
parameters = {
|
||||
@Parameter(name = "q", description = "Search term"),
|
||||
@Parameter(name = "offset", description = "The offset of the first element returned"),
|
||||
@Parameter(name = "limit", description = "Maximum number of items to return in the response"),
|
||||
@Parameter(name = "orderBy", description = "List of attribute used for sorting (user_id, fullName, email, created"),
|
||||
@Parameter(name = "order", description = "The sort order. Either ascending (asc) or descending (desc)")
|
||||
},
|
||||
security = {
|
||||
@SecurityRequirement(
|
||||
name = RedbackRoleConstants.USER_MANAGEMENT_USER_LIST_OPERATION
|
||||
|
@ -111,8 +120,11 @@ public interface UserService
|
|||
content = @Content(mediaType = APPLICATION_JSON, schema = @Schema(implementation = RedbackRestError.class )) )
|
||||
}
|
||||
)
|
||||
PagedResult<UserInfo> getUsers( @QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset,
|
||||
@QueryParam( "limit" ) @DefaultValue( value = DEFAULT_PAGE_LIMIT ) Integer limit)
|
||||
PagedResult<UserInfo> getUsers( @QueryParam("q") @DefaultValue( "" ) String searchTerm,
|
||||
@QueryParam( "offset" ) @DefaultValue( "0" ) Integer offset,
|
||||
@QueryParam( "limit" ) @DefaultValue( value = DEFAULT_PAGE_LIMIT ) Integer limit,
|
||||
@QueryParam( "orderBy") @DefaultValue( "id" ) List<String> orderBy,
|
||||
@QueryParam("order") @DefaultValue( "asc" ) String order)
|
||||
throws RedbackServiceException;
|
||||
|
||||
@Path( "" )
|
||||
|
|
|
@ -70,6 +70,7 @@ import org.apache.archiva.redback.system.SecuritySystem;
|
|||
import org.apache.archiva.redback.users.UserManager;
|
||||
import org.apache.archiva.redback.users.UserManagerException;
|
||||
import org.apache.archiva.redback.users.UserNotFoundException;
|
||||
import org.apache.archiva.redback.users.UserQuery;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
@ -90,9 +91,16 @@ import java.util.ArrayList;
|
|||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.BiPredicate;
|
||||
import java.util.function.Function;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service( "v2.userService#rest" )
|
||||
|
@ -105,6 +113,22 @@ public class DefaultUserService
|
|||
private static final String VALID_USERNAME_CHARS = "[a-zA-Z_0-9\\-.@]*";
|
||||
private static final String[] INVALID_CREATE_USER_NAMES = { "admin", "guest", "me" };
|
||||
|
||||
private static final String[] DEFAULT_SEARCH_FIELDS = {"user_id", "fullName", "email"};
|
||||
private static final Map<String, BiPredicate<String, org.apache.archiva.redback.users.User>> FILTER_MAP = new HashMap<>( );
|
||||
private static final Map<String,Comparator<org.apache.archiva.redback.users.User>> ORDER_MAP = new HashMap<>( );
|
||||
static {
|
||||
ORDER_MAP.put( "id", Comparator.comparing( org.apache.archiva.redback.users.User::getId ) );
|
||||
ORDER_MAP.put( "user_id", Comparator.comparing( org.apache.archiva.redback.users.User::getUsername ) );
|
||||
ORDER_MAP.put( "fullName", Comparator.comparing( org.apache.archiva.redback.users.User::getFullName) );
|
||||
ORDER_MAP.put( "email", Comparator.comparing( org.apache.archiva.redback.users.User::getEmail) );
|
||||
ORDER_MAP.put( "created", Comparator.comparing( org.apache.archiva.redback.users.User::getAccountCreationDate) );
|
||||
|
||||
FILTER_MAP.put( "user_id", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getUsername( ), q ) );
|
||||
FILTER_MAP.put( "fullName", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getFullName( ), q ) );
|
||||
FILTER_MAP.put( "email", ( String q, org.apache.archiva.redback.users.User u ) -> StringUtils.containsIgnoreCase( u.getEmail( ), q ) );
|
||||
}
|
||||
|
||||
|
||||
private UserManager userManager;
|
||||
|
||||
private SecuritySystem securitySystem;
|
||||
|
@ -333,26 +357,66 @@ public class DefaultUserService
|
|||
}
|
||||
}
|
||||
|
||||
Comparator<org.apache.archiva.redback.users.User> getAttributeComparator(String attributeName) {
|
||||
return ORDER_MAP.get( attributeName );
|
||||
}
|
||||
|
||||
Comparator<org.apache.archiva.redback.users.User> getComparator( List<String> orderBy, boolean ascending) {
|
||||
if (ascending)
|
||||
{
|
||||
return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ) ).reduce( Comparator::thenComparing ).get( );
|
||||
} else {
|
||||
return orderBy.stream( ).map( ( String name ) -> getAttributeComparator( name ).reversed() ).reduce( Comparator::thenComparing ).get( );
|
||||
}
|
||||
}
|
||||
|
||||
static Predicate<org.apache.archiva.redback.users.User> getFilter(final String attribute, final String queryToken) {
|
||||
if (FILTER_MAP.containsKey( attribute ))
|
||||
{
|
||||
return ( org.apache.archiva.redback.users.User u ) -> FILTER_MAP.get( attribute ).test( queryToken, u );
|
||||
} else {
|
||||
return Arrays.stream( DEFAULT_SEARCH_FIELDS )
|
||||
.map( att -> getFilter( att, queryToken ) ).reduce( Predicate::or ).get( );
|
||||
}
|
||||
}
|
||||
|
||||
Predicate<org.apache.archiva.redback.users.User> getUserFilter(String queryTerms) {
|
||||
return Arrays.stream( queryTerms.split( "\\s+" ) )
|
||||
.map( s -> {
|
||||
if ( s.contains( ":" ) )
|
||||
{
|
||||
String attr = StringUtils.substringBefore( s, ":" );
|
||||
String term = StringUtils.substringAfter( s, ":" );
|
||||
return getFilter( attr, term );
|
||||
}
|
||||
else
|
||||
{
|
||||
return Arrays.stream( DEFAULT_SEARCH_FIELDS )
|
||||
.map( att -> getFilter( att, s ) ).reduce( Predicate::or ).get( );
|
||||
}
|
||||
}
|
||||
).reduce( Predicate::or ).get();
|
||||
}
|
||||
|
||||
@Override
|
||||
public PagedResult<UserInfo> getUsers(Integer offset,
|
||||
Integer limit)
|
||||
public PagedResult<UserInfo> getUsers(String q, Integer offset,
|
||||
Integer limit, List<String> orderBy, String order)
|
||||
throws RedbackServiceException
|
||||
{
|
||||
boolean ascending = !"desc".equals( order );
|
||||
try
|
||||
{
|
||||
List<? extends org.apache.archiva.redback.users.User> users = userManager.getUsers();
|
||||
if (offset>=users.size()) {
|
||||
return new PagedResult<>( users.size( ), offset, limit, Collections.emptyList( ) );
|
||||
}
|
||||
int endIndex = PagingHelper.getLastIndex( offset, limit, users.size( ) );
|
||||
List<? extends org.apache.archiva.redback.users.User> resultList = users.subList( offset, endIndex );
|
||||
List<UserInfo> simpleUsers = new ArrayList<>( resultList.size() );
|
||||
|
||||
for ( org.apache.archiva.redback.users.User user : resultList )
|
||||
{
|
||||
simpleUsers.add( getRestUser( user ) );
|
||||
}
|
||||
return new PagedResult<>( users.size( ), offset, limit, simpleUsers );
|
||||
// UserQuery does not work here, because the configurable user manager does only return the query for
|
||||
// the first user manager in the list. So we have to fetch the whole user list
|
||||
List<? extends org.apache.archiva.redback.users.User> rawUsers =userManager.getUsers( );
|
||||
Predicate<org.apache.archiva.redback.users.User> filter = getUserFilter( q );
|
||||
long size = rawUsers.stream().filter(filter ).count();
|
||||
List<UserInfo> users = rawUsers.stream( )
|
||||
.filter( filter )
|
||||
.sorted( getComparator( orderBy, ascending ) ).skip( offset ).limit( limit )
|
||||
.map( user -> getRestUser( user ) )
|
||||
.collect( Collectors.toList( ) );
|
||||
return new PagedResult<>( (int)size, offset, limit, users );
|
||||
}
|
||||
catch ( UserManagerException e )
|
||||
{
|
||||
|
|
|
@ -81,10 +81,6 @@ public class NativeUserServiceTest extends AbstractNativeRestServices
|
|||
.when( ).get( ).then( ).statusCode( 200 ).extract( ).response( );
|
||||
assertNotNull( response );
|
||||
List<User> userData = response.body( ).jsonPath( ).getList( "data", User.class );
|
||||
for ( User user : userData )
|
||||
{
|
||||
System.out.println( user.getId( ) + " " + user.getFullName( ) );
|
||||
}
|
||||
assertNotNull( userData );
|
||||
assertEquals( 2, userData.size( ) );
|
||||
assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
|
||||
|
@ -92,6 +88,97 @@ public class NativeUserServiceTest extends AbstractNativeRestServices
|
|||
assertEquals( Integer.valueOf( 2 ), response.body( ).jsonPath( ).get( "pagination.totalCount" ) );
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
void getMultipleUsers( )
|
||||
{
|
||||
int userNum = 25;
|
||||
String token = getAdminToken( );
|
||||
try
|
||||
{
|
||||
for ( int i = 0; i < userNum; i++ )
|
||||
{
|
||||
String suffix = String.format( "%03d", i );
|
||||
Map<String, Object> jsonAsMap = new HashMap<>( );
|
||||
jsonAsMap.put( "user_id", "aragorn" + suffix );
|
||||
jsonAsMap.put( "email", "aragorn" + suffix+ "@lordoftherings.org" );
|
||||
jsonAsMap.put( "fullName", "Aragorn King of Gondor " + i );
|
||||
jsonAsMap.put( "password", "pAssw0rD" );
|
||||
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
|
||||
.body( jsonAsMap )
|
||||
.when( )
|
||||
.post( )
|
||||
.then( ).statusCode( 201 ).extract( ).response( );
|
||||
}
|
||||
Map<String, String> params = new HashMap<>( );
|
||||
Response response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
|
||||
.when( ).get( ).then( ).statusCode( 200 ).extract( ).response( );
|
||||
assertNotNull( response );
|
||||
List<User> userData = response.body( ).jsonPath( ).getList( "data", User.class );
|
||||
for(User user : userData) {
|
||||
System.out.println( "User " + user.getUserId( ) );
|
||||
}
|
||||
assertNotNull( userData );
|
||||
assertEquals( "admin", userData.get( 0 ).getUserId( ) );
|
||||
assertEquals( userNum+2, userData.size( ) );
|
||||
assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
|
||||
assertEquals( Integer.valueOf( 1000 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
|
||||
assertEquals( Integer.valueOf( userNum+2 ), response.body( ).jsonPath( ).get( "pagination.totalCount" ) );
|
||||
|
||||
params = new HashMap<>( );
|
||||
params.put( "limit", Integer.toString( 10 ) );
|
||||
params.put( "offset", Integer.toString( 1 ) );
|
||||
response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
|
||||
.when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( );
|
||||
userData = response.body( ).jsonPath( ).getList( "data", User.class );
|
||||
assertNotNull( userData );
|
||||
assertEquals( "aragorn000", userData.get( 0 ).getUserId( ) );
|
||||
assertEquals( "aragorn009", userData.get( 9 ).getUserId( ) );
|
||||
assertEquals( 10, userData.size( ) );
|
||||
assertEquals( Integer.valueOf( 1 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
|
||||
assertEquals( Integer.valueOf( 10 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
|
||||
assertEquals( Integer.valueOf( userNum+2 ), response.body( ).jsonPath( ).get( "pagination.totalCount" ) );
|
||||
|
||||
params = new HashMap<>( );
|
||||
params.put( "limit", Integer.toString( 10 ) );
|
||||
params.put( "offset", Integer.toString( 0 ) );
|
||||
params.put( "order", "desc" );
|
||||
response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
|
||||
.when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( );
|
||||
userData = response.body( ).jsonPath( ).getList( "data", User.class );
|
||||
assertNotNull( userData );
|
||||
assertEquals( "guest", userData.get( 0 ).getUserId( ) );
|
||||
assertEquals( "aragorn016", userData.get( 9 ).getUserId( ) );
|
||||
assertEquals( 10, userData.size( ) );
|
||||
assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
|
||||
assertEquals( Integer.valueOf( 10 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
|
||||
assertEquals( Integer.valueOf( userNum+2 ), response.body( ).jsonPath( ).get( "pagination.totalCount" ) );
|
||||
|
||||
params = new HashMap<>( );
|
||||
params.put( "limit", Integer.toString( 10 ) );
|
||||
params.put( "offset", Integer.toString( 0 ) );
|
||||
params.put( "order", "asc" );
|
||||
params.put( "q", "015" );
|
||||
response = given( ).spec( getRequestSpec( token ) ).contentType( JSON )
|
||||
.when( ).params( params ).get( ).then( ).statusCode( 200 ).extract( ).response( );
|
||||
userData = response.body( ).jsonPath( ).getList( "data", User.class );
|
||||
assertNotNull( userData );
|
||||
assertEquals( "aragorn015", userData.get( 0 ).getUserId( ) );
|
||||
assertEquals( 1, userData.size( ) );
|
||||
assertEquals( Integer.valueOf( 0 ), response.body( ).jsonPath( ).get( "pagination.offset" ) );
|
||||
assertEquals( Integer.valueOf( 10 ), response.body( ).jsonPath( ).get( "pagination.limit" ) );
|
||||
assertEquals( Integer.valueOf( 1 ), response.body( ).jsonPath( ).get( "pagination.totalCount" ) );
|
||||
|
||||
} finally {
|
||||
for (int i=0; i<userNum; i++)
|
||||
{
|
||||
String suffix = String.format( "%03d", i );
|
||||
given( ).spec( getRequestSpec( token ) ).contentType( JSON )
|
||||
.when( ).delete( "aragorn"+suffix ).then( ).statusCode( 200 );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void getUsersWithoutLogin( )
|
||||
{
|
||||
|
|
|
@ -123,7 +123,7 @@ public class UserServiceTest
|
|||
{
|
||||
String adminHeader = getAdminAuthzHeader( );
|
||||
UserService userService = getUserService( adminHeader );
|
||||
PagedResult<org.apache.archiva.redback.rest.api.model.v2.UserInfo> users = userService.getUsers( 0, Integer.MAX_VALUE );
|
||||
PagedResult<org.apache.archiva.redback.rest.api.model.v2.UserInfo> users = userService.getUsers( "", 0, Integer.MAX_VALUE, Collections.emptyList(), "asc" );
|
||||
assertNotNull( users );
|
||||
assertFalse( users.getData().isEmpty( ) );
|
||||
}
|
||||
|
@ -135,7 +135,7 @@ public class UserServiceTest
|
|||
assertThrows( ForbiddenException.class, ( ) -> {
|
||||
try
|
||||
{
|
||||
userService.getUsers( 0, Integer.MAX_VALUE);
|
||||
userService.getUsers( "", 0, Integer.MAX_VALUE, Collections.emptyList(), "asc");
|
||||
}
|
||||
catch ( ForbiddenException e )
|
||||
{
|
||||
|
|
|
@ -24,6 +24,12 @@ import java.util.List;
|
|||
/**
|
||||
* User Manager Interface
|
||||
*
|
||||
* @TODO: Add Streaming Methods
|
||||
* @TODO: Improve query to allow multiple sort values
|
||||
* @TODO: Improve query to avoid UnsupportedOperationExceptions (e.g. in LDAP or combined user manager)
|
||||
* @TODO: Improve query to allow upper/lowercase and substring queries for all user managers
|
||||
* @TODO: Add method for total count of users
|
||||
*
|
||||
* @author Jason van Zyl
|
||||
* @author <a href="mailto:joakim@erdfelt.com">Joakim Erdfelt</a>
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue