001: /*
002: * Copyright (c) 2002-2003 by OpenSymphony
003: * All rights reserved.
004: */
005: package com.opensymphony.webwork.views.xslt;
006:
007: import org.w3c.dom.*;
008: import org.apache.commons.logging.LogFactory;
009: import org.apache.commons.logging.Log;
010:
011: import java.util.Collection;
012: import java.util.Map;
013: import java.util.HashMap;
014:
015: import com.opensymphony.webwork.WebWorkException;
016:
017: /**
018: * AdapterFactory produces Node adapters for Java object types.
019: * Adapter classes are generally instantiated dynamically via a no-args constructor
020: * and populated with their context information via the AdapterNode interface.
021: *
022: * This factory supports proxying of generic DOM Node trees, allowing arbitrary
023: * Node types to be mixed together. You may simply return a Document or Node
024: * type as an object property and it will appear as a sub-tree in the XML as
025: * you'd expect. See #proxyNode().
026: *
027: * Customization of the result XML can be accomplished by providing
028: * alternate adapters for Java types. Adapters are associated with Java
029: * types through the registerAdapterType() method.
030: *
031: * For example, since there is no default Date adapter, Date objects will be
032: * rendered with the generic Bean introspecting adapter, producing output
033: * like:
034: * <pre>
035: <date>
036: <date>19</date>
037: <day>1</day>
038: <hours>0</hours>
039: <minutes>7</minutes>
040: <month>8</month>
041: <seconds>4</seconds>
042: <time>1127106424531</time>
043: <timezoneOffset>300</timezoneOffset>
044: <year>105</year>
045: </date>
046: * </pre>
047: *
048: * By extending the StringAdapter and overriding its normal behavior we can
049: * create a custom Date formatter:
050: *
051: * <pre>
052: public static class CustomDateAdapter extends StringAdapter {
053: protected String getStringValue() {
054: Date date = (Date)getPropertyValue();
055: return DateFormat.getTimeInstance( DateFormat.FULL ).format( date );
056: }
057: }
058: * </pre>
059: *
060: * Producing output like:
061: *
062: <pre>
063: <date>12:02:54 AM CDT</date>
064: </pre>
065: *
066: * The StringAdapter (which is normally invoked only to adapt String values)
067: * is a useful base for these kinds of customizations and can produce
068: * structured XML output as well as plain text by setting its parseStringAsXML()
069: * property to true.
070: *
071: * See provided examples.
072: *
073: * @author <a href="mailto:meier@meisterbohne.de">Philipp Meier</a>
074: * @author Pat Niemeyer (pat@pat.net)
075: */
076: public class AdapterFactory {
077: private Log log = LogFactory.getLog(this .getClass());
078: /** Map<Class, Class<AdapterNode>> */
079: private Map adapterTypes = new HashMap();
080:
081: /**
082: * Register an adapter type for a Java class type.
083: * @param type the Java class type which is to be handled by the adapter.
084: * @param adapterType The adapter class, which implements AdapterNode.
085: */
086: public void registerAdapterType(Class type, Class adapterType) {
087: adapterTypes.put(type, adapterType);
088: }
089:
090: /**
091: * Create a top level Document adapter for the specified Java object.
092: * The document will have a root element with the specified property name
093: * and contain the specified Java object content.
094: *
095: * @param propertyName The name of the root document element
096: * @return
097: * @throws IllegalAccessException
098: * @throws InstantiationException
099: */
100: public Document adaptDocument(String propertyName,
101: Object propertyValue) throws IllegalAccessException,
102: InstantiationException {
103: //if ( propertyValue instanceof Document )
104: // return (Document)propertyValue;
105:
106: return new SimpleAdapterDocument(this , null, propertyName,
107: propertyValue);
108: }
109:
110: /**
111: * Create an Node adapter for a child element.
112: * Note that the parent of the created node must be an AdapterNode, however
113: * the child node itself may be any type of Node.
114: *
115: * @see #adaptDocument( String, Object )
116: */
117: public Node adaptNode(AdapterNode parent, String propertyName,
118: Object value) {
119: Class adapterClass = getAdapterForValue(value);
120: if (adapterClass != null)
121: return constructAdapterInstance(adapterClass, parent,
122: propertyName, value);
123:
124: // If the property is a Document, "unwrap" it to the root element
125: if (value instanceof Document)
126: value = ((Document) value).getDocumentElement();
127:
128: // If the property is already a Node, proxy it
129: if (value instanceof Node)
130: return proxyNode(parent, (Node) value);
131:
132: // Check other supported types or default to generic JavaBean introspecting adapter
133: Class valueType = value.getClass();
134:
135: if (valueType.isArray())
136: adapterClass = ArrayAdapter.class;
137: else if (value instanceof String || value instanceof Number
138: || valueType.isPrimitive())
139: adapterClass = StringAdapter.class;
140: else if (value instanceof Collection)
141: adapterClass = CollectionAdapter.class;
142: else if (value instanceof Map)
143: adapterClass = MapAdapter.class;
144: else
145: adapterClass = BeanAdapter.class;
146:
147: return constructAdapterInstance(adapterClass, parent,
148: propertyName, value);
149: }
150:
151: /**
152: * Construct a proxy adapter for a value that is an existing DOM Node.
153: * This allows arbitrary DOM Node trees to be mixed in with our results.
154: * The proxied nodes are read-only and currently support only
155: * limited types of Nodes including Element, Text, and Attributes. (Other
156: * Node types may be ignored by the proxy and not appear in the result tree).
157: *
158: * // TODO:
159: * NameSpaces are not yet supported.
160: *
161: * This method is primarily for use by the adapter node classes.
162: */
163: public Node proxyNode(AdapterNode parent, Node node) {
164: // If the property is a Document, "unwrap" it to the root element
165: if (node instanceof Document)
166: node = ((Document) node).getDocumentElement();
167:
168: if (node == null)
169: return null;
170: if (node.getNodeType() == Node.ELEMENT_NODE)
171: return new ProxyElementAdapter(this , parent, (Element) node);
172: if (node.getNodeType() == Node.TEXT_NODE)
173: return new ProxyTextNodeAdapter(this , parent, (Text) node);
174: if (node.getNodeType() == Node.ATTRIBUTE_NODE)
175: return new ProxyAttrAdapter(this , parent, (Attr) node);
176:
177: return null; // Unsupported Node type - ignore for now
178: }
179:
180: public NamedNodeMap proxyNamedNodeMap(AdapterNode parent,
181: NamedNodeMap nnm) {
182: return new ProxyNamedNodeMap(this , parent, nnm);
183: }
184:
185: /**
186: * Create an instance of an adapter dynamically and set its context via
187: * the AdapterNode interface.
188: */
189: private Node constructAdapterInstance(Class adapterClass,
190: AdapterNode parent, String propertyName,
191: Object propertyValue) {
192: // Check to see if the class has a no-args constructor
193: try {
194: adapterClass.getConstructor(new Class[] {});
195: } catch (NoSuchMethodException e1) {
196: throw new WebWorkException("Adapter class: " + adapterClass
197: + " does not have a no-args consructor.");
198: }
199:
200: try {
201: AdapterNode adapterNode = (AdapterNode) adapterClass
202: .newInstance();
203: adapterNode.setAdapterFactory(this );
204: adapterNode.setParent(parent);
205: adapterNode.setPropertyName(propertyName);
206: adapterNode.setPropertyValue(propertyValue);
207:
208: return adapterNode;
209:
210: } catch (IllegalAccessException e) {
211: e.printStackTrace();
212: throw new WebWorkException("Cannot adapt " + propertyValue
213: + " (" + propertyName + ") :" + e.getMessage());
214: } catch (InstantiationException e) {
215: e.printStackTrace();
216: throw new WebWorkException("Cannot adapt " + propertyValue
217: + " (" + propertyName + ") :" + e.getMessage());
218: }
219: }
220:
221: /**
222: * Create an appropriate adapter for a null value.
223: * @param parent
224: * @param propertyName
225: */
226: public Node adaptNullValue(BeanAdapter parent, String propertyName) {
227: return new StringAdapter(this , parent, propertyName, "null");
228: }
229:
230: //TODO: implement Configuration option to provide additional adapter classes
231: public Class getAdapterForValue(Object value) {
232: return (Class) adapterTypes.get(value.getClass());
233: }
234: }
|