From 555e068d6a936810bb29a7d2b1394969825ee085 Mon Sep 17 00:00:00 2001 From: j2blake Date: Fri, 1 Jul 2011 16:13:23 +0000 Subject: [PATCH] NIHVIVO-2279 convert UserAccountsAjaxController from its first version, which was essentially a stub. --- .../admin/ajax/ExternalAuthChecker.java | 128 ++++++++++++ .../admin/ajax/ProfileAutoCompleter.java | 191 ++++++++++++++++++ .../ajax/UserAccountsAjaxController.java | 108 ++++------ 3 files changed, 354 insertions(+), 73 deletions(-) create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/ExternalAuthChecker.java create mode 100644 webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/ProfileAutoCompleter.java diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/ExternalAuthChecker.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/ExternalAuthChecker.java new file mode 100644 index 000000000..508c7fbcf --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/ExternalAuthChecker.java @@ -0,0 +1,128 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.admin.ajax; + +import java.io.IOException; +import java.util.List; + +import javax.servlet.http.HttpServlet; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONException; +import org.json.JSONObject; + +import edu.cornell.mannlib.vitro.webapp.beans.Individual; +import edu.cornell.mannlib.vitro.webapp.beans.SelfEditingConfiguration; +import edu.cornell.mannlib.vitro.webapp.beans.UserAccount; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.accounts.admin.ajax.UserAccountsAjaxController.AjaxResponder; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; + +/** + * What is our reaction to this possible External Auth ID? + * + * Is somebody already using it (other than ourselves)? Does it match an + * existing Profile? Neither? + * + * If the userAccountUri or the externalAuthId is empty, or if there is any + * error, say "neither". + */ +class ExternalAuthChecker extends AjaxResponder { + private static final Log log = LogFactory.getLog(ExternalAuthChecker.class); + + private static final String PARAMETER_USER_ACCOUNT_URI = "userAccountUri"; + private static final String PARAMETER_ETERNAL_AUTH_ID = "externalAuthId"; + private static final String RESPONSE_ID_IN_USE = "idInUse"; + private static final String RESPONSE_MATCHES_PROFILE = "matchesProfile"; + private static final String RESPONSE_PROFILE_URI = "profileUri"; + private static final String RESPONSE_PROFILE_URL = "profileUrl"; + private static final String RESPONSE_PROFILE_LABEL = "profileLabel"; + + private final String userAccountUri; + private final String externalAuthId; + + private Individual matchingProfile; + + public ExternalAuthChecker(HttpServlet parent, VitroRequest vreq, + HttpServletResponse resp) { + super(parent, vreq, resp); + userAccountUri = getStringParameter(PARAMETER_USER_ACCOUNT_URI, ""); + externalAuthId = getStringParameter(PARAMETER_ETERNAL_AUTH_ID, ""); + } + + @Override + public String prepareResponse() throws IOException, JSONException { + if (someoneElseIsUsingThisExternalAuthId()) { + return respondExternalAuthIdAlreadyUsed(); + } + + checkForMatchingProfile(); + if (matchingProfile != null) { + return respondWithMatchingProfile(); + } + + return EMPTY_RESPONSE; + } + + private boolean someoneElseIsUsingThisExternalAuthId() { + if (externalAuthId.isEmpty()) { + log.debug("externalAuthId is empty."); + return false; + } + + UserAccount user = uaDao.getUserAccountByExternalAuthId(externalAuthId); + if (user == null) { + log.debug("no user account has externalAuthId='" + externalAuthId + + "'"); + return false; + } + + if (user.getUri().equals(userAccountUri)) { + log.debug("externalAuthId '" + externalAuthId + + "' belongs to current user '" + userAccountUri + "'"); + return false; + } + + log.debug(user.getEmailAddress() + " is already using externalAuthId '" + + externalAuthId + "'"); + return true; + } + + private String respondExternalAuthIdAlreadyUsed() throws JSONException { + JSONObject jsonObject = new JSONObject(); + jsonObject.put(RESPONSE_ID_IN_USE, true); + return jsonObject.toString(); + } + + private void checkForMatchingProfile() { + if (userAccountUri.isEmpty()) { + log.debug("userAccountUri is empty"); + return; + } + + List inds = SelfEditingConfiguration.getBean(vreq) + .getAssociatedIndividuals(indDao, externalAuthId); + if (inds.isEmpty()) { + log.debug("No profiles match '" + externalAuthId + "'"); + return; + } + + this.matchingProfile = inds.get(0); + } + + private String respondWithMatchingProfile() throws JSONException { + String uri = matchingProfile.getURI(); + String url = UrlBuilder.getIndividualProfileUrl(uri, vreq); + String label = matchingProfile.getRdfsLabel(); + + JSONObject jsonObject = new JSONObject(); + jsonObject.put(RESPONSE_MATCHES_PROFILE, true); + jsonObject.put(RESPONSE_PROFILE_URI, uri); + jsonObject.put(RESPONSE_PROFILE_URL, url); + jsonObject.put(RESPONSE_PROFILE_LABEL, label); + return jsonObject.toString(); + } + +} \ No newline at end of file diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/ProfileAutoCompleter.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/ProfileAutoCompleter.java new file mode 100644 index 000000000..7777c5fb9 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/ProfileAutoCompleter.java @@ -0,0 +1,191 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.admin.ajax; + +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.http.HttpServlet; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONException; + +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 edu.cornell.mannlib.vitro.webapp.beans.SelfEditingConfiguration; +import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest; +import edu.cornell.mannlib.vitro.webapp.controller.accounts.admin.ajax.UserAccountsAjaxController.AjaxResponder; +import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder; + +/** + * Get a list of Profiles with last names that begin with this search term, and + * that have no matching property, unless it matches the current externalAuthId. + * (So a Profile that is currently associated with the user is not excluded from + * the list.) + * + * For each such Profile, return the label, the URL and the URI. + * + * If the matching property is not defined, or if the search term is empty, or + * if an error occurs, return an empty result. + */ +class ProfileAutoCompleter extends AjaxResponder { + private static final Log log = LogFactory + .getLog(ProfileAutoCompleter.class); + + private static final String PARAMETER_SEARCH_TERM = "term"; + private static final String PARAMETER_ETERNAL_AUTH_ID = "externalAuthId"; + + private static final Syntax SYNTAX = Syntax.syntaxARQ; + + private static final String QUERY_TEMPLATE = "" // + + "PREFIX rdf: \n" + + "PREFIX foaf: \n" + "\n" // + + "SELECT DISTINCT ?uri ?fn ?ln \n" // + + "WHERE {\n" // + + " ?uri rdf:type foaf:Person ; \n" // + + " foaf:firstName ?fn ; \n" // + + " foaf:lastName ?ln . \n" // + + " OPTIONAL { ?uri <%matchingPropertyUri%> ?id} \n" // + + " FILTER ( !bound(?id) || (?id = '%externalAuthId%') ) \n" // + + " FILTER ( REGEX(?ln, '%searchTerm%', 'i') ) \n" // + + "} \n" // + + "ORDER BY ?ln ?fn \n" // + + "LIMIT 20 \n"; + + private final String term; + private final String externalAuthId; + private final String selfEditingIdMatchingProperty; + private final OntModel fullModel; + + public ProfileAutoCompleter(HttpServlet parent, VitroRequest vreq, + HttpServletResponse resp) { + super(parent, vreq, resp); + term = getStringParameter(PARAMETER_SEARCH_TERM, ""); + externalAuthId = getStringParameter(PARAMETER_ETERNAL_AUTH_ID, ""); + + // TODO This seems to expose the matching property and mechanism too + // much. Can this be done within SelfEditingConfiguration somehow? + selfEditingIdMatchingProperty = SelfEditingConfiguration.getBean(vreq) + .getMatchingPropertyUri(); + + fullModel = vreq.getJenaOntModel(); + } + + @Override + public String prepareResponse() throws IOException, JSONException { + if (term.isEmpty()) { + return EMPTY_RESPONSE; + } + if (selfEditingIdMatchingProperty == null) { + return EMPTY_RESPONSE; + } + if (selfEditingIdMatchingProperty.isEmpty()) { + return EMPTY_RESPONSE; + } + return doSparqlQueryAndParseResult(); + } + + private String doSparqlQueryAndParseResult() throws JSONException { + String queryStr = prepareQueryString(); + + QueryExecution qe = null; + List results; + try { + Query query = QueryFactory.create(queryStr, SYNTAX); + qe = QueryExecutionFactory.create(query, fullModel); + results = parseResults(qe.execSelect()); + } catch (Exception e) { + log.error("Failed to execute the query: " + queryStr, e); + results = Collections.emptyList(); + } finally { + if (qe != null) { + qe.close(); + } + } + + JSONArray jsonArray = prepareJsonArray(results); + return jsonArray.toString(); + } + + private String prepareQueryString() { + String queryString = QUERY_TEMPLATE + .replace("%matchingPropertyUri%", selfEditingIdMatchingProperty) + .replace("%searchTerm%", term) + .replace("%externalAuthId%", externalAuthId); + log.debug("Query string is '" + queryString + "'"); + return queryString; + } + + private List parseResults(ResultSet results) { + List profiles = new ArrayList(); + while (results.hasNext()) { + QuerySolution solution = results.next(); + ProfileInfo pi = parseSolution(solution); + profiles.add(pi); + } + log.debug("Results are: " + profiles); + return profiles; + } + + private ProfileInfo parseSolution(QuerySolution solution) { + String uri = solution.getResource("uri").getURI(); + String url = UrlBuilder.getIndividualProfileUrl(uri, vreq); + + String firstName = solution.getLiteral("fn").getString(); + String lastName = solution.getLiteral("ln").getString(); + String label = lastName + ", " + firstName; + + return new ProfileInfo(uri, url, label); + } + + private JSONArray prepareJsonArray(List results) + throws JSONException { + JSONArray jsonArray = new JSONArray(); + + for (int i = 0; i < results.size(); i++) { + ProfileInfo profile = results.get(i); + + Map map = new HashMap(); + map.put("label", profile.label); + map.put("uri", profile.uri); + map.put("url", profile.url); + + jsonArray.put(i, map); + } + + return jsonArray; + } + + private static class ProfileInfo { + final String uri; + final String url; + final String label; + + public ProfileInfo(String uri, String url, String label) { + this.uri = uri; + this.url = url; + this.label = label; + } + + @Override + public String toString() { + return "ProfileInfo[label=" + label + ", uri=" + uri + ", url=" + + url + "]"; + } + } + +} \ No newline at end of file diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/UserAccountsAjaxController.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/UserAccountsAjaxController.java index 72b264877..95114e7fd 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/UserAccountsAjaxController.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/admin/ajax/UserAccountsAjaxController.java @@ -8,18 +8,24 @@ import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpServletResponse; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; import org.json.JSONException; -import org.json.JSONObject; 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.controller.VitroRequest; import edu.cornell.mannlib.vitro.webapp.controller.ajax.VitroAjaxController; +import edu.cornell.mannlib.vitro.webapp.dao.IndividualDao; +import edu.cornell.mannlib.vitro.webapp.dao.UserAccountsDao; /** * Handle the AJAX functions that are specific to the UserAccounts pages. */ public class UserAccountsAjaxController extends VitroAjaxController { + private static final Log log = LogFactory + .getLog(UserAccountsAjaxController.class); + private static final String PARAMETER_FUNCTION = "function"; @Override @@ -30,27 +36,49 @@ public class UserAccountsAjaxController extends VitroAjaxController { @Override protected void doRequest(VitroRequest vreq, HttpServletResponse resp) throws ServletException, IOException { - String function = vreq.getParameter(PARAMETER_FUNCTION); - if ("checkExternalAuth".equals(function)) { - new ExternalAuthChecker(this, vreq, resp).processRequest(); - } else { - new ErrorResponder(this, vreq, resp).processRequest(); + try { + String function = vreq.getParameter(PARAMETER_FUNCTION); + if ("checkExternalAuth".equals(function)) { + new ExternalAuthChecker(this, vreq, resp).processRequest(); + } else if ("autoCompleteProfile".equals(function)) { + new ProfileAutoCompleter(this, vreq, resp).processRequest(); + } else { + resp.getWriter().write("[]"); + } + } catch (Exception e) { + log.error(e, e); + resp.getWriter().write("[]"); } } static abstract class AjaxResponder { + protected static final String EMPTY_RESPONSE = "[]"; + protected final HttpServlet parent; protected final VitroRequest vreq; protected final HttpServletResponse resp; + protected final IndividualDao indDao; + protected final UserAccountsDao uaDao; public AjaxResponder(HttpServlet parent, VitroRequest vreq, HttpServletResponse resp) { this.parent = parent; this.vreq = vreq; this.resp = resp; + this.indDao = vreq.getWebappDaoFactory().getIndividualDao(); + this.uaDao = vreq.getWebappDaoFactory().getUserAccountsDao(); } - public abstract void processRequest() throws IOException; + public final void processRequest() { + try { + resp.getWriter().write(prepareResponse()); + } catch (Exception e) { + log.error("Problem with AJAX response", e); + } + } + + protected abstract String prepareResponse() throws IOException, + JSONException; protected String getStringParameter(String key, String defaultValue) { String value = vreq.getParameter(key); @@ -59,70 +87,4 @@ public class UserAccountsAjaxController extends VitroAjaxController { } - /** - * What is our reaction to this possible External Auth ID? - * - * Is somebody already using it (other than ourselves)? Does it match an - * existing Profile? Neither? - */ - private static class ExternalAuthChecker extends AjaxResponder { - private static final String PARAMETER_USER_ACCOUNT_URI = "userAccountUri"; - private static final String PARAMETER_ETERNAL_AUTH_ID = "externalAuthId"; - private static final String RESPONSE_ID_IN_USE = "idInUse"; - private static final String RESPONSE_MATCHES_PROFILE = "matchesProfile"; - private static final String RESPONSE_PROFILE_URI = "profileUri"; - private static final String RESPONSE_PROFILE_URL = "profileUrl"; - private static final String RESPONSE_PROFILE_LABEL = "profileLabel"; - - private final String userAccountUri; - private final String externalAuthId; - - public ExternalAuthChecker(HttpServlet parent, VitroRequest vreq, - HttpServletResponse resp) { - super(parent, vreq, resp); - userAccountUri = getStringParameter(PARAMETER_USER_ACCOUNT_URI, ""); - externalAuthId = getStringParameter(PARAMETER_ETERNAL_AUTH_ID, ""); - } - - @Override - public void processRequest() throws IOException { - // TODO For now, a totally bogus response: - // If "A", somebody else is already using the externalAuthId - // If "B", matches "Joe Blow" - // Anything else, no match. - try { - if ("A".equals(externalAuthId)) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put(RESPONSE_ID_IN_USE, true); - resp.getWriter().write(jsonObject.toString()); - } else if ("B".equals(externalAuthId)) { - JSONObject jsonObject = new JSONObject(); - jsonObject.put(RESPONSE_MATCHES_PROFILE, true); - jsonObject.put(RESPONSE_PROFILE_URI, - "http://some.bogus.profile"); - jsonObject.put(RESPONSE_PROFILE_URL, - "http://some.bogus.profileUrl"); - jsonObject.put(RESPONSE_PROFILE_LABEL, "bogus label"); - resp.getWriter().write(jsonObject.toString()); - } else { - resp.getWriter().write("[]"); - } - } catch (JSONException e) { - resp.getWriter().write("[]"); - } - } - } - - private static class ErrorResponder extends AjaxResponder { - public ErrorResponder(HttpServlet parent, VitroRequest vreq, - HttpServletResponse resp) { - super(parent, vreq, resp); - } - - @Override - public void processRequest() throws IOException { - resp.getWriter().write("[]"); - } - } - }