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