diff --git a/solr/exampleSolr/conf/schema.xml b/solr/exampleSolr/conf/schema.xml index 0a4cc7445..ede99f821 100644 --- a/solr/exampleSolr/conf/schema.xml +++ b/solr/exampleSolr/conf/schema.xml @@ -488,7 +488,8 @@ - + + diff --git a/solr/exampleSolr/conf/solrconfig.xml b/solr/exampleSolr/conf/solrconfig.xml index 300d04530..b37a027ca 100644 --- a/solr/exampleSolr/conf/solrconfig.xml +++ b/solr/exampleSolr/conf/solrconfig.xml @@ -385,7 +385,10 @@ be based on the last SolrCore to be initialized. --> - 1024 + --> + + 50000 + + + + + + IndividualListController /individuallist @@ -976,12 +982,6 @@ JSON Service edu.cornell.mannlib.vitro.webapp.controller.JSONServlet - JSON Service /dataservice diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java index 5f7858b16..26d09c8a0 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/VitroHttpServlet.java @@ -197,18 +197,20 @@ public class VitroHttpServlet extends HttpServlet { } /** - * If logging is set to the TRACE level, dump the HTTP headers on the - * request. + * If logging on the subclass is set to the TRACE level, dump the HTTP + * headers on the request. */ private void dumpRequestHeaders(HttpServletRequest req) { @SuppressWarnings("unchecked") Enumeration names = req.getHeaderNames(); - log.trace("----------------------request:" + req.getRequestURL()); + Log subclassLog = LogFactory.getLog(this.getClass()); + subclassLog.trace("----------------------request:" + + req.getRequestURL()); while (names.hasMoreElements()) { String name = names.nextElement(); if (!BORING_HEADERS.contains(name)) { - log.trace(name + "=" + req.getHeader(name)); + subclassLog.trace(name + "=" + req.getHeader(name)); } } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPage.java new file mode 100644 index 000000000..3f0513735 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsAddPage.java @@ -0,0 +1,148 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount.Status; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; + +/** + * TODO + */ +public class UserAccountsAddPage extends UserAccountsPage { + private static final String PARAMETER_SUBMIT = "submitAdd"; + private static final String PARAMETER_EMAIL_ADDRESS = "emailAddress"; + private static final String PARAMETER_FIRST_NAME = "firstName"; + private static final String PARAMETER_LAST_NAME = "lastName"; + private static final String PARAMETER_ROLE = "role"; + private static final String PARAMETER_ASSOCIATE_WITH_PROFILE = "associate"; + + private static final String ERROR_NO_EMAIL = "errorEmailIsEmpty"; + private static final String ERROR_EMAIL_IN_USE = "errorEmailInUse"; + private static final String ERROR_NO_FIRST_NAME = "errorFirstNameIsEmpty"; + private static final String ERROR_NO_LAST_NAME = "errorLastNameIsEmpty"; + private static final String ERROR_NO_ROLE = "errorNoRoleSelected"; + + private static final String TEMPLATE_NAME = "userAccounts-add.ftl"; + + /* The request parameters */ + private boolean submit; + private String emailAddress = ""; + private String firstName = ""; + private String lastName = ""; + private String selectedRoleUri = ""; + private boolean associateWithProfile; + + /* The result of validating a "submit" request. */ + private String errorCode = ""; + + public UserAccountsAddPage(VitroRequest vreq) { + super(vreq); + } + + public void parseParametersAndValidate() { + parseRequestParameters(); + + if (submit) { + validateParameters(); + } + } + + private void parseRequestParameters() { + submit = isFlagOnRequest(PARAMETER_SUBMIT); + emailAddress = getStringParameter(PARAMETER_EMAIL_ADDRESS, ""); + firstName = getStringParameter(PARAMETER_FIRST_NAME, ""); + lastName = getStringParameter(PARAMETER_LAST_NAME, ""); + selectedRoleUri = getRoleChoices(); + associateWithProfile = getAssociateFlag(); + } + + public boolean isSubmit() { + return submit; + } + + private void validateParameters() { + if (emailAddress.isEmpty()) { + errorCode = ERROR_NO_EMAIL; + } else if (isEmailInUse()) { + errorCode = ERROR_EMAIL_IN_USE; + } else if (firstName.isEmpty()) { + errorCode = ERROR_NO_FIRST_NAME; + } else if (lastName.isEmpty()) { + errorCode = ERROR_NO_LAST_NAME; + } else if (selectedRoleUri.isEmpty()) { + errorCode = ERROR_NO_ROLE; + } + } + + private boolean isEmailInUse() { + return userAccountsDao.getUserAccountByEmail(emailAddress) != null; + } + + public boolean isValid() { + return errorCode.isEmpty(); + } + + public UserAccount createNewAccount() { + UserAccount u = new UserAccount(); + u.setEmailAddress(emailAddress); + u.setFirstName(firstName); + u.setLastName(lastName); + u.setExternalAuthId(""); + + u.setMd5Password(""); + u.setOldPassword(""); + u.setPasswordChangeRequired(false); + u.setPasswordLinkExpires(0); + u.setLoginCount(0); + u.setStatus(Status.INACTIVE); + + u.setPermissionSetUris(Collections.singleton(selectedRoleUri)); + + String uri = userAccountsDao.insertUserAccount(u); + return userAccountsDao.getUserAccountByUri(uri); + } + + /** What role are they asking for? */ + private String getRoleChoices() { + String[] roles = vreq.getParameterValues(PARAMETER_ROLE); + if ((roles == null) || (roles.length == 0)) { + return ""; + } else { + return roles[0]; + } + } + + /** Are they associating with an Individual profile? */ + private boolean getAssociateFlag() { + return "yes".equals(getStringParameter( + PARAMETER_ASSOCIATE_WITH_PROFILE, "no")); + } + + public ResponseValues showPage() { + Map body = new HashMap(); + + body.put("emailAddress", emailAddress); + body.put("firstName", firstName); + body.put("lastName", lastName); + body.put("selectedRole", selectedRoleUri); + body.put("associate", associateWithProfile); + + body.put("roles", buildRolesList()); + + body.put("formUrls", buildUrlsMap()); + + if (!errorCode.isEmpty()) { + body.put(errorCode, Boolean.TRUE); + } + + return new TemplateResponseValues(TEMPLATE_NAME, body); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsController.java new file mode 100644 index 000000000..fd01a956b --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsController.java @@ -0,0 +1,75 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts; + +import java.util.Collection; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; +import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages.ManageUserAccounts; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; + +/** + * Parcel out the different actions required of the UserAccounts GUI. + */ +public class UserAccountsController extends FreemarkerHttpServlet { + private static final Log log = LogFactory + .getLog(UserAccountsController.class); + + private static final String ACTION_ADD = "/add"; + private static final String ACTION_DELETE = "/delete"; + private static final String ACTION_EDIT = "/edit"; + + @Override + protected Actions requiredActions(VitroRequest vreq) { + return new Actions(new ManageUserAccounts()); + } + + @Override + protected ResponseValues processRequest(VitroRequest vreq) { + if (log.isDebugEnabled()) { + dumpRequestParameters(vreq); + } + + String action = vreq.getPathInfo(); + log.debug("action = '" + action + "'"); + + if (ACTION_ADD.equals(action)) { + UserAccountsAddPage page = new UserAccountsAddPage(vreq); + page.parseParametersAndValidate(); + if (page.isSubmit() && page.isValid()) { + return addAccountAndShowList(vreq, page); + } else { + return page.showPage(); + } + + } else if (ACTION_EDIT.equals(action)) { + return new UserAccountsEditPage(vreq).showPage(); + + } else if (ACTION_DELETE.equals(action)) { + UserAccountsDeleter deleter = new UserAccountsDeleter(vreq); + Collection deletedUris = deleter.delete(); + + return new UserAccountsListPage(vreq) + .showPageWithDeletions(deletedUris); + + } else { + UserAccountsListPage page = new UserAccountsListPage(vreq); + return page.showPage(); + } + } + + private ResponseValues addAccountAndShowList(VitroRequest vreq, + UserAccountsAddPage addPage) { + UserAccount userAccount = addPage.createNewAccount(); + + UserAccountsListPage listPage = new UserAccountsListPage(vreq); + return listPage.showPageWithNewAccount(userAccount); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsDeleter.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsDeleter.java new file mode 100644 index 000000000..02d5b5931 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsDeleter.java @@ -0,0 +1,27 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts; + +import java.util.Collection; + +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; + +/** + * TODO + */ +public class UserAccountsDeleter extends UserAccountsPage { + + protected UserAccountsDeleter(VitroRequest vreq) { + super(vreq); + } + + /** + * @return + * + */ + public Collection delete() { + // TODO Auto-generated method stub + throw new RuntimeException("UserAccountsDeleter.delete() not implemented."); + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsEditPage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsEditPage.java new file mode 100644 index 000000000..f1e7d637b --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsEditPage.java @@ -0,0 +1,24 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts; + +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; + +/** + * TODO + */ +public class UserAccountsEditPage extends UserAccountsPage { + private static final String TEMPLATE_NAME = "userAccounts-edit.ftl"; + + public UserAccountsEditPage(VitroRequest vreq) { + super(vreq); + } + + public ResponseValues showPage() { + return new TemplateResponseValues(TEMPLATE_NAME); + } + + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListPage.java similarity index 57% rename from webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListController.java rename to webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListPage.java index 40c36689a..d65f7b72e 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsListPage.java @@ -5,41 +5,32 @@ package edu.cornell.mannlib.vitro.webapp.controller.accounts; import static edu.cornell.mannlib.vitro.webapp.controller.accounts.UserAccountsSelectionCriteria.DEFAULT_ACCOUNTS_PER_PAGE; import java.util.ArrayList; +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 javax.servlet.ServletException; - import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; -import com.hp.hpl.jena.ontology.OntModel; - -import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.Actions; -import edu.cornell.mannlib.vitro.webapp.auth.requestedAction.usepages.ManageUserAccounts; import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet; import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; import edu.cornell.mannlib.vitro.webapp.beans.UserAccount.Status; import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; 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.freemarker.FreemarkerHttpServlet; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; -import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao; -import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; -import edu.cornell.mannlib.vitro.webapp.dao.jena.OntModelSelector; /** - * Display the paginated list of User Accounts. + * Handle the List page. */ -public class UserAccountsListController extends FreemarkerHttpServlet { +public class UserAccountsListPage extends UserAccountsPage { private static final Log log = LogFactory - .getLog(UserAccountsListController.class); + .getLog(UserAccountsListPage.class); public static final String PARAMETER_ACCOUNTS_PER_PAGE = "accountsPerPage"; public static final String PARAMETER_PAGE_INDEX = "pageIndex"; @@ -54,67 +45,19 @@ public class UserAccountsListController extends FreemarkerHttpServlet { private static final String TEMPLATE_NAME = "userAccounts-list.ftl"; - private OntModel userAccountsModel; - private UserAccountsDao userAccountsDao; + private UserAccountsSelectionCriteria criteria = UserAccountsSelectionCriteria.DEFAULT_CRITERIA; - @Override - public void init() throws ServletException { - super.init(); - - OntModelSelector oms = (OntModelSelector) getServletContext() - .getAttribute("baseOntModelSelector"); - userAccountsModel = oms.getUserAccountsModel(); - - WebappDaoFactory wdf = (WebappDaoFactory) getServletContext() - .getAttribute("webappDaoFactory"); - userAccountsDao = wdf.getUserAccountsDao(); - } - - @Override - protected Actions requiredActions(VitroRequest vreq) { - return new Actions(new ManageUserAccounts()); + public UserAccountsListPage(VitroRequest vreq) { + super(vreq); } /** - * Assume the default criteria for display. Modify the criteria based on - * parameters in the request. Get the selected accounts and display them. + * Build the criteria from the request parameters. */ - @Override - protected ResponseValues processRequest(VitroRequest vreq) { - if (log.isDebugEnabled()) { - dumpRequestParameters(vreq); - } - - Map body = new HashMap(); - - UserAccountsSelectionCriteria criteria = buildCriteria(vreq); - - body.put("accountsPerPage", criteria.getAccountsPerPage()); - body.put("pageIndex", criteria.getPageIndex()); - body.put("orderDirection", criteria.getOrderBy().getDirection().keyword); - body.put("orderField", criteria.getOrderBy().getField().name); - body.put("roleFilterUri", criteria.getRoleFilterUri()); - body.put("searchTerm", criteria.getSearchTerm()); - - UserAccountsSelection selection = UserAccountsSelector.select( - userAccountsModel, criteria); - - body.put("accounts", wrapUserAccounts(selection)); - body.put("total", selection.getResultCount()); - body.put("page", buildPageMap(selection)); - - body.put("formUrl", buildFormUrl(vreq)); - body.put("roles", buildRolesList()); - - body.put("messages", buildMessagesMap(vreq)); - - return new TemplateResponseValues(TEMPLATE_NAME, body); - } - - private UserAccountsSelectionCriteria buildCriteria(VitroRequest vreq) { - int accountsPerPage = getIntegerParameter(vreq, - PARAMETER_ACCOUNTS_PER_PAGE, DEFAULT_ACCOUNTS_PER_PAGE); - int pageIndex = getIntegerParameter(vreq, PARAMETER_PAGE_INDEX, 1); + public void parseParameters() { + int accountsPerPage = getIntegerParameter(PARAMETER_ACCOUNTS_PER_PAGE, + DEFAULT_ACCOUNTS_PER_PAGE); + int pageIndex = getIntegerParameter(PARAMETER_PAGE_INDEX, 1); Direction orderingDirection = Direction.fromKeyword(vreq .getParameter(PARAMETER_ORDERING_DIRECTION)); @@ -123,33 +66,72 @@ public class UserAccountsListController extends FreemarkerHttpServlet { UserAccountsOrdering ordering = new UserAccountsOrdering(orderingField, orderingDirection); - String roleFilterUri = getStringParameter(vreq, - PARAMETER_ROLE_FILTER_URI, ""); - String searchTerm = getStringParameter(vreq, PARAMETER_SEARCH_TERM, ""); + String roleFilterUri = getStringParameter(PARAMETER_ROLE_FILTER_URI, ""); + String searchTerm = getStringParameter(PARAMETER_SEARCH_TERM, ""); - return new UserAccountsSelectionCriteria(accountsPerPage, pageIndex, - ordering, roleFilterUri, searchTerm); + criteria = new UserAccountsSelectionCriteria(accountsPerPage, + pageIndex, ordering, roleFilterUri, searchTerm); } - private String getStringParameter(VitroRequest vreq, String key, - String defaultValue) { - String value = vreq.getParameter(key); - return (value == null) ? defaultValue : value; + /** + * Build the selection criteria from the request, select the accounts, and + * create the ResponseValues to display the page. + */ + public ResponseValues showPage() { + UserAccountsSelection selection = UserAccountsSelector.select( + userAccountsModel, criteria); + Map body = buildTemplateBodyMap(selection); + return new TemplateResponseValues(TEMPLATE_NAME, body); } - private int getIntegerParameter(VitroRequest vreq, String key, - int defaultValue) { - String value = vreq.getParameter(key); - if (value == null) { - return defaultValue; - } + /** + * We just came from adding a new account. Show the list with a message. + */ + public ResponseValues showPageWithNewAccount(UserAccount userAccount) { + UserAccountsSelection selection = UserAccountsSelector.select( + userAccountsModel, criteria); + Map body = buildTemplateBodyMap(selection); - try { - return Integer.parseInt(value); - } catch (NumberFormatException e) { - log.warn("Invalid integer for parameter '" + key + "': " + value); - return defaultValue; - } + body.put("newUserAccount", new UserAccountWrapper(vreq, userAccount, + Collections. emptyList())); + + return new TemplateResponseValues(TEMPLATE_NAME, body); + } + + /** + * We just came from deleting accounts. Show the list with a message. + */ + public ResponseValues showPageWithDeletions(Collection deletedUris) { + UserAccountsSelection selection = UserAccountsSelector.select( + userAccountsModel, criteria); + Map body = buildTemplateBodyMap(selection); + + body.put("deletedAccountCount", deletedUris.size()); + + return new TemplateResponseValues(TEMPLATE_NAME, body); + } + + private Map buildTemplateBodyMap( + UserAccountsSelection selection) { + Map body = new HashMap(); + + body.put("accountsPerPage", criteria.getAccountsPerPage()); + body.put("pageIndex", criteria.getPageIndex()); + body.put("orderDirection", criteria.getOrderBy().getDirection().keyword); + body.put("orderField", criteria.getOrderBy().getField().name); + body.put("roleFilterUri", criteria.getRoleFilterUri()); + body.put("searchTerm", criteria.getSearchTerm()); + + body.put("accounts", wrapUserAccounts(selection)); + body.put("total", selection.getResultCount()); + body.put("page", buildPageMap(selection)); + + body.put("formUrls", buildUrlsMap()); + body.put("roles", buildRolesList()); + + body.put("messages", buildMessagesMap()); + + return body; } private Map buildPageMap(UserAccountsSelection selection) { @@ -175,50 +157,31 @@ public class UserAccountsListController extends FreemarkerHttpServlet { return map; } - private String buildFormUrl(VitroRequest vreq) { - UrlBuilder urlBuilder = new UrlBuilder(vreq.getAppBean()); - return urlBuilder.getPortalUrl("/listUserAccounts"); - } - - private List buildRolesList() { - List list = new ArrayList(); - list.addAll(userAccountsDao.getAllPermissionSets()); - Collections.sort(list, new Comparator() { - @Override - public int compare(PermissionSet ps1, PermissionSet ps2) { - return ps1.getUri().compareTo(ps2.getUri()); - } - }); - return list; - } - - private Map buildMessagesMap(VitroRequest vreq) { + private Map buildMessagesMap() { Map map = new HashMap(); - UserAccount newUser = getUserFromUriParameter(vreq, - PARAMETER_NEW_USER_URI); + UserAccount newUser = getUserFromUriParameter(PARAMETER_NEW_USER_URI); if (newUser != null) { map.put("newUser", newUser); } - UserAccount updatedUser = getUserFromUriParameter(vreq, - PARAMETER_UPDATED_USER_URI); + UserAccount updatedUser = getUserFromUriParameter(PARAMETER_UPDATED_USER_URI); if (updatedUser != null) { map.put("updatedUser", updatedUser); } - if (isFlagOnRequest(vreq, FLAG_UPDATED_USER_PW)) { + if (isFlagOnRequest(FLAG_UPDATED_USER_PW)) { map.put("updatedUserPw", true); } - if (isFlagOnRequest(vreq, FLAG_USERS_DELETED)) { + if (isFlagOnRequest(FLAG_USERS_DELETED)) { map.put("usersDeleted", true); } return map; } - private UserAccount getUserFromUriParameter(VitroRequest vreq, String key) { + private UserAccount getUserFromUriParameter(String key) { String uri = vreq.getParameter(key); if ((uri == null) || uri.isEmpty()) { return null; @@ -227,11 +190,6 @@ public class UserAccountsListController extends FreemarkerHttpServlet { return userAccountsDao.getUserAccountByUri(uri); } - private boolean isFlagOnRequest(VitroRequest vreq, String key) { - String value = vreq.getParameter(key); - return (value != null); - } - /** * The UserAccount has a list of PermissionSetUris, but the Freemarker * template needs a list of PermissionSet labels instead. @@ -240,7 +198,7 @@ public class UserAccountsListController extends FreemarkerHttpServlet { UserAccountsSelection selection) { List list = new ArrayList(); for (UserAccount account : selection.getUserAccounts()) { - list.add(new UserAccountWrapper(account, + list.add(new UserAccountWrapper(vreq, account, findPermissionSetLabels(account))); } return list; @@ -263,11 +221,16 @@ public class UserAccountsListController extends FreemarkerHttpServlet { public static class UserAccountWrapper { private final UserAccount account; private final List permissionSets; + private final String editUrl; - public UserAccountWrapper(UserAccount account, + public UserAccountWrapper(VitroRequest vreq, UserAccount account, List permissionSets) { this.account = account; this.permissionSets = permissionSets; + + UrlBuilder urlBuilder = new UrlBuilder(vreq.getAppBean()); + this.editUrl = urlBuilder.getPortalUrl("/userAccounts/edit", + new ParamMap("editAccount", account.getUri())); } public String getUri() { @@ -303,6 +266,10 @@ public class UserAccountsListController extends FreemarkerHttpServlet { return permissionSets; } + public String getEditUrl() { + return editUrl; + } + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsPage.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsPage.java new file mode 100644 index 000000000..92cae670a --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsPage.java @@ -0,0 +1,108 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import com.hp.hpl.jena.ontology.OntModel; + +import edu.cornell.mannlib.vitro.webapp.beans.PermissionSet; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; +import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao; +import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory; +import edu.cornell.mannlib.vitro.webapp.dao.jena.OntModelSelector; + +/** + * Common routines for the page controllers. + */ +public abstract class UserAccountsPage { + private static final Log log = LogFactory.getLog(UserAccountsPage.class); + + protected final VitroRequest vreq; + protected final ServletContext ctx; + protected final OntModel userAccountsModel; + protected final UserAccountsDao userAccountsDao; + + protected UserAccountsPage(VitroRequest vreq) { + this.vreq = vreq; + this.ctx = vreq.getSession().getServletContext(); + + OntModelSelector oms = (OntModelSelector) this.ctx + .getAttribute("baseOntModelSelector"); + userAccountsModel = oms.getUserAccountsModel(); + + WebappDaoFactory wdf = (WebappDaoFactory) this.ctx + .getAttribute("webappDaoFactory"); + userAccountsDao = wdf.getUserAccountsDao(); + } + + protected String getStringParameter(String key, String defaultValue) { + String value = vreq.getParameter(key); + return (value == null) ? defaultValue : value; + } + + protected int getIntegerParameter(String key, int defaultValue) { + String value = vreq.getParameter(key); + if (value == null) { + return defaultValue; + } + + try { + return Integer.parseInt(value); + } catch (NumberFormatException e) { + log.warn("Invalid integer for parameter '" + key + "': " + value); + return defaultValue; + } + } + + /** + * Check for the presence of a parameter, regardless of its value, even if + * it's an empty string. + */ + protected boolean isFlagOnRequest(String key) { + String value = vreq.getParameter(key); + return (value != null); + } + + /** + * Create a list of all known PermissionSets. + */ + protected List buildRolesList() { + List list = new ArrayList(); + list.addAll(userAccountsDao.getAllPermissionSets()); + Collections.sort(list, new Comparator() { + @Override + public int compare(PermissionSet ps1, PermissionSet ps2) { + return ps1.getUri().compareTo(ps2.getUri()); + } + }); + return list; + } + + /** + * Make these URLs available to all of the pages. + */ + protected Map buildUrlsMap() { + UrlBuilder urlBuilder = new UrlBuilder(vreq.getAppBean()); + + Map map = new HashMap(); + + map.put("list", urlBuilder.getPortalUrl("/userAccounts/list")); + map.put("add", urlBuilder.getPortalUrl("/userAccounts/add")); + map.put("delete", urlBuilder.getPortalUrl("/userAccounts/delete")); + + return map; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelectionCriteria.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelectionCriteria.java index 42f0b854f..8a10c5c2d 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelectionCriteria.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/UserAccountsSelectionCriteria.java @@ -11,6 +11,10 @@ package edu.cornell.mannlib.vitro.webapp.controller.accounts; public class UserAccountsSelectionCriteria { public static final int DEFAULT_ACCOUNTS_PER_PAGE = 25; + public static final UserAccountsSelectionCriteria DEFAULT_CRITERIA = new UserAccountsSelectionCriteria( + DEFAULT_ACCOUNTS_PER_PAGE, 1, + UserAccountsOrdering.DEFAULT_ORDERING, "", ""); + /** How many accounts should we bring back, at most? */ private final int accountsPerPage; diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualController.java index ec2d97ac1..2b2df0341 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/IndividualController.java @@ -18,6 +18,7 @@ import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import com.hp.hpl.jena.datatypes.TypeMapper; +import com.hp.hpl.jena.datatypes.xsd.XSDDatatype; import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.rdf.model.Literal; import com.hp.hpl.jena.rdf.model.Model; @@ -25,6 +26,9 @@ import com.hp.hpl.jena.rdf.model.ModelFactory; import com.hp.hpl.jena.rdf.model.Property; import com.hp.hpl.jena.rdf.model.RDFNode; import com.hp.hpl.jena.rdf.model.Resource; +import com.hp.hpl.jena.rdf.model.ResourceFactory; +import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.shared.Lock; import com.hp.hpl.jena.vocabulary.RDF; import com.hp.hpl.jena.vocabulary.RDFS; @@ -73,7 +77,10 @@ public class IndividualController extends FreemarkerHttpServlet { put("vitro", VitroVocabulary.vitroURI); put("vitroPublic", VitroVocabulary.VITRO_PUBLIC); }}; - + + private static final Property extendedLinkedDataProperty = ResourceFactory.createProperty(namespaces.get("vitro") + "extendedLinkedData"); + private static final Literal xsdTrue = ResourceFactory.createTypedLiteral("true", XSDDatatype.XSDboolean); + private static final String TEMPLATE_INDIVIDUAL_DEFAULT = "individual.ftl"; private static final String TEMPLATE_HELP = "individual-help.ftl"; private static MapqsMap; @@ -329,8 +336,8 @@ public class IndividualController extends FreemarkerHttpServlet { ontModel = (OntModel)session.getAttribute("jenaOntModel"); if( ontModel == null) ontModel = (OntModel)getServletContext().getAttribute("jenaOntModel"); - - Model newModel = getRDF(individual, ontModel, ModelFactory.createDefaultModel(), 0); + + Model newModel = getRDF(individual, ontModel, ModelFactory.createDefaultModel(),0); return new RdfResponseValues(rdfFormat, newModel); } @@ -548,7 +555,6 @@ public class IndividualController extends FreemarkerHttpServlet { return null; } - @SuppressWarnings("unused") private boolean checkForSunset(VitroRequest vreq, Individual entity) { // TODO Auto-generated method stub @@ -591,11 +597,11 @@ public class IndividualController extends FreemarkerHttpServlet { return "enabled".equals(property); } - private Model getRDF(Individual entity, OntModel contextModel, Model newModel, int recurseDepth ) { + private Model getRDF(Individual entity, OntModel contextModel, Model newModel, int recurseDepth) { + Resource subj = newModel.getResource(entity.getURI()); List dstates = entity.getDataPropertyStatements(); - //System.out.println("data: "+dstates.size()); TypeMapper typeMapper = TypeMapper.getInstance(); for (DataPropertyStatement ds: dstates) { Property dp = newModel.getProperty(ds.getDatapropURI()); @@ -610,22 +616,48 @@ public class IndividualController extends FreemarkerHttpServlet { newModel.add(newModel.createStatement(subj, dp, lit)); } - if( recurseDepth < 5 ){ + if (recurseDepth < 5) { List ostates = entity.getObjectPropertyStatements(); + for (ObjectPropertyStatement os: ostates) { ObjectProperty objProp = os.getProperty(); - Property op = newModel.getProperty(os.getPropertyURI()); + Property prop = newModel.getProperty(os.getPropertyURI()); Resource obj = newModel.getResource(os.getObjectURI()); - newModel.add(newModel.createStatement(subj, op, obj)); - if( objProp.getStubObjectRelation() ) + newModel.add(newModel.createStatement(subj, prop, obj)); + if ( includeInLinkedData(obj, contextModel)) { newModel.add(getRDF(os.getObject(), contextModel, newModel, recurseDepth + 1)); + } else { + contextModel.enterCriticalSection(Lock.READ); + try { + newModel.add(contextModel.listStatements(obj, RDFS.label, (RDFNode)null)); + } finally { + contextModel.leaveCriticalSection(); + } + } } } newModel = getLabelAndTypes(entity, contextModel, newModel ); + + // get all the statements not covered by the object property / datatype property code above + // note implication that extendedLinkedData individuals will only be evaulated for the + // recognized object properties. + contextModel.enterCriticalSection(Lock.READ); + try { + StmtIterator iter = contextModel.listStatements(subj, (Property) null, (RDFNode) null); + while (iter.hasNext()) { + Statement stmt = iter.next(); + if (!newModel.contains(stmt)) { + newModel.add(stmt); + } + } + } finally { + contextModel.leaveCriticalSection(); + } + return newModel; } - + /* Get the properties that are difficult to get via a filtered WebappDaoFactory. */ private Model getLabelAndTypes(Individual entity, Model ontModel, Model newModel){ for( VClass vclass : entity.getVClasses()){ @@ -692,7 +724,27 @@ public class IndividualController extends FreemarkerHttpServlet { return qsMap; } -// static String getAcceptedContentType(String acceptHeader,Mapqs){ -// -// } + public static boolean includeInLinkedData(Resource object, Model contextModel) { + + boolean retval = false; + + contextModel.enterCriticalSection(Lock.READ); + + try { + StmtIterator iter = contextModel.listStatements(object, RDF.type, (RDFNode)null); + + while (iter.hasNext()) { + Statement stmt = iter.next(); + + if (stmt.getObject().isResource() && contextModel.contains(stmt.getObject().asResource(), extendedLinkedDataProperty, xsdTrue)) { + retval = true; + break; + } + } + } finally { + contextModel.leaveCriticalSection(); + } + + return retval; + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/SiteAdminController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/SiteAdminController.java index 51b9c8570..ebc82cd76 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/SiteAdminController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/SiteAdminController.java @@ -124,7 +124,7 @@ public class SiteAdminController extends FreemarkerHttpServlet { urls.put("users", urlBuilder.getPortalUrl("/listUsers")); } if (PolicyHelper.isAuthorizedForActions(vreq, new ManageUserAccounts())) { - urls.put("userList", urlBuilder.getPortalUrl("/listUserAccounts")); + urls.put("userList", urlBuilder.getPortalUrl("/userAccounts")); } if (PolicyHelper.isAuthorizedForActions(vreq, new EditSiteInformation())) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/SolrIndividualListController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/SolrIndividualListController.java new file mode 100644 index 000000000..eec707bec --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/freemarker/SolrIndividualListController.java @@ -0,0 +1,337 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.freemarker; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.servlet.ServletContext; +import javax.servlet.ServletException; + +import org.apache.commons.lang.StringUtils; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.apache.lucene.document.Document; +import org.apache.lucene.index.CorruptIndexException; +import org.apache.lucene.index.Term; +import org.apache.lucene.search.BooleanClause; +import org.apache.lucene.search.BooleanQuery; +import org.apache.lucene.search.IndexSearcher; +import org.apache.lucene.search.PrefixQuery; +import org.apache.lucene.search.Query; +import org.apache.lucene.search.ScoreDoc; +import org.apache.lucene.search.Sort; +import org.apache.lucene.search.TermQuery; +import org.apache.lucene.search.TopDocs; + +import edu.cornell.mannlib.vitro.webapp.beans.Individual; +import edu.cornell.mannlib.vitro.webapp.beans.VClass; +import edu.cornell.mannlib.vitro.webapp.beans.VClassGroup; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ExceptionResponseValues; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues; +import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao; +import edu.cornell.mannlib.vitro.webapp.search.lucene.Entity2LuceneDoc; +import edu.cornell.mannlib.vitro.webapp.search.lucene.LuceneIndexFactory; +import edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.ListedIndividualTemplateModel; +import freemarker.ext.beans.BeansWrapper; +import freemarker.template.TemplateModel; + +/** + * Generates a list of individuals for display in a template + */ +public class SolrIndividualListController extends FreemarkerHttpServlet { + + private static final long serialVersionUID = 1L; + private static final Log log = LogFactory.getLog(SolrIndividualListController.class.getName()); + + public static final int ENTITY_LIST_CONTROLLER_MAX_RESULTS = 30000; + public static final int INDIVIDUALS_PER_PAGE = 30; + public static final int MAX_PAGES = 40; //must be even + + private static final String TEMPLATE_DEFAULT = "individualList.ftl"; + + @Override + protected ResponseValues processRequest(VitroRequest vreq) { + + String templateName = TEMPLATE_DEFAULT; + Map body = new HashMap(); + String errorMessage = null; + String message = null; + + try { + Object obj = vreq.getAttribute("vclass"); + VClass vclass = null; + if ( obj == null ) { // look for vitroclass id parameter + String vitroClassIdStr = vreq.getParameter("vclassId"); + if ( !StringUtils.isEmpty(vitroClassIdStr)) { + try { + //TODO have to change this so vclass's group and entity count are populated + vclass = vreq.getWebappDaoFactory().getVClassDao().getVClassByURI(vitroClassIdStr); + if (vclass == null) { + log.error("Couldn't retrieve vclass " + vitroClassIdStr); + errorMessage = "Class " + vitroClassIdStr + " not found"; + } + } catch (Exception ex) { + throw new HelpException("IndividualListController: request parameter 'vclassId' must be a URI string."); + } + } + } else if (obj instanceof VClass) { + vclass = (VClass)obj; + } else { + throw new HelpException("IndividualListController: attribute 'vclass' must be of type " + + VClass.class.getName() + "."); + } + + body.put("vclassId", vclass.getURI()); + + if (vclass != null) { + String alpha = getAlphaParameter(vreq); + int page = getPageParameter(vreq); + Map map = getResultsForVClass( + vclass.getURI(), + page, + alpha, + vreq.getWebappDaoFactory().getIndividualDao(), + getServletContext()); + body.putAll(map); + + List inds = (List)map.get("entities"); + List indsTm = new ArrayList(); + for(Individual ind : inds ){ + indsTm.add(new ListedIndividualTemplateModel(ind,vreq)); + } + body.put("individuals", indsTm); + + List wpages = new ArrayList(); + List pages = (List)body.get("pages"); + BeansWrapper wrapper = new BeansWrapper(); + for( PageRecord pr: pages ){ + wpages.add( wrapper.wrap(pr) ); + } + + // Set title and subtitle. Title will be retrieved later in getTitle(). + VClassGroup classGroup = vclass.getGroup(); + String title; + if (classGroup == null) { + title = vclass.getName(); + } else { + title = classGroup.getPublicName(); + body.put("subtitle", vclass.getName()); + } + body.put("title", title); + body.put("redirecturl", vreq.getContextPath()+"/entityurl/"); + getServletContext().setAttribute("classuri", vclass.getURI()); + } + + } catch (HelpException help){ + errorMessage = "Request attribute 'vclass' or request parameter 'vclassId' must be set before calling. Its value must be a class uri."; + } catch (Throwable e) { + return new ExceptionResponseValues(e); + } + + if (errorMessage != null) { + templateName = Template.ERROR_MESSAGE.toString(); + body.put("errorMessage", errorMessage); + } else if (message != null) { + body.put("message", message); + } + + return new TemplateResponseValues(templateName, body); + } + + private class HelpException extends Throwable { + private static final long serialVersionUID = 1L; + + public HelpException(String string) { + super(string); + } + } + + public static String getAlphaParameter(VitroRequest request){ + return request.getParameter("alpha"); + } + + public static int getPageParameter(VitroRequest request) { + String pageStr = request.getParameter("page"); + if( pageStr != null ){ + try{ + return Integer.parseInt(pageStr); + }catch(NumberFormatException nfe){ + log.debug("could not parse page parameter"); + return 1; + } + }else{ + return 1; + } + } + + /** + * This method is now called in a couple of places. It should be refactored + * into a DAO or similar object. + */ + public static Map getResultsForVClass(String vclassURI, int page, String alpha, IndividualDao indDao, ServletContext context) + throws CorruptIndexException, IOException, ServletException{ + Map rvMap = new HashMap(); + + //make lucene query for this rdf:type + Query query = getQuery(vclassURI, alpha); + + //execute lucene query for individuals of the specified type + IndexSearcher index = LuceneIndexFactory.getIndexSearcher(context); + TopDocs docs = null; + try{ + docs = index.search(query, null, + ENTITY_LIST_CONTROLLER_MAX_RESULTS, + new Sort(Entity2LuceneDoc.term.NAME_LOWERCASE)); + }catch(Throwable th){ + log.error("Could not run search. " + th.getMessage()); + docs = null; + } + + if( docs == null ) + throw new ServletException("Could not run search in IndividualListController"); + + //get list of individuals for the search results + int size = docs.totalHits; + log.debug("Number of search results: " + size); + + // don't get all the results, only get results for the requestedSize + List individuals = new ArrayList(INDIVIDUALS_PER_PAGE); + int individualsAdded = 0; + int ii = (page-1)*INDIVIDUALS_PER_PAGE; + while( individualsAdded < INDIVIDUALS_PER_PAGE && ii < size ){ + ScoreDoc hit = docs.scoreDocs[ii]; + if (hit != null) { + Document doc = index.doc(hit.doc); + if (doc != null) { + String uri = doc.getField(Entity2LuceneDoc.term.URI).stringValue(); + Individual ind = indDao.getIndividualByURI( uri ); + if( ind != null ){ + individuals.add( ind ); + individualsAdded++; + } + } else { + log.warn("no document found for lucene doc id " + hit.doc); + } + } else { + log.debug("hit was null"); + } + ii++; + } + + rvMap.put("count", size); + + if( size > INDIVIDUALS_PER_PAGE ){ + rvMap.put("showPages", Boolean.TRUE); + List pageRecords = makePagesList(size, INDIVIDUALS_PER_PAGE, page); + rvMap.put("pages", pageRecords); + }else{ + rvMap.put("showPages", Boolean.FALSE); + rvMap.put("pages", Collections.emptyList()); + } + + rvMap.put("alpha",alpha); + + rvMap.put("totalCount", size); + rvMap.put("entities",individuals); + if (individuals == null) + log.debug("entities list is null for vclass " + vclassURI ); + + return rvMap; + } + + private static BooleanQuery getQuery(String vclassUri, String alpha){ + BooleanQuery query = new BooleanQuery(); + try{ + //query term for rdf:type + query.add( + new TermQuery( new Term(Entity2LuceneDoc.term.RDFTYPE, vclassUri)), + BooleanClause.Occur.MUST ); + + //Add alpha filter if it is needed + Query alphaQuery = null; + if( alpha != null && !"".equals(alpha) && alpha.length() == 1){ + alphaQuery = + new PrefixQuery(new Term(Entity2LuceneDoc.term.NAME_LOWERCASE, alpha.toLowerCase())); + query.add(alphaQuery,BooleanClause.Occur.MUST); + } + + log.debug("Query: " + query); + return query; + } catch (Exception ex){ + log.error(ex,ex); + return new BooleanQuery(); + } + } + + public static List makePagesList( int count, int pageSize, int selectedPage){ + + List records = new ArrayList( MAX_PAGES + 1 ); + int requiredPages = count/pageSize ; + int remainder = count % pageSize ; + if( remainder > 0 ) + requiredPages++; + + if( selectedPage < MAX_PAGES && requiredPages > MAX_PAGES ){ + //the selected pages is within the first maxPages, just show the normal pages up to maxPages. + for(int page = 1; page < requiredPages && page <= MAX_PAGES ; page++ ){ + records.add( new PageRecord( "page=" + page, Integer.toString(page), Integer.toString(page), selectedPage == page ) ); + } + records.add( new PageRecord( "page="+ (MAX_PAGES+1), Integer.toString(MAX_PAGES+1), "more...", false)); + }else if( requiredPages > MAX_PAGES && selectedPage+1 > MAX_PAGES && selectedPage < requiredPages - MAX_PAGES){ + //the selected pages is in the middle of the list of page + int startPage = selectedPage - MAX_PAGES / 2; + int endPage = selectedPage + MAX_PAGES / 2; + for(int page = startPage; page <= endPage ; page++ ){ + records.add( new PageRecord( "page=" + page, Integer.toString(page), Integer.toString(page), selectedPage == page ) ); + } + records.add( new PageRecord( "page="+ endPage+1, Integer.toString(endPage+1), "more...", false)); + }else if ( requiredPages > MAX_PAGES && selectedPage > requiredPages - MAX_PAGES ){ + //the selected page is in the end of the list + int startPage = requiredPages - MAX_PAGES; + double max = Math.ceil(count/pageSize); + for(int page = startPage; page <= max; page++ ){ + records.add( new PageRecord( "page=" + page, Integer.toString(page), Integer.toString(page), selectedPage == page ) ); + } + }else{ + //there are fewer than maxPages pages. + for(int i = 1; i <= requiredPages; i++ ){ + records.add( new PageRecord( "page=" + i, Integer.toString(i), Integer.toString(i), selectedPage == i ) ); + } + } + return records; + } + + public static class PageRecord { + public PageRecord(String param, String index, String text, boolean selected) { + this.param = param; + this.index = index; + this.text = text; + this.selected = selected; + } + public String param; + public String index; + public String text; + public boolean selected=false; + + public String getParam() { + return param; + } + public String getIndex() { + return index; + } + public String getText() { + return text; + } + public boolean getSelected(){ + return selected; + } + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDao.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDao.java index ba00580f6..d0baff184 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDao.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/UserAccountsDao.java @@ -20,6 +20,14 @@ public interface UserAccountsDao { */ UserAccount getUserAccountByUri(String uri); + /** + * Get the UserAccount for this Email address. + * + * @return null if the Email address is null, or if there is no such + * UserAccount + */ + UserAccount getUserAccountByEmail(String emailAddress); + /** * Create a new UserAccount in the model. * diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/UserAccountsDaoFiltering.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/UserAccountsDaoFiltering.java index 7da3d2fc1..ab46abac6 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/UserAccountsDaoFiltering.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/filtering/UserAccountsDaoFiltering.java @@ -17,7 +17,7 @@ public class UserAccountsDaoFiltering extends BaseFiltering implements UserAccountsDao { private final UserAccountsDao innerDao; - + @SuppressWarnings("unused") private final VitroFilters filters; @@ -32,6 +32,11 @@ public class UserAccountsDaoFiltering extends BaseFiltering implements return innerDao.getUserAccountByUri(uri); } + @Override + public UserAccount getUserAccountByEmail(String emailAddress) { + return innerDao.getUserAccountByEmail(emailAddress); + } + @Override public String insertUserAccount(UserAccount userAccount) { return innerDao.insertUserAccount(userAccount); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java index fd28ece40..512c80b81 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJena.java @@ -11,6 +11,7 @@ import com.hp.hpl.jena.ontology.OntModel; import com.hp.hpl.jena.ontology.OntResource; import com.hp.hpl.jena.rdf.model.Resource; import com.hp.hpl.jena.rdf.model.Statement; +import com.hp.hpl.jena.rdf.model.StmtIterator; import com.hp.hpl.jena.shared.Lock; import com.hp.hpl.jena.util.iterator.ClosableIterator; import com.hp.hpl.jena.vocabulary.RDF; @@ -71,6 +72,28 @@ public class UserAccountsDaoJena extends JenaBaseDao implements UserAccountsDao } } + @Override + public UserAccount getUserAccountByEmail(String emailAddress) { + if (emailAddress == null) { + return null; + } + + String userUri = null; + + getOntModel().enterCriticalSection(Lock.READ); + try { + StmtIterator stmts = getOntModel().listStatements(null, USERACCOUNT_EMAIL_ADDRESS, + getOntModel().createLiteral(emailAddress)); + if (stmts.hasNext()) { + userUri = stmts.next().getSubject().getURI(); + } + } finally { + getOntModel().leaveCriticalSection(); + } + + return getUserAccountByUri(userUri); + } + @Override public String insertUserAccount(UserAccount userAccount) { if (userAccount == null) { diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/SolrAutocompleteController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/SolrAutocompleteController.java index 4a06424c7..95c7fbf9b 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/SolrAutocompleteController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/controller/SolrAutocompleteController.java @@ -13,15 +13,16 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.lang.StringUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.lucene.search.BooleanQuery; import org.apache.solr.client.solrj.SolrQuery; import org.apache.solr.client.solrj.SolrServer; import org.apache.solr.client.solrj.response.QueryResponse; +import org.apache.solr.client.solrj.response.TermsResponse; import org.apache.solr.common.SolrDocument; import org.apache.solr.common.SolrDocumentList; -import org.apache.solr.common.params.FacetParams; import org.json.JSONArray; import org.json.JSONObject; @@ -113,6 +114,7 @@ public class SolrAutocompleteController extends VitroAjaxController { } } + // Since SolrQuery.setSortField() is buggy, sort the results here Collections.sort(results); // map.put("results", results); @@ -130,38 +132,43 @@ public class SolrAutocompleteController extends VitroAjaxController { } } - private SolrQuery getQuery(String querystr, VitroRequest vreq) { + private SolrQuery getQuery(String queryStr, VitroRequest vreq) { - if ( querystr == null) { + if ( queryStr == null) { log.error("There was no parameter '"+ PARAM_QUERY +"' in the request."); return null; - } else if( querystr.length() > MAX_QUERY_LENGTH ) { + } else if( queryStr.length() > MAX_QUERY_LENGTH ) { log.debug("The search was too long. The maximum " + "query length is " + MAX_QUERY_LENGTH ); return null; } SolrQuery query = new SolrQuery(); - query = query.setStart(0); - query = query.setRows(DEFAULT_MAX_HIT_COUNT); + query.setStart(0) + .setRows(DEFAULT_MAX_HIT_COUNT); - query = setNameQuery(query, querystr, vreq); + setQuery(query, queryStr, vreq); // Filter by type String typeParam = (String) vreq.getParameter(PARAM_RDFTYPE); if (typeParam != null) { - query = query.addFilterQuery(VitroLuceneTermNames.RDFTYPE + ":\"" + typeParam + "\""); + query.addFilterQuery(VitroLuceneTermNames.RDFTYPE + ":\"" + typeParam + "\""); } - // Set the fields to retrieve **** RY - // query = query.setFields( ... ); - + query.setFields(VitroLuceneTermNames.NAME_RAW, VitroLuceneTermNames.URI); // fields to retrieve + // Solr bug: generates sort=nameLowercase asc instead of sort=nameLowercase+asc + //.setSortField(VitroLuceneTermNames.NAME_LOWERCASE, SolrQuery.ORDER.asc); + return query; } - private SolrQuery setNameQuery(SolrQuery query, String querystr, HttpServletRequest request) { + private void setQuery(SolrQuery query, String queryStr, HttpServletRequest request) { + if (StringUtils.isBlank(queryStr)) { + log.error("No query string"); + } + String tokenizeParam = (String) request.getParameter("tokenize"); boolean tokenize = "true".equals(tokenizeParam); @@ -169,19 +176,21 @@ public class SolrAutocompleteController extends VitroAjaxController { // query will not be stemmed. So we don't look at the stem parameter until we get to // setTokenizedNameQuery(). if (tokenize) { - return setTokenizedNameQuery(query, querystr, request); + setTokenizedQuery(query, queryStr, request); } else { - return setUntokenizedNameQuery(query, querystr); + setUntokenizedQuery(query, queryStr); } } - private SolrQuery setTokenizedNameQuery(SolrQuery query, String querystr, HttpServletRequest request) { + private void setTokenizedQuery(SolrQuery query, String queryStr, HttpServletRequest request) { + + // RY 5/18/2011 For now, just doing untokenized query, due to the interactions of wildcard + // query and stemming described below. Need to find a way to do this in Solr. + // Should take the same approach if we can figure out how to do a disjunction. - String stemParam = (String) request.getParameter("stem"); - boolean stem = "true".equals(stemParam); - String termName = stem ? VitroLuceneTermNames.AC_NAME_STEMMED : VitroLuceneTermNames.AC_NAME_UNSTEMMED ; - - BooleanQuery boolQuery = new BooleanQuery(); +// String stemParam = (String) request.getParameter("stem"); +// boolean stem = "true".equals(stemParam); +// String termName = stem ? VitroLuceneTermNames.AC_NAME_STEMMED : VitroLuceneTermNames.AC_NAME_UNSTEMMED ; // // Use the query parser to analyze the search term the same way the indexed text was analyzed. // // For example, text is lowercased, and function words are stripped out. @@ -190,7 +199,7 @@ public class SolrAutocompleteController extends VitroAjaxController { // // The wildcard query doesn't play well with stemming. Query term name:tales* doesn't match // // "tales", which is indexed as "tale", while query term name:tales does. Obviously we need // // the wildcard for name:tal*, so the only way to get them all to match is use a disjunction -// // of wildcard and non-wildcard queries. The query will look have only an implicit disjunction +// // of wildcard and non-wildcard queries. The query will have only an implicit disjunction // // operator: e.g., +(name:tales name:tales*) // try { // log.debug("Adding non-wildcard query for " + querystr); @@ -210,20 +219,18 @@ public class SolrAutocompleteController extends VitroAjaxController { // log.warn(e, e); // } - return query; + setUntokenizedQuery(query, queryStr); } - private SolrQuery setUntokenizedNameQuery(SolrQuery query, String querystr) { + private void setUntokenizedQuery(SolrQuery query, String queryStr) { - // Using facet method described in http://solr.pl/en/2010/10/18/solr-and-autocomplete-part-1/ - // Consider using Solr Suggester in a future version. - return query.setFacet(true) - .addFacetField(VitroLuceneTermNames.NAME_LOWERCASE) - .setFacetMinCount(1) - .setFacetLimit(MAX_QUERY_LENGTH) - .setFacetPrefix(querystr)//.toLowerCase()) - //.setFacetSort(FacetParams.FACET_SORT_INDEX) // sort by alpha (but doesn't work) - .setQuery("*:*"); + // We have to lowercase manually, because Solr doesn't do text analysis on wildcard queries + queryStr = queryStr.toLowerCase(); + // Solr wants whitespace to be escaped with a backslash + // Better: replace \s+ + queryStr = queryStr.replaceAll(" ", "\\\\ "); + queryStr = VitroLuceneTermNames.NAME_LOWERCASE + ":" + queryStr + "*"; + query.setQuery(queryStr); } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/solr/IndividualToSolrDocument.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/solr/IndividualToSolrDocument.java index bd6ac321e..4d2d1858d 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/search/solr/IndividualToSolrDocument.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/search/solr/IndividualToSolrDocument.java @@ -161,7 +161,7 @@ public class IndividualToSolrDocument implements Obj2DocIface { contextNodePropertyValues += contextNodesInclusionFactory.getPropertiesAssociatedWithPosition(ent.getURI()); contextNodePropertyValues += contextNodesInclusionFactory.getPropertiesAssociatedWithRelationship(ent.getURI()); contextNodePropertyValues += contextNodesInclusionFactory.getPropertiesAssociatedWithAwardReceipt(ent.getURI()); - contextNodePropertyValues += contextNodesInclusionFactory.getPropertiesAssociatedWithInformationResource(ent.getURI()); + contextNodePropertyValues += contextNodesInclusionFactory.getPropertiesAssociatedWithInformationResource(ent.getURI()); doc.addField(term.CONTEXTNODE, contextNodePropertyValues); diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJenaTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJenaTest.java index 33399e2d9..157f826f1 100644 --- a/webapp/test/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJenaTest.java +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/dao/jena/UserAccountsDaoJenaTest.java @@ -2,6 +2,7 @@ package edu.cornell.mannlib.vitro.webapp.dao.jena; +import static org.junit.Assert.*; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNull; import static org.junit.Assert.fail; @@ -46,6 +47,9 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass { private static final String URI_USER1 = NS_MINE + "user01"; private static final String URI_NO_SUCH_USER = NS_MINE + "bogusUser"; + private static final String EMAIL_USER1 = "email@able.edu"; + private static final String EMAIL_NO_SUCH_USER = NS_MINE + "bogus@email.com"; + private static final String URI_ROLE1 = NS_MINE + "role1"; private static final String URI_ROLE2 = NS_MINE + "role2"; private static final String URI_ROLE3 = NS_MINE + "role3"; @@ -100,6 +104,24 @@ public class UserAccountsDaoJenaTest extends AbstractTestClass { assertNull("null result", u); } + @Test + public void getUserAccountByEmailSuccess() { + UserAccount u = dao.getUserAccountByEmail(EMAIL_USER1); + assertEquals("uri", URI_USER1, u.getUri()); + } + + @Test + public void getUserAccountByEmailNull() { + UserAccount u = dao.getUserAccountByEmail(null); + assertEquals("uri", null, u); + } + + @Test + public void getUserAccountByEmailNotFound() { + UserAccount u = dao.getUserAccountByEmail(EMAIL_NO_SUCH_USER); + assertEquals("uri", null, u); + } + @Test public void insertUserAccountSuccess() { UserAccount in = new UserAccount(); diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-add.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-add.ftl new file mode 100644 index 000000000..1f937f6c1 --- /dev/null +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-add.ftl @@ -0,0 +1,70 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> + +<#-- Template for adding a user account --> + +

Add new account

+ + <#if errorEmailIsEmpty??> + <#assign errorMessage = "You must supply an email address." /> + + + <#if errorEmailInUse??> + <#assign errorMessage = "An account with that email address already exists." /> + + + <#if errorFirstNameIsEmpty??> + <#assign errorMessage = "You must supply a first name." /> + + + <#if errorLastNameIsEmpty??> + <#assign errorMessage = "You must supply a last name." /> + + + <#if errorNoRoleSelected??> + <#assign errorMessage = "You must select a role." /> + + + <#if errorMessage?has_content> + + + +
+ Email address * +
+ +
+ First name * +
+ +
+ Last name * +
+ +
+
+ Roles * +
+ <#list roles as role> + selected />${role.label} +
+ +
+ Associate a profile with this account +
+ checked />Yes +
+ checked />No +
+ +

+ Note: An email will be sent to the address entered above + notifying that an account has been created. + It will include instructions for activating the account and creating a password. +

+ + + or Cancel +
diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-edit.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-edit.ftl new file mode 100644 index 000000000..95dc4a47f --- /dev/null +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-edit.ftl @@ -0,0 +1,6 @@ +<#-- $This file is distributed under the terms of the license in /doc/license.txt$ --> + +<#-- Template for editing a user account --> + +

Edit user account

+ diff --git a/webapp/web/templates/freemarker/body/accounts/userAccounts-list.ftl b/webapp/web/templates/freemarker/body/accounts/userAccounts-list.ftl index e6264302d..d4cc2cc31 100644 --- a/webapp/web/templates/freemarker/body/accounts/userAccounts-list.ftl +++ b/webapp/web/templates/freemarker/body/accounts/userAccounts-list.ftl @@ -2,7 +2,7 @@ <#-- Template for displaying list of user accounts --> -
+ <#--current page:
--> @@ -38,12 +38,28 @@ -->
-

Account |

- +
+

Account |

+
- +<#if newUserAccount?? > + + + +<#if deletedAccountCount?? > + +
-
- - - -
+
+
+ + + +
+
-
- - + -