NIHVIVO-1633 Support collation toggling for all object properties. Basic functionality in place but not all queries working perfectly yet.
This commit is contained in:
parent
43167c6c5c
commit
b71400d74e
8 changed files with 129 additions and 102 deletions
|
@ -2,21 +2,15 @@
|
|||
|
||||
package edu.cornell.mannlib.vitro.webapp.controller.freemarker;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Calendar;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import edu.cornell.mannlib.vitro.webapp.beans.Portal;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.ResponseValues;
|
||||
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.responsevalues.TemplateResponseValues;
|
||||
import freemarker.template.Configuration;
|
||||
|
||||
/**
|
||||
* Freemarker controller and template sandbox.
|
||||
|
|
|
@ -12,6 +12,7 @@ import java.util.TreeMap;
|
|||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.apache.commons.lang.StringUtils;
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
|
@ -26,7 +27,6 @@ import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
|
|||
public class CollatedObjectPropertyTemplateModel extends ObjectPropertyTemplateModel {
|
||||
|
||||
private static final Log log = LogFactory.getLog(CollatedObjectPropertyTemplateModel.class);
|
||||
private static final String DEFAULT_CONFIG_FILE = "listViewConfig-default-collated.xml";
|
||||
private static final Pattern SELECT_SUBCLASS_PATTERN =
|
||||
// SELECT ?subclass
|
||||
Pattern.compile("SELECT[^{]*\\?subclass\\b", Pattern.CASE_INSENSITIVE);
|
||||
|
@ -35,18 +35,41 @@ public class CollatedObjectPropertyTemplateModel extends ObjectPropertyTemplateM
|
|||
private static final Pattern ORDER_BY_SUBCLASS_PATTERN =
|
||||
Pattern.compile("ORDER\\s+BY\\s+(DESC\\s*\\(\\s*)?\\?subclass", Pattern.CASE_INSENSITIVE);
|
||||
|
||||
private static enum ConfigError {
|
||||
NO_QUERY("Missing query specification"),
|
||||
NO_SUBCLASS_SELECT("Query does not select a subclass variable"),
|
||||
NO_SUBCLASS_ORDER_BY("Query does not sort first by subclass variable");
|
||||
|
||||
String message;
|
||||
|
||||
ConfigError(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private SortedMap<String, List<ObjectPropertyStatementTemplateModel>> subclasses;
|
||||
|
||||
CollatedObjectPropertyTemplateModel(ObjectProperty op, Individual subject,
|
||||
VitroRequest vreq, EditingPolicyHelper policyHelper)
|
||||
throws InvalidConfigurationException {
|
||||
throws InvalidCollatedPropertyConfigurationException {
|
||||
|
||||
super(op, subject, vreq, policyHelper);
|
||||
|
||||
String invalidConfigMessage = checkConfiguration();
|
||||
if ( ! invalidConfigMessage.isEmpty() ) {
|
||||
throw new InvalidConfigurationException("Invalid configuration for collated property " +
|
||||
op.getURI() + ":" + invalidConfigMessage + ". Creating uncollated display instead.");
|
||||
// RY It would be more efficient to check for these errors in the super constructor, so that we don't
|
||||
// go through the rest of that constructor before throwing an error. In that case, the subclasses
|
||||
// could each have their own checkConfiguration() method.
|
||||
ConfigError configError = checkConfiguration();
|
||||
if ( configError != null ) {
|
||||
throw new InvalidCollatedPropertyConfigurationException("Invalid configuration for collated property " +
|
||||
op.getURI() + ":" + configError + ". Creating uncollated display instead.");
|
||||
}
|
||||
|
||||
/* Get the data */
|
||||
|
@ -78,22 +101,26 @@ public class CollatedObjectPropertyTemplateModel extends ObjectPropertyTemplateM
|
|||
}
|
||||
}
|
||||
|
||||
private String checkConfiguration() {
|
||||
private ConfigError checkConfiguration() {
|
||||
|
||||
String queryString = getQueryString();
|
||||
Matcher m;
|
||||
|
||||
if (StringUtils.isBlank(queryString)) {
|
||||
return ConfigError.NO_QUERY;
|
||||
}
|
||||
|
||||
Matcher m;
|
||||
m = SELECT_SUBCLASS_PATTERN.matcher(queryString);
|
||||
if ( ! m.find() ) {
|
||||
return("Query does not select a subclass variable.");
|
||||
return ConfigError.NO_SUBCLASS_SELECT;
|
||||
}
|
||||
|
||||
m = ORDER_BY_SUBCLASS_PATTERN.matcher(queryString);
|
||||
if ( ! m.find() ) {
|
||||
return("Query does not sort first by subclass variable.");
|
||||
return ConfigError.NO_SUBCLASS_ORDER_BY;
|
||||
}
|
||||
|
||||
return "";
|
||||
return null;
|
||||
}
|
||||
|
||||
private Map<String, List<ObjectPropertyStatementTemplateModel>> collate(String subjectUri, String propertyUri,
|
||||
|
@ -134,11 +161,6 @@ public class CollatedObjectPropertyTemplateModel extends ObjectPropertyTemplateM
|
|||
return subclassName;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultConfigFileName() {
|
||||
return DEFAULT_CONFIG_FILE;
|
||||
}
|
||||
|
||||
/* Access methods for templates */
|
||||
|
||||
public Map<String, List<ObjectPropertyStatementTemplateModel>> getSubclasses() {
|
||||
|
|
|
@ -41,16 +41,13 @@ public class ObjectPropertyStatementTemplateModel extends PropertyStatementTempl
|
|||
/**
|
||||
* This method handles the special case where we are creating a DataPropertyStatementTemplateModel
|
||||
* outside the GroupedPropertyList. Specifically, it allows vitro:primaryLink and vitro:additionalLink
|
||||
* to be treated like data property statements and thus have editing links. (In a future version,
|
||||
* to be treated like object property statements and thus have editing links. (In a future version,
|
||||
* these properties will be replaced by vivo core ontology properties.) It could potentially be used
|
||||
* for other properties outside the property list as well.
|
||||
*/
|
||||
ObjectPropertyStatementTemplateModel(String subjectUri, String propertyUri,
|
||||
VitroRequest vreq, EditingPolicyHelper policyHelper) {
|
||||
super(subjectUri, propertyUri, policyHelper);
|
||||
|
||||
ObjectPropertyStatementDao opsDao = vreq.getWebappDaoFactory().getObjectPropertyStatementDao();
|
||||
|
||||
}
|
||||
|
||||
private void setEditAccess(EditingPolicyHelper policyHelper) {
|
||||
|
|
|
@ -69,6 +69,26 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
// ?subject ?property ?\w+
|
||||
Pattern.compile("\\?" + KEY_SUBJECT + "\\s+\\?" + KEY_PROPERTY + "\\s+\\?(\\w+)");
|
||||
|
||||
private static enum ConfigError {
|
||||
NO_QUERY("Missing query specification"),
|
||||
NO_TEMPLATE("Missing template specification"),
|
||||
TEMPLATE_NOT_FOUND("Specified template does not exist");
|
||||
|
||||
String message;
|
||||
|
||||
ConfigError(String message) {
|
||||
this.message = message;
|
||||
}
|
||||
|
||||
public String getMessage() {
|
||||
return message;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
private PropertyListConfig config;
|
||||
private String objectKey;
|
||||
|
||||
|
@ -76,7 +96,11 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
private boolean addAccess = false;
|
||||
|
||||
ObjectPropertyTemplateModel(ObjectProperty op, Individual subject, VitroRequest vreq, EditingPolicyHelper policyHelper) {
|
||||
|
||||
super(op, subject, policyHelper);
|
||||
|
||||
log.debug("Creating template model for object property " + op.getURI());
|
||||
|
||||
setName(op.getDomainPublic());
|
||||
|
||||
// Get the config for this object property
|
||||
|
@ -140,7 +164,7 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
if (op.getCollateBySubclass()) {
|
||||
try {
|
||||
return new CollatedObjectPropertyTemplateModel(op, subject, vreq, policyHelper);
|
||||
} catch (InvalidConfigurationException e) {
|
||||
} catch (InvalidCollatedPropertyConfigurationException e) {
|
||||
log.warn(e.getMessage());
|
||||
return new UncollatedObjectPropertyTemplateModel(op, subject, vreq, policyHelper);
|
||||
}
|
||||
|
@ -225,12 +249,9 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
if (dateTimeEnd == null) {
|
||||
// If the first statement has a null end datetime, all subsequent statements in the list also do,
|
||||
// so there is nothing to reorder.
|
||||
// NB This assumption is FALSE if the query orders by subclass but the property is not collated.
|
||||
// This happens when a query is written with a subclass variable to support turning on collation
|
||||
// in the back end.
|
||||
// if (statements.indexOf(stmt) == 0) {
|
||||
// break;
|
||||
// }
|
||||
if (statements.indexOf(stmt) == 0) {
|
||||
break;
|
||||
}
|
||||
tempList.add(stmt);
|
||||
iterator.remove();
|
||||
}
|
||||
|
@ -244,12 +265,13 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
return objectKey;
|
||||
}
|
||||
|
||||
protected abstract String getDefaultConfigFileName();
|
||||
|
||||
private class PropertyListConfig {
|
||||
|
||||
private static final String CONFIG_FILE_PATH = "/config/";
|
||||
private static final String NODE_NAME_QUERY = "query";
|
||||
private static final String DEFAULT_CONFIG_FILE_NAME = "listViewConfig-default.xml";
|
||||
|
||||
private static final String NODE_NAME_QUERY_BASE = "query-base";
|
||||
private static final String NODE_NAME_QUERY_COLLATED = "query-collated";
|
||||
private static final String NODE_NAME_TEMPLATE = "template";
|
||||
private static final String NODE_NAME_POSTPROCESSOR = "postprocessor";
|
||||
|
||||
|
@ -258,27 +280,26 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
private String templateName;
|
||||
private String postprocessor;
|
||||
|
||||
PropertyListConfig(ObjectProperty op, VitroRequest vreq) throws Exception {
|
||||
PropertyListConfig(ObjectProperty op, VitroRequest vreq) {
|
||||
|
||||
// Get the custom config filename
|
||||
WebappDaoFactory wdf = vreq.getWebappDaoFactory();
|
||||
ObjectPropertyDao opDao = wdf.getObjectPropertyDao();
|
||||
String configFileName = opDao.getCustomListViewConfigFileName(op);
|
||||
String configFileName = vreq.getWebappDaoFactory().getObjectPropertyDao().getCustomListViewConfigFileName(op);
|
||||
if (configFileName == null) { // no custom config; use default config
|
||||
configFileName = getDefaultConfigFileName();
|
||||
configFileName = DEFAULT_CONFIG_FILE_NAME;
|
||||
}
|
||||
log.debug("Using list view config file " + configFileName + " for object property " + op.getURI());
|
||||
|
||||
String configFilePath = getConfigFilePath(configFileName);
|
||||
|
||||
try {
|
||||
File config = new File(configFilePath);
|
||||
if ( ! isDefaultConfig(configFileName) && ! config.exists() ) {
|
||||
log.warn("Can't find config file " + configFilePath + " for object property " + op.getURI() + "\n" +
|
||||
". Using default config file instead.");
|
||||
configFilePath = getConfigFilePath(getDefaultConfigFileName());
|
||||
configFilePath = getConfigFilePath(DEFAULT_CONFIG_FILE_NAME);
|
||||
// Should we test for the existence of the default, and throw an error if it doesn't exist?
|
||||
}
|
||||
setValuesFromConfigFile(configFilePath);
|
||||
setValuesFromConfigFile(configFilePath, op);
|
||||
|
||||
} catch (Exception e) {
|
||||
log.error("Error processing config file " + configFilePath + " for object property " + op.getURI(), e);
|
||||
|
@ -286,13 +307,18 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
}
|
||||
|
||||
if ( ! isDefaultConfig(configFileName) ) {
|
||||
String invalidConfigMessage = checkForInvalidConfig(vreq);
|
||||
if ( StringUtils.isNotEmpty(invalidConfigMessage) ) {
|
||||
ConfigError configError = checkForInvalidConfig(vreq);
|
||||
// If the configuration contains an error, use the default configuration.
|
||||
// Exception: a collated property with a missing collated query. This will
|
||||
// be caught in CollatedObjectPropertyTemplateModel constructor and result
|
||||
// in using an UncollatedObjectPropertyTemplateModel instead.
|
||||
if ( configError != null &&
|
||||
! (configError == ConfigError.NO_QUERY && op.getCollateBySubclass()) ) {
|
||||
log.warn("Invalid list view config for object property " + op.getURI() +
|
||||
" in " + configFilePath + ":\n" +
|
||||
invalidConfigMessage + " Using default config instead.");
|
||||
configFilePath = getConfigFilePath(getDefaultConfigFileName());
|
||||
setValuesFromConfigFile(configFilePath);
|
||||
configError + " Using default config instead.");
|
||||
configFilePath = getConfigFilePath(DEFAULT_CONFIG_FILE_NAME);
|
||||
setValuesFromConfigFile(configFilePath, op);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -300,31 +326,31 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
}
|
||||
|
||||
private boolean isDefaultConfig(String configFileName) {
|
||||
return configFileName.equals(getDefaultConfigFileName());
|
||||
return configFileName.equals(DEFAULT_CONFIG_FILE_NAME);
|
||||
}
|
||||
|
||||
private String checkForInvalidConfig(VitroRequest vreq) {
|
||||
String invalidConfigMessage = null;
|
||||
private ConfigError checkForInvalidConfig(VitroRequest vreq) {
|
||||
ConfigError error = null;
|
||||
|
||||
if ( StringUtils.isBlank(queryString)) {
|
||||
invalidConfigMessage = "Missing query specification.";
|
||||
error = ConfigError.NO_QUERY;
|
||||
} else if ( StringUtils.isBlank(templateName)) {
|
||||
invalidConfigMessage = "Missing template specification.";
|
||||
error = ConfigError.NO_TEMPLATE;
|
||||
} else {
|
||||
Configuration fmConfig = (Configuration) vreq.getAttribute("freemarkerConfig");
|
||||
TemplateLoader tl = fmConfig.getTemplateLoader();
|
||||
try {
|
||||
if ( tl.findTemplateSource(templateName) == null ) {
|
||||
invalidConfigMessage = "Specified template " + templateName + " does not exist.";
|
||||
error = ConfigError.TEMPLATE_NOT_FOUND;
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.error("Error finding template " + templateName, e);
|
||||
}
|
||||
}
|
||||
return invalidConfigMessage;
|
||||
return error;
|
||||
}
|
||||
|
||||
private void setValuesFromConfigFile(String configFilePath) {
|
||||
private void setValuesFromConfigFile(String configFilePath, ObjectProperty op) {
|
||||
|
||||
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
|
||||
DocumentBuilder db;
|
||||
|
@ -332,8 +358,11 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
try {
|
||||
db = dbf.newDocumentBuilder();
|
||||
Document doc = db.parse(configFilePath);
|
||||
|
||||
// Required values
|
||||
queryString = getConfigValue(doc, NODE_NAME_QUERY);
|
||||
String queryNodeName = op.getCollateBySubclass() ? NODE_NAME_QUERY_COLLATED : NODE_NAME_QUERY_BASE;
|
||||
log.debug("Using query element " + queryNodeName + " for object property " + op.getURI());
|
||||
queryString = getConfigValue(doc, queryNodeName);
|
||||
templateName = getConfigValue(doc, NODE_NAME_TEMPLATE);
|
||||
|
||||
// Optional values
|
||||
|
@ -362,11 +391,11 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
|
|||
}
|
||||
}
|
||||
|
||||
protected class InvalidConfigurationException extends Exception {
|
||||
protected class InvalidCollatedPropertyConfigurationException extends Exception {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
protected InvalidConfigurationException(String s) {
|
||||
protected InvalidCollatedPropertyConfigurationException(String s) {
|
||||
super(s);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -18,7 +18,6 @@ import edu.cornell.mannlib.vitro.webapp.dao.WebappDaoFactory;
|
|||
public class UncollatedObjectPropertyTemplateModel extends ObjectPropertyTemplateModel {
|
||||
|
||||
private static final Log log = LogFactory.getLog(UncollatedObjectPropertyTemplateModel.class);
|
||||
private static final String DEFAULT_CONFIG_FILE = "listViewConfig-default-uncollated.xml";
|
||||
|
||||
private List<ObjectPropertyStatementTemplateModel> statements;
|
||||
|
||||
|
@ -47,11 +46,6 @@ public class UncollatedObjectPropertyTemplateModel extends ObjectPropertyTemplat
|
|||
postprocessStatementList(statements);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDefaultConfigFileName() {
|
||||
return DEFAULT_CONFIG_FILE;
|
||||
}
|
||||
|
||||
/* Access methods for templates */
|
||||
|
||||
public List<ObjectPropertyStatementTemplateModel> getStatements() {
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
||||
|
||||
<!-- Default list view config file for uncollated object properties
|
||||
|
||||
See guidelines in vitro/doc/list_view_configuration_guidelines.txt -->
|
||||
|
||||
<list-view-config>
|
||||
<query>
|
||||
PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#>
|
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||
|
||||
SELECT ?object ?name ?moniker {
|
||||
GRAPH ?g1 { ?subject ?property ?object }
|
||||
OPTIONAL { GRAPH ?g2 { ?object rdfs:label ?name } }
|
||||
OPTIONAL { GRAPH ?g3 { ?object vitro:moniker ?moniker } }
|
||||
}
|
||||
</query>
|
||||
|
||||
<postprocessor>edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DefaultListViewDataPostProcessor</postprocessor>
|
||||
|
||||
<template>propStatement-default.ftl</template>
|
||||
</list-view-config>
|
|
@ -1,12 +1,23 @@
|
|||
<?xml version="1.0" encoding="ISO-8859-1"?>
|
||||
<!-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
||||
|
||||
<!-- Default list view config file for collated object properties
|
||||
<!-- Default list view config file for uncollated object properties
|
||||
|
||||
See guidelines in vitro/doc/list_view_configuration_guidelines.txt -->
|
||||
|
||||
<list-view-config>
|
||||
<query>
|
||||
<query-base>
|
||||
PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#>
|
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||
|
||||
SELECT ?object ?name ?moniker {
|
||||
GRAPH ?g1 { ?subject ?property ?object }
|
||||
OPTIONAL { GRAPH ?g2 { ?object rdfs:label ?name } }
|
||||
OPTIONAL { GRAPH ?g3 { ?object vitro:moniker ?moniker } }
|
||||
}
|
||||
</query-base>
|
||||
|
||||
<query-collated>
|
||||
PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#>
|
||||
PREFIX rdfs: <http://www.w3.org/2000/01/rdf-schema#>
|
||||
|
||||
|
@ -18,8 +29,8 @@
|
|||
FILTER (?g4 != <http://vitro.mannlib.cornell.edu/default/inferred-tbox> &&
|
||||
?g4 != <http://vitro.mannlib.cornell.edu/default/vitro-kb-inf> )
|
||||
}
|
||||
}
|
||||
</query>
|
||||
} ORDER BY ?subclass
|
||||
</query-collated>
|
||||
|
||||
<postprocessor>edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DefaultListViewDataPostProcessor</postprocessor>
|
||||
|
|
@ -6,17 +6,20 @@
|
|||
See guidelines in vitro/doc/list_view_configuration_guidelines.txt -->
|
||||
|
||||
<list-view-config>
|
||||
<query>
|
||||
<query-base>
|
||||
PREFIX vitro: <http://vitro.mannlib.cornell.edu/ns/vitro/0.7#>
|
||||
PREFIX afn: <http://jena.hpl.hp.com/ARQ/function#>
|
||||
|
||||
SELECT ?link (afn:localname(?link) AS ?linkName) ?anchor ?url WHERE {
|
||||
SELECT ?link
|
||||
(afn:localname(?link) AS ?linkName)
|
||||
?anchor
|
||||
?url WHERE {
|
||||
GRAPH ?g1 { ?subject ?property ?link }
|
||||
OPTIONAL { GRAPH ?g2 { ?link vitro:linkAnchor ?anchor } }
|
||||
OPTIONAL { GRAPH ?g3 { ?link vitro:linkURL ?url } }
|
||||
OPTIONAL { GRAPH ?g4 { ?link vitro:linkDisplayRank ?rank } }
|
||||
} ORDER BY ?rank
|
||||
</query>
|
||||
</query-base>
|
||||
|
||||
<template>propStatement-vitroLink.ftl</template>
|
||||
</list-view-config>
|
||||
|
|
Loading…
Add table
Reference in a new issue