Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
freemarker.ext.dump.BaseDumpDirective Maven / Gradle / Ivy
/* $This file is distributed under the terms of the license in LICENSE$ */
package freemarker.ext.dump;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.reflect.Method;
import java.util.ArrayList;
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;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import freemarker.core.Environment;
import freemarker.ext.beans.BeanModel;
import freemarker.ext.beans.BeansWrapper;
import freemarker.ext.beans.CollectionModel;
import freemarker.ext.beans.SimpleMethodModel;
import freemarker.ext.beans.StringModel;
import freemarker.ext.beans.WrapperExtractor;
import freemarker.template.ObjectWrapper;
import freemarker.template.SimpleScalar;
import freemarker.template.Template;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateCollectionModel;
import freemarker.template.TemplateDateModel;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateHashModel;
import freemarker.template.TemplateHashModelEx;
import freemarker.template.TemplateMethodModel;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateModelIterator;
import freemarker.template.TemplateNumberModel;
import freemarker.template.TemplateScalarModel;
import freemarker.template.TemplateSequenceModel;
import freemarker.template.utility.DeepUnwrap;
/* TODO
* - Check error messages generated for TemplateModelException-s. If too generic, need to catch, create specific
* error message, and rethrow.
*/
public abstract class BaseDumpDirective implements TemplateDirectiveModel {
private static final Log log = LogFactory.getLog(BaseDumpDirective.class);
private static final String TEMPLATE_DEFAULT = "dump.ftl"; // change to dump.ftl when old dump is removed
private static final Pattern PROPERTY_NAME_PATTERN = Pattern.compile("^(get|is)\\w");
private ObjectWrapper defaultWrapper;
enum Key {
CLASS("class"),
DATE_TYPE("dateType"),
HELP("help"),
METHODS("methods"),
PROPERTIES("properties"),
TYPE("type"),
VALUE("value");
private final String key;
Key(String key) {
this.key = key;
}
public String toString() {
return key;
}
}
enum Value {
NULL("[null]"),
UNDEFINED("[undefined]");
private final String value;
Value(String value) {
this.value = value;
}
public String toString() {
return value;
}
}
enum Type {
BOOLEAN("Boolean"),
COLLECTION("Collection"),
DATE("Date"),
DIRECTIVE("Directive"),
HASH("Hash"),
// Technically it's a HashEx, but for the templates call it a Hash
HASH_EX("Hash"), // ("HashEx")
METHOD("Method"),
NUMBER("Number"),
SEQUENCE("Sequence"),
STRING("String");
private final String type;
Type(String type) {
this.type = type;
}
public String toString() {
return type;
}
}
enum DateType {
DATE("Date"),
DATETIME("DateTime"),
TIME("Time"),
UNKNOWN("Unknown");
private final String type;
DateType(String type) {
this.type = type;
}
public String toString() {
return type;
}
}
protected Map getTemplateVariableDump(String varName, Environment env)
throws TemplateModelException {
defaultWrapper = env.getObjectWrapper();
if (defaultWrapper == null) {
defaultWrapper = env.getConfiguration().getObjectWrapper();
}
TemplateHashModel dataModel = env.getDataModel();
TemplateModel valueToDump = dataModel.get(varName);
return getTemplateVariableDump(varName, valueToDump);
}
protected Map getTemplateVariableDump(String varName, TemplateModel valueToDump)
throws TemplateModelException {
Map value = new HashMap();
if (valueToDump == null) {
value.put(Key.VALUE.toString(), Value.UNDEFINED.toString());
// TemplateMethodModel and TemplateDirectiveModel objects can only be
// included in the data model at the top level.
} else if (valueToDump instanceof TemplateMethodModel) {
value.putAll( getTemplateModelDump( ( TemplateMethodModel)valueToDump, varName ) );
} else if (valueToDump instanceof TemplateDirectiveModel) {
value.putAll( getTemplateModelDump( ( TemplateDirectiveModel)valueToDump, varName ) );
} else {
value.putAll(getDump(valueToDump));
}
Map dump = new HashMap();
dump.put(varName, value);
return dump;
}
private Map getDump(TemplateModel valueToDump) throws TemplateModelException {
Map map = new HashMap();
// Don't return null if model == null. We still want to send the map to the template.
if (valueToDump != null) {
if ( valueToDump instanceof TemplateSequenceModel ) {
if (valueToDump instanceof CollectionModel && ! ((CollectionModel)valueToDump).getSupportsIndexedAccess()) {
map.putAll( getTemplateModelDump( (TemplateCollectionModel)valueToDump ) );
} else {
map.putAll( getTemplateModelDump( (TemplateSequenceModel)valueToDump ) );
}
} else if ( valueToDump instanceof TemplateNumberModel ) {
map.putAll( getTemplateModelDump( (TemplateNumberModel)valueToDump ) );
} else if ( valueToDump instanceof TemplateBooleanModel ) {
map.putAll( getTemplateModelDump( (TemplateBooleanModel)valueToDump ) );
} else if ( valueToDump instanceof TemplateDateModel ) {
map.putAll( getTemplateModelDump( (TemplateDateModel)valueToDump ) );
} else if ( valueToDump instanceof TemplateCollectionModel ) {
map.putAll( getTemplateModelDump( (TemplateCollectionModel)valueToDump ) );
} else if ( valueToDump instanceof StringModel ) {
// A StringModel can wrap either a String or a plain Java object.
// Unwrap it to figure out what to do.
Object unwrappedModel = DeepUnwrap.permissiveUnwrap(valueToDump);
if (unwrappedModel instanceof String) {
map.putAll( getTemplateModelDump( (TemplateScalarModel)valueToDump ) );
} else {
map.putAll( getTemplateModelDump( (TemplateHashModelEx)valueToDump ) );
}
} else if ( valueToDump instanceof TemplateScalarModel ) {
map.putAll( getTemplateModelDump( (TemplateScalarModel)valueToDump ) );
} else if ( valueToDump instanceof TemplateHashModelEx ) {
map.putAll( getTemplateModelDump( (TemplateHashModelEx)valueToDump ) );
} else if (valueToDump instanceof TemplateHashModel ) {
map.putAll( getTemplateModelDump( (TemplateHashModel)valueToDump ) );
// Nodes and transforms not included here
} else {
// We shouldn't get here; provide as a safety net.
map.putAll( getTemplateModelDump( (TemplateModel)valueToDump ) );
}
} else {
map.put(Key.VALUE.toString(), Value.NULL.toString());
}
return map;
}
private Map getTemplateModelDump(TemplateScalarModel model) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.STRING);
map.put(Key.VALUE.toString(), model.getAsString());
return map;
}
private Map getTemplateModelDump(TemplateBooleanModel model) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.BOOLEAN);
map.put(Key.VALUE.toString(), model.getAsBoolean());
return map;
}
private Map getTemplateModelDump(TemplateNumberModel model) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.NUMBER);
map.put(Key.VALUE.toString(), model.getAsNumber());
return map;
}
private Map getTemplateModelDump(TemplateDateModel model) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.DATE);
int dateType = model.getDateType();
DateType type;
switch (dateType) {
case TemplateDateModel.DATE:
type = DateType.DATE;
break;
case TemplateDateModel.DATETIME:
type = DateType.DATETIME;
break;
case TemplateDateModel.TIME:
type = DateType.TIME;
break;
default:
type = DateType.UNKNOWN;
}
map.put(Key.DATE_TYPE.toString(), type);
map.put(Key.VALUE.toString(), model.getAsDate());
return map;
}
private Map getTemplateModelDump(TemplateHashModel model) throws TemplateModelException {
// The data model is a hash; when else do we get here?
log.debug("Dumping model " + model);
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.HASH);
//map.put(Key.VALUE.toString(), ????);
return map;
}
private Map getTemplateModelDump(TemplateSequenceModel model) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.SEQUENCE);
int itemCount = model.size();
List> items = new ArrayList>(itemCount);
for ( int i = 0; i < itemCount; i++ ) {
TemplateModel item = model.get(i);
items.add(getDump(item));
}
map.put(Key.VALUE.toString(), items);
return map;
}
private Map getTemplateModelDump(TemplateHashModelEx model) throws TemplateModelException {
Object unwrappedModel = DeepUnwrap.permissiveUnwrap(model);
// This seems to be the most reliable way of distinguishing a wrapped map from a wrapped object.
// A map may be wrapped as a SimpleHash, and an object may be wrapped as a StringModel, but they could
// be wrapped as other types as well.
if ( unwrappedModel instanceof Map ) {
return getMapDump(model);
}
// Java objects are wrapped as TemplateHashModelEx-s.
return getObjectDump(model, unwrappedModel);
}
private Map getMapDump(TemplateHashModelEx model) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.HASH_EX);
SortedMap items = new TreeMap();
TemplateCollectionModel keys = model.keys();
TemplateModelIterator iModel = keys.iterator();
while (iModel.hasNext()) {
String key = iModel.next().toString();
// Work around this oddity: model.object does not contain
// values for "empty" and "keys", but model.keys() does.
if ("class".equals(key) || "empty".equals(key)) {
continue;
}
TemplateModel value = model.get(key);
// A map with exposed methods includes methods inherited from Map and Object like
// size(), getClass(), etc. Punt on these for now by suppressing in the dump,
// though this is not the optimal solution. If they are exposed to the templates,
// the dump should also expose them. I'm guessing that in most cases these
// methods are not relevant to template authors.
if (! (value instanceof TemplateMethodModel)) {
items.put(key, getDump(value));
}
}
map.put(Key.VALUE.toString(), items);
return map;
}
private Map getObjectDump(TemplateHashModelEx model, Object object) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), object.getClass().getName());
if( object instanceof java.lang.reflect.Method)
return map;
// Compile the collections of properties and methods available to the template
SortedMap properties = new TreeMap();
SortedMap methods = new TreeMap();
// keys() gets only values visible to template based on the BeansWrapper used.
// Note: if the BeansWrapper exposure level > BeansWrapper.EXPOSE_PROPERTIES_ONLY,
// keys() returns both method and property name for any available method with no
// parameters: e.g., both name and getName(). We are going to eliminate the latter.
TemplateCollectionModel keys = model.keys();
TemplateModelIterator iModel = keys.iterator();
// Create a Set from keys so we can use the Set API.
Set keySet = new HashSet();
while (iModel.hasNext()) {
String key = iModel.next().toString();
keySet.add(key);
}
if (keySet.size() > 0) {
Class cls = object.getClass();
Method[] classMethods = cls.getMethods();
// Iterate through the methods rather than the keys, so that we can remove
// some keys based on reflection on the methods. We also want to remove duplicates
// like name/getName - we'll keep only the first form.
for ( Method method : classMethods ) {
if( "declaringClass".equals( method.getName() ))
continue;
// Eliminate methods declared on Object
// and other unusual places that can cause problems.
Class c = method.getDeclaringClass();
if (c == null ||
c.equals(java.lang.Object.class) ||
c.equals(java.lang.reflect.Constructor.class) ||
c.equals(java.lang.reflect.Field.class ) )
continue;
if(
c.getPackage().getName().startsWith("sun.") ||
c.getPackage().getName().startsWith("java.lang") ||
c.getPackage().getName().startsWith("java.security") )
continue;
// Eliminate deprecated methods
if (method.isAnnotationPresent(Deprecated.class)) {
continue;
}
// Include only methods included in keys(). This factors in visibility
// defined by the model's BeansWrapper.
String methodName = method.getName();
Matcher matcher = PROPERTY_NAME_PATTERN.matcher(methodName);
// If the method name starts with "get" or "is", check if it's available
// as a property
if (matcher.find()) {
String propertyName = getPropertyName(methodName);
// The method is available as a property
if (keySet.contains(propertyName)) {
try{
TemplateModel value = model.get(propertyName);
properties.put(propertyName, getDump(value));
}catch(Throwable th){
log.error("problem dumping " + propertyName + " on " + object.getClass().getName() +
" declared in " + c.getName(), th);
}
continue;
}
}
// Else look for the entire methodName in the key set, to include
// those that are exposed as methods rather than properties.
if (keySet.contains(methodName)) {
String methodDisplayName = getMethodDisplayName(method);
// If no arguments, invoke the method to get the result
if ( methodDisplayName.endsWith("()") ) {
SimpleMethodModel methodModel = (SimpleMethodModel)model.get(methodName);
try {
Object result = methodModel.exec(null);
ObjectWrapper wrapper = getWrapper(model);
TemplateModel wrappedResult = wrapper.wrap(result);
methods.put(methodDisplayName, getDump(wrappedResult));
} catch (Exception e) {
log.error(e, e);
}
// Else display method name, parameter types, and return type
} else {
String returnTypeName = getReturnTypeName(method);
Map methodValue = new HashMap();
if ( ! returnTypeName.equals("void") ) {
methodValue.put(Key.TYPE.toString(), returnTypeName);
}
methods.put(methodDisplayName, methodValue);
}
}
}
}
Map objectValue = new HashMap(2);
objectValue.put(Key.PROPERTIES.toString(), properties);
objectValue.put(Key.METHODS.toString(), methods);
map.put(Key.VALUE.toString(), objectValue);
return map;
}
private ObjectWrapper getWrapper(TemplateHashModelEx model) {
// Attempt to find the wrapper that this template model object was wrapped with.
if (model instanceof BeanModel) {
return WrapperExtractor.getWrapper((BeanModel)model);
// Otherwise return the wrapper defined for the Environment or Configuration,
// if there is one. Why can't we get the wrapper for any type of TemplateModel??
} else if (defaultWrapper != null) {
return defaultWrapper;
} else {
return new BeansWrapper();
}
}
private String getMethodDisplayName(Method method) {
String methodName = method.getName();
Class[] paramTypes = method.getParameterTypes();
List paramTypeList = new ArrayList(paramTypes.length);
if (paramTypes.length > 0) {
for (Class cls : paramTypes) {
paramTypeList.add(getSimpleTypeName(cls));
}
}
methodName += "(" + StringUtils.join(paramTypeList, ", ") + ")";
return methodName;
}
private String getReturnTypeName(Method method) {
Class cls = method.getReturnType();
Package pkg = cls.getPackage();
if (pkg != null) { // void return type has null package
String packageName = pkg.getName();
if (packageName.startsWith("java")) {
return getSimpleTypeName(cls);
}
}
return cls.getName();
}
private String getSimpleTypeName(Class cls) {
return cls.getSimpleName().replace("[]", "s");
}
// Return the method name as it is represented in TemplateHashModelEx.keys()
private String getPropertyName(String methodName) {
String keyName = methodName.replaceAll("^(get|is)", "");
return StringUtils.uncapitalize(keyName);
}
private Map getTemplateModelDump(TemplateCollectionModel model) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.COLLECTION);
List> items = new ArrayList>();
TemplateModelIterator iModel = model.iterator();
while (iModel.hasNext()) {
TemplateModel m = iModel.next();
items.add(getDump(m));
}
map.put(Key.VALUE.toString(), items);
return map;
}
private Map getTemplateModelDump(TemplateMethodModel model, String varName) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.METHOD);
map.put(Key.CLASS.toString(), model.getClass().getName());
map.put(Key.HELP.toString(), getHelp(model, varName));
return map;
}
private Map getTemplateModelDump(TemplateDirectiveModel model, String varName) throws TemplateModelException {
Map map = new HashMap();
map.put(Key.TYPE.toString(), Type.DIRECTIVE);
map.put(Key.CLASS.toString(), model.getClass().getName());
map.put(Key.HELP.toString(), getHelp(model, varName));
return map;
}
@SuppressWarnings("unchecked")
private Map getHelp(TemplateModel model, String varName) {
if ( model instanceof TemplateMethodModel || model instanceof TemplateDirectiveModel ) {
String modelClass = model instanceof TemplateMethodModel ? "TemplateMethodModel" : "TemplateDirectiveModel";
Class cls = model.getClass();
try {
Method help = cls.getMethod("help", String.class);
try {
return (Map) help.invoke(model, varName);
} catch (ClassCastException e) {
log.error("Method help() of " + modelClass + " of class " + cls.getName() + " has incorrect return type.");
return null;
} catch (Exception e) {
log.error("Error invoking method help() on " + modelClass + " of class " + cls.getName());
return null;
}
} catch (NoSuchMethodException e) {
log.info("No help() method defined for " + modelClass + " of class " + cls.getName());
return null;
} catch (Exception e) {
log.error("Error getting method help() for " + modelClass + " " + cls.getName());
return null;
}
}
return null;
}
private Map getTemplateModelDump(TemplateModel model) throws TemplateModelException {
// One of the more specific cases should have applied. Track whether this actually occurs.
log.debug("Found template model of type " + model.getClass().getName());
Map map = new HashMap();
Object unwrappedModel = DeepUnwrap.permissiveUnwrap(model);
map.put(Key.TYPE.toString(), unwrappedModel.getClass().getName());
map.put(Key.VALUE.toString(), unwrappedModel.toString());
return map;
}
protected void dump(Map dump, Environment env, String title)
throws TemplateException, IOException {
dump(dump, env, title, TEMPLATE_DEFAULT);
}
protected void dump(Map dump, Environment env, String title, String templateName)
throws TemplateException, IOException {
// Wrap the dump in another map so the template has a handle to iterate through
// the values: <#list dump?keys as key>...
Map map = new HashMap();
//put the TemplateModel to be dumped at 'dumpValue'
map.put("dumpValue", dump);
map.put("title", title);
writeDump(map, env, templateName);
}
protected void writeDump(Map map, Environment env, String templateName)
throws TemplateException, IOException {
//bdc34: not sure what to do there to
//get the scope from the env to this dump template, trying an ugly copy
map.putAll( toMap( env.getDataModel() ));
Template template = env.getConfiguration().getTemplate(templateName);
StringWriter sw = new StringWriter();
template.process(map, sw);
Writer out = env.getOut();
out.write(sw.toString());
}
public Map help(String name) {
return new HashMap();
}
/**
* Convert a Freemarker TemplateObject to a usable map.
* @throws TemplateModelException
*/
protected Map toMap( Object hash ) throws TemplateModelException{
Map outMap = new HashMap();
if( hash instanceof TemplateHashModelEx ){
TemplateHashModelEx thme = (TemplateHashModelEx) hash;
for( String key : junkToStrings( thme.keys() )){
outMap.put(key, thme.get(key));
}
}else{
log.error("Freemarker is passing odd objects to method toMap(): " + hash.getClass().getName());
}
return outMap;
}
protected List junkToStrings( TemplateCollectionModel junk ){
List keys = new ArrayList();
try{
TemplateModelIterator it = junk.iterator();
while( it.hasNext() ){
Object obj = it.next();
if( obj instanceof StringModel){
keys.add( ((StringModel)obj).getAsString() );
}else if( obj instanceof SimpleScalar ){
keys.add( ((SimpleScalar)obj).getAsString());
}else{
log.error("Freemarker is setting keys to hashes as non-strings: " + obj.getClass().getName());
}
}
}catch(Exception ex){
log.error("Freemarker is messing with us",ex);
}
return keys;
}
}