NIHVIVO-1304 Initial implementation of Widget directive. Requires two templates, one for assets and one for markup; would like to combine into a single template with two macros instead.

This commit is contained in:
rjy7 2010-11-10 19:50:28 +00:00
parent 3eed766716
commit 8dc645cfb6
18 changed files with 440 additions and 187 deletions

View file

@ -46,6 +46,9 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(FreemarkerHttpServlet.class);
private static final int FILTER_SECURITY_LEVEL = LoginStatusBean.EDITOR;
public static final String PAGE_TEMPLATE_TYPE = "page";
public static final String BODY_TEMPLATE_TYPE = "body";
protected enum Template {
STANDARD_ERROR("error-standard.ftl"),
@ -159,7 +162,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
root.putAll(getPageTemplateValues(vreq));
// Tell the template and any directives it uses that we're processing a page template.
root.put("templateType", "page");
root.put("templateType", PAGE_TEMPLATE_TYPE);
// Add the values that we got, and merge to the template.
String bodyTemplate = values.getTemplateName();
@ -172,7 +175,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
Map<String, Object> body = new HashMap<String, Object>(sharedVariables);
body.putAll(bodyMap);
// Tell the template and any directives it uses that we're processing a body template.
body.put("templateType", "body");
body.put("templateType", BODY_TEMPLATE_TYPE);
bodyString = processTemplateToString(bodyTemplate, body, config, vreq);
} else {
// The subcontroller has not defined a body template. All markup for the page
@ -343,7 +346,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
map.put("dumpAll", new edu.cornell.mannlib.vitro.webapp.web.directives.dump.DumpAllDirective());
map.put("help", new edu.cornell.mannlib.vitro.webapp.web.directives.dump.HelpDirective());
//map.put("url", new edu.cornell.mannlib.vitro.webapp.web.directives.UrlDirective());
map.put("widget", new edu.cornell.mannlib.vitro.webapp.web.directives.widgets.BaseWidgetDirective());
map.put("widget", new edu.cornell.mannlib.vitro.webapp.web.directives.WidgetDirective());
return map;
}
@ -438,7 +441,7 @@ public class FreemarkerHttpServlet extends VitroHttpServlet {
protected StringWriter processTemplate(String templateName, Map<String, Object> map, Configuration config,
HttpServletRequest request) {
FreemarkerHelper helper = new FreemarkerHelper(config, request, getServletContext());
TemplateProcessingHelper helper = new TemplateProcessingHelper(config, request, getServletContext());
return helper.processTemplate(templateName, map);
}

View file

@ -0,0 +1,105 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
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.FreemarkerHttpServlet.ResponseValues;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet.TemplateResponseValues;
import freemarker.template.Configuration;
/**
* Freemarker controller and template samples.
* @author rjy7
*
*/
public class SamplesController extends FreemarkerHttpServlet {
private static final long serialVersionUID = 1L;
private static final Log log = LogFactory.getLog(SamplesController.class);
private static final String TEMPLATE_DEFAULT = "samples.ftl";
@Override
protected ResponseValues processRequest(VitroRequest vreq) {
Portal portal = vreq.getPortal();
Map<String, Object> body = new HashMap<String, Object>();
// Test of #list directive in template on undefined, null, and empty values.
// Basic idea: empty list okay, null or undefined value not okay.
List<String> apples = new ArrayList<String>(); // no error
// List<String> apples = null; // error
body.put("apples", apples); // without this: error
Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
body.put("now", now);
// In template: ${now?date}, ${now?datetime}, ${now?time}
// You can add to a collection AFTER putting it in the template data model.
// The data model contains a reference to the collection, not a copy.
List<String> fruit = new ArrayList<String>();
fruit.add("apples");
fruit.add("bananas");
body.put("fruit", fruit);
fruit.add("oranges");
// But you cannot modify a scalar after putting it in the data model - the
// template still gets the old value
String animal = "elephant";
body.put("animal", animal);
animal = "camel";
// Because the data model contains a reference to the collection, changing
// one also changes the other.
List<String> animals = new ArrayList<String>();
animals.add("elephant");
animals.add("tiger");
Map<String, List> zoo1 = new HashMap<String, List>();
Map<String, List> zoo2 = new HashMap<String, List>();
zoo1.put("animals", animals);
zoo2.put("animals", animals);
zoo1.get("animals").add("monkey");
body.put("zoo1", zoo1);
body.put("zoo2", zoo2);
// Test recursive dump - array of arrays
// String[] fruitArray = { "apples", "bananas", "strawberries" };
// String[] animalArray = { "cat", "dog", "mouse" };
// String[] dayArray = { "Monday", "Tuesday", "Wednesday" };
// String[][] arrays = { fruitArray, animalArray, dayArray };
// body.put("arrays", arrays);
body.put("trueStatement", true);
body.put("falseStatement", false);
getBerries(body);
body.put("bookTitle", "Pride and Prejudice");
body.put("bookTitle", "Persuasion");
body.put("title", "Freemarker Samples");
return new TemplateResponseValues(TEMPLATE_DEFAULT, body);
}
@Override
protected String getTitle(String siteName) {
return "Freemarker Samples";
}
private void getBerries(Map<String, Object> body) {
body.put("berries", "strawberries, raspberries, blueberries");
}
}

View file

@ -18,15 +18,15 @@ import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateException;
public class FreemarkerHelper {
public class TemplateProcessingHelper {
private static final Log log = LogFactory.getLog(FreemarkerHelper.class);
private static final Log log = LogFactory.getLog(TemplateProcessingHelper.class);
private Configuration config = null;
private HttpServletRequest request = null;
private ServletContext context = null;
public FreemarkerHelper(Configuration config, HttpServletRequest request, ServletContext context) {
public TemplateProcessingHelper(Configuration config, HttpServletRequest request, ServletContext context) {
this.config = config;
this.request = request;
this.context = context;
@ -65,7 +65,7 @@ public class FreemarkerHelper {
// return processTemplate(templateName, map).toString();
// }
private Template getTemplate(String templateName) {
public Template getTemplate(String templateName) {
Template template = null;
try {
template = config.getTemplate(templateName);

View file

@ -19,7 +19,7 @@ import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServ
import freemarker.template.Configuration;
/**
* A place for storing test cases.
* Freemarker controller and template sandbox.
* @author rjy7
*
*/
@ -31,63 +31,11 @@ public class TestController extends FreemarkerHttpServlet {
@Override
protected ResponseValues processRequest(VitroRequest vreq) {
Portal portal = vreq.getPortal();
//Portal portal = vreq.getPortal();
Map<String, Object> body = new HashMap<String, Object>();
// Test of #list directive in template on undefined, null, and empty values.
// Basic idea: empty list okay, null or undefined value not okay.
List<String> apples = new ArrayList<String>(); // no error
// List<String> apples = null; // error
body.put("apples", apples); // without this: error
Calendar cal = Calendar.getInstance();
Date now = cal.getTime();
body.put("now", now);
// In template: ${now?date}, ${now?datetime}, ${now?time}
// You can add to a collection AFTER putting it in the template data model.
// The data model contains a reference to the collection, not a copy.
List<String> fruit = new ArrayList<String>();
fruit.add("apples");
fruit.add("bananas");
body.put("fruit", fruit);
fruit.add("oranges");
// But you cannot modify a scalar after putting it in the data model - the
// template still gets the old value
String animal = "elephant";
body.put("animal", animal);
animal = "camel";
// Because the data model contains a reference to the collection, changing
// one also changes the other.
List<String> animals = new ArrayList<String>();
animals.add("elephant");
animals.add("tiger");
Map<String, List> zoo1 = new HashMap<String, List>();
Map<String, List> zoo2 = new HashMap<String, List>();
zoo1.put("animals", animals);
zoo2.put("animals", animals);
zoo1.get("animals").add("monkey");
body.put("zoo1", zoo1);
body.put("zoo2", zoo2);
// Test recursive dump - array of arrays
// String[] fruitArray = { "apples", "bananas", "strawberries" };
// String[] animalArray = { "cat", "dog", "mouse" };
// String[] dayArray = { "Monday", "Tuesday", "Wednesday" };
// String[][] arrays = { fruitArray, animalArray, dayArray };
// body.put("arrays", arrays);
body.put("trueStatement", true);
body.put("falseStatement", false);
getBerries(body);
body.put("bookTitle", "Pride and Prejudice");
body.put("bookTitle", "Persuasion");
body.put("title", "VIVO Test");
body.put("title", "Freemarker Test");
return new TemplateResponseValues(TEMPLATE_DEFAULT, body);
}
@ -97,9 +45,5 @@ public class TestController extends FreemarkerHttpServlet {
return "Test";
}
private void getBerries(Map<String, Object> body) {
body.put("berries", "strawberries, raspberries, blueberries");
}
}

View file

@ -11,7 +11,7 @@ import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHelper;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.TemplateProcessingHelper;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.TemplateDirectiveModel;
@ -39,17 +39,17 @@ public abstract class BaseTemplateDirectiveModel implements TemplateDirectiveMod
}
protected String mergeToHelpTemplate(Map<String, Object> map, Environment environment) {
FreemarkerHelper helper = getFreemarkerHelper(environment);
TemplateProcessingHelper helper = getFreemarkerHelper(environment);
return helper.processTemplateToString("help-directive.ftl", map);
}
public static FreemarkerHelper getFreemarkerHelper(Environment env) {
public static TemplateProcessingHelper getFreemarkerHelper(Environment env) {
Configuration config = env.getConfiguration();
// In a directive, custom attributes for request and context are available in the Environment.
// They are put there when the enclosing template is processed.
HttpServletRequest request = (HttpServletRequest) env.getCustomAttribute("request");
ServletContext context = (ServletContext) env.getCustomAttribute("context");
return new FreemarkerHelper(config, request, context);
return new TemplateProcessingHelper(config, request, context);
}
}

View file

@ -0,0 +1,98 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.web.directives;
import java.io.IOException;
import java.io.Writer;
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHttpServlet;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.TemplateProcessingHelper;
import edu.cornell.mannlib.vitro.webapp.utils.StringUtils;
import edu.cornell.mannlib.vitro.webapp.web.widgets.Widget;
import freemarker.core.Environment;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateScalarModel;
public class WidgetDirective extends BaseTemplateDirectiveModel {
private static final Log log = LogFactory.getLog(WidgetDirective.class);
private static final String WIDGET_PACKAGE = "edu.cornell.mannlib.vitro.webapp.web.widgets";
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
if (loopVars.length != 0) {
throw new TemplateModelException(
"The dump directive doesn't allow loop variables.");
}
if (body != null) {
throw new TemplateModelException(
"The dump directive doesn't allow nested content.");
}
Object nameParam = params.get("name");
if ( !(nameParam instanceof SimpleScalar)) {
throw new TemplateModelException(
"Value of parameter 'name' must be a string.");
}
String widgetName = nameParam.toString();
// Optional param
Object includeParam = params.get("include");
String methodName;
// If include param is missing, or something other than "assets",
// assign default value "markup"
if (includeParam == null) {
methodName = "markup";
} else {
methodName = includeParam.toString();
if ( ! ("assets".equals(methodName)) ) {
methodName = "markup";
}
}
methodName = "do" + StringUtils.capitalize(methodName);
try {
String widgetClassName = WIDGET_PACKAGE + "." + StringUtils.capitalize(widgetName) + "Widget";
Class<?> widgetClass = Class.forName(widgetClassName);
// Use Constructor.newInstance() rather than Class.newInstance() so we can pass arguments
// to the constructor.
// Widget widget = (Widget) widgetClass.newInstance();
Constructor<?> widgetConstructor = widgetClass.getConstructor(new Class[]{Environment.class, String.class});
Widget widget = (Widget) widgetConstructor.newInstance(env, widgetName);
Method method = widgetClass.getMethod(methodName);
String output = (String) method.invoke(widget);
String templateType = env.getDataModel().get("templateType").toString();
// If we're in the body template, automatically invoke the doAssets method, so it
// doesn't need to be called explicitly.
if ("doMarkup".equals(methodName) && FreemarkerHttpServlet.BODY_TEMPLATE_TYPE.equals(templateType)) {
output += widgetClass.getMethod("doAssets").invoke(widget);
}
Writer out = env.getOut();
out.write(output);
} catch (ClassNotFoundException e) {
log.error("Widget " + widgetName + " not found.");
} catch (IOException e) {
log.error("Error writing output for widget " + widgetName, e);
} catch (Exception e) {
log.error("Error invoking widget " + widgetName, e);
}
}
}

View file

@ -10,7 +10,7 @@ import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.FreemarkerHelper;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.TemplateProcessingHelper;
import edu.cornell.mannlib.vitro.webapp.web.directives.BaseTemplateDirectiveModel;
import edu.cornell.mannlib.vitro.webapp.web.templatemodels.BaseTemplateModel;
import freemarker.core.Environment;
@ -36,7 +36,7 @@ public class DumpHelper {
public String getVariableDump(String varName) {
Map<String, Object> map = getVariableDumpData(varName);
FreemarkerHelper helper = BaseTemplateDirectiveModel.getFreemarkerHelper(environment);
TemplateProcessingHelper helper = BaseTemplateDirectiveModel.getFreemarkerHelper(environment);
return helper.processTemplateToString("dump-var.ftl", map);
}
@ -106,7 +106,7 @@ public class DumpHelper {
public void writeDump(String templateName, Map<String, Object> map, String modelName) {
FreemarkerHelper helper = BaseTemplateDirectiveModel.getFreemarkerHelper(environment);
TemplateProcessingHelper helper = BaseTemplateDirectiveModel.getFreemarkerHelper(environment);
String output = helper.processTemplateToString(templateName, map);
Writer out = environment.getOut();
try {

View file

@ -1,49 +0,0 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.web.directives.widgets;
import java.io.IOException;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.web.directives.BaseTemplateDirectiveModel;
import freemarker.core.Environment;
import freemarker.ext.servlet.AllHttpScopesHashModel;
import freemarker.template.SimpleScalar;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
public class BaseWidgetDirective extends BaseTemplateDirectiveModel {
private static final Log log = LogFactory.getLog(BaseWidgetDirective.class);
@Override
public void execute(Environment env, Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body) throws TemplateException, IOException {
if (loopVars.length != 0) {
throw new TemplateModelException(
"The dump directive doesn't allow loop variables.");
}
if (body != null) {
throw new TemplateModelException(
"The dump directive doesn't allow nested content.");
}
Object o = params.get("name");
if ( !(o instanceof SimpleScalar)) {
throw new TemplateModelException(
"Value of parameter 'name' must be a string.");
}
AllHttpScopesHashModel dataModel = (AllHttpScopesHashModel)(env.getDataModel());
System.out.println("In widget " + o.toString());
}
}

View file

@ -0,0 +1,28 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.web.widgets;
import java.util.HashMap;
import java.util.Map;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import freemarker.core.Environment;
public class LoginWidget extends Widget {
private static final Log log = LogFactory.getLog(LoginWidget.class);
public LoginWidget(Environment env, String name) {
super(env, name);
}
@Override
protected Map<String, Object> getDataModel() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("fruit", "bananas");
return map;
}
}

View file

@ -0,0 +1,23 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.web.widgets;
import java.util.HashMap;
import java.util.Map;
import freemarker.core.Environment;
public class TestWidget extends Widget {
public TestWidget(Environment env, String name) {
super(env, name);
}
@Override
protected Map<String, Object> getDataModel() {
Map<String, Object> map = new HashMap<String, Object>();
map.put("fruit", "bananas");
return map;
}
}

View file

@ -0,0 +1,73 @@
/* $This file is distributed under the terms of the license in /doc/license.txt$ */
package edu.cornell.mannlib.vitro.webapp.web.widgets;
import java.util.HashMap;
import java.util.Map;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.TemplateProcessingHelper;
import freemarker.core.Environment;
import freemarker.template.Configuration;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateModelException;
public abstract class Widget {
private static final Log log = LogFactory.getLog(Widget.class);
protected Environment env = null;
private TemplateProcessingHelper helper = null;
private String name = null;
// protected TemplateDirectiveModel directive = null;
// protected Macro markupMacro = null;
// protected Macro assetsMacro = null;
public Widget(Environment env, String name) {
this.env = env;
this.name = name;
Configuration config = env.getConfiguration();
HttpServletRequest request = (HttpServletRequest) env.getCustomAttribute("request");
ServletContext context = (ServletContext) env.getCustomAttribute("context");
this.helper = new TemplateProcessingHelper(config, request, context);
//this.directive = directive;
//Template template = getTemplate();
//Map templateMacros = template.getMacros();
//markupMacro = (Macro) templateMacros.get("markup");
//assetsMacro = (Macro) templateMacros.get("assets");
}
public String doAssets() {
String templateName = "widget-" + name + "-assets.ftl";
Map<String, Object> map = new HashMap<String, Object>();
TemplateHashModel dataModel = env.getDataModel();
try {
map.put("stylesheets", dataModel.get("stylesheets"));
map.put("scripts", dataModel.get("scripts"));
map.put("headScripts", dataModel.get("headScripts"));
} catch (TemplateModelException e) {
log.error("Error getting asset values from data model.");
}
return helper.processTemplateToString(templateName, map);
}
public String doMarkup() {
String templateName = "widget-" + name + "-markup.ftl";
Map<String, Object> map = getDataModel();
return helper.processTemplateToString(templateName, map);
}
protected abstract Map<String, Object> getDataModel();
// # You can capture the output of an arbitrary part of the template into a context variable.
// # You can interpret arbitrary context variable as if it were a template definition.
}