diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/dump/DescribeDirective.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/dump/DescribeDirective.java index b8bd839ac..d6148d6d4 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/dump/DescribeDirective.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/dump/DescribeDirective.java @@ -71,7 +71,7 @@ public class DescribeDirective extends BaseTemplateDirectiveModel { } DumpHelper helper = new DumpHelper(env); - List methods = helper.getMethodsAvailableToTemplate(wrappedModel, unwrappedModel.getClass()); + List methods = helper.getMethodsAvailableToTemplate(wrappedModel, (BaseTemplateModel)unwrappedModel); List methodDisplayNames = new ArrayList(methods.size()); for (Method m : methods) { methodDisplayNames.add(helper.getMethodDisplayName(m)); diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/dump/DumpHelper.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/dump/DumpHelper.java index 3411fdf01..a881b2717 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/dump/DumpHelper.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/directives/dump/DumpHelper.java @@ -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 map = new HashMap(); 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 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 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 getMethodsAvailableToTemplate(int exposureLevel, Class cls) { - List methods = new ArrayList(); - - // 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 getDeclaredMethodsAvailableToTemplate(int exposureLevel, Class cls) { - - List methods = new ArrayList(); - 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 getMethodsAvailableToTemplate(TemplateModel wrappedModel, BaseTemplateModel unwrappedModel) { + int exposureLevel = getExposureLevel(wrappedModel, unwrappedModel); + return getMethodsAvailableToTemplate(exposureLevel, unwrappedModel); } + private List getMethodsAvailableToTemplate(int exposureLevel, BaseTemplateModel unwrappedModel) { + List availableMethods = new ArrayList(); + + 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 getTemplateModelValues(TemplateModel wrappedModel, BaseTemplateModel unwrappedModel) { - int exposureLevel = getExposureLevel(wrappedModel, unwrappedModel.getClass()); - return getTemplateModelValues(unwrappedModel, exposureLevel); + private Map getBaseTemplateModelValues(TemplateModel wrappedModel, BaseTemplateModel unwrappedModel) { + int exposureLevel = getExposureLevel(wrappedModel, unwrappedModel); + return getBaseTemplateModelValues(unwrappedModel, exposureLevel); } - private Map getTemplateModelValues(BaseTemplateModel model, int exposureLevel) { + private Map getBaseTemplateModelValues(BaseTemplateModel model, int exposureLevel) { Map map = new HashMap(); map.put("value", model.toString()); - List publicMethods = getMethodsAvailableToTemplate(exposureLevel, model.getClass()); + List availableMethods = getMethodsAvailableToTemplate(exposureLevel, model); SortedMap properties = new TreeMap(); List methods = new ArrayList(); - 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 map = getTemplateModelValues(model, exposureLevel); + Map map = getBaseTemplateModelValues(model, exposureLevel); return BaseTemplateDirectiveModel.processTemplateToString("dump-var.ftl", map, env); } + + public void writeDump(String templateName, Map 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 + "."); + } + } } diff --git a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/BaseTemplateModel.java b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/BaseTemplateModel.java index 5d8c617e2..7752935e5 100644 --- a/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/BaseTemplateModel.java +++ b/webapp/src/edu/cornell/mannlib/vitro/webapp/web/templatemodels/BaseTemplateModel.java @@ -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() - } - } diff --git a/webapp/web/templates/freemarker/lib/lib-dump.ftl b/webapp/web/templates/freemarker/lib/lib-dump.ftl new file mode 100644 index 000000000..dec302011 --- /dev/null +++ b/webapp/web/templates/freemarker/lib/lib-dump.ftl @@ -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
-tags to get the wanted
+  -- result:
+  -- 
+  -- <@dumper.dump foo />
+  -- 
+  -->
+
+<#-- 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,[] />
+
+
+
+<#-- 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>  | 
+<#list has_next_array as has_next><#if !has_next>    <#else>  | <#t>
+<#t><@printItem item?if_exists,has_next_array+[item_has_next], counter />
+<#local counter = counter + 1/>
+
+
+
+<#macro printHashEx hash has_next_array>
+<#list hash?keys as key>
+<#list has_next_array+[true] as has_next><#if !has_next>    <#else>  | 
+<#list has_next_array as has_next><#if !has_next>    <#else>  | <#t>
+<#t><@printItem hash[key]?if_exists,has_next_array+[key_has_next], key />
+
+
+
+<#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)
+
+
+
+<#function omit key>
+    <#local what = key?lower_case>
+    <#list black_list as item>
+        <#if what?index_of(item) gte 0>
+            <#return true>
+        
+    
+    <#return false>
+
\ No newline at end of file