NIHVIVO-2343 First stab at a ProxyRelationshipSelector and tests.

This commit is contained in:
j2blake 2011-11-02 20:33:53 +00:00
parent b3edf5cb3d
commit b06467e893
9 changed files with 1251 additions and 0 deletions

View file

@ -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 + "]";
}
}

View file

@ -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<ProxyItemInfo> proxyInfos;
private final List<ProxyItemInfo> profileInfos;
public ProxyRelationship(List<ProxyItemInfo> proxyInfos,
List<ProxyItemInfo> profileInfos) {
this.proxyInfos = Collections
.unmodifiableList(new ArrayList<ProxyItemInfo>(proxyInfos));
this.profileInfos = Collections
.unmodifiableList(new ArrayList<ProxyItemInfo>(profileInfos));
}
public List<ProxyItemInfo> getProxyInfos() {
return proxyInfos;
}
public List<ProxyItemInfo> getProfileInfos() {
return profileInfos;
}
@Override
public String toString() {
return "ProxyRelationship[proxyInfos=" + proxyInfos + ", profileInfos="
+ profileInfos + "]";
}
}

View file

@ -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<ProxyRelationship> proxyRelationships;
private final int totalResultCount;
public ProxyRelationshipSelection(
ProxyRelationshipSelectionCriteria criteria,
List<ProxyRelationship> proxyRelationships, int totalResultCount) {
this.criteria = criteria;
this.proxyRelationships = Collections
.unmodifiableList(new ArrayList<ProxyRelationship>(
proxyRelationships));
this.totalResultCount = totalResultCount;
}
public ProxyRelationshipSelectionCriteria getCriteria() {
return criteria;
}
public List<ProxyRelationship> getProxyRelationships() {
return proxyRelationships;
}
public int getTotalResultCount() {
return totalResultCount;
}
@Override
public String toString() {
return "ProxyRelationshipSelection[count=" + totalResultCount
+ ", relationships=" + proxyRelationships + ", criteria="
+ criteria + "]";
}
}

View file

@ -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<Relationship> relationships = new ArrayList<Relationship>();
int count;
public ProxyRelationshipSelectionBuilder(
ProxyRelationshipSelectionCriteria criteria) {
this.criteria = criteria;
}
public ProxyRelationshipSelection build() {
List<ProxyRelationship> proxyRelationships = new ArrayList<ProxyRelationship>();
for (Relationship r : relationships) {
proxyRelationships.add(buildProxyRelationship(r));
}
return new ProxyRelationshipSelection(criteria, proxyRelationships,
count);
}
private ProxyRelationship buildProxyRelationship(Relationship r) {
List<ProxyItemInfo> proxyInfos = buildInfos(r.proxyInfos);
List<ProxyItemInfo> profileInfos = buildInfos(r.profileInfos);
return new ProxyRelationship(proxyInfos, profileInfos);
}
private List<ProxyItemInfo> buildInfos(List<ItemInfo> infos) {
List<ProxyItemInfo> result = new ArrayList<ProxyItemInfo>();
for (ItemInfo info : infos) {
result.add(new ProxyItemInfo(info.uri, info.label, info.classLabel,
info.imageUrl));
}
return result;
}
public static class Relationship {
final List<ItemInfo> proxyInfos = new ArrayList<ItemInfo>();
final List<ItemInfo> profileInfos = new ArrayList<ItemInfo>();
}
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;
}
}
}

View file

@ -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> 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 + "']";
}
}

View file

@ -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: <http://www.w3.org/2005/xpath-functions#> \n"
+ "PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#> \n"
+ "PREFIX auth: <http://vitro.mannlib.cornell.edu/ns/vitro/authorization#> \n"
+ "PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#> \n"
+ "PREFIX vpublic: <http://vitro.mannlib.cornell.edu/ns/vitro/public#> \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<Integer>(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<Relationship> relationships = new SparqlQueryRunner<List<Relationship>>(
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<ItemInfo>(
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<String> profileUris = new SparqlQueryRunner<List<String>>(
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<ItemInfo>(
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<List<Relationship>> {
@Override
protected List<Relationship> defaultValue() {
return Collections.emptyList();
}
@Override
protected List<Relationship> parseResults(String queryStr,
ResultSet results) {
List<Relationship> relationships = new ArrayList<Relationship>();
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<Integer> {
@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<ItemInfo> {
@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<List<String>> {
@Override
protected List<String> defaultValue() {
return Collections.emptyList();
}
@Override
protected List<String> parseResults(String queryStr, ResultSet results) {
List<String> proxyUris = new ArrayList<String>();
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<ItemInfo> {
@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;
}
}
}