diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyItemInfo.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyItemInfo.java new file mode 100644 index 000000000..1aa368912 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyItemInfo.java @@ -0,0 +1,77 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies; + +/** + * An immutable collection of fields that will be displayed in a + * ProxyRelationship. + */ +public class ProxyItemInfo { + private final String uri; + private final String label; + private final String classLabel; + private final String imageUrl; + + public ProxyItemInfo(String uri, String label, String classLabel, + String imageUrl) { + this.uri = uri; + this.label = label; + this.classLabel = classLabel; + this.imageUrl = imageUrl; + } + + public String getUri() { + return uri; + } + + public String getLabel() { + return label; + } + + public String getClassLabel() { + return classLabel; + } + + public String getImageUrl() { + return imageUrl; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (o == null) { + return false; + } + if (!o.getClass().equals(this.getClass())) { + return false; + } + ProxyItemInfo that = (ProxyItemInfo) o; + return equivalent(this.uri, that.uri) + && equivalent(this.label, that.label) + && equivalent(this.classLabel, that.classLabel) + && equivalent(this.imageUrl, that.imageUrl); + } + + private boolean equivalent(Object o1, Object o2) { + return (o1 == null) ? (o2 == null) : o1.equals(o2); + } + + @Override + public int hashCode() { + return hash(this.uri) ^ hash(this.label) ^ hash(this.classLabel) + ^ hash(this.imageUrl); + } + + private int hash(Object o) { + return (o == null) ? 0 : o.hashCode(); + } + + @Override + public String toString() { + return "ProxyItemInfo[uri=" + uri + ", label=" + label + + ", classLabel=" + classLabel + ", imageUrl=" + imageUrl + "]"; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationship.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationship.java new file mode 100644 index 000000000..8dcb7982c --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationship.java @@ -0,0 +1,42 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * An immutable relationship between Proxies and Profiles. + * + * In most cases, this will either be between one Proxy and many Profiles (view + * by Proxy), or between on Profile and many Proxies (view by Profile). However, + * we can imagine it being a many-to-many relationship. + */ +public class ProxyRelationship { + private final List proxyInfos; + private final List profileInfos; + + public ProxyRelationship(List proxyInfos, + List profileInfos) { + this.proxyInfos = Collections + .unmodifiableList(new ArrayList(proxyInfos)); + this.profileInfos = Collections + .unmodifiableList(new ArrayList(profileInfos)); + } + + public List getProxyInfos() { + return proxyInfos; + } + + public List getProfileInfos() { + return profileInfos; + } + + @Override + public String toString() { + return "ProxyRelationship[proxyInfos=" + proxyInfos + ", profileInfos=" + + profileInfos + "]"; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelection.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelection.java new file mode 100644 index 000000000..97e2882a7 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelection.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.accounts.manageproxies; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * An immutable group of relationships (might be empty), with the criteria that + * were used to select them. + */ + +public class ProxyRelationshipSelection { + private final ProxyRelationshipSelectionCriteria criteria; + private final List proxyRelationships; + private final int totalResultCount; + + public ProxyRelationshipSelection( + ProxyRelationshipSelectionCriteria criteria, + List proxyRelationships, int totalResultCount) { + this.criteria = criteria; + this.proxyRelationships = Collections + .unmodifiableList(new ArrayList( + proxyRelationships)); + this.totalResultCount = totalResultCount; + } + + public ProxyRelationshipSelectionCriteria getCriteria() { + return criteria; + } + + public List getProxyRelationships() { + return proxyRelationships; + } + + public int getTotalResultCount() { + return totalResultCount; + } + + @Override + public String toString() { + return "ProxyRelationshipSelection[count=" + totalResultCount + + ", relationships=" + proxyRelationships + ", criteria=" + + criteria + "]"; + } + +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectionBuilder.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectionBuilder.java new file mode 100644 index 000000000..9c7276d89 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectionBuilder.java @@ -0,0 +1,79 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies; + +import java.util.ArrayList; +import java.util.List; + +/** + * A mutable version of ProxyRelationshipSelection, that can be assembled + * piecemeal as the info becomes available and then translated to an immutable + * ProxyRelationshipSelection. + * + * Uses mutable subclasses Relationship and ItemInfo. + * + * ItemInfo contains a field for externalAuthId only because it is useful when + * gathering the classLabel and imageUrl. + */ +public class ProxyRelationshipSelectionBuilder { + final ProxyRelationshipSelectionCriteria criteria; + + final List relationships = new ArrayList(); + int count; + + public ProxyRelationshipSelectionBuilder( + ProxyRelationshipSelectionCriteria criteria) { + this.criteria = criteria; + } + + public ProxyRelationshipSelection build() { + List proxyRelationships = new ArrayList(); + for (Relationship r : relationships) { + proxyRelationships.add(buildProxyRelationship(r)); + } + return new ProxyRelationshipSelection(criteria, proxyRelationships, + count); + } + + private ProxyRelationship buildProxyRelationship(Relationship r) { + List proxyInfos = buildInfos(r.proxyInfos); + List profileInfos = buildInfos(r.profileInfos); + return new ProxyRelationship(proxyInfos, profileInfos); + } + + private List buildInfos(List infos) { + List result = new ArrayList(); + for (ItemInfo info : infos) { + result.add(new ProxyItemInfo(info.uri, info.label, info.classLabel, + info.imageUrl)); + } + return result; + } + + public static class Relationship { + final List proxyInfos = new ArrayList(); + final List profileInfos = new ArrayList(); + } + + public static class ItemInfo { + String uri = ""; + String label = ""; + String externalAuthId = ""; + String classLabel = ""; + String imageUrl = ""; + + public ItemInfo() { + // leave fields at default values. + } + + public ItemInfo(String uri, String label, String externalAuthId, + String classLabel, String imageUrl) { + this.uri = uri; + this.label = label; + this.externalAuthId = externalAuthId; + this.classLabel = classLabel; + this.imageUrl = imageUrl; + } + + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectionCriteria.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectionCriteria.java new file mode 100644 index 000000000..d0d4f7d06 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectionCriteria.java @@ -0,0 +1,84 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies; + +/** + * On what basis are we selecting proxy relationships? + * + * Are we viewing by Proxy or by Profile? What is the search term, if any? How + * many results per page, and what page are we on? + * + * Search terms are matched against last name combined with first name, of + * either UserAccount(Proxy) or Individual(Profile), depending on how we are + * listing. Searches are case-insensitive. + */ +public class ProxyRelationshipSelectionCriteria { + public static final int DEFAULT_RELATIONSHIPS_PER_PAGE = 20; + + public static final ProxyRelationshipSelectionCriteria DEFAULT_CRITERIA = new ProxyRelationshipSelectionCriteria( + DEFAULT_RELATIONSHIPS_PER_PAGE, 1, ProxyRelationshipView.BY_PROXY, + ""); + + public enum ProxyRelationshipView { + BY_PROXY, BY_PROFILE; + + public static ProxyRelationshipView DEFAULT_VIEW = BY_PROXY; + } + + /** How many relationships should we bring back, at most? */ + private final int relationshipsPerPage; + + /** What page are we on? (1-origin) */ + private final int pageIndex; + + /** What view are we using? */ + private final ProxyRelationshipView viewBy; + + /** What term are we searching on, if any? */ + private final String searchTerm; + + public ProxyRelationshipSelectionCriteria(int relationshipsPerPage, + int pageIndex, ProxyRelationshipView viewBy, String searchTerm) { + if (relationshipsPerPage <= 0) { + throw new IllegalArgumentException("relationshipsPerPage " + + "must be a positive integer, not " + relationshipsPerPage); + } + this.relationshipsPerPage = relationshipsPerPage; + + if (pageIndex <= 0) { + throw new IllegalArgumentException("pageIndex must be a " + + "non-negative integer, not " + pageIndex); + } + this.pageIndex = pageIndex; + + this.viewBy = nonNull(viewBy, ProxyRelationshipView.DEFAULT_VIEW); + this.searchTerm = nonNull(searchTerm, ""); + } + + public int getRelationshipsPerPage() { + return relationshipsPerPage; + } + + public int getPageIndex() { + return pageIndex; + } + + public ProxyRelationshipView getViewBy() { + return viewBy; + } + + public String getSearchTerm() { + return searchTerm; + } + + private T nonNull(T t, T nullValue) { + return (t == null) ? nullValue : t; + } + + @Override + public String toString() { + return "ProxyRelationshipSelectionCriteria[relationshipsPerPage=" + + relationshipsPerPage + ", pageIndex=" + pageIndex + + ", viewBy=" + viewBy + "', searchTerm='" + searchTerm + "']"; + } +} diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelector.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelector.java new file mode 100644 index 000000000..2601ffd73 --- /dev/null +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelector.java @@ -0,0 +1,402 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +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.QuerySolution; +import com.hp.hpl.jena.query.ResultSet; + +import edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies.ProxyRelationshipSelectionBuilder.ItemInfo; +import edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies.ProxyRelationshipSelectionBuilder.Relationship; +import edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies.ProxyRelationshipSelectionCriteria.ProxyRelationshipView; +import edu.cornell.mannlib.vitro.webapp.utils.SparqlQueryRunner; +import edu.cornell.mannlib.vitro.webapp.utils.SparqlQueryRunner.QueryParser; + +/** + * A class which will accept a ProxyRelationshipSelectionCriteria and produce a + * ProxyRelationshipSelection. + */ +public class ProxyRelationshipSelector { + private static final Log log = LogFactory + .getLog(ProxyRelationshipSelector.class); + + /** + * Convenience method. + */ + public static ProxyRelationshipSelection select(Context context, + ProxyRelationshipSelectionCriteria criteria) { + return new ProxyRelationshipSelector(context, criteria).select(); + } + + private static final String PREFIX_LINES = "" + + "PREFIX fn: \n" + + "PREFIX rdfs: \n" + + "PREFIX auth: \n" + + "PREFIX vitro: \n" + + "PREFIX vpublic: \n"; + + private final Context context; + private final ProxyRelationshipSelectionCriteria criteria; + private final ProxyRelationshipSelectionBuilder builder; + + public ProxyRelationshipSelector(Context context, + ProxyRelationshipSelectionCriteria criteria) { + if (context == null) { + throw new NullPointerException("context may not be null."); + } + this.context = context; + + if (criteria == null) { + throw new NullPointerException("criteria may not be null."); + } + this.criteria = criteria; + + this.builder = new ProxyRelationshipSelectionBuilder(criteria); + } + + public ProxyRelationshipSelection select() { + if (criteria.getViewBy() == ProxyRelationshipView.BY_PROXY) { + figureTotalResultCount(); + getProxyBasics(); + expandProxies(); + getRelationships(); + expandProfiles(); + } else { + // This implementation is brain-dead if you ask for BY_PROFILE. + // Maybe someday soon. + log.error("Trying to select ProxyRelationships by profile!"); + } + return builder.build(); + } + + private static final String COUNT_QUERY_TEMPLATE = "" // + + "%prefixes% \n" // + + "SELECT count(DISTINCT ?uri) \n" // + + "WHERE {\n" // + + " ?uri a auth:UserAccount ; \n" // + + " auth:firstName ?firstName ; \n" // + + " auth:lastName ?lastName ; \n" // + + " auth:proxyEditorFor ?profile . \n" // + + "} \n"; // + + private void figureTotalResultCount() { + String qString = COUNT_QUERY_TEMPLATE.replace("%prefixes%", + PREFIX_LINES); + + int count = new SparqlQueryRunner(context.userAccountsModel, + new CountQueryParser()).executeQuery(qString); + + log.debug("result count: " + count); + builder.count = count; + } + + private static final String QUERY_PROXY_BASICS = "" // + + "%prefixes% \n" // + + "SELECT DISTINCT ?uri ?label ?externalAuthId \n" // + + "WHERE { \n" // + + " ?uri a auth:UserAccount ; \n" // + + " auth:firstName ?firstName ; \n" // + + " auth:lastName ?lastName ; \n" // + + " auth:proxyEditorFor ?profile . \n" // + + " LET ( ?label := fn:concat(?lastName, ', ', ?firstName) )" // + + " OPTIONAL { ?uri auth:externalAuthId ?externalAuthId } \n" // + + "} \n" // + + "ORDER BY ASC(?lastName) ASC(?firstName) \n" // + + "LIMIT %perPage% \n" // + + "OFFSET %offset%\n"; + + private void getProxyBasics() { + String qString = QUERY_PROXY_BASICS + .replace("%prefixes%", PREFIX_LINES) + .replace("%perPage%", + String.valueOf(criteria.getRelationshipsPerPage())) + .replace("%offset%", offset()); + + List relationships = new SparqlQueryRunner>( + context.userAccountsModel, new ProxyBasicsParser()) + .executeQuery(qString); + log.debug("getProxyBasics returns: " + relationships); + builder.relationships.addAll(relationships); + } + + private static final String QUERY_EXPAND_PROXY = "" // + + "%prefixes% \n" // + + "SELECT ?classLabel ?imageUrl \n" // + + "WHERE { \n" // + + " ?uri <%matchingProperty%> '%externalAuthId%'. \n" // + + " OPTIONAL { \n" + + " ?uri vitro:mostSpecificType ?type. \n" // + + " ?type rdfs:label ?classLabel \n" // + + " } \n" // + + " OPTIONAL { \n" // + + " ?uri vpublic:mainImage ?imageUri. \n" // + + " ?imageUri vpublic:thumbnailImage ?thumbUri. \n" // + + " ?thumbUri vpublic:downloadLocation ?thumbstreamUri. \n" // + + " ?thumbstreamUri vpublic:directDownloadUrl ?imageUrl. \n" // + + " } \n" // + + "} \n" // + + "LIMIT 1 \n"; // + + private void expandProxies() { + if (context.matchingProperty.isEmpty()) { + return; + } + + for (Relationship r : builder.relationships) { + for (ItemInfo proxy : r.proxyInfos) { + if (proxy.externalAuthId.isEmpty()) { + continue; + } + + String qString = QUERY_EXPAND_PROXY + .replace("%prefixes%", PREFIX_LINES) + .replace("%matchingProperty%", context.matchingProperty) + .replace("%externalAuthId%", proxy.externalAuthId); + + ItemInfo expansion = new SparqlQueryRunner( + context.unionModel, new ExpandProxyParser()) + .executeQuery(qString); + proxy.classLabel = expansion.classLabel; + proxy.imageUrl = expansion.imageUrl; + } + } + } + + private static final String QUERY_RELATIONSHIPS = "" // + + "%prefixes% \n" // + + "SELECT DISTINCT ?profileUri \n" // + + "WHERE { \n" // + + " <%proxyUri%> a auth:UserAccount ; \n" // + + " auth:proxyEditorFor ?profileUri . \n" // + + "} \n"; // + + private void getRelationships() { + for (Relationship r : builder.relationships) { + for (ItemInfo proxy : r.proxyInfos) { + String qString = QUERY_RELATIONSHIPS.replace("%prefixes%", + PREFIX_LINES).replace("%proxyUri%", proxy.uri); + + List profileUris = new SparqlQueryRunner>( + context.userAccountsModel, new RelationshipsParser()) + .executeQuery(qString); + + for (String profileUri : profileUris) { + r.profileInfos + .add(new ItemInfo(profileUri, "", "", "", "")); + } + } + } + } + + private static final String QUERY_EXPAND_PROFILE = "" // + + "%prefixes% \n" // + + "SELECT ?label ?classLabel ?imageUrl \n" // + + "WHERE { \n" // + + " <%profileUri%> rdfs:label ?label . \n" // + + " OPTIONAL { \n" + + " <%profileUri%> vitro:mostSpecificType ?type. \n" // + + " ?type rdfs:label ?classLabel \n" // + + " } \n" // + + " OPTIONAL { \n" // + + " <%profileUri%> vpublic:mainImage ?imageUri. \n" // + + " ?imageUri vpublic:thumbnailImage ?thumbUri. \n" // + + " ?thumbUri vpublic:downloadLocation ?thumbstreamUri. \n" // + + " ?thumbstreamUri vpublic:directDownloadUrl ?imageUrl. \n" // + + " } \n" // + + "} \n" // + + "LIMIT 1 \n"; // + + private void expandProfiles() { + for (Relationship r : builder.relationships) { + for (ItemInfo profile : r.profileInfos) { + String qString = QUERY_EXPAND_PROFILE.replace("%prefixes%", + PREFIX_LINES).replace("%profileUri%", profile.uri); + + ItemInfo expansion = new SparqlQueryRunner( + context.unionModel, new ExpandProfileParser()) + .executeQuery(qString); + profile.label = expansion.label; + profile.classLabel = expansion.classLabel; + profile.imageUrl = expansion.imageUrl; + } + } + } + + private String offset() { + int offset = criteria.getRelationshipsPerPage() + * (criteria.getPageIndex() - 1); + return String.valueOf(offset); + } + + /** + * What do we need to make this work? 2 models and an optional property. + */ + public static class Context { + private final OntModel userAccountsModel; + private final OntModel unionModel; + private final String matchingProperty; + + public Context(OntModel userAccountsModel, OntModel unionModel, + String matchingProperty) { + if (userAccountsModel == null) { + throw new NullPointerException( + "userAccountsModel may not be null."); + } + this.userAccountsModel = userAccountsModel; + + if (unionModel == null) { + throw new NullPointerException("unionModel may not be null."); + } + this.unionModel = unionModel; + + if (matchingProperty == null) { + this.matchingProperty = ""; + } else { + this.matchingProperty = matchingProperty; + } + } + + } + + // ---------------------------------------------------------------------- + // Parser classes + // ---------------------------------------------------------------------- + + private static class ProxyBasicsParser extends + QueryParser> { + @Override + protected List defaultValue() { + return Collections.emptyList(); + } + + @Override + protected List parseResults(String queryStr, + ResultSet results) { + List relationships = new ArrayList(); + while (results.hasNext()) { + try { + relationships.add(parseSolution(results.next())); + } catch (Exception e) { + log.warn("Failed to parse the query result: " + queryStr, e); + } + } + return relationships; + } + + private Relationship parseSolution(QuerySolution solution) { + ItemInfo info = new ItemInfo(); + info.uri = solution.getResource("uri").getURI(); + info.label = solution.getLiteral("label").getString(); + info.externalAuthId = ifLiteralPresent(solution, "externalAuthId", + ""); + + Relationship r = new Relationship(); + r.proxyInfos.add(info); + return r; + } + } + + private static class CountQueryParser extends QueryParser { + @Override + protected Integer defaultValue() { + return 0; + } + + @Override + protected 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 ExpandProxyParser extends QueryParser { + @Override + protected ItemInfo defaultValue() { + return new ItemInfo(); + } + + @Override + protected ItemInfo parseResults(String queryStr, ResultSet results) { + ItemInfo item = new ItemInfo(); + + if (results.hasNext()) { + try { + QuerySolution solution = results.next(); + item.classLabel = ifLiteralPresent(solution, "classLabel", + ""); + item.imageUrl = ifLiteralPresent(solution, "imageUrl", ""); + } catch (Exception e) { + log.warn("Failed to parse the query result" + queryStr, e); + } + } + + return item; + } + } + + private static class RelationshipsParser extends QueryParser> { + @Override + protected List defaultValue() { + return Collections.emptyList(); + } + + @Override + protected List parseResults(String queryStr, ResultSet results) { + List proxyUris = new ArrayList(); + while (results.hasNext()) { + try { + QuerySolution solution = results.next(); + proxyUris.add(solution.getResource("profileUri").getURI()); + } catch (Exception e) { + log.warn("Failed to parse the query result: " + queryStr, e); + } + } + return proxyUris; + } + } + + private static class ExpandProfileParser extends QueryParser { + @Override + protected ItemInfo defaultValue() { + return new ItemInfo(); + } + + @Override + protected ItemInfo parseResults(String queryStr, ResultSet results) { + ItemInfo item = new ItemInfo(); + + if (results.hasNext()) { + try { + QuerySolution solution = results.next(); + item.label = ifLiteralPresent(solution, "label", ""); + item.classLabel = ifLiteralPresent(solution, "classLabel", + ""); + item.imageUrl = ifLiteralPresent(solution, "imageUrl", ""); + } catch (Exception e) { + log.warn("Failed to parse the query result" + queryStr, e); + } + } + + return item; + } + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectorTest.java b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectorTest.java new file mode 100644 index 000000000..6215740b6 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectorTest.java @@ -0,0 +1,281 @@ +/* $This file is distributed under the terms of the license in /doc/license.txt$ */ + +package edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies; + +import static edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies.ProxyRelationshipSelectionCriteria.ProxyRelationshipView.DEFAULT_VIEW; +import static org.junit.Assert.assertEquals; + +import java.io.IOException; +import java.io.InputStream; +import java.util.List; + +import org.apache.log4j.Level; +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.controller.accounts.manageproxies.ProxyRelationshipSelectionCriteria.ProxyRelationshipView; +import edu.cornell.mannlib.vitro.webapp.controller.accounts.manageproxies.ProxyRelationshipSelector.Context; +import edu.cornell.mannlib.vitro.webapp.utils.SparqlQueryRunner; + +/** + * TODO + */ +public class ProxyRelationshipSelectorTest extends AbstractTestClass { + /** + * + */ + private static final String URL_PROFILE_IMAGE = "http://mydomain.edu/profileImage.jpg"; + /** + * + */ + private static final String URL_SELF_IMAGE = "http://mydomain.edu/selfImage.jpg"; + private static final String USER_ACCOUNT_DATA_FILENAME = "ProxyRelationshipSelectorTest_UserAccountsModel.n3"; + private static final String UNION_DATA_FILENAME = "ProxyRelationshipSelectorTest_UnionModel.n3"; + + private static final String NS_MINE = "http://vivo.mydomain.edu/individual/"; + private static final String MATCHING_PROPERTY = NS_MINE + "matching"; + + private static OntModel userAccountsModel; + private static OntModel unionModel; + private static Context context; + + private ProxyRelationshipSelection selection; + private ProxyRelationshipSelectionCriteria criteria; + + @BeforeClass + public static void setupModel() throws IOException { + userAccountsModel = prepareModel(USER_ACCOUNT_DATA_FILENAME); + unionModel = prepareModel(UNION_DATA_FILENAME); + context = new Context(userAccountsModel, unionModel, MATCHING_PROPERTY); + } + + private static OntModel prepareModel(String filename) throws IOException { + InputStream stream = ProxyRelationshipSelectorTest.class + .getResourceAsStream(filename); + Model model = ModelFactory.createDefaultModel(); + model.read(stream, null, "N3"); + stream.close(); + + OntModel ontModel = ModelFactory.createOntologyModel( + OntModelSpec.OWL_DL_MEM, model); + ontModel.prepare(); + return ontModel; + } + + // ---------------------------------------------------------------------- + // exceptions tests + // ---------------------------------------------------------------------- + + @Test(expected = NullPointerException.class) + public void contextIsNull() { + ProxyRelationshipSelector.select(null, + criteria(10, 1, DEFAULT_VIEW, "")); + } + + @Test(expected = NullPointerException.class) + public void userAccountsModelIsNull_select_nullPointerException() { + Context brokenContext = new Context(null, unionModel, MATCHING_PROPERTY); + ProxyRelationshipSelector.select(brokenContext, + criteria(10, 1, DEFAULT_VIEW, "")); + } + + @Test(expected = NullPointerException.class) + public void unionModelIsNull_select_nullPointerException() { + Context brokenContext = new Context(userAccountsModel, null, + MATCHING_PROPERTY); + ProxyRelationshipSelector.select(brokenContext, + criteria(10, 1, DEFAULT_VIEW, "")); + } + + @Test(expected = NullPointerException.class) + public void criteriaIsNull() { + ProxyRelationshipSelector.select(context, null); + } + + // ---------------------------------------------------------------------- + // fields tests + // ---------------------------------------------------------------------- + + @Test + public void checkAllFieldsOnFirstRelationship() { + setLoggerLevel(SparqlQueryRunner.class, Level.DEBUG); + selectOnCriteria(1, 1, DEFAULT_VIEW, ""); + System.out.println("SELECTION: " + selection); + assertExpectedCounts(7, counts(1, 1)); + + ProxyRelationship pr = selection.getProxyRelationships().get(0); + assertEquals( + "proxy", + item(NS_MINE + "userFirstProxy", "AAAA, FirstProxy", "Self", + URL_SELF_IMAGE), pr.getProxyInfos().get(0)); + assertEquals( + "profile", + item(NS_MINE + "firstProfile", "AAAA, FirstProfile", "Profile", + URL_PROFILE_IMAGE), pr.getProfileInfos().get(0)); + } + + /** + * test plan: + * + *
+	 * pagination tests: (repeat both views?)
+	 *   page 1 of several
+	 *   page 1 of 1
+	 *   page 2 of several
+	 *   page out of range (zero results)
+	 *   last page divides evenly
+	 *   last page divides unevenly
+	 *   
+	 * search tests: (repeat both views)
+	 *   some results
+	 *   no results
+	 *   special REGEX characters
+	 *   
+	 *   profile w/no proxies
+	 * profile w/proxies
+	 * 	no associated profile
+	 * 	profile w/no classLabel
+	 * 	profile w/no imageUrl
+	 * 	profile w/neither
+	 * 	profile w/both
+	 * 	
+	 * proxy w/no profiles
+	 * proxy w profiles:
+	 * 	no classLabel
+	 * 	no imageUrl
+	 * 	neither
+	 * 	both
+	 * 
+ */ + + // ---------------------------------------------------------------------- + // 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); + // } + + // ---------------------------------------------------------------------- + // search tests + // ---------------------------------------------------------------------- + // + // @Test + // public void searchTermFoundInAllThreeFields() { + // selectOnCriteria(20, 1, DEFAULT_ORDERING, "", "bob"); + // assertSelectedUris(3, "user02", "user05", "user10"); + // } + // + // @Test + // public void searchTermNotFound() { + // selectOnCriteria(20, 1, DEFAULT_ORDERING, "", "bogus"); + // assertSelectedUris(0); + // } + // + // /** + // * If the special characters were allowed into the Regex, this would have + // 3 + // * matches. If they are escaped properly, it will have none. + // */ + // @Test + // public void searchTermContainsSpecialRegexCharacters() { + // selectOnCriteria(20, 1, DEFAULT_ORDERING, "", "b.b"); + // assertSelectedUris(0); + // } + // + // // ---------------------------------------------------------------------- + // // combination tests + // // ---------------------------------------------------------------------- + // + // @Test + // public void searchWithFilter() { + // selectOnCriteria(20, 1, DEFAULT_ORDERING, NS_MINE + "role1", "bob"); + // assertSelectedUris(2, "user02", "user05"); + // } + // + // @Test + // public void searchWithFilterPaginatedWithFunkySortOrder() { + // selectOnCriteria(1, 2, new UserAccountsOrdering(Field.STATUS, + // Direction.ASCENDING), NS_MINE + "role1", "bob"); + // assertSelectedUris(2, "user02"); + // } + // + // ---------------------------------------------------------------------- + // helper methods + // ---------------------------------------------------------------------- + + /** Create a new criteria object */ + private ProxyRelationshipSelectionCriteria criteria(int accountsPerPage, + int pageIndex, ProxyRelationshipView view, String searchTerm) { + return new ProxyRelationshipSelectionCriteria(accountsPerPage, + pageIndex, view, searchTerm); + } + + /** Create a criteria object and select against it. */ + private void selectOnCriteria(int relationshipsPerPage, int pageIndex, + ProxyRelationshipView viewBy, String searchTerm) { + criteria = new ProxyRelationshipSelectionCriteria(relationshipsPerPage, + pageIndex, viewBy, searchTerm); + selection = ProxyRelationshipSelector.select(context, criteria); + } + + private int[] counts(int proxyCount, int profileCount) { + return new int[] { proxyCount, profileCount }; + } + + private ProxyItemInfo item(String uri, String label, String classLabel, + String imageUrl) { + return new ProxyItemInfo(uri, label, classLabel, imageUrl); + } + + private void assertExpectedCounts(int total, int[]... counts) { + assertEquals("total result count", total, + selection.getTotalResultCount()); + + List relationships = selection + .getProxyRelationships(); + assertEquals("number of returns", counts.length, relationships.size()); + + for (int i = 0; i < counts.length; i++) { + ProxyRelationship r = relationships.get(i); + assertEquals("number of proxies in result " + i, counts[i][0], r + .getProxyInfos().size()); + assertEquals("number of profiles in result " + i, counts[i][1], r + .getProfileInfos().size()); + } + } + +} diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectorTest_UnionModel.n3 b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectorTest_UnionModel.n3 new file mode 100644 index 000000000..6eb167b59 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectorTest_UnionModel.n3 @@ -0,0 +1,150 @@ +# $This file is distributed under the terms of the license in /doc/license.txt$ + +@prefix rdfs: . +@prefix vitro: . +@prefix public: . +@prefix foaf: . +@prefix auth: . +@prefix mydomain: . + +### This file provides a UserAccounts model for ProxyRelationshipSelectorTest.java + +# +# The first relationship to be returned, regardless of view, is this profile to its proxy, +# and here is the self for that proxy also. +# +mydomain:firstProfile + a foaf:Person ; + rdfs:label "AAAA, FirstProfile" ; + vitro:mostSpecificType mydomain:profileType ; + public:mainImage mydomain:profileImage ; + . +mydomain:firstSelf + a foaf:Person ; + vitro:mostSpecificType mydomain:selfType ; + public:mainImage mydomain:selfImage ; + mydomain:matching "firstSelf" ; + . + + +# +# An individual with no proxy +# +mydomain:bozo + a foaf:Person ; + rdfs:label "Bozo, Not Just Any" ; + vitro:mostSpecificType mydomain:profileType; + . + + +# +# This Individual can be edited by a bunch of proxies. +# +mydomain:popularProfile + a foaf:Person ; + rdfs:label "Profile, Popular" ; + vitro:mostSpecificType mydomain:profileType; + public:mainImage mydomain:profileImage; + . + + +# +# These Individuals can all be edited by one popular proxy. +# Each profile has different combinations of attributes. +# +mydomain:profileWithNoClassLabel + a foaf:Person ; + rdfs:label "NoClassLabel, Profile with" ; + public:mainImage mydomain:profileImage; + . + +mydomain:profileWithNoImageUrl + a foaf:Person ; + rdfs:label "NoImageUrl, Profile with" ; + vitro:mostSpecificType mydomain:profileType; + . + +mydomain:profileWithNeither + a foaf:Person ; + rdfs:label "Neither, Profile with" ; + . + +mydomain:profileWithBoth + a foaf:Person ; + rdfs:label "Both, Profile with" ; + vitro:mostSpecificType mydomain:profileType; + public:mainImage mydomain:profileImage; + . + + +# +# These Individiauls are each the "self" for a different proxy. +# Each profile has a different combination of attributes. +# +mydomain:individualWithNoClassLabel + a foaf:Person ; + rdfs:label "Person, Bozo" ; + public:mainImage mydomain:selfImage; + mydomain:matching "individualWithNoClassLabel" ; + . + +mydomain:individualWithNoImageUrl + a foaf:Person ; + rdfs:label "Person, Bozo" ; + vitro:mostSpecificType mydomain:selfType; + mydomain:matching "individualWithNoImageUrl" ; + . + +mydomain:individualWithNeither + a foaf:Person ; + rdfs:label "Person, Bozo" ; + mydomain:matching "individualWithNeither" ; + . + +mydomain:individualWithBoth + a foaf:Person ; + rdfs:label "Person, Bozo" ; + vitro:mostSpecificType mydomain:selfType; + public:mainImage mydomain:selfImage; + mydomain:matching "individualWithBoth" ; + . + +# +# Use this as a "most specific type" for Profiles. +# +mydomain:profileType + rdfs:label "Profile"; + . + +# +# Use this as a "most specific type" for "selves". +# +mydomain:selfType + rdfs:label "Self"; + . + +# +# Image hierarchy for those profiles that need one. +# +mydomain:profileImage + public:thumbnailImage mydomain:profileThumbnail; + . +mydomain:profileThumbnail + public:downloadLocation mydomain:profileThumbStream; + . +mydomain:profileThumbStream + public:directDownloadUrl "http://mydomain.edu/profileImage.jpg" + . + +# +# Image hierarchy for those "selves" that need one. +# +mydomain:selfImage + public:thumbnailImage mydomain:selfThumbnail; + . +mydomain:selfThumbnail + public:downloadLocation mydomain:selfThumbStream; + . +mydomain:selfThumbStream + public:directDownloadUrl "http://mydomain.edu/selfImage.jpg" + . diff --git a/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectorTest_UserAccountsModel.n3 b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectorTest_UserAccountsModel.n3 new file mode 100644 index 000000000..8edeb6374 --- /dev/null +++ b/webapp/test/edu/cornell/mannlib/vitro/webapp/controller/accounts/manageproxies/ProxyRelationshipSelectorTest_UserAccountsModel.n3 @@ -0,0 +1,88 @@ +# $This file is distributed under the terms of the license in /doc/license.txt$ + +@prefix auth: . +@prefix mydomain: . + +### This file provides a UserAccounts model for ProxyRelationshipSelectorTest.java + +# +# The first relationship to be returned, regardless of view, is this proxy to its profile. +# +mydomain:userFirstProxy + a auth:UserAccount ; + auth:emailAddress "firstProxy@some.edu" ; + auth:firstName "FirstProxy" ; + auth:lastName "AAAA" ; + auth:externalAuthId "firstSelf" ; + auth:proxyEditorFor mydomain:firstProfile ; + . + + +# +# A user account with no proxy relationship and no profile. +# +mydomain:userProxyForNone + a auth:UserAccount ; + auth:emailAddress "proxyForNone@some.edu" ; + auth:firstName "Proxy4" ; + auth:lastName "None" ; + . + +# +# This user account is proxy for a bunch of profiles. +# This user account has no self +# +mydomain:userPopularProxy + a auth:UserAccount ; + auth:emailAddress "popularProxy@some.edu" ; + auth:firstName "Popular" ; + auth:lastName "Proxy" ; + auth:proxyEditorFor mydomain:profileWithNoClassLabel ; + auth:proxyEditorFor mydomain:profileWithNoImageUrl ; + auth:proxyEditorFor mydomain:profileWithNeither ; + auth:proxyEditorFor mydomain:profileWithBoth ; + . + +# +# These user accounts are each proxy for a popular profile. +# All but one has a self, but each self has different combinations of attributes. +# +mydomain:userProxyWithNoSelf + a auth:UserAccount ; + auth:emailAddress "proxyWithNoSelf@some.edu" ; + auth:firstName "ProxyWith" ; + auth:lastName "NoSelf" ; + auth:proxyEditorFor mydomain:popularProfile ; + . +mydomain:userProxyWithSelfWithNoClassLabel + a auth:UserAccount ; + auth:emailAddress "proxyWithSelfWithNoClassLabel@some.edu" ; + auth:firstName "ProxyWithSelfWith" ; + auth:lastName "NoClassLabel" ; + auth:externalAuthId "individualWithNoClassLabel" ; + auth:proxyEditorFor mydomain:popularProfile ; + . +mydomain:userProxyWithSelfWithNoImageUrl + a auth:UserAccount ; + auth:emailAddress "proxyWithSelfWithNoImageUrl@some.edu" ; + auth:firstName "ProxyWithSelfWith" ; + auth:lastName "NoImageUrl" ; + auth:externalAuthId "individualWithNoImageUrl" ; + auth:proxyEditorFor mydomain:popularProfile ; + . +mydomain:userProxyWithSelfWithNeither + a auth:UserAccount ; + auth:emailAddress "proxyWithSelfWithNeither@some.edu" ; + auth:firstName "ProxyWithSelfWith" ; + auth:lastName "Neither" ; + auth:externalAuthId "individualWithNeither" ; + auth:proxyEditorFor mydomain:popularProfile ; + . +mydomain:userProxyWithSelfWithBoth + a auth:UserAccount ; + auth:emailAddress "proxyWithSelfWithBoth@some.edu" ; + auth:firstName "ProxyWithSelfWith" ; + auth:lastName "Both" ; + auth:externalAuthId "individualWithBoth" ; + auth:proxyEditorFor mydomain:popularProfile ; + .