Improvements to dump methods

This commit is contained in:
rjy7 2011-03-11 21:26:52 +00:00
parent 28b6c4b867
commit 316cb523c3
4 changed files with 180 additions and 78 deletions

View file

@ -71,7 +71,7 @@ public class DescribeDirective extends BaseTemplateDirectiveModel {
}
DumpHelper helper = new DumpHelper(env);
List<Method> methods = helper.getMethodsAvailableToTemplate(wrappedModel, unwrappedModel.getClass());
List<Method> methods = helper.getMethodsAvailableToTemplate(wrappedModel, (BaseTemplateModel)unwrappedModel);
List<String> methodDisplayNames = new ArrayList<String>(methods.size());
for (Method m : methods) {
methodDisplayNames.add(helper.getMethodDisplayName(m));

View file

@ -9,8 +9,10 @@ import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedMap;
import java.util.TreeMap;
@ -41,7 +43,7 @@ import freemarker.template.utility.DeepUnwrap;
public class DumpHelper {
private static final Log log = LogFactory.getLog(DumpHelper.class);
private Environment env = null;
public DumpHelper(Environment env) {
@ -66,6 +68,10 @@ public class DumpHelper {
Map<String, Object> map = new HashMap<String, Object>();
map.put("var", varName);
// RY Separate out here into a method that gets the rest of the map. This method will
// be called recursively on the result of invoking a method (for a BaseTemplateModel) or
// the members of a hash or sequence. See NewDumpHelper.getVariableDumpData().
// DON'T return null if wrappedModel == null. We still want to return the map to the template.
if (wrappedModel != null) {
Object unwrappedModel = null;
@ -93,7 +99,7 @@ public class DumpHelper {
// if (wrappedModel instanceof TemplateHashModelEx) -
// a subcase of this is unwrappedModel instanceof BaseTemplateModel
if (unwrappedModel instanceof BaseTemplateModel) {
map.putAll(getTemplateModelValues(wrappedModel, (BaseTemplateModel)unwrappedModel));
map.putAll(getBaseTemplateModelValues(wrappedModel, (BaseTemplateModel)unwrappedModel));
type = className;
// Do TemplateHashModel case first, because wrappedModel of (at least some) POJOs are
// StringModels, which are both TemplateScalarModels and TemplateHashModels
@ -131,39 +137,14 @@ public class DumpHelper {
return map;
}
public void writeDump(String templateName, Map<String, Object> map, String modelName, TemplateHashModel dataModel) {
// Add objects to data model of calling template that are needed by
// dump templates.
try {
map.put("stylesheets", dataModel.get("stylesheets"));
map.put("urls", dataModel.get("urls"));
} catch (TemplateModelException e) {
log.error("Error getting values from data model.");
}
String output = BaseTemplateDirectiveModel.processTemplateToString(templateName, map, env);
Writer out = env.getOut();
try {
out.write(output);
} catch (IOException e) {
log.error("Error writing dump of " + modelName + ".");
}
}
protected List<Method> getMethodsAvailableToTemplate(TemplateModel wrappedModel, Class<?> cls) {
int exposureLevel = getExposureLevel(wrappedModel, cls);
return getMethodsAvailableToTemplate(exposureLevel, cls);
}
private int getExposureLevel(TemplateModel model, Class<?> cls) {
private int getExposureLevel(TemplateModel model, BaseTemplateModel unwrappedModel) {
int exposureLevel;
// Get the exposure level of the BeansWrapper that wrapped this object.
if (model instanceof BeanModel) {
exposureLevel = WrapperExtractor.getWrapperExposureLevel((BeanModel) model);
log.debug("Exposure level for class " + cls.getCanonicalName() + " wrapped as " + model.getClass() + " = " + exposureLevel);
log.debug("Exposure level for class " + unwrappedModel.getClass().getCanonicalName() + " wrapped as " + model.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 {
@ -171,43 +152,51 @@ public class DumpHelper {
Configuration config = (Configuration) request.getAttribute("freemarkerConfig");
BeansWrapper wrapper = (BeansWrapper) config.getObjectWrapper();
exposureLevel = WrapperExtractor.getWrapperExposureLevel(wrapper);
log.debug("Class " + cls.getCanonicalName() + " wrapped as " + model.getClass() + " uses default exposure level " + exposureLevel);
log.debug("Class " + unwrappedModel.getClass().getCanonicalName() + " wrapped as " + model.getClass() + " uses default exposure level " + exposureLevel);
}
return exposureLevel;
}
private List<Method> getMethodsAvailableToTemplate(int exposureLevel, Class<?> cls) {
List<Method> methods = new ArrayList<Method>();
// 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")) {
methods = getDeclaredMethodsAvailableToTemplate(exposureLevel, cls);
methods.addAll(getMethodsAvailableToTemplate(exposureLevel, cls.getSuperclass()));
}
return methods;
}
private List<Method> getDeclaredMethodsAvailableToTemplate(int exposureLevel, Class<?> cls) {
List<Method> methods = new ArrayList<Method>();
Method[] declaredMethods = cls.getDeclaredMethods();
for (Method method : declaredMethods) {
int mod = method.getModifiers();
if (Modifier.isPublic(mod) && !Modifier.isStatic(mod)) {
Class<?>[] params = method.getParameterTypes();
// If the method takes arguments, then it is not available to the template unless
// the exposure level of the BeansWrapper that wrapped the object allows it.
if (params.length > 0 && exposureLevel > BeansWrapper.EXPOSE_SAFE) {
continue;
}
methods.add(method);
}
}
return methods;
List<Method> getMethodsAvailableToTemplate(TemplateModel wrappedModel, BaseTemplateModel unwrappedModel) {
int exposureLevel = getExposureLevel(wrappedModel, unwrappedModel);
return getMethodsAvailableToTemplate(exposureLevel, unwrappedModel);
}
private List<Method> getMethodsAvailableToTemplate(int exposureLevel, BaseTemplateModel unwrappedModel) {
List<Method> availableMethods = new ArrayList<Method>();
Class<?> cls = unwrappedModel.getClass();
Method[] classMethods = cls.getMethods();
for (Method method : classMethods) {
// Exclude static methods
int mod = method.getModifiers();
if (Modifier.isStatic(mod)) {
continue;
}
// Include only methods declared on BaseTemplateModel or a subclass;
// exclude methods inherited from higher up the hierarchy like
// toString(), getClass(), etc.
Class<?> c = method.getDeclaringClass();
if ( ! BaseTemplateModel.class.isAssignableFrom(c)) {
continue;
}
// If the method takes arguments, then it is not available to the template unless
// the exposure level of the BeansWrapper that wrapped the object allows it.
Class<?>[] params = method.getParameterTypes();
if (params.length > 0 && exposureLevel > BeansWrapper.EXPOSE_SAFE) {
continue;
}
availableMethods.add(method);
}
return availableMethods;
}
protected String getMethodDisplayName(Method method) {
String methodName = method.getName();
Class<?>[] paramTypes = method.getParameterTypes();
@ -230,25 +219,28 @@ public class DumpHelper {
return methodName;
}
private Map<String, Object> getTemplateModelValues(TemplateModel wrappedModel, BaseTemplateModel unwrappedModel) {
int exposureLevel = getExposureLevel(wrappedModel, unwrappedModel.getClass());
return getTemplateModelValues(unwrappedModel, exposureLevel);
private Map<String, Object> getBaseTemplateModelValues(TemplateModel wrappedModel, BaseTemplateModel unwrappedModel) {
int exposureLevel = getExposureLevel(wrappedModel, unwrappedModel);
return getBaseTemplateModelValues(unwrappedModel, exposureLevel);
}
private Map<String, Object> getTemplateModelValues(BaseTemplateModel model, int exposureLevel) {
private Map<String, Object> getBaseTemplateModelValues(BaseTemplateModel model, int exposureLevel) {
Map<String, Object> map = new HashMap<String, Object>();
map.put("value", model.toString());
List<Method> publicMethods = getMethodsAvailableToTemplate(exposureLevel, model.getClass());
List<Method> availableMethods = getMethodsAvailableToTemplate(exposureLevel, model);
SortedMap<String, String> properties = new TreeMap<String, String>();
List<String> methods = new ArrayList<String>();
for (Method method : publicMethods) {
for (Method method : availableMethods) {
String key = getMethodDisplayName(method);
if (key.endsWith(")")) {
methods.add(key);
} else {
try {
Object result = method.invoke(model);
// RY Here we need to recurse in order to dump the full value of
// the result, by calling a method formed from the second part
// of getVariableDumpData.
String value;
if (result == null) {
value = "null";
@ -275,8 +267,28 @@ public class DumpHelper {
}
private String getTemplateModelDump(BaseTemplateModel model, int exposureLevel) {
Map<String, Object> map = getTemplateModelValues(model, exposureLevel);
Map<String, Object> map = getBaseTemplateModelValues(model, exposureLevel);
return BaseTemplateDirectiveModel.processTemplateToString("dump-var.ftl", map, env);
}
public void writeDump(String templateName, Map<String, Object> map, String modelName, TemplateHashModel dataModel) {
// Add objects to data model of calling template that are needed by
// all dump templates.
try {
map.put("stylesheets", dataModel.get("stylesheets"));
map.put("urls", dataModel.get("urls"));
} catch (TemplateModelException e) {
log.error("Error getting values from data model.");
}
String output = BaseTemplateDirectiveModel.processTemplateToString(templateName, map, env);
Writer out = env.getOut();
try {
out.write(output);
} catch (IOException e) {
log.error("Error writing dump of " + modelName + ".");
}
}
}

View file

@ -2,15 +2,11 @@
package edu.cornell.mannlib.vitro.webapp.web.templatemodels;
import java.util.ArrayList;
import java.util.List;
import javax.servlet.ServletContext;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import edu.cornell.mannlib.vitro.webapp.controller.VitroRequest;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder;
import edu.cornell.mannlib.vitro.webapp.controller.freemarker.UrlBuilder.ParamMap;
@ -20,8 +16,8 @@ public abstract class BaseTemplateModel {
protected static ServletContext servletContext = null;
// Wrap UrlBuilder method so templates can call ${item.url}
public String getUrl(String path) {
// Convenience method so subclasses can call getUrl(path)
protected String getUrl(String path) {
return UrlBuilder.getUrl(path);
}
@ -43,8 +39,4 @@ public abstract class BaseTemplateModel {
servletContext = context;
}
public String dump() {
return toString(); // fallback when subclass doesn't define a class-specific dump()
}
}

View file

@ -0,0 +1,98 @@
<#-- dump.ftl
--
-- Generates tree representations of data model items.
--
-- Usage:
-- <#import "dump.ftl" as dumper>
--
-- <#assign foo = something.in["your"].data[0].model />
--
-- <@dumper.dump foo />
--
-- When used within html pages you've to use <pre>-tags to get the wanted
-- result:
-- <pre>
-- <@dumper.dump foo />
-- <pre>
-->
<#-- The black_list contains bad hash keys. Any hash key which matches a
-- black_list entry is prevented from being displayed.
-->
<#assign black_list = ["class"] />
<#--
-- The main macro.
-->
<#macro dump data>
(root)
<#if data?is_enumerable>
<@printList data,[] />
<#elseif data?is_hash_ex>
<@printHashEx data,[] />
</#if>
</#macro>
<#-- private helper macros. it's not recommended to use these macros from
-- outside the macro library.
-->
<#macro printList list has_next_array>
<#local counter=0 />
<#list list as item>
<#list has_next_array+[true] as has_next><#if !has_next> <#else> | </#if></#list>
<#list has_next_array as has_next><#if !has_next> <#else> | </#if></#list><#t>
<#t><@printItem item?if_exists,has_next_array+[item_has_next], counter />
<#local counter = counter + 1/>
</#list>
</#macro>
<#macro printHashEx hash has_next_array>
<#list hash?keys as key>
<#list has_next_array+[true] as has_next><#if !has_next> <#else> | </#if></#list>
<#list has_next_array as has_next><#if !has_next> <#else> | </#if></#list><#t>
<#t><@printItem hash[key]?if_exists,has_next_array+[key_has_next], key />
</#list>
</#macro>
<#macro printItem item has_next_array key>
<#if item?is_method>
+- ${key} = ?? (method)
<#elseif item?is_enumerable>
+- ${key}
<@printList item, has_next_array /><#t>
<#elseif item?is_hash_ex && omit(key?string)><#-- omit bean-wrapped java.lang.Class objects -->
+- ${key} (omitted)
<#elseif item?is_hash_ex>
+- ${key}
<@printHashEx item, has_next_array /><#t>
<#elseif item?is_number>
+- ${key} = ${item}
<#elseif item?is_string>
+- ${key} = "${item}"
<#elseif item?is_boolean>
+- ${key} = ${item?string}
<#elseif item?is_date>
+- ${key} = ${item?string("yyyy-MM-dd HH:mm:ss zzzz")}
<#elseif item?is_transform>
+- ${key} = ?? (transform)
<#elseif item?is_macro>
+- ${key} = ?? (macro)
<#elseif item?is_hash>
+- ${key} = ?? (hash)
<#elseif item?is_node>
+- ${key} = ?? (node)
</#if>
</#macro>
<#function omit key>
<#local what = key?lower_case>
<#list black_list as item>
<#if what?index_of(item) gte 0>
<#return true>
</#if>
</#list>
<#return false>
</#function>