NIHVIVO-1569 Remove duplicate entries returned from an object property sparql query
This commit is contained in:
parent
9b4bcb3b6a
commit
72d2b8f0d4
9 changed files with 194 additions and 97 deletions
|
@ -0,0 +1,114 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
|
||||||
|
|
||||||
|
public abstract class BaseObjectPropertyDataPostProcessor implements
|
||||||
|
ObjectPropertyDataPostProcessor {
|
||||||
|
|
||||||
|
private static final Log log = LogFactory.getLog(BaseObjectPropertyDataPostProcessor.class);
|
||||||
|
|
||||||
|
private static String KEY_SUBJECT = "subject";
|
||||||
|
private static final String KEY_PROPERTY = "property";
|
||||||
|
private static final String DEFAULT_KEY_OBJECT = "object";
|
||||||
|
private static final Pattern QUERY_PATTERN = Pattern.compile("\\?" + KEY_SUBJECT + "\\s+\\?" + KEY_PROPERTY + "\\s+\\?(\\w+)");
|
||||||
|
|
||||||
|
protected ObjectPropertyTemplateModel objectPropertyTemplateModel;
|
||||||
|
protected WebappDaoFactory wdf;
|
||||||
|
|
||||||
|
public BaseObjectPropertyDataPostProcessor(ObjectPropertyTemplateModel optm, WebappDaoFactory wdf) {
|
||||||
|
this.objectPropertyTemplateModel = optm;
|
||||||
|
this.wdf = wdf;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(List<Map<String, String>> data) {
|
||||||
|
removeDuplicates(data);
|
||||||
|
for (Map<String, String> map : data) {
|
||||||
|
process(map);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected abstract void process(Map<String, String> map);
|
||||||
|
|
||||||
|
/** The SPARQL query results may contain duplicate rows for a single object, if there are multiple assertions for some of the
|
||||||
|
* other query variables. Remove duplicates here by arbitrarily selecting only the first row returned.
|
||||||
|
* @param List<Map<String, String>> data
|
||||||
|
*/
|
||||||
|
protected void removeDuplicates(List<Map<String, String>> data) {
|
||||||
|
String objectVariableName = getQueryObjectVariableName();
|
||||||
|
if (objectVariableName == null) {
|
||||||
|
log.error("Cannot remove duplicate statements for property " + objectPropertyTemplateModel.getName() + " because no object found to dedupe.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<String> foundObjects = new ArrayList<String>();
|
||||||
|
for (Map<String, String> map : data) {
|
||||||
|
String objectValue = map.get(objectVariableName);
|
||||||
|
// We arbitrarily remove all but the first. Not sure what selection criteria could be brought to bear on this.
|
||||||
|
if (foundObjects.contains(objectValue)) {
|
||||||
|
data.remove(map);
|
||||||
|
} else {
|
||||||
|
foundObjects.add(objectValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Return the name of the primary object variable of the query by inspecting the query string.
|
||||||
|
* The primary object is the X in the assertion "?subject ?property ?X".
|
||||||
|
*/
|
||||||
|
private String getQueryObjectVariableName() {
|
||||||
|
|
||||||
|
String object = null;
|
||||||
|
|
||||||
|
if (objectPropertyTemplateModel.hasDefaultListView()) {
|
||||||
|
object = DEFAULT_KEY_OBJECT;
|
||||||
|
log.debug("Using default list view for property " + objectPropertyTemplateModel.getUri() +
|
||||||
|
", so query object = '" + DEFAULT_KEY_OBJECT + "'");
|
||||||
|
} else {
|
||||||
|
String queryString = objectPropertyTemplateModel.getQueryString();
|
||||||
|
Matcher m = QUERY_PATTERN.matcher(queryString);
|
||||||
|
if (m.find()) {
|
||||||
|
object = m.group(1);
|
||||||
|
log.debug("Query object for property " + objectPropertyTemplateModel.getUri() + " = '" + object + "'");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return object;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Postprocessor helper methods callable from any postprocessor */
|
||||||
|
|
||||||
|
protected void addName(Map<String, String> map, String nameKey, String objectKey) {
|
||||||
|
String name = map.get(nameKey);
|
||||||
|
if (name == null) {
|
||||||
|
map.put(nameKey, getIndividual(map.get(objectKey)).getName());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* This is a temporary measure to handle the fact that the current Individual.getMoniker()
|
||||||
|
* method returns the individual's VClass if moniker is null. We want to replicate that
|
||||||
|
* behavior here, but in future the moniker property (along with other Vitro namespace
|
||||||
|
* properties) will be removed. In addition, this type of logic (display x if it exists, otherwise y)
|
||||||
|
* will be moved into the display modules (Editing and Display Configuration Improvements).
|
||||||
|
*/
|
||||||
|
protected void addMoniker(Map<String, String> map, String monikerKey, String objectKey) {
|
||||||
|
String moniker = map.get(monikerKey);
|
||||||
|
if (moniker == null) {
|
||||||
|
map.put(monikerKey, getIndividual(map.get(objectKey)).getMoniker());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected Individual getIndividual(String uri) {
|
||||||
|
return wdf.getIndividualDao().getIndividualByURI(uri);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,56 +0,0 @@
|
||||||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
|
||||||
package edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.beans.Individual;
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
|
|
||||||
|
|
||||||
public abstract class BaseObjectPropertyDataPostprocessor implements
|
|
||||||
ObjectPropertyDataPostprocessor {
|
|
||||||
|
|
||||||
protected ObjectPropertyTemplateModel objectPropertyTemplateModel;
|
|
||||||
protected WebappDaoFactory wdf;
|
|
||||||
|
|
||||||
public BaseObjectPropertyDataPostprocessor(ObjectPropertyTemplateModel optm, WebappDaoFactory wdf) {
|
|
||||||
this.objectPropertyTemplateModel = optm;
|
|
||||||
this.wdf = wdf;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void process(List<Map<String, String>> data) {
|
|
||||||
for (Map<String, String> map : data) {
|
|
||||||
process(map);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected abstract void process(Map<String, String> map);
|
|
||||||
|
|
||||||
|
|
||||||
/* Postprocessor helper methods callable from any postprocessor */
|
|
||||||
|
|
||||||
protected void addName(Map<String, String> map, String nameKey, String objectKey) {
|
|
||||||
String name = map.get(nameKey);
|
|
||||||
if (name == null) {
|
|
||||||
map.put(nameKey, getIndividual(map.get(objectKey)).getName());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/* This is a temporary measure to handle the fact that the current Individual.getMoniker()
|
|
||||||
* method returns the individual's VClass if moniker is null. We want to replicate that
|
|
||||||
* behavior here, but in future the moniker property (along with other Vitro namespace
|
|
||||||
* properties) will be removed. In addition, this type of logic (display x if it exists, otherwise y)
|
|
||||||
* will be moved into the display modules (Editing and Display Configuration Improvements).
|
|
||||||
*/
|
|
||||||
protected void addMoniker(Map<String, String> map, String monikerKey, String objectKey) {
|
|
||||||
String moniker = map.get(monikerKey);
|
|
||||||
if (moniker == null) {
|
|
||||||
map.put(monikerKey, getIndividual(map.get(objectKey)).getMoniker());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Individual getIndividual(String uri) {
|
|
||||||
return wdf.getIndividualDao().getIndividualByURI(uri);
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -53,9 +53,9 @@ public class CollatedObjectPropertyTemplateModel extends ObjectPropertyTemplateM
|
||||||
String propertyUri = op.getURI();
|
String propertyUri = op.getURI();
|
||||||
List<Map<String, String>> statementData = opDao.getObjectPropertyStatementsForIndividualByProperty(subjectUri, propertyUri, getQueryString());
|
List<Map<String, String>> statementData = opDao.getObjectPropertyStatementsForIndividualByProperty(subjectUri, propertyUri, getQueryString());
|
||||||
|
|
||||||
Map<String, List<ObjectPropertyStatementTemplateModel>> unsortedSubclasses = hasCustomListView() ?
|
Map<String, List<ObjectPropertyStatementTemplateModel>> unsortedSubclasses = hasDefaultListView() ?
|
||||||
collateCustomListView(subjectUri, propertyUri, statementData, vreq) :
|
collateDefaultListView(subjectUri, propertyUri, statementData, vreq) :
|
||||||
collateDefaultListView(subjectUri, propertyUri, statementData, vreq);
|
collateCustomListView(subjectUri, propertyUri, statementData, vreq);
|
||||||
|
|
||||||
/* Sort by subclass name */
|
/* Sort by subclass name */
|
||||||
Comparator<String> comparer = new Comparator<String>() {
|
Comparator<String> comparer = new Comparator<String>() {
|
||||||
|
|
|
@ -0,0 +1,33 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
|
||||||
|
|
||||||
|
/** Data postprocessor for default object property list view **/
|
||||||
|
public class DefaultListViewDataPostProcessor extends
|
||||||
|
BaseObjectPropertyDataPostProcessor {
|
||||||
|
|
||||||
|
private static final Log log = LogFactory.getLog(DefaultListViewDataPostProcessor.class);
|
||||||
|
|
||||||
|
private static final String KEY_NAME = "name";
|
||||||
|
private static final String KEY_MONIKER = "moniker";
|
||||||
|
private static final String KEY_OBJECT = "object";
|
||||||
|
|
||||||
|
public DefaultListViewDataPostProcessor(ObjectPropertyTemplateModel optm, WebappDaoFactory wdf) {
|
||||||
|
super(optm, wdf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
/* Apply processing specific to this postprocessor */
|
||||||
|
protected void process(Map<String, String> map) {
|
||||||
|
addName(map, KEY_NAME, KEY_OBJECT);
|
||||||
|
addMoniker(map, KEY_MONIKER, KEY_OBJECT);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,27 @@
|
||||||
|
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
||||||
|
|
||||||
|
package edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
import org.apache.commons.logging.Log;
|
||||||
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
|
||||||
|
|
||||||
|
/** The object property data postprocessor that is used if the view does not specify another postprocessor */
|
||||||
|
public class DefaultObjectPropertyDataPostProcessor extends BaseObjectPropertyDataPostProcessor {
|
||||||
|
|
||||||
|
private static final Log log = LogFactory.getLog(DefaultObjectPropertyDataPostProcessor.class);
|
||||||
|
|
||||||
|
public DefaultObjectPropertyDataPostProcessor(ObjectPropertyTemplateModel optm,
|
||||||
|
WebappDaoFactory wdf) {
|
||||||
|
super(optm, wdf);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void process(Map<String, String> map) {
|
||||||
|
// no default data postprocessing defined yet
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -1,29 +0,0 @@
|
||||||
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
|
|
||||||
|
|
||||||
package edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual;
|
|
||||||
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
|
|
||||||
|
|
||||||
public class DefaultObjectPropertyDataPostprocessor extends
|
|
||||||
BaseObjectPropertyDataPostprocessor {
|
|
||||||
|
|
||||||
protected String KEY_NAME = "name";
|
|
||||||
protected String KEY_MONIKER = "moniker";
|
|
||||||
protected String KEY_OBJECT = "object";
|
|
||||||
|
|
||||||
public DefaultObjectPropertyDataPostprocessor(ObjectPropertyTemplateModel optm, WebappDaoFactory wdf) {
|
|
||||||
super(optm, wdf);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
/* Apply processing specific to this postprocessor */
|
|
||||||
protected void process(Map<String, String> map) {
|
|
||||||
addName(map, KEY_NAME, KEY_OBJECT);
|
|
||||||
addMoniker(map, KEY_MONIKER, KEY_OBJECT);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
|
@ -6,14 +6,14 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* These postprocessors take a list of object property statement data derived from a
|
* These postprocessors take a list of object property statement data returned from a
|
||||||
* SPARQL query (or other source) and prepare it for insertion into the template data model.
|
* SPARQL query and prepare it for insertion into the template data model.
|
||||||
*
|
*
|
||||||
* @author rjy7
|
* @author rjy7
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public interface ObjectPropertyDataPostprocessor {
|
public interface ObjectPropertyDataPostProcessor {
|
||||||
|
|
||||||
public void process(List<Map<String, String>> data);
|
public void process(List<Map<String, String>> data);
|
||||||
|
|
|
@ -30,7 +30,14 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
||||||
|
|
||||||
private static final Log log = LogFactory.getLog(ObjectPropertyTemplateModel.class);
|
private static final Log log = LogFactory.getLog(ObjectPropertyTemplateModel.class);
|
||||||
private static final String TYPE = "object";
|
private static final String TYPE = "object";
|
||||||
|
|
||||||
|
/* NB The default postprocessor is not the same as the postprocessor for the default view. The latter
|
||||||
|
* actually defines its own postprocessor, whereas the default postprocessor is used for custom views
|
||||||
|
* that don't define a postprocessor, to ensure that the standard postprocessing applies.
|
||||||
|
*/
|
||||||
|
private static final String DEFAULT_POSTPROCESSOR =
|
||||||
|
"edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DefaultObjectPropertyDataPostProcessor";
|
||||||
|
|
||||||
private PropertyListConfig config;
|
private PropertyListConfig config;
|
||||||
|
|
||||||
ObjectPropertyTemplateModel(ObjectProperty op, Individual subject, VitroRequest vreq) {
|
ObjectPropertyTemplateModel(ObjectProperty op, Individual subject, VitroRequest vreq) {
|
||||||
|
@ -53,8 +60,8 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
||||||
return config.collationTarget;
|
return config.collationTarget;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected boolean hasCustomListView() {
|
protected boolean hasDefaultListView() {
|
||||||
return !config.isDefaultConfig;
|
return config.isDefaultConfig;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected static ObjectPropertyTemplateModel getObjectPropertyTemplateModel(ObjectProperty op, Individual subject, VitroRequest vreq) {
|
protected static ObjectPropertyTemplateModel getObjectPropertyTemplateModel(ObjectProperty op, Individual subject, VitroRequest vreq) {
|
||||||
|
@ -74,13 +81,14 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
||||||
protected void postprocess(List<Map<String, String>> data, WebappDaoFactory wdf) {
|
protected void postprocess(List<Map<String, String>> data, WebappDaoFactory wdf) {
|
||||||
String postprocessorName = config.postprocessor;
|
String postprocessorName = config.postprocessor;
|
||||||
if (postprocessorName == null) {
|
if (postprocessorName == null) {
|
||||||
return;
|
//return;
|
||||||
|
postprocessorName = DEFAULT_POSTPROCESSOR;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
Class<?> postprocessorClass = Class.forName(postprocessorName);
|
Class<?> postprocessorClass = Class.forName(postprocessorName);
|
||||||
Constructor<?> constructor = postprocessorClass.getConstructor(ObjectPropertyTemplateModel.class, WebappDaoFactory.class);
|
Constructor<?> constructor = postprocessorClass.getConstructor(ObjectPropertyTemplateModel.class, WebappDaoFactory.class);
|
||||||
ObjectPropertyDataPostprocessor postprocessor = (ObjectPropertyDataPostprocessor) constructor.newInstance(this, wdf);
|
ObjectPropertyDataPostProcessor postprocessor = (ObjectPropertyDataPostProcessor) constructor.newInstance(this, wdf);
|
||||||
postprocessor.process(data);
|
postprocessor.process(data);
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error(e, e);
|
log.error(e, e);
|
||||||
|
|
|
@ -31,7 +31,7 @@
|
||||||
}
|
}
|
||||||
</query>
|
</query>
|
||||||
|
|
||||||
<postprocessor>edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DefaultObjectPropertyDataPostprocessor</postprocessor>
|
<postprocessor>edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DefaultListViewDataPostProcessor</postprocessor>
|
||||||
|
|
||||||
<template>propStatement-default.ftl</template>
|
<template>propStatement-default.ftl</template>
|
||||||
</list-view-config>
|
</list-view-config>
|
||||||
|
|
Loading…
Add table
Reference in a new issue