From 78f966eb0f9b3faac9259b051d719cf7d91ca2b4 Mon Sep 17 00:00:00 2001 From: j2blake Date: Wed, 4 May 2011 22:01:05 +0000 Subject: [PATCH] NIHVIVO-2279 First stab at UserAccount and UserAccountsSelector, etc. --- .../vitro/webapp/beans/UserAccount.java | 112 ++++++ .../UserAccountsOrdering.java | 48 +++ .../UserAccountsSelection.java | 40 ++ .../UserAccountsSelectionCriteria.java | 68 ++++ .../UserAccountsSelector.java | 353 ++++++++++++++++++ .../UserAccountsSelectionCriteriaTest.java | 49 +++ .../UserAccountsSelectorTest.java | 284 ++++++++++++++ .../UserAccountsSelectorTest.n3 | 146 ++++++++ 8 files changed, 1100 insertions(+) create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsOrdering.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelection.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectionCriteria.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelector.java create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectionCriteriaTest.java create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectorTest.java create mode 100644 webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectorTest.n3 diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java new file mode 100644 index 000000000..df428a959 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/beans/UserAccount.java @@ -0,0 +1,112 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.beans; + +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +/** + * Information about the account of a user. URI, email, password, etc. + */ +public class UserAccount { + public enum Status { + ACTIVE, INACTIVE + } + + private String uri = ""; + + private String emailAddress = ""; + private String firstName = ""; + private String lastName = ""; + + private String md5password = ""; + private long passwordChangeExpires = 0L; + private int loginCount = 0; + private Status status = Status.INACTIVE; + + private Set permissionSetUris = Collections.emptySet(); + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getEmailAddress() { + return emailAddress; + } + + public void setEmailAddress(String emailAddress) { + this.emailAddress = emailAddress; + } + + public String getFirstName() { + return firstName; + } + + public void setFirstName(String firstName) { + this.firstName = firstName; + } + + public String getLastName() { + return lastName; + } + + public void setLastName(String lastName) { + this.lastName = lastName; + } + + public String getMd5password() { + return md5password; + } + + public void setMd5password(String md5password) { + this.md5password = md5password; + } + + public long getPasswordChangeExpires() { + return passwordChangeExpires; + } + + public void setPasswordChangeExpires(long passwordChangeExpires) { + this.passwordChangeExpires = passwordChangeExpires; + } + + public int getLoginCount() { + return loginCount; + } + + public void setLoginCount(int loginCount) { + this.loginCount = loginCount; + } + + public Status getStatus() { + return status; + } + + public void setStatus(Status status) { + this.status = status; + } + + public Set getPermissionSetUris() { + return new HashSet(permissionSetUris); + } + + public void setPermissionSetUris(Collection permissionSetUris) { + this.permissionSetUris = new HashSet(permissionSetUris); + } + + @Override + public String toString() { + return "UserAccount[uri=" + uri + ", emailAddress=" + emailAddress + + ", firstName=" + firstName + ", lastName=" + lastName + + ", md5password=" + md5password + ", passwordChangeExpires=" + + passwordChangeExpires + ", loginCount=" + loginCount + + ", status=" + status + ", permissionSetUris=" + + permissionSetUris + "]"; + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsOrdering.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsOrdering.java new file mode 100644 index 000000000..8fdcd3128 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsOrdering.java @@ -0,0 +1,48 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement; + +/** + * How are the accounts to be sorted? + */ +public class UserAccountsOrdering { + public enum Direction { + ASCENDING("ASC"), DESCENDING("DESC"); + + public final String keyword; + + Direction(String keyword) { + this.keyword = keyword; + } + } + + public enum Field { + EMAIL("email"), FIRST_NAME("firstName"), LAST_NAME("lastName"), STATUS( + "status"), ROLE("ps"), LOGIN_COUNT("count"); + + public final String variableName; + + Field(String variableName) { + this.variableName = variableName; + } + } + + public static final UserAccountsOrdering DEFAULT_ORDERING = new UserAccountsOrdering( + Field.EMAIL, Direction.ASCENDING); + + private final Field field; + private final Direction direction; + + public UserAccountsOrdering(Field field, Direction direction) { + this.field = field; + this.direction = direction; + } + + public Field getField() { + return field; + } + + public Direction getDirection() { + return direction; + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelection.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelection.java new file mode 100644 index 000000000..42694a617 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelection.java @@ -0,0 +1,40 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; + +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; + +/** + * A group of accounts (might be empty), with the criteria that were used to + * select them. + */ +public class UserAccountsSelection { + private final UserAccountsSelectionCriteria criteria; + private final List userAccounts; + private final int resultCount; + + public UserAccountsSelection(UserAccountsSelectionCriteria criteria, + Collection userAccounts, int resultCount) { + this.criteria = criteria; + this.userAccounts = Collections + .unmodifiableList(new ArrayList(userAccounts)); + this.resultCount = resultCount; + } + + public UserAccountsSelectionCriteria getCriteria() { + return criteria; + } + + public List getUserAccounts() { + return userAccounts; + } + + public int getResultCount() { + return resultCount; + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectionCriteria.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectionCriteria.java new file mode 100644 index 000000000..7ad5fb7df --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectionCriteria.java @@ -0,0 +1,68 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement; + +/** + * On what basis are we selecting user accounts? + */ +public class UserAccountsSelectionCriteria { + /** How many accounts should we bring back, at most? */ + private final int accountsPerPage; + + /** What page are we on? (1-origin) */ + private final int pageIndex; + + /** How are they sorted? */ + private final UserAccountsOrdering orderBy; + + /** What role are we filtering by, if any? */ + private final String roleFilterUri; + + /** What term are we searching on, if any? */ + private final String searchTerm; + + public UserAccountsSelectionCriteria(int accountsPerPage, int pageIndex, + UserAccountsOrdering orderBy, String roleFilterUri, + String searchTerm) { + if (accountsPerPage <= 0) { + throw new IllegalArgumentException("accountsPerPage " + + "must be a positive integer, not " + accountsPerPage); + } + this.accountsPerPage = accountsPerPage; + + if (pageIndex <= 0) { + throw new IllegalArgumentException("pageIndex must be a " + + "non-negative integer, not " + pageIndex); + } + this.pageIndex = pageIndex; + + this.orderBy = nonNull(orderBy, UserAccountsOrdering.DEFAULT_ORDERING); + + this.roleFilterUri = nonNull(roleFilterUri, ""); + this.searchTerm = nonNull(searchTerm, ""); + } + + public int getAccountsPerPage() { + return accountsPerPage; + } + + public int getPageIndex() { + return pageIndex; + } + + public UserAccountsOrdering getOrderBy() { + return orderBy; + } + + public String getRoleFilterUri() { + return roleFilterUri; + } + + public String getSearchTerm() { + return searchTerm; + } + + private T nonNull(T t, T nullValue) { + return (t == null) ? nullValue : t; + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelector.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelector.java new file mode 100644 index 000000000..6d8c345d8 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelector.java @@ -0,0 +1,353 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.query.Query; +import com.hp.hpl.jena.query.QueryExecution; +import com.hp.hpl.jena.query.QueryExecutionFactory; +import com.hp.hpl.jena.query.QueryFactory; +import com.hp.hpl.jena.query.QuerySolution; +import com.hp.hpl.jena.query.ResultSet; +import com.hp.hpl.jena.query.Syntax; +import com.hp.hpl.jena.rdf.model.Literal; +import com.hp.hpl.jena.rdf.model.Resource; + +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount.Status; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement.UserAccountsOrdering.Field; + +/** + * Pull some UserAccounts from the model, based on a set of criteria. + */ +public class UserAccountsSelector { + private static final Log log = LogFactory + .getLog(UserAccountsSelector.class); + + private static final String PREFIX_LINES = "" + + "PREFIX rdf: \n" + + "PREFIX auth: \n"; + + private static final String ALL_VARIABLES = "?uri ?email ?firstName ?lastName ?pwd ?expire ?count ?status"; + + private static final String COUNT_VARIABLE = "?uri"; + + private static final String MAIN_QUERY_TEMPLATE = "" // + + "%prefixes% \n" // + + "SELECT DISTINCT %variables% \n" // + + "WHERE {\n" // + + " %requiredClauses% \n" // + + " %optionalClauses% \n" // + + " %filterClauses% \n" // + + "} \n" // + + "ORDER BY %ordering% \n" // + + "LIMIT %limit% \n" // + + "OFFSET %offset% \n"; + + private static final String COUNT_QUERY_TEMPLATE = "" // + + "%prefixes% \n" // + + "SELECT count(DISTINCT %countVariable%) \n" // + + "WHERE {\n" // + + " %requiredClauses% \n" // + + " %filterClauses% \n" // + + "} \n"; + + private static final String PERMISSIONS_QUERY_TEMPLATE = "" // + + "%prefixes% \n" // + + "SELECT ?ps \n" // + + "WHERE {\n" // + + " <%uri%> auth:hasPermissionSet ?ps \n" // + + "} \n"; + + private static final Syntax SYNTAX = Syntax.syntaxARQ; + + /** + * Convenience method. + */ + public static UserAccountsSelection select(OntModel userAccountsModel, + UserAccountsSelectionCriteria criteria) { + return new UserAccountsSelector(userAccountsModel, criteria).select(); + } + + private final OntModel model; + private final UserAccountsSelectionCriteria criteria; + + public UserAccountsSelector(OntModel userAccountsModel, + UserAccountsSelectionCriteria criteria) { + if (userAccountsModel == null) { + throw new NullPointerException("userAccountsModel may not be null."); + } + this.model = userAccountsModel; + + if (criteria == null) { + throw new NullPointerException("criteria may not be null."); + } + this.criteria = criteria; + } + + public UserAccountsSelection select() { + List accounts = queryForAccounts(); + int resultCount = queryForCount(); + queryToPopulatePermissionSets(accounts); + return new UserAccountsSelection(criteria, accounts, resultCount); + } + + private List queryForAccounts() { + String qString = MAIN_QUERY_TEMPLATE + .replace("%prefixes%", PREFIX_LINES) + .replace("%variables%", ALL_VARIABLES) + .replace("%requiredClauses%", requiredClauses()) + .replace("%optionalClauses%", optionalClauses()) + .replace("%filterClauses%", filterClauses()) + .replace("%ordering%", ordering()).replace("%limit%", limit()) + .replace("%offset%", offset()); + log.debug("main query: " + qString); + + List accounts = executeQuery(qString, + new MainQueryParser()); + log.debug("query returns: " + accounts); + return accounts; + } + + private int queryForCount() { + String qString = COUNT_QUERY_TEMPLATE + .replace("%prefixes%", PREFIX_LINES) + .replace("%countVariable%", COUNT_VARIABLE) + .replace("%requiredClauses%", requiredClauses()) + .replace("%filterClauses%", filterClauses()); + log.debug("count query: " + qString); + + int count = executeQuery(qString, new CountQueryParser()); + log.debug("result count: " + count); + return count; + } + + private void queryToPopulatePermissionSets(List accounts) { + for (UserAccount account : accounts) { + String uri = account.getUri(); + String qString = PERMISSIONS_QUERY_TEMPLATE.replace("%prefixes%", + PREFIX_LINES).replace("%uri%", uri); + log.debug("permissions query: " + qString); + + Collection permissions = executeQuery(qString, + new PermissionsQueryParser()); + log.debug("permissions for '" + uri + "': " + permissions); + account.setPermissionSetUris(permissions); + } + } + + /** If the result doesn't have URI and Email Address, we don't want it. */ + private String requiredClauses() { + return "?uri a auth:UserAccount ; \n" + + " auth:emailAddress ?email ."; + } + + /** If any of these fields are missing, show the result anyway. */ + private String optionalClauses() { + return "OPTIONAL { ?uri auth:firstName ?firstName } \n" + + " OPTIONAL { ?uri auth:lastName ?lastName } \n" + + " OPTIONAL { ?uri auth:md5password ?pwd } \n" + + " OPTIONAL { ?uri auth:passwordChangeExpires ?expire } \n" + + " OPTIONAL { ?uri auth:loginCount ?count } \n" + + " OPTIONAL { ?uri auth:status ?status }"; + } + + private String filterClauses() { + log.warn("UserAccountsSelector.filterClauses() not implemented."); + return ""; + } + + /** Sort as desired, and within ties, sort by EMail address. */ + private String ordering() { + UserAccountsOrdering orderBy = criteria.getOrderBy(); + String keyword = orderBy.getDirection().keyword; + String variable = orderBy.getField().variableName; + if (orderBy.getField() == Field.EMAIL) { + return keyword + "(?" + variable + ")"; + } else { + return keyword + "(?" + variable + ") ?" + Field.EMAIL.variableName; + } + } + + private String limit() { + return String.valueOf(criteria.getAccountsPerPage()); + } + + private String offset() { + int offset = criteria.getAccountsPerPage() + * (criteria.getPageIndex() - 1); + return String.valueOf(offset); + } + + private T executeQuery(String queryStr, QueryParser parser) { + QueryExecution qe = null; + try { + Query query = QueryFactory.create(queryStr, SYNTAX); + qe = QueryExecutionFactory.create(query, model); + return parser.parseResults(queryStr, qe.execSelect()); + } catch (Exception e) { + log.error("Failed to execute the query: " + queryStr, e); + return parser.defaultValue(); + } finally { + if (qe != null) { + qe.close(); + } + } + } + + private static abstract class QueryParser { + abstract T parseResults(String queryStr, ResultSet results); + + abstract T defaultValue(); + + protected String ifLiteralPresent(QuerySolution solution, + String variableName, String defaultValue) { + Literal literal = solution.getLiteral(variableName); + if (literal == null) { + return defaultValue; + } else { + return literal.getString(); + } + } + + protected long ifLongPresent(QuerySolution solution, + String variableName, long defaultValue) { + Literal literal = solution.getLiteral(variableName); + if (literal == null) { + return defaultValue; + } else { + return literal.getLong(); + } + } + + protected int ifIntPresent(QuerySolution solution, String variableName, + int defaultValue) { + Literal literal = solution.getLiteral(variableName); + if (literal == null) { + return defaultValue; + } else { + return literal.getInt(); + } + } + + } + + private static class MainQueryParser extends QueryParser> { + @Override + public List defaultValue() { + return Collections.emptyList(); + } + + @Override + public List parseResults(String queryStr, ResultSet results) { + List accounts = new ArrayList(); + while (results.hasNext()) { + try { + QuerySolution solution = results.next(); + UserAccount user = parseSolution(solution); + accounts.add(user); + } catch (Exception e) { + log.warn("Failed to parse the query result: " + queryStr, e); + } + } + return accounts; + } + + private UserAccount parseSolution(QuerySolution solution) { + UserAccount user = new UserAccount(); + user.setUri(getUriFromSolution(solution)); + user.setEmailAddress(solution.getLiteral("email").getString()); + user.setFirstName(ifLiteralPresent(solution, "firstName", "")); + user.setLastName(ifLiteralPresent(solution, "lastName", "")); + user.setMd5password(ifLiteralPresent(solution, "pwd", "")); + user.setPasswordChangeExpires(ifLongPresent(solution, "expire", 0L)); + user.setLoginCount(ifIntPresent(solution, "count", 0)); + user.setStatus(parseStatus(solution, "status", Status.INACTIVE)); + return user; + } + + private Status parseStatus(QuerySolution solution, String variableName, + Status defaultValue) { + Literal literal = solution.getLiteral(variableName); + if (literal == null) { + return defaultValue; + } else { + String string = literal.getString(); + try { + return Status.valueOf(string); + } catch (Exception e) { + String uri = getUriFromSolution(solution); + log.warn("Failed to parse the status value for '" + uri + + "': '" + string + "'"); + return defaultValue; + } + } + } + + private String getUriFromSolution(QuerySolution solution) { + return solution.getResource("uri").getURI(); + } + } + + private static class CountQueryParser extends QueryParser { + @Override + public Integer defaultValue() { + return 0; + } + + @Override + public Integer parseResults(String queryStr, ResultSet results) { + int count = 0; + + if (!results.hasNext()) { + log.warn("count query returned no results."); + } + try { + QuerySolution solution = results.next(); + count = ifIntPresent(solution, ".1", 0); + } catch (Exception e) { + log.warn("Failed to parse the query result" + queryStr, e); + } + + return count; + } + } + + private static class PermissionsQueryParser extends + QueryParser> { + @Override + Set defaultValue() { + return Collections.emptySet(); + } + + @Override + Set parseResults(String queryStr, ResultSet results) { + Set permissions = new HashSet(); + + while (results.hasNext()) { + try { + QuerySolution solution = results.next(); + Resource r = solution.getResource("ps"); + if (r != null) { + permissions.add(r.getURI()); + } + } catch (Exception e) { + log.warn("Failed to parse the query result: " + queryStr, e); + } + } + + return permissions; + } + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectionCriteriaTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectionCriteriaTest.java new file mode 100644 index 000000000..5ed6febe0 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectionCriteriaTest.java @@ -0,0 +1,49 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement; + +import static edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement.UserAccountsOrdering.DEFAULT_ORDERING; +import static org.junit.Assert.assertEquals; + +import org.junit.Test; + +public class UserAccountsSelectionCriteriaTest { + private UserAccountsSelectionCriteria criteria; + + @Test(expected = IllegalArgumentException.class) + public void accountsPerPageOutOfRange() { + criteria = create(0, 10, DEFAULT_ORDERING, "role", "search"); + } + + @Test(expected = IllegalArgumentException.class) + public void pageIndexOutOfRange() { + criteria = create(10, -1, DEFAULT_ORDERING, "role", "search"); + } + + @Test + public void orderByIsNull() { + criteria = create(10, 1, null, "role", "search"); + assertEquals("ordering", UserAccountsOrdering.DEFAULT_ORDERING, + criteria.getOrderBy()); + } + + @Test + public void roleFilterUriIsNull() { + criteria = create(10, 1, DEFAULT_ORDERING, null, "search"); + assertEquals("roleFilter", "", criteria.getRoleFilterUri()); + } + + @Test + public void searchTermIsNull() { + criteria = create(10, 1, DEFAULT_ORDERING, "role", null); + assertEquals("searchTerm", "", criteria.getSearchTerm()); + } + + private UserAccountsSelectionCriteria create(int accountsPerPage, + int pageIndex, UserAccountsOrdering orderBy, String roleFilterUri, + String searchTerm) { + return new UserAccountsSelectionCriteria(accountsPerPage, pageIndex, + orderBy, roleFilterUri, searchTerm); + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectorTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectorTest.java new file mode 100644 index 000000000..e2fe7081e --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectorTest.java @@ -0,0 +1,284 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement; + +import static edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement.UserAccountsOrdering.DEFAULT_ORDERING; +import static org.junit.Assert.*; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.fail; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; + +import org.apache.log4j.Level; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import com.hp.hpl.jena.ontology.OntModel; +import com.hp.hpl.jena.ontology.OntModelSpec; +import com.hp.hpl.jena.rdf.model.Model; +import com.hp.hpl.jena.rdf.model.ModelFactory; + +import edu.cornell.mannlib.vitro.testing.AbstractTestClass; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement.UserAccountsOrdering.Direction; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.accountmanagement.UserAccountsOrdering.Field; + +public class UserAccountsSelectorTest extends AbstractTestClass { + /** + * Where the model statements are stored for this test. + */ + private static final String N3_DATA_FILENAME = "UserAccountsSelectorTest.n3"; + + private static OntModel ontModel; + + @BeforeClass + public static void setupModel() throws IOException { + InputStream stream = UserAccountsSelectorTest.class + .getResourceAsStream(N3_DATA_FILENAME); + Model model = ModelFactory.createDefaultModel(); + model.read(stream, null, "N3"); + stream.close(); + + ontModel = ModelFactory.createOntologyModel(OntModelSpec.OWL_DL_MEM, + model); + ontModel.prepare(); + } + + private UserAccountsSelection selection; + private UserAccountsSelectionCriteria criteria; + + @Before + public void setLoggingLevel() { + setLoggerLevel(UserAccountsSelector.class, Level.DEBUG); + } + + // ---------------------------------------------------------------------- + // exceptions tests + // ---------------------------------------------------------------------- + + @Test(expected = NullPointerException.class) + @SuppressWarnings("unused") + public void modelIsNull() { + UserAccountsSelector.select(null, + criteria(10, 1, DEFAULT_ORDERING, "", "")); + } + + @Test(expected = NullPointerException.class) + public void criteriaIsNull() { + UserAccountsSelector.select(ontModel, null); + } + + // ---------------------------------------------------------------------- + // fields tests + // ---------------------------------------------------------------------- + + @Test + public void checkAllFields() { + selectOnCriteria(1, 10, DEFAULT_ORDERING, "", ""); + assertSelectedUris(10, "user10"); + + UserAccount acct = selection.getUserAccounts().get(0); + assertEquals("uri", "http://vivo.mydomain.edu/individual/user10", + acct.getUri()); + assertEquals("email", "email@jones.edu", acct.getEmailAddress()); + assertEquals("firstName", "Brian", acct.getFirstName()); + assertEquals("lastName", "Caruso", acct.getLastName()); + assertEquals("password", "garbage", acct.getMd5password()); + assertEquals("expires", 1100234965897L, acct.getPasswordChangeExpires()); + assertEquals("loginCount", 50, acct.getLoginCount()); + assertEquals("status", UserAccount.Status.ACTIVE, acct.getStatus()); + assertEqualSets( + "permissions", + Collections + .singleton("http://vivo.mydomain.edu/individual/role2"), + acct.getPermissionSetUris()); + } + + // ---------------------------------------------------------------------- + // pagination tests + // ---------------------------------------------------------------------- + + @Test + public void showFirstPageOfFifteen() { + selectOnCriteria(15, 1, DEFAULT_ORDERING, "", ""); + assertSelectedUris(10, "user01", "user02", "user03", "user04", + "user05", "user06", "user07", "user08", "user09", "user10"); + } + + @Test + public void showFirstPageOfOne() { + selectOnCriteria(1, 1, DEFAULT_ORDERING, "", ""); + assertSelectedUris(10, "user01"); + } + + @Test + public void showFirstPageOfFive() { + selectOnCriteria(5, 1, DEFAULT_ORDERING, "", ""); + assertSelectedUris(10, "user01", "user02", "user03", "user04", "user05"); + } + + @Test + public void showSecondPageOfSeven() { + selectOnCriteria(7, 2, DEFAULT_ORDERING, "", ""); + assertSelectedUris(10, "user08", "user09", "user10"); + } + + @Test + public void showTenthPageOfThree() { + selectOnCriteria(3, 10, DEFAULT_ORDERING, "", ""); + assertSelectedUris(10); + } + + // ---------------------------------------------------------------------- + // sorting tests + // ---------------------------------------------------------------------- + + @Test + public void sortByEmailAscending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering(Field.EMAIL, + Direction.ASCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + assertSelectedUris(10, "user01", "user02", "user03"); + } + + @Test + public void sortByEmailDescending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering(Field.EMAIL, + Direction.DESCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + assertSelectedUris(10, "user10", "user09", "user08"); + } + + @Test + public void sortByFirstNameAscending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering( + Field.FIRST_NAME, Direction.ASCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + // user02 has no first name: collates as least value. + assertSelectedUris(10, "user02", "user10", "user09"); + } + + @Test + public void sortByFirstNameDescending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering( + Field.FIRST_NAME, Direction.DESCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + // user02 has no first name: collates as least value. + assertSelectedUris(10, "user01", "user03", "user04"); + } + + @Test + public void sortByLastNameAscending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering( + Field.LAST_NAME, Direction.ASCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + // user03 has no last name: collates as least value. + assertSelectedUris(10, "user03", "user05", "user09"); + } + + @Test + public void sortByLastNameDescending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering( + Field.LAST_NAME, Direction.DESCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + assertSelectedUris(10, "user06", "user07", "user01"); + } + + @Test + public void sortByStatusAscending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering( + Field.STATUS, Direction.ASCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + // user07 has no status: collates as least value. + assertSelectedUris(10, "user07", "user01", "user04"); + } + + @Test + public void sortByStatusDescending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering( + Field.STATUS, Direction.DESCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + assertSelectedUris(10, "user02", "user03", "user06"); + } + + @Test + public void sortByLoginCountAscending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering( + Field.LOGIN_COUNT, Direction.ASCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + // user06 has no login count: reads as 0. + assertSelectedUris(10, "user06", "user03", "user07"); + } + + @Test + public void sortByLoginCountDescending() { + UserAccountsOrdering orderBy = new UserAccountsOrdering( + Field.LOGIN_COUNT, Direction.DESCENDING); + selectOnCriteria(3, 1, orderBy, "", ""); + assertSelectedUris(10, "user10", "user04", "user08"); + } + + /** + * Test plan + * + *
+	 * -- 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;
+	 * 
+ */ + + // ---------------------------------------------------------------------- + // helper methods + // ---------------------------------------------------------------------- + + private UserAccountsSelectionCriteria criteria(int accountsPerPage, + int pageIndex, UserAccountsOrdering orderBy, String roleFilterUri, + String searchTerm) { + return new UserAccountsSelectionCriteria(accountsPerPage, pageIndex, + orderBy, roleFilterUri, searchTerm); + } + + private void selectOnCriteria(int accountsPerPage, int pageIndex, + UserAccountsOrdering orderBy, String roleFilterUri, + String searchTerm) { + criteria = new UserAccountsSelectionCriteria(accountsPerPage, + pageIndex, orderBy, roleFilterUri, searchTerm); + selection = UserAccountsSelector.select(ontModel, criteria); + } + + private void assertExpectedCount(int expected) { + 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) { + assertEquals("result count", resultCount, selection.getResultCount()); + + List expectedList = Arrays.asList(uris); + List actualList = new ArrayList(); + for (UserAccount a : selection.getUserAccounts()) { + String[] uriParts = a.getUri().split("/"); + actualList.add(uriParts[uriParts.length - 1]); + } + assertEquals("uris", expectedList, actualList); + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectorTest.n3 b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectorTest.n3 new file mode 100644 index 000000000..d5c75faf0 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/freemarker/accountmanagement/UserAccountsSelectorTest.n3 @@ -0,0 +1,146 @@ +# $This file is distributed under the terms of the license in /doc/license.txt$ + +@prefix rdf: . +@prefix rdfs: . +@prefix owl: . +@prefix auth: . +@prefix mydomain: . + +### This file is for the test UserAccountsSelectorTest.java. + +# +# Note: each optional field (everything except URI and emailAddress) is missing +# from some user account. +# +# Note: user accounts have 0, 1, or 2 permission sets. +# + +mydomain:user01 + a auth:UserAccount ; + auth:emailAddress "email@able.edu" ; + auth:firstName "Zack" ; + auth:lastName "Roberts" ; + auth:md5password "garbage" ; + auth:passwordChangeExpires 0 ; + auth:loginCount 5 ; + auth:status "ACTIVE" ; + auth:hasPermissionSet mydomain:role1 ; + . + +mydomain:user02 + a auth:UserAccount ; + auth:emailAddress "email@bob.edu" ; +# auth:firstName NONE ; + auth:lastName "Cole" ; + auth:md5password "garbage" ; + auth:passwordChangeExpires 0 ; + auth:loginCount 5 ; + auth:status "INACTIVE" ; + auth:hasPermissionSet mydomain:role1 ; + . + +mydomain:user03 + a auth:UserAccount ; + auth:emailAddress "email@charlie.edu" ; + auth:firstName "Ralph" ; +# auth:lastName NONE ; + auth:md5password "garbage" ; + auth:passwordChangeExpires 0 ; + auth:loginCount 0 ; + auth:status "INACTIVE" ; + auth:hasPermissionSet mydomain:role1 ; + auth:hasPermissionSet mydomain:role2 ; + . + +mydomain:user04 + a auth:UserAccount ; + auth:emailAddress "email@delta.edu" ; + auth:firstName "Queen" ; + auth:lastName "Latifah" ; +# auth:md5password NONE ; + auth:passwordChangeExpires 0 ; + auth:loginCount 9 ; + auth:status "ACTIVE" ; + . + +mydomain:user05 + a auth:UserAccount ; + auth:emailAddress "email@echo.edu" ; + auth:firstName "Paul" ; + auth:lastName "Archibob" ; + auth:md5password "garbage" ; +# auth:passwordChangeExpires NONE ; + auth:loginCount 2 ; + auth:status "ACTIVE" ; + auth:hasPermissionSet mydomain:role1 ; + . + +mydomain:user06 + a auth:UserAccount ; + auth:emailAddress "email@foxtrot.edu" ; + auth:firstName "Nancy" ; + auth:lastName "Xavier" ; + auth:md5password "garbage" ; + auth:passwordChangeExpires 0 ; +# auth:loginCount NONE ; + auth:status "INACTIVE" ; + auth:hasPermissionSet mydomain:role1 ; + . + +mydomain:user07 + a auth:UserAccount ; + auth:emailAddress "email@golf.edu" ; + auth:firstName "Oprah" ; + auth:lastName "Winfrey" ; + auth:md5password "garbage" ; + auth:passwordChangeExpires 0 ; + auth:loginCount 1 ; +# auth:status NONE ; + auth:hasPermissionSet mydomain:role2 ; + . + +mydomain:user08 + a auth:UserAccount ; + auth:emailAddress "email@henry.edu" ; + auth:firstName "Mary" ; + auth:lastName "McInerney" ; + auth:md5password "garbage" ; + auth:passwordChangeExpires 0 ; + auth:loginCount 7 ; + auth:status "ACTIVE" ; + . + +mydomain:user09 + a auth:UserAccount ; + auth:emailAddress "email@indigo.edu" ; + auth:firstName "Jim" ; + auth:lastName "Blake" ; + auth:md5password "garbage" ; + auth:passwordChangeExpires 0 ; + auth:loginCount 3 ; + auth:status "ACTIVE" ; + auth:hasPermissionSet mydomain:role1 ; + . + +mydomain:user10 + a auth:UserAccount ; + auth:emailAddress "email@jones.edu" ; + auth:firstName "Brian" ; + auth:lastName "Caruso" ; + auth:md5password "garbage" ; + auth:passwordChangeExpires "1100234965897" ; + auth:loginCount 50 ; + auth:status "ACTIVE" ; + auth:hasPermissionSet mydomain:role2 ; + . + +mydomain:role1 + a auth:PermissionSet ; + rdfs:label "Role 1" ; + . + +mydomain:role2 + a auth:PermissionSet ; + rdfs:label "Role 2" ; + . +