1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 package org.apache.struts2.views.freemarker;
19
20 import java.util.Map;
21 import java.util.Set;
22
23 import freemarker.core.CollectionAndSequence;
24 import freemarker.ext.beans.BeansWrapper;
25 import freemarker.ext.beans.MapModel;
26 import freemarker.ext.util.ModelFactory;
27 import freemarker.template.ObjectWrapper;
28 import freemarker.template.SimpleSequence;
29 import freemarker.template.TemplateBooleanModel;
30 import freemarker.template.TemplateCollectionModel;
31 import freemarker.template.TemplateHashModelEx;
32 import freemarker.template.TemplateModel;
33 import freemarker.template.TemplateModelException;
34
35 /***
36 * <!-- START SNIPPET: javadoc -->
37 *
38 * The StrutsBeanWrapper extends the default FreeMarker BeansWrapper and provides almost no change in functionality,
39 * <b>except</b> for how it handles maps. Normally, FreeMarker has two modes of operation: either support for friendly
40 * map built-ins (?keys, ?values, etc) but only support for String keys; OR no special built-in support (ie: ?keys
41 * returns the methods on the map instead of the keys) but support for String and non-String keys alike. Struts
42 * provides an alternative implementation that gives us the best of both worlds.
43 *
44 * <p/> It is possible that this special behavior may be confusing or can cause problems. Therefore, you can set the
45 * <b>struts.freemarker.wrapper.altMap</b> property in struts.properties to false, allowing the normal BeansWrapper
46 * logic to take place instead.
47 *
48 * <!-- END SNIPPET: javadoc -->
49 */
50 public class StrutsBeanWrapper extends BeansWrapper {
51 private static final boolean altMapWrapper
52 = "true".equals(org.apache.struts2.config.Settings.get("struts.freemarker.wrapper.altMap"));
53
54 public TemplateModel wrap(Object object) throws TemplateModelException {
55 if (object instanceof TemplateBooleanModel) {
56 return super.wrap(object);
57 }
58
59
60 if (altMapWrapper && object instanceof Map) {
61 return getInstance(object, FriendlyMapModel.FACTORY);
62 }
63
64 return super.wrap(object);
65 }
66
67 /***
68 * Attempting to get the best of both worlds of FM's MapModel and SimpleMapModel, by reimplementing the isEmpty(),
69 * keySet() and values() methods. ?keys and ?values built-ins are thus available, just as well as plain Map
70 * methods.
71 */
72 private final static class FriendlyMapModel extends MapModel implements TemplateHashModelEx {
73 static final ModelFactory FACTORY = new ModelFactory() {
74 public TemplateModel create(Object object, ObjectWrapper wrapper) {
75 return new FriendlyMapModel((Map) object, (BeansWrapper) wrapper);
76 }
77 };
78
79 public FriendlyMapModel(Map map, BeansWrapper wrapper) {
80 super(map, wrapper);
81 }
82
83 public boolean isEmpty() {
84 return ((Map) object).isEmpty();
85 }
86
87 protected Set keySet() {
88 return ((Map) object).keySet();
89 }
90
91 public TemplateCollectionModel values() {
92 return new CollectionAndSequence(new SimpleSequence(((Map) object).values(), wrapper));
93 }
94 }
95 }