NIHVIVO-2279 Implement searching and role filtering.
This commit is contained in:
parent
3ad03c0767
commit
9682b91f94
4 changed files with 131 additions and 39 deletions
|
@ -2,9 +2,11 @@
|
||||||
|
|
||||||
package edu.cornell.mannlib.vitro.webapp.controller.accounts;
|
package edu.cornell.mannlib.vitro.webapp.controller.accounts;
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* On what basis are we selecting user accounts?
|
* On what basis are we selecting user accounts?
|
||||||
|
*
|
||||||
|
* Search terms are matched against email, and against firstName combined with
|
||||||
|
* lastName. Searches are case-insensitive.
|
||||||
*/
|
*/
|
||||||
public class UserAccountsSelectionCriteria {
|
public class UserAccountsSelectionCriteria {
|
||||||
public static final int DEFAULT_ACCOUNTS_PER_PAGE = 25;
|
public static final int DEFAULT_ACCOUNTS_PER_PAGE = 25;
|
||||||
|
|
|
@ -36,6 +36,7 @@ public class UserAccountsSelector {
|
||||||
|
|
||||||
private static final String PREFIX_LINES = ""
|
private static final String PREFIX_LINES = ""
|
||||||
+ "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> \n"
|
+ "PREFIX rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> \n"
|
||||||
|
+ "PREFIX fn: <http://www.w3.org/2005/xpath-functions#> \n"
|
||||||
+ "PREFIX auth: <http://vitro.mannlib.cornell.edu/ns/vitro/authorization#> \n";
|
+ "PREFIX auth: <http://vitro.mannlib.cornell.edu/ns/vitro/authorization#> \n";
|
||||||
|
|
||||||
private static final String ALL_VARIABLES = "?uri ?email ?firstName ?lastName ?pwd ?expire ?count ?status";
|
private static final String ALL_VARIABLES = "?uri ?email ?firstName ?lastName ?pwd ?expire ?count ?status";
|
||||||
|
@ -59,6 +60,7 @@ public class UserAccountsSelector {
|
||||||
+ "SELECT count(DISTINCT %countVariable%) \n" //
|
+ "SELECT count(DISTINCT %countVariable%) \n" //
|
||||||
+ "WHERE {\n" //
|
+ "WHERE {\n" //
|
||||||
+ " %requiredClauses% \n" //
|
+ " %requiredClauses% \n" //
|
||||||
|
+ " %optionalClauses% \n" //
|
||||||
+ " %filterClauses% \n" //
|
+ " %filterClauses% \n" //
|
||||||
+ "} \n";
|
+ "} \n";
|
||||||
|
|
||||||
|
@ -71,6 +73,13 @@ public class UserAccountsSelector {
|
||||||
|
|
||||||
private static final Syntax SYNTAX = Syntax.syntaxARQ;
|
private static final Syntax SYNTAX = Syntax.syntaxARQ;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the user enters any of these characters in a search term, escape it
|
||||||
|
* with a backslash.
|
||||||
|
*/
|
||||||
|
private static final char[] REGEX_SPECIAL_CHARACTERS = "[\\^$.|?*+()]"
|
||||||
|
.toCharArray();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Convenience method.
|
* Convenience method.
|
||||||
*/
|
*/
|
||||||
|
@ -124,6 +133,7 @@ public class UserAccountsSelector {
|
||||||
.replace("%prefixes%", PREFIX_LINES)
|
.replace("%prefixes%", PREFIX_LINES)
|
||||||
.replace("%countVariable%", COUNT_VARIABLE)
|
.replace("%countVariable%", COUNT_VARIABLE)
|
||||||
.replace("%requiredClauses%", requiredClauses())
|
.replace("%requiredClauses%", requiredClauses())
|
||||||
|
.replace("%optionalClauses%", optionalClauses())
|
||||||
.replace("%filterClauses%", filterClauses());
|
.replace("%filterClauses%", filterClauses());
|
||||||
log.debug("count query: " + qString);
|
log.debug("count query: " + qString);
|
||||||
|
|
||||||
|
@ -163,8 +173,52 @@ public class UserAccountsSelector {
|
||||||
}
|
}
|
||||||
|
|
||||||
private String filterClauses() {
|
private String filterClauses() {
|
||||||
log.warn("UserAccountsSelector.filterClauses() not implemented.");
|
String filters = "";
|
||||||
return "";
|
|
||||||
|
String roleFilterUri = criteria.getRoleFilterUri();
|
||||||
|
String searchTerm = criteria.getSearchTerm();
|
||||||
|
|
||||||
|
if (!roleFilterUri.isEmpty()) {
|
||||||
|
String clean = escapeForRegex(roleFilterUri);
|
||||||
|
filters += "OPTIONAL { ?uri auth:hasPermissionSet ?role } \n"
|
||||||
|
+ " FILTER (REGEX(str(?role), '" + clean + "'))";
|
||||||
|
}
|
||||||
|
|
||||||
|
if ((!roleFilterUri.isEmpty()) && (!searchTerm.isEmpty())) {
|
||||||
|
filters += " \n ";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!searchTerm.isEmpty()) {
|
||||||
|
String clean = escapeForRegex(searchTerm);
|
||||||
|
filters += "FILTER ("
|
||||||
|
+ ("REGEX(?email, '" + clean + "', 'i')")
|
||||||
|
+ " || "
|
||||||
|
+ ("REGEX(fn:concat(?firstName, ' ', ?lastName), '" + clean + "', 'i')")
|
||||||
|
+ ")";
|
||||||
|
}
|
||||||
|
|
||||||
|
return filters;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape any regex special characters in the string.
|
||||||
|
*
|
||||||
|
* Note that the SPARQL
|
||||||
|
* parser requires two backslashes, in order to pass a single backslash to
|
||||||
|
* the REGEX function.
|
||||||
|
*/
|
||||||
|
private String escapeForRegex(String raw) {
|
||||||
|
StringBuilder clean = new StringBuilder();
|
||||||
|
outer: for (char c : raw.toCharArray()) {
|
||||||
|
for (char special : REGEX_SPECIAL_CHARACTERS) {
|
||||||
|
if (c == special) {
|
||||||
|
clean.append('\\').append('\\').append(c);
|
||||||
|
continue outer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
clean.append(c);
|
||||||
|
}
|
||||||
|
return clean.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sort as desired, and within ties, sort by EMail address. */
|
/** Sort as desired, and within ties, sort by EMail address. */
|
||||||
|
|
|
@ -3,9 +3,7 @@
|
||||||
package edu.cornell.mannlib.vitro.webapp.controller.accounts;
|
package edu.cornell.mannlib.vitro.webapp.controller.accounts;
|
||||||
|
|
||||||
import static edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsOrdering.DEFAULT_ORDERING;
|
import static edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsOrdering.DEFAULT_ORDERING;
|
||||||
import static org.junit.Assert.*;
|
|
||||||
import static org.junit.Assert.assertEquals;
|
import static org.junit.Assert.assertEquals;
|
||||||
import static org.junit.Assert.fail;
|
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
|
@ -26,10 +24,6 @@ import com.hp.hpl.jena.rdf.model.ModelFactory;
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
|
import edu.cornell.mannlib.vitro.testing.AbstractTestClass;
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
|
import edu.cornell.mannlib.vitro.webapp.beans.UserAccount;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsOrdering;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsSelection;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsSelectionCriteria;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsSelector;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsOrdering.Direction;
|
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsOrdering.Direction;
|
||||||
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsOrdering.Field;
|
import edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsOrdering.Field;
|
||||||
|
|
||||||
|
@ -39,6 +33,8 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
||||||
*/
|
*/
|
||||||
private static final String N3_DATA_FILENAME = "UserAccountsSelectorTest.n3";
|
private static final String N3_DATA_FILENAME = "UserAccountsSelectorTest.n3";
|
||||||
|
|
||||||
|
private static final String NS_MINE = "http://vivo.mydomain.edu/individual/";
|
||||||
|
|
||||||
private static OntModel ontModel;
|
private static OntModel ontModel;
|
||||||
|
|
||||||
@BeforeClass
|
@BeforeClass
|
||||||
|
@ -67,7 +63,6 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
@Test(expected = NullPointerException.class)
|
@Test(expected = NullPointerException.class)
|
||||||
@SuppressWarnings("unused")
|
|
||||||
public void modelIsNull() {
|
public void modelIsNull() {
|
||||||
UserAccountsSelector.select(null,
|
UserAccountsSelector.select(null,
|
||||||
criteria(10, 1, DEFAULT_ORDERING, "", ""));
|
criteria(10, 1, DEFAULT_ORDERING, "", ""));
|
||||||
|
@ -91,7 +86,7 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
||||||
assertEquals("uri", "http://vivo.mydomain.edu/individual/user10",
|
assertEquals("uri", "http://vivo.mydomain.edu/individual/user10",
|
||||||
acct.getUri());
|
acct.getUri());
|
||||||
assertEquals("email", "email@jones.edu", acct.getEmailAddress());
|
assertEquals("email", "email@jones.edu", acct.getEmailAddress());
|
||||||
assertEquals("firstName", "Brian", acct.getFirstName());
|
assertEquals("firstName", "Bob", acct.getFirstName());
|
||||||
assertEquals("lastName", "Caruso", acct.getLastName());
|
assertEquals("lastName", "Caruso", acct.getLastName());
|
||||||
assertEquals("password", "garbage", acct.getMd5Password());
|
assertEquals("password", "garbage", acct.getMd5Password());
|
||||||
assertEquals("expires", 1100234965897L, acct.getPasswordLinkExpires());
|
assertEquals("expires", 1100234965897L, acct.getPasswordLinkExpires());
|
||||||
|
@ -196,8 +191,8 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void sortByStatusAscending() {
|
public void sortByStatusAscending() {
|
||||||
UserAccountsOrdering orderBy = new UserAccountsOrdering(
|
UserAccountsOrdering orderBy = new UserAccountsOrdering(Field.STATUS,
|
||||||
Field.STATUS, Direction.ASCENDING);
|
Direction.ASCENDING);
|
||||||
selectOnCriteria(3, 1, orderBy, "", "");
|
selectOnCriteria(3, 1, orderBy, "", "");
|
||||||
// user07 has no status: collates as least value.
|
// user07 has no status: collates as least value.
|
||||||
assertSelectedUris(10, "user07", "user01", "user04");
|
assertSelectedUris(10, "user07", "user01", "user04");
|
||||||
|
@ -205,8 +200,8 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void sortByStatusDescending() {
|
public void sortByStatusDescending() {
|
||||||
UserAccountsOrdering orderBy = new UserAccountsOrdering(
|
UserAccountsOrdering orderBy = new UserAccountsOrdering(Field.STATUS,
|
||||||
Field.STATUS, Direction.DESCENDING);
|
Direction.DESCENDING);
|
||||||
selectOnCriteria(3, 1, orderBy, "", "");
|
selectOnCriteria(3, 1, orderBy, "", "");
|
||||||
assertSelectedUris(10, "user02", "user03", "user06");
|
assertSelectedUris(10, "user02", "user03", "user06");
|
||||||
}
|
}
|
||||||
|
@ -228,28 +223,75 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
||||||
assertSelectedUris(10, "user10", "user04", "user08");
|
assertSelectedUris(10, "user10", "user04", "user08");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// filtering tests
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterAgainstRole1() {
|
||||||
|
setLoggerLevel(UserAccountsSelector.class, Level.DEBUG);
|
||||||
|
selectOnCriteria(20, 1, DEFAULT_ORDERING, NS_MINE + "role1", "");
|
||||||
|
assertSelectedUris(6, "user01", "user02", "user03", "user05", "user06",
|
||||||
|
"user09");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void filterAgainstNoSuchRole() {
|
||||||
|
selectOnCriteria(20, 1, DEFAULT_ORDERING, "BogusRole", "");
|
||||||
|
assertSelectedUris(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// search tests
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchTermFoundInAllThreeFields() {
|
||||||
|
setLoggerLevel(UserAccountsSelector.class, Level.DEBUG);
|
||||||
|
selectOnCriteria(20, 1, DEFAULT_ORDERING, "", "bob");
|
||||||
|
assertSelectedUris(3, "user02", "user05", "user10");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchTermNotFound() {
|
||||||
|
setLoggerLevel(UserAccountsSelector.class, Level.DEBUG);
|
||||||
|
selectOnCriteria(20, 1, DEFAULT_ORDERING, "", "bogus");
|
||||||
|
assertSelectedUris(0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Test plan
|
* If the special characters were allowed into the Regex, this would have 3
|
||||||
*
|
* matches. If they are escaped properly, it will have none.
|
||||||
* <pre>
|
|
||||||
* -- searching (match against first, last, email)
|
|
||||||
* app=10, pi=1, orderBy=email,A, search=bob
|
|
||||||
* app=10, pi=1, orderBy=email,A, search=nomatch
|
|
||||||
*
|
|
||||||
* -- filter
|
|
||||||
* app=10, pi=1, orderBy=email,A, filter=role1Uri
|
|
||||||
* app=10, pi=1, orderBy=email,A, filter=noSuchRole
|
|
||||||
*
|
|
||||||
* -- combine
|
|
||||||
* app=10, pi=1, orderBy=email,A, search=bob, filter=role1Uri;
|
|
||||||
* app=2, pi=2, orderBy=lastName,D, search=bob, filter=role1Uri;
|
|
||||||
* </pre>
|
|
||||||
*/
|
*/
|
||||||
|
@Test
|
||||||
|
public void searchTermContainsSpecialRegexCharacters() {
|
||||||
|
setLoggerLevel(UserAccountsSelector.class, Level.DEBUG);
|
||||||
|
selectOnCriteria(20, 1, DEFAULT_ORDERING, "", "b.b");
|
||||||
|
assertSelectedUris(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
// combination tests
|
||||||
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchWithFilter() {
|
||||||
|
selectOnCriteria(20, 1, DEFAULT_ORDERING, NS_MINE + "role1", "bob");
|
||||||
|
assertSelectedUris(2, "user02", "user05");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void searchWithFilterPaginatedWithFunkySortOrder() {
|
||||||
|
selectOnCriteria(1, 2, new UserAccountsOrdering(Field.STATUS,
|
||||||
|
Direction.ASCENDING), NS_MINE + "role1", "bob");
|
||||||
|
assertSelectedUris(2, "user02");
|
||||||
|
}
|
||||||
|
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
// helper methods
|
// helper methods
|
||||||
// ----------------------------------------------------------------------
|
// ----------------------------------------------------------------------
|
||||||
|
|
||||||
|
/** Create a new criteria object */
|
||||||
private UserAccountsSelectionCriteria criteria(int accountsPerPage,
|
private UserAccountsSelectionCriteria criteria(int accountsPerPage,
|
||||||
int pageIndex, UserAccountsOrdering orderBy, String roleFilterUri,
|
int pageIndex, UserAccountsOrdering orderBy, String roleFilterUri,
|
||||||
String searchTerm) {
|
String searchTerm) {
|
||||||
|
@ -257,6 +299,7 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
||||||
orderBy, roleFilterUri, searchTerm);
|
orderBy, roleFilterUri, searchTerm);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Create a criteria object and select against it. */
|
||||||
private void selectOnCriteria(int accountsPerPage, int pageIndex,
|
private void selectOnCriteria(int accountsPerPage, int pageIndex,
|
||||||
UserAccountsOrdering orderBy, String roleFilterUri,
|
UserAccountsOrdering orderBy, String roleFilterUri,
|
||||||
String searchTerm) {
|
String searchTerm) {
|
||||||
|
@ -265,14 +308,7 @@ public class UserAccountsSelectorTest extends AbstractTestClass {
|
||||||
selection = UserAccountsSelector.select(ontModel, criteria);
|
selection = UserAccountsSelector.select(ontModel, criteria);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void assertExpectedCount(int expected) {
|
/** How many URIs should we expect, and which ones (local names only). */
|
||||||
int actual = selection.getResultCount();
|
|
||||||
assertEquals("count", expected, actual);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Give us just the list of local names from the URIs we should expect.
|
|
||||||
*/
|
|
||||||
private void assertSelectedUris(int resultCount, String... uris) {
|
private void assertSelectedUris(int resultCount, String... uris) {
|
||||||
assertEquals("result count", resultCount, selection.getResultCount());
|
assertEquals("result count", resultCount, selection.getResultCount());
|
||||||
|
|
||||||
|
|
|
@ -125,7 +125,7 @@ mydomain:user09
|
||||||
mydomain:user10
|
mydomain:user10
|
||||||
a auth:UserAccount ;
|
a auth:UserAccount ;
|
||||||
auth:emailAddress "email@jones.edu" ;
|
auth:emailAddress "email@jones.edu" ;
|
||||||
auth:firstName "Brian" ;
|
auth:firstName "Bob" ;
|
||||||
auth:lastName "Caruso" ;
|
auth:lastName "Caruso" ;
|
||||||
auth:md5password "garbage" ;
|
auth:md5password "garbage" ;
|
||||||
auth:passwordChangeExpires "1100234965897" ;
|
auth:passwordChangeExpires "1100234965897" ;
|
||||||
|
|
Loading…
Add table
Reference in a new issue