Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Freemarker dataGetter directive #440

Merged
merged 3 commits into from
Mar 21, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,14 @@ public class DisplayVocabulary {
public static final String SAVE_TO_VAR = DISPLAY_NS + "saveToVar" ;
public static final String QUERY_MODEL = DISPLAY_NS + "queryModel";
public static final String QUERY = DISPLAY_NS + "query";
public static final String DISPLAY_URI_PARAM = DISPLAY_NS + "uri";
public static final String DISPLAY_STRING_PARAM = DISPLAY_NS + "string";
public static final String DISPLAY_INT_PARAM = DISPLAY_NS + "int";
public static final String DISPLAY_LONG_PARAM = DISPLAY_NS + "long";
public static final String DISPLAY_FLOAT_PARAM = DISPLAY_NS + "float";
public static final String DISPLAY_DOUBLE_PARAM = DISPLAY_NS + "double";
public static final String DISPLAY_BOOLEAN_PARAM = DISPLAY_NS + "boolean";


/* URI of property for Fixed HTML Generator */
public static final String FIXED_HTML_VALUE = DISPLAY_NS + "htmlValue";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import edu.cornell.mannlib.vitro.webapp.startup.StartupStatus;
import edu.cornell.mannlib.vitro.webapp.utils.developer.DeveloperSettings;
import edu.cornell.mannlib.vitro.webapp.utils.developer.Key;
import edu.cornell.mannlib.vitro.webapp.web.directives.DataGetterDirective;
import edu.cornell.mannlib.vitro.webapp.web.directives.IndividualShortViewDirective;
import edu.cornell.mannlib.vitro.webapp.web.directives.UrlDirective;
import edu.cornell.mannlib.vitro.webapp.web.directives.WidgetDirective;
Expand Down Expand Up @@ -263,6 +264,7 @@ private void addDirectives(FreemarkerConfigurationImpl c) {
c.setSharedVariable("shortView", new IndividualShortViewDirective());
c.setSharedVariable("url", new UrlDirective());
c.setSharedVariable("widget", new WidgetDirective());
c.setSharedVariable("dataGetter", new DataGetterDirective());
}

private void addMethods(FreemarkerConfigurationImpl c) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,6 @@

import static edu.cornell.mannlib.vitro.webapp.modelaccess.ModelNames.DISPLAY;

import javax.servlet.ServletContext;

import org.apache.commons.lang3.StringUtils;

import org.apache.jena.rdf.model.Model;
Expand All @@ -18,7 +16,7 @@ public abstract class DataGetterBase implements DataGetter {
/**
* Get the model to use based on a model URI.
*/
protected Model getModel(ServletContext context, VitroRequest vreq , String modelName) {
protected Model getModel(VitroRequest vreq , String modelName) {
//if not set use jenaOntModel from the request
if( StringUtils.isEmpty(modelName) ){
return vreq.getJenaOntModel();
Expand All @@ -27,16 +25,13 @@ protected Model getModel(ServletContext context, VitroRequest vreq , String mode
}else if( REQUEST_JENA_ONT_MODEL.equals(modelName)){
return vreq.getJenaOntModel();
}else if( CONTEXT_DISPLAY_MODEL.equals(modelName)){
return ModelAccess.on(context).getOntModel(DISPLAY);
}else if( ! StringUtils.isEmpty( modelName)){
return ModelAccess.getInstance().getOntModel(DISPLAY);
}else{
Model model = JenaIngestController.getModel( modelName, vreq);
if( model == null )
throw new IllegalAccessError("Cannot get model <" + modelName +"> for DataGetter.");
else
return model;
}else{
//default is just the JeanOntModel from the vreq.
return vreq.getJenaOntModel();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,16 @@
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import javax.servlet.ServletContext;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import org.apache.jena.query.ParameterizedSparqlString;
import org.apache.jena.query.Query;
import org.apache.jena.query.QueryExecution;
import org.apache.jena.query.QueryExecutionFactory;
Expand All @@ -37,6 +38,13 @@ public class SparqlQueryDataGetter extends DataGetterBase implements DataGetter{
private static final String queryPropertyURI = "<" + DisplayVocabulary.QUERY + ">";
private static final String saveToVarPropertyURI= "<" + DisplayVocabulary.SAVE_TO_VAR+ ">";
private static final String queryModelPropertyURI= "<" + DisplayVocabulary.QUERY_MODEL+ ">";
private static final String uriParam = "<" + DisplayVocabulary.DISPLAY_URI_PARAM + ">";
private static final String stringParam = "<" + DisplayVocabulary.DISPLAY_STRING_PARAM + ">";
private static final String intParam = "<" + DisplayVocabulary.DISPLAY_INT_PARAM + ">";
private static final String longParam = "<" + DisplayVocabulary.DISPLAY_LONG_PARAM + ">";
private static final String floatParam = "<" + DisplayVocabulary.DISPLAY_FLOAT_PARAM + ">";
private static final String doubleParam = "<" + DisplayVocabulary.DISPLAY_DOUBLE_PARAM + ">";
private static final String booleanParam = "<" + DisplayVocabulary.DISPLAY_BOOLEAN_PARAM + ">";

public static final String defaultVarNameForResults = "results";
private static final String defaultTemplate = "menupage--defaultSparql.ftl";
Expand All @@ -45,8 +53,15 @@ public class SparqlQueryDataGetter extends DataGetterBase implements DataGetter{
String queryText;
String saveToVar;
String modelURI;
Set<String> uriParams = new HashSet<String>();
Set<String> stringParams = new HashSet<String>();
Set<String> intParams = new HashSet<String>();
Set<String> longParams = new HashSet<String>();
Set<String> floatParams = new HashSet<String>();
Set<String> doubleParams = new HashSet<String>();
Set<String> booleanParams = new HashSet<String>();

VitroRequest vreq;
ServletContext context;

/**
* Constructor with display model and data getter URI that will be called by reflection.
Expand All @@ -68,7 +83,6 @@ protected void configure(VitroRequest vreq, Model displayModel, String dataGette
throw new IllegalArgumentException("PageUri may not be null.");

this.vreq = vreq;
this.context = vreq.getSession().getServletContext();
this.dataGetterURI = dataGetterURI;

QuerySolutionMap initBindings = new QuerySolutionMap();
Expand Down Expand Up @@ -108,31 +122,58 @@ protected void configure(VitroRequest vreq, Model displayModel, String dataGette
}else{
this.saveToVar = defaultVarNameForResults;
}

addTypedParameter("uriParam", uriParams, soln);
addTypedParameter("stringParam", stringParams, soln);
addTypedParameter("intParam", intParams, soln);
addTypedParameter("longParam", longParams, soln);
addTypedParameter("floatParam", floatParams, soln);
addTypedParameter("doubleParam", doubleParams, soln);
addTypedParameter("booleanParam", booleanParams, soln);
}
}finally{ qexec.close(); }
}finally{ displayModel.leaveCriticalSection(); }
}

private void addTypedParameter(String name, Set<String> set, QuerySolution soln) {
RDFNode uriNode = soln.get(name);
if (uriNode != null && uriNode.isLiteral()) {
String uriParam = uriNode.asLiteral().getLexicalForm();
if (!StringUtils.isBlank(uriParam)) {
set.add(uriParam);
}
}
}

/**
* Query to get the definition of the SparqlDataGetter for a given URI.
*/
private static final String dataGetterQuery =
"PREFIX display: <" + DisplayVocabulary.DISPLAY_NS +"> \n" +
"SELECT ?query ?saveToVar ?queryModel WHERE { \n" +
" ?dataGetterURI "+queryPropertyURI+" ?query . \n" +
" OPTIONAL{ ?dataGetterURI "+saveToVarPropertyURI+" ?saveToVar } \n " +
" OPTIONAL{ ?dataGetterURI "+queryModelPropertyURI+" ?queryModel } \n" +
"SELECT ?query ?saveToVar ?queryModel ?uriParam ?stringParam "
+ "?intParam ?longParam ?floatParam ?doubleParam ?booleanParam \n" +
"WHERE { \n" +
" ?dataGetterURI " + queryPropertyURI + " ?query . \n" +
" OPTIONAL{ ?dataGetterURI " + saveToVarPropertyURI + " ?saveToVar } \n " +
" OPTIONAL{ ?dataGetterURI " + queryModelPropertyURI + " ?queryModel } \n" +
" OPTIONAL{ ?dataGetterURI " + uriParam + " ?uriParam } \n" +
" OPTIONAL{ ?dataGetterURI " + stringParam + " ?stringParam } \n" +
" OPTIONAL{ ?dataGetterURI " + intParam + " ?intParam } \n" +
" OPTIONAL{ ?dataGetterURI " + longParam + " ?longParam } \n" +
" OPTIONAL{ ?dataGetterURI " + floatParam + " ?floatParam } \n" +
" OPTIONAL{ ?dataGetterURI " + doubleParam + " ?doubleParam } \n" +
" OPTIONAL{ ?dataGetterURI " + booleanParam + " ?booleanParam } \n" +
"}";


@Override
public Map<String, Object> getData(Map<String, Object> pageData) {
Map<String, String> merged = mergeParameters(vreq.getParameterMap(), pageData);

String boundQueryText = bindParameters(queryText, merged);

if (modelURI != null) {
return doQueryOnModel(boundQueryText, getModel(context, vreq, modelURI));
Model modelByUri = getModel(vreq, modelURI);
return doQueryOnModel(boundQueryText, modelByUri);
} else {
return doQueryOnRDFService(boundQueryText);
}
Expand All @@ -159,32 +200,70 @@ private Map<String, String> mergeParameters(
* InitialBindings don't always work, and besides, RDFService doesn't accept
* them. So do a text-based substitution.
*
* This assumes that every parameter is a URI. What if we want to substitute
* a string value?
* This assumes that every parameter is a URI unless data getter has specified
* parameters.
*/
private String bindParameters(String text, Map<String, String> merged) {
String bound = text;
for (String key : merged.keySet()) {
String value = merged.get(key);
if (value.startsWith("http://") || value.startsWith("https://")) {
/*
* UQAM-Optimization if the "value" looks like an URI then wrap the value with the characters '<' '>'
*
*/
bound = bound.replaceAll("([?$]" + key + ")([^a-zA-Z0-9_\\-])", "<" + value + ">$2");
} else {
bound = bound.replaceAll("([?$]" + key + ")([^a-zA-Z0-9_\\-])", value + "$2");
}
}
if (log.isDebugEnabled()) {
log.debug("parameters: " + merged);
log.debug("query before binding parameters:" + text);
log.debug("query after binding parameters: " + bound);
}
return bound;
}
private String bindParameters(String text, Map<String, String> parameters) {
ParameterizedSparqlString queryText = new ParameterizedSparqlString(text);
if (!isLegacyMode()) {
substitute(parameters, uriParams, queryText,
(pss, key, value) -> pss.setIri(key, value));
substitute(parameters, stringParams, queryText,
(pss, key, value) -> pss.setLiteral(key, value));
substitute(parameters, intParams, queryText,
(pss, key, value) -> pss.setLiteral(key, Integer.parseInt(value)));
chenejac marked this conversation as resolved.
Show resolved Hide resolved
substitute(parameters, longParams, queryText,
(pss, key, value) -> pss.setLiteral(key, Long.parseLong(value)));
substitute(parameters, floatParams, queryText,
(pss, key, value) -> pss.setLiteral(key, Float.parseFloat(value)));
substitute(parameters, doubleParams, queryText,
(pss, key, value) -> pss.setLiteral(key, Double.parseDouble(value)));
substitute(parameters, booleanParams, queryText,
(pss, key, value) -> pss.setLiteral(key, Boolean.parseBoolean(value)));
} else {
//Substitute all variables as uris
substitute(parameters, parameters.keySet(), queryText,
(pss, key, value) -> pss.setIri(key, value));
}

/**
if (log.isDebugEnabled()) {
log.debug("parameters: " + parameters);
log.debug("query before binding parameters:" + text);
log.debug("query after binding parameters: " + queryText.toString());
}
return queryText.toString();
}

private void substitute(Map<String, String> parameters, Set<String> keys, ParameterizedSparqlString pss,
Substitution<ParameterizedSparqlString, String, String> substitution) {
for (String key : keys) {
String value = parameters.get(key);
if (value != null) {
substitution.apply(pss, key, value);
}
}
}

/**
* Checks if at least one parameter was defined in data getter,
* if not then work in legacy mode.
* @return
*/
private boolean isLegacyMode() {
if (!uriParams.isEmpty() ||
!stringParams.isEmpty() ||
!intParams.isEmpty() ||
!longParams.isEmpty() ||
!floatParams.isEmpty() ||
!doubleParams.isEmpty() ||
!booleanParams.isEmpty()
) {
return false;
}
return true;
}

/**
* Do the query and return a result. This is in its own method, with
* protected access, to make testing easy.
*/
Expand Down Expand Up @@ -287,4 +366,8 @@ private Map<String, Object> assembleMap(List<Map<String, String>> results) {
return rmap;
}

@FunctionalInterface
interface Substitution<Pss, Key, Value> {
public void apply(ParameterizedSparqlString pss, String key, String value);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

import java.io.IOException;
import java.io.StringWriter;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import org.apache.commons.logging.Log;
Expand All @@ -14,7 +16,11 @@
import freemarker.template.Template;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelIterator;

public abstract class BaseTemplateDirectiveModel implements TemplateDirectiveModel {

Expand Down Expand Up @@ -83,4 +89,23 @@ protected String getOptionalSimpleScalarParameter(Map<?, ?> params,
return o.toString();
}

protected Map<String, Object> getOptionalHashModelParameter(Map<?, ?> params, String name) throws TemplateModelException {
Object object = params.get(name);
if (object == null) {
return Collections.emptyMap();
}
if (!(object instanceof TemplateHashModelEx)) {
throw new TemplateModelException(String.format("The %s parameter must be a string value.", name));
}
TemplateHashModelEx hashModel = (TemplateHashModelEx) object;
Map<String, Object> map = new HashMap<>();

TemplateModelIterator it = hashModel.keys().iterator();
while (it.hasNext()) {
TemplateModel key = it.next();
TemplateModel value = hashModel.get(key.toString());
map.put(key.toString(), value);
}
return map;
}
}
Loading
Loading