View Javadoc

    1   /*
    2    * $Id: AdapterFactory.java 491622 2007-01-01 20:22:54Z mrdon $
    3    *
    4    * Licensed to the Apache Software Foundation (ASF) under one
    5    * or more contributor license agreements.  See the NOTICE file
    6    * distributed with this work for additional information
    7    * regarding copyright ownership.  The ASF licenses this file
    8    * to you under the Apache License, Version 2.0 (the
    9    * "License"); you may not use this file except in compliance
    10   * with the License.  You may obtain a copy of the License at
    11   *
    12   *  http://www.apache.org/licenses/LICENSE-2.0
    13   *
    14   * Unless required by applicable law or agreed to in writing,
    15   * software distributed under the License is distributed on an
    16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    17   * KIND, either express or implied.  See the License for the
    18   * specific language governing permissions and limitations
    19   * under the License.
    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         //if ( propertyValue instanceof Document )
    118         //  return (Document)propertyValue;
    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         // If the property is a Document, "unwrap" it to the root element
    137         if (value instanceof Document)
    138             value = ((Document) value).getDocumentElement();
    139
    140         // If the property is already a Node, proxy it
    141         if (value instanceof Node)
    142             return proxyNode(parent, (Node) value);
    143
    144         // Check other supported types or default to generic JavaBean introspecting adapter
    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         // If the property is a Document, "unwrap" it to the root element
    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; // Unsupported Node type - ignore for now
    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         // Check to see if the class has a no-args constructor
    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     //TODO: implement Configuration option to provide additional adapter classes
    236     public Class getAdapterForValue(Object value) {
    237         return adapterTypes.get(value.getClass());
    238     }
    239 }