1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.struts2.views.xslt;
23
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.Map;
27
28 import org.apache.struts2.StrutsException;
29 import org.w3c.dom.Attr;
30 import org.w3c.dom.Document;
31 import org.w3c.dom.Element;
32 import org.w3c.dom.NamedNodeMap;
33 import org.w3c.dom.Node;
34 import org.w3c.dom.Text;
35
36 /***
37 * AdapterFactory produces Node adapters for Java object types.
38 * Adapter classes are generally instantiated dynamically via a no-args constructor
39 * and populated with their context information via the AdapterNode interface.
40 *
41 * This factory supports proxying of generic DOM Node trees, allowing arbitrary
42 * Node types to be mixed together. You may simply return a Document or Node
43 * type as an object property and it will appear as a sub-tree in the XML as
44 * you'd expect. See #proxyNode().
45 *
46 * Customization of the result XML can be accomplished by providing
47 * alternate adapters for Java types. Adapters are associated with Java
48 * types through the registerAdapterType() method.
49 *
50 * For example, since there is no default Date adapter, Date objects will be
51 * rendered with the generic Bean introspecting adapter, producing output
52 * like:
53 * <pre>
54 <date>
55 <date>19</date>
56 <day>1</day>
57 <hours>0</hours>
58 <minutes>7</minutes>
59 <month>8</month>
60 <seconds>4</seconds>
61 <time>1127106424531</time>
62 <timezoneOffset>300</timezoneOffset>
63 <year>105</year>
64 </date>
65 * </pre>
66 *
67 * By extending the StringAdapter and overriding its normal behavior we can
68 * create a custom Date formatter:
69 *
70 * <pre>
71 public static class CustomDateAdapter extends StringAdapter {
72 protected String getStringValue() {
73 Date date = (Date)getPropertyValue();
74 return DateFormat.getTimeInstance( DateFormat.FULL ).format( date );
75 }
76 }
77 * </pre>
78 *
79 * Producing output like:
80 *
81 <pre>
82 <date>12:02:54 AM CDT</date>
83 </pre>
84 *
85 * The StringAdapter (which is normally invoked only to adapt String values)
86 * is a useful base for these kinds of customizations and can produce
87 * structured XML output as well as plain text by setting its parseStringAsXML()
88 * property to true.
89 *
90 * See provided examples.
91 */
92 public class AdapterFactory {
93
94 private Map<Class, Class> adapterTypes = new HashMap<Class, Class>();
95
96 /***
97 * Register an adapter type for a Java class type.
98 *
99 * @param type the Java class type which is to be handled by the adapter.
100 * @param adapterType The adapter class, which implements AdapterNode.
101 */
102 public void registerAdapterType(Class type, Class adapterType) {
103 adapterTypes.put(type, adapterType);
104 }
105
106 /***
107 * Create a top level Document adapter for the specified Java object.
108 * The document will have a root element with the specified property name
109 * and contain the specified Java object content.
110 *
111 * @param propertyName The name of the root document element
112 * @return
113 * @throws IllegalAccessException
114 * @throws InstantiationException
115 */
116 public Document adaptDocument(String propertyName, Object propertyValue)
117 throws IllegalAccessException, InstantiationException {
118
119
120
121 return new SimpleAdapterDocument(this, null, propertyName, propertyValue);
122 }
123
124
125 /***
126 * Create an Node adapter for a child element.
127 * Note that the parent of the created node must be an AdapterNode, however
128 * the child node itself may be any type of Node.
129 *
130 * @see #adaptDocument( String, Object )
131 */
132 public Node adaptNode(AdapterNode parent, String propertyName, Object value) {
133 Class adapterClass = getAdapterForValue(value);
134 if (adapterClass != null)
135 return constructAdapterInstance(adapterClass, parent, propertyName, value);
136
137
138 if (value instanceof Document)
139 value = ((Document) value).getDocumentElement();
140
141
142 if (value instanceof Node)
143 return proxyNode(parent, (Node) value);
144
145
146 Class valueType = value.getClass();
147
148 if (valueType.isArray())
149 adapterClass = ArrayAdapter.class;
150 else if (value instanceof String || value instanceof Number || value instanceof Boolean || valueType.isPrimitive())
151 adapterClass = StringAdapter.class;
152 else if (value instanceof Collection)
153 adapterClass = CollectionAdapter.class;
154 else if (value instanceof Map)
155 adapterClass = MapAdapter.class;
156 else
157 adapterClass = BeanAdapter.class;
158
159 return constructAdapterInstance(adapterClass, parent, propertyName, value);
160 }
161
162 /***
163 * Construct a proxy adapter for a value that is an existing DOM Node.
164 * This allows arbitrary DOM Node trees to be mixed in with our results.
165 * The proxied nodes are read-only and currently support only
166 * limited types of Nodes including Element, Text, and Attributes. (Other
167 * Node types may be ignored by the proxy and not appear in the result tree).
168 * <p/>
169 * // TODO:
170 * NameSpaces are not yet supported.
171 * <p/>
172 * This method is primarily for use by the adapter node classes.
173 */
174 public Node proxyNode(AdapterNode parent, Node node) {
175
176 if (node instanceof Document)
177 node = ((Document) node).getDocumentElement();
178
179 if (node == null)
180 return null;
181 if (node.getNodeType() == Node.ELEMENT_NODE)
182 return new ProxyElementAdapter(this, parent, (Element) node);
183 if (node.getNodeType() == Node.TEXT_NODE)
184 return new ProxyTextNodeAdapter(this, parent, (Text) node);
185 if (node.getNodeType() == Node.ATTRIBUTE_NODE)
186 return new ProxyAttrAdapter(this, parent, (Attr) node);
187
188 return null;
189 }
190
191 public NamedNodeMap proxyNamedNodeMap(AdapterNode parent, NamedNodeMap nnm) {
192 return new ProxyNamedNodeMap(this, parent, nnm);
193 }
194
195 /***
196 * Create an instance of an adapter dynamically and set its context via
197 * the AdapterNode interface.
198 */
199 private Node constructAdapterInstance(Class adapterClass, AdapterNode parent, String propertyName, Object propertyValue) {
200
201 try {
202 adapterClass.getConstructor(new Class []{});
203 } catch (NoSuchMethodException e1) {
204 throw new StrutsException("Adapter class: " + adapterClass
205 + " does not have a no-args consructor.");
206 }
207
208 try {
209 AdapterNode adapterNode = (AdapterNode) adapterClass.newInstance();
210 adapterNode.setAdapterFactory(this);
211 adapterNode.setParent(parent);
212 adapterNode.setPropertyName(propertyName);
213 adapterNode.setPropertyValue(propertyValue);
214
215 return adapterNode;
216
217 } catch (IllegalAccessException e) {
218 e.printStackTrace();
219 throw new StrutsException("Cannot adapt " + propertyValue + " (" + propertyName + ") :" + e.getMessage());
220 } catch (InstantiationException e) {
221 e.printStackTrace();
222 throw new StrutsException("Cannot adapt " + propertyValue + " (" + propertyName + ") :" + e.getMessage());
223 }
224 }
225
226 /***
227 * Create an appropriate adapter for a null value.
228 *
229 * @param parent
230 * @param propertyName
231 */
232 public Node adaptNullValue(BeanAdapter parent, String propertyName) {
233 return new StringAdapter(this, parent, propertyName, "null");
234 }
235
236
237 public Class getAdapterForValue(Object value) {
238 return adapterTypes.get(value.getClass());
239 }
240 }