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:
rjy7 2011-01-19 20:46:36 +00:00
parent 43167c6c5c
commit b71400d74e
8 changed files with 129 additions and 102 deletions

View file

@ -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.

View file

@ -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);
@ -34,19 +34,42 @@ public class CollatedObjectPropertyTemplateModel extends ObjectPropertyTemplateM
// ORDER BY DESC(?subclass)
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,
@ -133,11 +160,6 @@ public class CollatedObjectPropertyTemplateModel extends ObjectPropertyTemplateM
}
return subclassName;
}
@Override
protected String getDefaultConfigFileName() {
return DEFAULT_CONFIG_FILE;
}
/* Access methods for templates */

View file

@ -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();
super(subjectUri, propertyUri, policyHelper);
}
private void setEditAccess(EditingPolicyHelper policyHelper) {

View file

@ -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,9 +96,13 @@ public abstract class ObjectPropertyTemplateModel extends PropertyTemplateModel
private boolean addAccess = false;
ObjectPropertyTemplateModel(ObjectProperty op, Individual subject, VitroRequest vreq, EditingPolicyHelper policyHelper) {
super(op, subject, policyHelper);
setName(op.getDomainPublic());
super(op, subject, policyHelper);
log.debug("Creating template model for object property " + op.getURI());
setName(op.getDomainPublic());
// Get the config for this object property
try {
config = new PropertyListConfig(op, vreq);
@ -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 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);
}
}

View file

@ -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() {

View file

@ -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: &lt;http://vitro.mannlib.cornell.edu/ns/vitro/0.7#&gt;
PREFIX rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt;
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>

View file

@ -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 -->
See guidelines in vitro/doc/list_view_configuration_guidelines.txt -->
<list-view-config>
<query>
<query-base>
PREFIX vitro: &lt;http://vitro.mannlib.cornell.edu/ns/vitro/0.7#&gt;
PREFIX rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt;
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: &lt;http://vitro.mannlib.cornell.edu/ns/vitro/0.7#&gt;
PREFIX rdfs: &lt;http://www.w3.org/2000/01/rdf-schema#&gt;
@ -18,9 +29,9 @@
FILTER (?g4 != &lt;http://vitro.mannlib.cornell.edu/default/inferred-tbox&gt; &amp;&amp;
?g4 != &lt;http://vitro.mannlib.cornell.edu/default/vitro-kb-inf&gt; )
}
}
</query>
} ORDER BY ?subclass
</query-collated>
<postprocessor>edu.cornell.mannlib.vitro.webapp.web.templatemodels.individual.DefaultListViewDataPostProcessor</postprocessor>
<template>propStatement-default.ftl</template>

View file

@ -6,17 +6,20 @@
See guidelines in vitro/doc/list_view_configuration_guidelines.txt -->
<list-view-config>
<query>
<query-base>
PREFIX vitro: &lt;http://vitro.mannlib.cornell.edu/ns/vitro/0.7#&gt;
PREFIX afn: &lt;http://jena.hpl.hp.com/ARQ/function#&gt;
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>