001: /*
002: * $Id: AdapterFactory.java 491622 2007-01-01 20:22:54Z mrdon $
003: *
004: * Licensed to the Apache Software Foundation (ASF) under one
005: * or more contributor license agreements. See the NOTICE file
006: * distributed with this work for additional information
007: * regarding copyright ownership. The ASF licenses this file
008: * to you under the Apache License, Version 2.0 (the
009: * "License"); you may not use this file except in compliance
010: * with the License. You may obtain a copy of the License at
011: *
012: * http://www.apache.org/licenses/LICENSE-2.0
013: *
014: * Unless required by applicable law or agreed to in writing,
015: * software distributed under the License is distributed on an
016: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
017: * KIND, either express or implied. See the License for the
018: * specific language governing permissions and limitations
019: * under the License.
020: */
021: package org.apache.struts2.views.xslt;
022:
023: import java.util.Collection;
024: import java.util.HashMap;
025: import java.util.Map;
026:
027: import org.apache.struts2.StrutsException;
028: import org.w3c.dom.Attr;
029: import org.w3c.dom.Document;
030: import org.w3c.dom.Element;
031: import org.w3c.dom.NamedNodeMap;
032: import org.w3c.dom.Node;
033: import org.w3c.dom.Text;
034:
035: /**
036: * AdapterFactory produces Node adapters for Java object types.
037: * Adapter classes are generally instantiated dynamically via a no-args constructor
038: * and populated with their context information via the AdapterNode interface.
039: *
040: * This factory supports proxying of generic DOM Node trees, allowing arbitrary
041: * Node types to be mixed together. You may simply return a Document or Node
042: * type as an object property and it will appear as a sub-tree in the XML as
043: * you'd expect. See #proxyNode().
044: *
045: * Customization of the result XML can be accomplished by providing
046: * alternate adapters for Java types. Adapters are associated with Java
047: * types through the registerAdapterType() method.
048: *
049: * For example, since there is no default Date adapter, Date objects will be
050: * rendered with the generic Bean introspecting adapter, producing output
051: * like:
052: * <pre>
053: <date>
054: <date>19</date>
055: <day>1</day>
056: <hours>0</hours>
057: <minutes>7</minutes>
058: <month>8</month>
059: <seconds>4</seconds>
060: <time>1127106424531</time>
061: <timezoneOffset>300</timezoneOffset>
062: <year>105</year>
063: </date>
064: * </pre>
065: *
066: * By extending the StringAdapter and overriding its normal behavior we can
067: * create a custom Date formatter:
068: *
069: * <pre>
070: public static class CustomDateAdapter extends StringAdapter {
071: protected String getStringValue() {
072: Date date = (Date)getPropertyValue();
073: return DateFormat.getTimeInstance( DateFormat.FULL ).format( date );
074: }
075: }
076: * </pre>
077: *
078: * Producing output like:
079: *
080: <pre>
081: <date>12:02:54 AM CDT</date>
082: </pre>
083: *
084: * The StringAdapter (which is normally invoked only to adapt String values)
085: * is a useful base for these kinds of customizations and can produce
086: * structured XML output as well as plain text by setting its parseStringAsXML()
087: * property to true.
088: *
089: * See provided examples.
090: */
091: public class AdapterFactory {
092:
093: private Map<Class, Class> adapterTypes = new HashMap<Class, Class>();
094:
095: /**
096: * Register an adapter type for a Java class type.
097: *
098: * @param type the Java class type which is to be handled by the adapter.
099: * @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,
116: Object propertyValue) throws IllegalAccessException,
117: InstantiationException {
118: //if ( propertyValue instanceof Document )
119: // return (Document)propertyValue;
120:
121: return new SimpleAdapterDocument(this , null, propertyName,
122: propertyValue);
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,
133: Object value) {
134: Class adapterClass = getAdapterForValue(value);
135: if (adapterClass != null)
136: return constructAdapterInstance(adapterClass, parent,
137: propertyName, value);
138:
139: // If the property is a Document, "unwrap" it to the root element
140: if (value instanceof Document)
141: value = ((Document) value).getDocumentElement();
142:
143: // If the property is already a Node, proxy it
144: if (value instanceof Node)
145: return proxyNode(parent, (Node) value);
146:
147: // Check other supported types or default to generic JavaBean introspecting adapter
148: Class valueType = value.getClass();
149:
150: if (valueType.isArray())
151: adapterClass = ArrayAdapter.class;
152: else if (value instanceof String || value instanceof Number
153: || valueType.isPrimitive())
154: adapterClass = StringAdapter.class;
155: else if (value instanceof Collection)
156: adapterClass = CollectionAdapter.class;
157: else if (value instanceof Map)
158: adapterClass = MapAdapter.class;
159: else
160: adapterClass = BeanAdapter.class;
161:
162: return constructAdapterInstance(adapterClass, parent,
163: propertyName, value);
164: }
165:
166: /**
167: * Construct a proxy adapter for a value that is an existing DOM Node.
168: * This allows arbitrary DOM Node trees to be mixed in with our results.
169: * The proxied nodes are read-only and currently support only
170: * limited types of Nodes including Element, Text, and Attributes. (Other
171: * Node types may be ignored by the proxy and not appear in the result tree).
172: * <p/>
173: * // TODO:
174: * NameSpaces are not yet supported.
175: * <p/>
176: * This method is primarily for use by the adapter node classes.
177: */
178: public Node proxyNode(AdapterNode parent, Node node) {
179: // If the property is a Document, "unwrap" it to the root element
180: if (node instanceof Document)
181: node = ((Document) node).getDocumentElement();
182:
183: if (node == null)
184: return null;
185: if (node.getNodeType() == Node.ELEMENT_NODE)
186: return new ProxyElementAdapter(this , parent, (Element) node);
187: if (node.getNodeType() == Node.TEXT_NODE)
188: return new ProxyTextNodeAdapter(this , parent, (Text) node);
189: if (node.getNodeType() == Node.ATTRIBUTE_NODE)
190: return new ProxyAttrAdapter(this , parent, (Attr) node);
191:
192: return null; // Unsupported Node type - ignore for now
193: }
194:
195: public NamedNodeMap proxyNamedNodeMap(AdapterNode parent,
196: NamedNodeMap nnm) {
197: return new ProxyNamedNodeMap(this , parent, nnm);
198: }
199:
200: /**
201: * Create an instance of an adapter dynamically and set its context via
202: * the AdapterNode interface.
203: */
204: private Node constructAdapterInstance(Class adapterClass,
205: AdapterNode parent, String propertyName,
206: Object propertyValue) {
207: // Check to see if the class has a no-args constructor
208: try {
209: adapterClass.getConstructor(new Class[] {});
210: } catch (NoSuchMethodException e1) {
211: throw new StrutsException("Adapter class: " + adapterClass
212: + " does not have a no-args consructor.");
213: }
214:
215: try {
216: AdapterNode adapterNode = (AdapterNode) adapterClass
217: .newInstance();
218: adapterNode.setAdapterFactory(this );
219: adapterNode.setParent(parent);
220: adapterNode.setPropertyName(propertyName);
221: adapterNode.setPropertyValue(propertyValue);
222:
223: return adapterNode;
224:
225: } catch (IllegalAccessException e) {
226: e.printStackTrace();
227: throw new StrutsException("Cannot adapt " + propertyValue
228: + " (" + propertyName + ") :" + e.getMessage());
229: } catch (InstantiationException e) {
230: e.printStackTrace();
231: throw new StrutsException("Cannot adapt " + propertyValue
232: + " (" + propertyName + ") :" + e.getMessage());
233: }
234: }
235:
236: /**
237: * Create an appropriate adapter for a null value.
238: *
239: * @param parent
240: * @param propertyName
241: */
242: public Node adaptNullValue(BeanAdapter parent, String propertyName) {
243: return new StringAdapter(this , parent, propertyName, "null");
244: }
245:
246: //TODO: implement Configuration option to provide additional adapter classes
247: public Class getAdapterForValue(Object value) {
248: return adapterTypes.get(value.getClass());
249: }
250: }
|