NIHVIVO-1564 Improvements to dump of template model objects
This commit is contained in:
parent
af7d959ecd
commit
e03f35a81c
7 changed files with 89 additions and 75 deletions
|
@ -101,11 +101,13 @@ public class FreemarkerConfigurationLoader {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Specify how templates will see the data model.
|
// Specify how templates will see the data model.
|
||||||
// The default wrapper exposes set methods unless exposure level is set.
|
// The Freemarker default wrapper exposes set methods and get methods that take
|
||||||
// By default we want to block exposure of set methods.
|
// arguments. We block exposure to these methods by default.
|
||||||
BeansWrapper wrapper = new DefaultObjectWrapper();
|
BeansWrapper wrapper = new DefaultObjectWrapper();
|
||||||
wrapper.setExposureLevel(BeansWrapper.EXPOSE_PROPERTIES_ONLY);
|
int defaultExposureLevel = BeansWrapper.EXPOSE_PROPERTIES_ONLY;
|
||||||
|
wrapper.setExposureLevel(defaultExposureLevel);
|
||||||
config.setObjectWrapper(wrapper);
|
config.setObjectWrapper(wrapper);
|
||||||
|
config.setCustomAttribute("defaultExposureLevel", defaultExposureLevel);
|
||||||
|
|
||||||
// Set some formatting defaults. These can be overridden at the template
|
// Set some formatting defaults. These can be overridden at the template
|
||||||
// or environment (template-processing) level, or for an individual
|
// or environment (template-processing) level, or for an individual
|
||||||
|
|
|
@ -11,6 +11,8 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
import org.apache.commons.logging.Log;
|
import org.apache.commons.logging.Log;
|
||||||
import org.apache.commons.logging.LogFactory;
|
import org.apache.commons.logging.LogFactory;
|
||||||
|
|
||||||
|
@ -21,6 +23,7 @@ import freemarker.core.Environment;
|
||||||
import freemarker.ext.beans.BeanModel;
|
import freemarker.ext.beans.BeanModel;
|
||||||
import freemarker.ext.beans.BeansWrapper;
|
import freemarker.ext.beans.BeansWrapper;
|
||||||
import freemarker.ext.beans.WrapperExtractor;
|
import freemarker.ext.beans.WrapperExtractor;
|
||||||
|
import freemarker.template.Configuration;
|
||||||
import freemarker.template.TemplateBooleanModel;
|
import freemarker.template.TemplateBooleanModel;
|
||||||
import freemarker.template.TemplateDateModel;
|
import freemarker.template.TemplateDateModel;
|
||||||
import freemarker.template.TemplateHashModel;
|
import freemarker.template.TemplateHashModel;
|
||||||
|
@ -53,17 +56,12 @@ public class DumpHelper {
|
||||||
tm = dataModel.get(varName);
|
tm = dataModel.get(varName);
|
||||||
} catch (TemplateModelException e) {
|
} catch (TemplateModelException e) {
|
||||||
log.error("Error getting value of template model '" + varName + "' from data model.");
|
log.error("Error getting value of template model '" + varName + "' from data model.");
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tm == null) {
|
|
||||||
log.error("No variable '" + varName + "' defined in data model." );
|
|
||||||
return null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> map = new HashMap<String, Object>();
|
||||||
map.put("var", varName);
|
map.put("var", varName);
|
||||||
|
|
||||||
|
// DON'T return null if tm == null. We still want to return the map to the template.
|
||||||
if (tm != null) {
|
if (tm != null) {
|
||||||
Object unwrappedModel = null;
|
Object unwrappedModel = null;
|
||||||
try {
|
try {
|
||||||
|
@ -87,7 +85,7 @@ public class DumpHelper {
|
||||||
// view in the dump. Not sure if we should handle our application-specific, non-template
|
// view in the dump. Not sure if we should handle our application-specific, non-template
|
||||||
// model objects in the same way. For now, these get assigned a shorthand type below.
|
// model objects in the same way. For now, these get assigned a shorthand type below.
|
||||||
if (unwrappedModel instanceof BaseTemplateModel) {
|
if (unwrappedModel instanceof BaseTemplateModel) {
|
||||||
value = getTemplateModelDump(tm, (BaseTemplateModel)unwrappedModel); //((BaseTemplateModel)unwrappedModel).dump();
|
map.putAll(getTemplateModelValues(tm, (BaseTemplateModel)unwrappedModel));
|
||||||
type = className;
|
type = className;
|
||||||
}
|
}
|
||||||
// Can't use this, because tm of (at least some) POJOs are
|
// Can't use this, because tm of (at least some) POJOs are
|
||||||
|
@ -115,8 +113,13 @@ public class DumpHelper {
|
||||||
type = className;
|
type = className;
|
||||||
}
|
}
|
||||||
|
|
||||||
map.put("value", value);
|
|
||||||
map.put("type", type);
|
map.put("type", type);
|
||||||
|
|
||||||
|
// Don't overwrite value returned from getTemplateModelValues().
|
||||||
|
if (! map.containsKey("value")) {
|
||||||
|
map.put("value", value);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return map;
|
return map;
|
||||||
|
@ -133,18 +136,37 @@ public class DumpHelper {
|
||||||
}
|
}
|
||||||
|
|
||||||
protected List<Method> getMethodsAvailableToTemplate(TemplateModel wrappedModel, Class<?> cls) {
|
protected List<Method> getMethodsAvailableToTemplate(TemplateModel wrappedModel, Class<?> cls) {
|
||||||
|
|
||||||
|
int exposureLevel;
|
||||||
|
// Get the exposure level of the BeansWrapper that wrapped this object.
|
||||||
|
if (wrappedModel instanceof BeanModel) {
|
||||||
|
exposureLevel = WrapperExtractor.getWrapperExposureLevel((BeanModel) wrappedModel);
|
||||||
|
log.debug("Exposure level for class " + cls.getCanonicalName() + " of type " + wrappedModel.getClass() + " = " + exposureLevel);
|
||||||
|
// We don't expect to get here, since we are dealing only with BaseTemplateModel objects, which get wrapped into BeanModel objects,
|
||||||
|
// but it's here as a safety net.
|
||||||
|
} else {
|
||||||
|
HttpServletRequest request = (HttpServletRequest) env.getCustomAttribute("request");
|
||||||
|
Configuration config = (Configuration) request.getAttribute("freemarkerConfig");
|
||||||
|
exposureLevel = (Integer) config.getCustomAttribute("defaultExposureLevel");
|
||||||
|
log.debug("Class " + cls.getCanonicalName() + " of type " + wrappedModel.getClass() + " uses default exposure level " + exposureLevel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return getMethodsAvailableToTemplate(exposureLevel, cls);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Method> getMethodsAvailableToTemplate(int exposureLevel, Class<?> cls) {
|
||||||
List<Method> methods = new ArrayList<Method>();
|
List<Method> methods = new ArrayList<Method>();
|
||||||
|
|
||||||
// Go up the class hierarchy only as far as the immediate subclass of BaseTemplateModel
|
// Go up the class hierarchy only as far as the immediate subclass of BaseTemplateModel
|
||||||
if (! cls.getName().equals("edu.cornell.mannlib.vitro.webapp.web.templatemodels.BaseTemplateModel")) {
|
if (! cls.getName().equals("edu.cornell.mannlib.vitro.webapp.web.templatemodels.BaseTemplateModel")) {
|
||||||
methods = getDeclaredMethodsAvailableToTemplate(wrappedModel, cls);
|
methods = getDeclaredMethodsAvailableToTemplate(exposureLevel, cls);
|
||||||
methods.addAll(getMethodsAvailableToTemplate(wrappedModel, cls.getSuperclass()));
|
methods.addAll(getMethodsAvailableToTemplate(exposureLevel, cls.getSuperclass()));
|
||||||
}
|
}
|
||||||
|
|
||||||
return methods;
|
return methods;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<Method> getDeclaredMethodsAvailableToTemplate(TemplateModel wrappedModel, Class<?> cls) {
|
private List<Method> getDeclaredMethodsAvailableToTemplate(int exposureLevel, Class<?> cls) {
|
||||||
|
|
||||||
List<Method> methods = new ArrayList<Method>();
|
List<Method> methods = new ArrayList<Method>();
|
||||||
Method[] declaredMethods = cls.getDeclaredMethods();
|
Method[] declaredMethods = cls.getDeclaredMethods();
|
||||||
|
@ -152,18 +174,10 @@ public class DumpHelper {
|
||||||
int mod = method.getModifiers();
|
int mod = method.getModifiers();
|
||||||
if (Modifier.isPublic(mod) && !Modifier.isStatic(mod)) {
|
if (Modifier.isPublic(mod) && !Modifier.isStatic(mod)) {
|
||||||
Class<?>[] params = method.getParameterTypes();
|
Class<?>[] params = method.getParameterTypes();
|
||||||
// If the method takes arguments...
|
// If the method takes arguments, then it is not available to the template unless
|
||||||
if (params.length > 0) {
|
// the exposure level of the BeansWrapper that wrapped the object allows it.
|
||||||
// Unless the object has been wrapped with a non-default BeansWrapper with an exposure
|
if (params.length > 0 && exposureLevel > BeansWrapper.EXPOSE_SAFE) {
|
||||||
// level that is more permissive than the Configuration's default BeansWrapper, this
|
continue;
|
||||||
// method is not visible to the template.
|
|
||||||
if ( ! ( wrappedModel instanceof BeanModel ) ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
int exposureLevel = WrapperExtractor.getWrapperExposureLevel((BeanModel)wrappedModel);
|
|
||||||
if ( exposureLevel > BeansWrapper.EXPOSE_SAFE ) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
methods.add(method);
|
methods.add(method);
|
||||||
}
|
}
|
||||||
|
@ -194,20 +208,20 @@ public class DumpHelper {
|
||||||
return methodName + paramList;
|
return methodName + paramList;
|
||||||
}
|
}
|
||||||
|
|
||||||
private String getTemplateModelDump(TemplateModel wrappedModel, BaseTemplateModel unwrappedModel) {
|
private Map<String, Object> getTemplateModelValues(TemplateModel wrappedModel, BaseTemplateModel unwrappedModel) {
|
||||||
|
|
||||||
// if (tm instanceof BeanModel) {
|
|
||||||
// int exposureLevel = WrapperExtractor.getWrapperExposureLevel((BeanModel) tm);
|
|
||||||
// log.debug(varName + " is an instance of BeanModel. Exposure level = " + exposureLevel);
|
|
||||||
// }
|
|
||||||
|
|
||||||
Map<String, Object> map = new HashMap<String, Object>();
|
Map<String, Object> map = new HashMap<String, Object>();
|
||||||
|
map.put("value", unwrappedModel.dump());
|
||||||
|
|
||||||
List<Method> publicMethods = getMethodsAvailableToTemplate(wrappedModel, unwrappedModel.getClass());
|
List<Method> publicMethods = getMethodsAvailableToTemplate(wrappedModel, unwrappedModel.getClass());
|
||||||
Map<String, String> properties = new HashMap<String, String>();
|
Map<String, String> properties = new HashMap<String, String>();
|
||||||
List<String> methods = new ArrayList<String>();
|
List<String> methods = new ArrayList<String>();
|
||||||
for (Method method : publicMethods) {
|
for (Method method : publicMethods) {
|
||||||
|
// Don't include the dump method, since this is used above to provide the value of the object.
|
||||||
|
if (method.getName().equals("dump")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
String key = getMethodDisplayName(method);
|
String key = getMethodDisplayName(method);
|
||||||
|
|
||||||
if (key.endsWith(")")) {
|
if (key.endsWith(")")) {
|
||||||
methods.add(key);
|
methods.add(key);
|
||||||
} else {
|
} else {
|
||||||
|
@ -224,8 +238,7 @@ public class DumpHelper {
|
||||||
|
|
||||||
map.put("properties", properties);
|
map.put("properties", properties);
|
||||||
map.put("methods", methods);
|
map.put("methods", methods);
|
||||||
return BaseTemplateDirectiveModel.processTemplateToString("dump-tm.ftl", map, env);
|
return map;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -43,8 +43,8 @@ public abstract class BaseTemplateModel {
|
||||||
servletContext = context;
|
servletContext = context;
|
||||||
}
|
}
|
||||||
|
|
||||||
// public String dump() {
|
public String dump() {
|
||||||
// return toString(); // fallback when subclass doesn't define a class-specific dump()
|
return toString(); // fallback when subclass doesn't define a class-specific dump()
|
||||||
// }
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -75,6 +75,10 @@ public abstract class Files extends BaseTemplateModel {
|
||||||
return tags;
|
return tags;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String dump() {
|
||||||
|
return list.toString();
|
||||||
|
}
|
||||||
|
|
||||||
protected abstract String getTag(String url);
|
protected abstract String getTag(String url);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,11 @@
|
||||||
margin: 1em 0;
|
margin: 1em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.dump ol li,
|
||||||
|
.dump ul li {
|
||||||
|
margin-bottom: .5em;
|
||||||
|
}
|
||||||
|
|
||||||
.dump .var p {
|
.dump .var p {
|
||||||
margin-bottom: .3em;
|
margin-bottom: .3em;
|
||||||
}
|
}
|
||||||
|
@ -33,11 +38,6 @@
|
||||||
margin-left: 1.5em;
|
margin-left: 1.5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dump .directive ol li,
|
|
||||||
.dump .directive ul li {
|
|
||||||
margin-bottom: .5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dump .directive ol li {
|
.dump .directive ol li {
|
||||||
list-style: decimal inside none;
|
list-style: decimal inside none;
|
||||||
}
|
}
|
|
@ -1,25 +0,0 @@
|
||||||
<#-- $This file is distributed under the terms of the license in /doc/license.txt$ -->
|
|
||||||
|
|
||||||
<#-- Template for dumping the value template model object -->
|
|
||||||
|
|
||||||
|
|
||||||
<#if properties?has_content>
|
|
||||||
<p><strong>Property values</strong></p>
|
|
||||||
<ul>
|
|
||||||
<#list properties?keys as property>
|
|
||||||
<li>${property}: ${properties[property]?html}</li>
|
|
||||||
</#list>
|
|
||||||
</ul>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
<#if methods?has_content>
|
|
||||||
<p><strong>Methods</strong></p>
|
|
||||||
<ul>
|
|
||||||
<#list methods as method>
|
|
||||||
<li>${method}</li>
|
|
||||||
</#list>
|
|
||||||
</ul>
|
|
||||||
</#if>
|
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -3,12 +3,32 @@
|
||||||
<#-- Template for dumping a template variable -->
|
<#-- Template for dumping a template variable -->
|
||||||
|
|
||||||
<div class="var">
|
<div class="var">
|
||||||
<p><strong>Variable name: <em>${var}</em></strong></p>
|
<p><strong>Variable name: ${var}</strong></p>
|
||||||
<#if value??>
|
<#if value??>
|
||||||
<p><strong>Type:</strong> ${type}</p>
|
<p><strong>Type:</strong> ${type}</p>
|
||||||
<div><strong>Value:</strong> ${value}</div>
|
<p><strong>Value:</strong> ${value}</p>
|
||||||
|
|
||||||
|
<#-- Template model objects -->
|
||||||
|
<#if properties?has_content>
|
||||||
|
<p><strong>Properties:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<#list properties?keys as property>
|
||||||
|
<li>${property}: ${properties[property]?html}</li>
|
||||||
|
</#list>
|
||||||
|
</ul>
|
||||||
|
</#if>
|
||||||
|
|
||||||
|
<#if methods?has_content>
|
||||||
|
<p><strong>Methods:</strong></p>
|
||||||
|
<ul>
|
||||||
|
<#list methods as method>
|
||||||
|
<li>${method}</li>
|
||||||
|
</#list>
|
||||||
|
</ul>
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#else>
|
<#else>
|
||||||
<p>Variable is undefined in the data model</p>
|
<p><strong>Value:</strong> null</p>
|
||||||
</#if>
|
</#if>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
Loading…
Add table
Reference in a new issue