View Javadoc

    1   /*
    2    * $Id: AbstractRenderer.java 471754 2006-11-06 14:55:09Z husted $
    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
    22  package org.apache.struts.faces.renderer;
    23
    24
    25  import java.io.IOException;
    26  import java.util.Iterator;
    27  import java.util.Map;
    28
    29  import javax.faces.application.FacesMessage;
    30  import javax.faces.component.EditableValueHolder;
    31  import javax.faces.component.UIComponent;
    32  import javax.faces.component.ValueHolder;
    33  import javax.faces.context.FacesContext;
    34  import javax.faces.context.ResponseWriter;
    35  import javax.faces.convert.Converter;
    36  import javax.faces.convert.ConverterException;
    37  import javax.faces.el.ValueBinding;
    38  import javax.faces.render.Renderer;
    39
    40  import org.apache.commons.logging.Log;
    41  import org.apache.commons.logging.LogFactory;
    42
    43
    44  /**
    45   * <p>Abstract base class for concrete implementations of
    46   * <code>javax.faces.render.Renderer</code> for the
    47   * <em>Struts-Faces Integration Library</em>.</p>
    48   *
    49   * @version $Rev: 471754 $ $Date: 2006-11-06 08:55:09 -0600 (Mon, 06 Nov 2006) $
    50   */
    51
    52  public abstract class AbstractRenderer extends Renderer {
    53
    54
    55      // -------------------------------------------------------- Static Variables
    56
    57
    58      private static final Log log =
    59    LogFactory.getLog(AbstractRenderer.class);
    60
    61
    62      // -------------------------------------------------------- Renderer Methods
    63
    64
    65      /**
    66       * <p>Decode any new state of the specified <code>UIComponent</code>
    67       * from the request contained in the specified <code>FacesContext</code>,
    68       * and store that state on the <code>UIComponent</code>.</p>
    69       *
    70       * <p>The default implementation calls <code>setSubmittedValue()</code>
    71       * unless this component has a boolean <code>disabled</code> or
    72       * <code>readonly</code> attribute that is set to <code>true</code>.</p>
    73       *
    74       * @param context <code>FacesContext</code> for the current request
    75       * @param component <code>UIComponent</code> to be decoded
    76       *
    77       * @exception NullPointerException if <code>context</code> or
    78       *  <code>component</code> is <code>null</code>
    79       */
    80      public void decode(FacesContext context, UIComponent component) {
    81
    82          // Enforce NPE requirements in the Javadocs
    83          if ((context == null) || (component == null)) {
    84              throw new NullPointerException();
    85          }
    86
    87          // Disabled or readonly components are not decoded
    88          if (isDisabled(component) || isReadOnly(component)) {
    89              return;
    90          }
    91
    92          // Save submitted value on EditableValueHolder components
    93          if (component instanceof EditableValueHolder) {
    94              setSubmittedValue(context, component);
    95          }
    96
    97      }
    98
    99
    100     /**
    101      * <p>Render the beginning of the specified <code>UIComponent</code>
    102      * to the output stream or writer associated with the response we are
    103      * creating.</p>
    104      *
    105      * <p>The default implementation calls <code>renderStart()</code> and
    106      * <code>renderAttributes()</code>.</p>
    107      *
    108      * @param context <code>FacesContext</code> for the current request
    109      * @param component <code>UIComponent</code> to be decoded
    110      *
    111      * @exception NullPointerException if <code>context</code> or
    112      *  <code>component</code> is <code>null</code>
    113      *
    114      * @exception IOException if an input/output error occurs
    115      */
    116     public void encodeBegin(FacesContext context, UIComponent component)
    117         throws IOException {
    118
    119         // Enforce NPE requirements in the Javadocs
    120         if ((context == null) || (component == null)) {
    121             throw new NullPointerException();
    122         }
    123
    124         if (log.isTraceEnabled()) {
    125             log.trace("encodeBegin(id=" + component.getId() +
    126                 ", family=" + component.getFamily() +
    127                 ", rendererType=" + component.getRendererType() + ")");
    128         }
    129
    130         // Render the element and attributes for this component
    131         ResponseWriter writer = context.getResponseWriter();
    132         renderStart(context, component, writer);
    133         renderAttributes(context, component, writer);
    134
    135     }
    136
    137
    138     /**
    139      * <p>Render the children of the specified <code>UIComponent</code>
    140      * to the output stream or writer associated with the response we are
    141      * creating.</p>
    142      *
    143      * <p>The default implementation iterates through the children of
    144      * this component and renders them.</p>
    145      *
    146      * @param context <code>FacesContext</code> for the current request
    147      * @param component <code>UIComponent</code> to be decoded
    148      *
    149      * @exception NullPointerException if <code>context</code> or
    150      *  <code>component</code> is <code>null</code>
    151      *
    152      * @exception IOException if an input/output error occurs
    153      */
    154     public void encodeChildren(FacesContext context, UIComponent component)
    155         throws IOException {
    156
    157         if (context == null || component == null) {
    158             throw new NullPointerException();
    159         }
    160
    161         if (log.isTraceEnabled()) {
    162             log.trace("encodeChildren(id=" + component.getId() +
    163                     ", family=" + component.getFamily() +
    164                     ", rendererType=" + component.getRendererType() + ")");
    165         }
    166         Iterator kids = component.getChildren().iterator();
    167         while (kids.hasNext()) {
    168             UIComponent kid = (UIComponent) kids.next();
    169             kid.encodeBegin(context);
    170             if (kid.getRendersChildren()) {
    171                 kid.encodeChildren(context);
    172             }
    173             kid.encodeEnd(context);
    174         }
    175         if (log.isTraceEnabled()) {
    176             log.trace("encodeChildren(id=" + component.getId() + ") end");
    177         }
    178
    179     }
    180
    181
    182     /**
    183      * <p>Render the ending of the specified <code>UIComponent</code>
    184      * to the output stream or writer associated with the response we are
    185      * creating.</p>
    186      *
    187      * <p>The default implementation calls <code>renderEnd()</code>.</p>
    188      *
    189      * @param context <code>FacesContext</code> for the current request
    190      * @param component <code>UIComponent</code> to be decoded
    191      *
    192      * @exception NullPointerException if <code>context</code> or
    193      *  <code>component</code> is <code>null</code>
    194      *
    195      * @exception IOException if an input/output error occurs
    196      */
    197     public void encodeEnd(FacesContext context, UIComponent component)
    198         throws IOException {
    199
    200         // Enforce NPE requirements in the Javadocs
    201         if ((context == null) || (component == null)) {
    202             throw new NullPointerException();
    203         }
    204
    205         if (log.isTraceEnabled()) {
    206             log.trace("encodeEnd(id=" + component.getId() +
    207                     ", family=" + component.getFamily() +
    208                     ", rendererType=" + component.getRendererType() + ")");
    209         }
    210
    211         // Render the element closing for this component
    212         ResponseWriter writer = context.getResponseWriter();
    213         renderEnd(context, component, writer);
    214
    215     }
    216
    217
    218     // --------------------------------------------------------- Package Methods
    219
    220
    221     // ------------------------------------------------------- Protected Methods
    222
    223
    224     /**
    225      * <p>Render nested child components by invoking the encode methods
    226      * on those components, but only when the <code>rendered</code>
    227      * property is <code>true</code>.</p>
    228      */
    229     protected void encodeRecursive(FacesContext context, UIComponent component)
    230         throws IOException {
    231
    232         // suppress rendering if "rendered" property on the component is
    233         // false.
    234         if (!component.isRendered()) {
    235             return;
    236         }
    237
    238         // Render this component and its children recursively
    239         if (log.isTraceEnabled()) {
    240             log.trace("encodeRecursive(id=" + component.getId() +
    241                     ", family=" + component.getFamily() +
    242                     ", rendererType=" + component.getRendererType() +
    243                     ") encodeBegin");
    244         }
    245         component.encodeBegin(context);
    246         if (component.getRendersChildren()) {
    247             if (log.isTraceEnabled()) {
    248                 log.trace("encodeRecursive(id=" + component.getId() +
    249                         ") delegating");
    250             }
    251             component.encodeChildren(context);
    252         } else {
    253             if (log.isTraceEnabled()) {
    254                 log.trace("encodeRecursive(id=" + component.getId() +
    255                         ") recursing");
    256             }
    257             Iterator kids = component.getChildren().iterator();
    258             while (kids.hasNext()) {
    259                 UIComponent kid = (UIComponent) kids.next();
    260                 encodeRecursive(context, kid);
    261             }
    262         }
    263         if (log.isTraceEnabled()) {
    264             log.trace("encodeRecursive(id=" + component.getId() + ") encodeEnd");
    265         }
    266         component.encodeEnd(context);
    267
    268     }
    269
    270
    271     /**
    272      * <p>Return <code>true</code> if the specified component is disabled.</p>
    273      *
    274      * @param component <code>UIComponent</code> to be checked
    275      */
    276     protected boolean isDisabled(UIComponent component) {
    277
    278         Object disabled = component.getAttributes().get("disabled");
    279         if (disabled == null) {
    280             return (false);
    281         }
    282         if (disabled instanceof String) {
    283             return (Boolean.valueOf((String) disabled).booleanValue());
    284         } else {
    285             return (disabled.equals(Boolean.TRUE));
    286         }
    287
    288     }
    289
    290
    291     /**
    292      * <p>Return <code>true</code> if the specified component is read only.</p>
    293      *
    294      * @param component <code>UIComponent</code> to be checked
    295      */
    296     protected boolean isReadOnly(UIComponent component) {
    297
    298         Object readonly = component.getAttributes().get("readonly");
    299         if (readonly == null) {
    300             return (false);
    301         }
    302         if (readonly instanceof String) {
    303             return (Boolean.valueOf((String) readonly).booleanValue());
    304         } else {
    305             return (readonly.equals(Boolean.TRUE));
    306         }
    307
    308     }
    309
    310
    311     /**
    312      * <p>Render the element attributes for the generated markup related to this
    313      * component.  Simple renderers that create a single markup element
    314      * for this component should override this method and include calls to
    315      * to <code>writeAttribute()</code> and <code>writeURIAttribute</code>
    316      * on the specified <code>ResponseWriter</code>.</p>
    317      *
    318      * <p>The default implementation does nothing.</p>
    319      *
    320      * @param context <code>FacesContext</code> for the current request
    321      * @param component <code>EditableValueHolder</code> component whose
    322      *  submitted value is to be stored
    323      * @param writer <code>ResponseWriter</code> to which the element
    324      *  start should be rendered
    325      *
    326      * @exception IOException if an input/output error occurs
    327      */
    328     protected void renderAttributes(FacesContext context, UIComponent component,
    329                                     ResponseWriter writer) throws IOException {
    330
    331     }
    332
    333
    334     /**
    335      * <p>Render the element end for the generated markup related to this
    336      * component.  Simple renderers that create a single markup element
    337      * for this component should override this method and include a call
    338      * to <code>endElement()</code> on the specified
    339      * <code>ResponseWriter</code>.</p>
    340      *
    341      * <p>The default implementation does nothing.</p>
    342      *
    343      * @param context <code>FacesContext</code> for the current request
    344      * @param component <code>EditableValueHolder</code> component whose
    345      *  submitted value is to be stored
    346      * @param writer <code>ResponseWriter</code> to which the element
    347      *  start should be rendered
    348      *
    349      * @exception IOException if an input/output error occurs
    350      */
    351     protected void renderEnd(FacesContext context, UIComponent component,
    352                              ResponseWriter writer) throws IOException {
    353
    354     }
    355
    356
    357     /**
    358      * <p>Render any boolean attributes on the specified list that have
    359      * <code>true</code> values on the corresponding attribute of the
    360      * specified <code>UIComponent</code>.</p>
    361      *
    362      * @param context <code>FacesContext</code> for the current request
    363      * @param component <code>EditableValueHolder</code> component whose
    364      *  submitted value is to be stored
    365      * @param writer <code>ResponseWriter</code> to which the element
    366      *  start should be rendered
    367      * @param names List of attribute names to be passed through
    368      *
    369      * @exception IOException if an input/output error occurs
    370      */
    371     protected void renderBoolean(FacesContext context,
    372                                  UIComponent component,
    373                                  ResponseWriter writer,
    374                                  String names[]) throws IOException {
    375
    376         if (names == null) {
    377             return;
    378         }
    379         Map attributes = component.getAttributes();
    380         boolean flag;
    381         Object value;
    382         for (int i = 0; i < names.length; i++) {
    383             value = attributes.get(names[i]);
    384             if (value != null) {
    385                 if (value instanceof String) {
    386                     flag = Boolean.valueOf((String) value).booleanValue();
    387                 } else {
    388                     flag = Boolean.valueOf(value.toString()).booleanValue();
    389                 }
    390                 if (flag) {
    391                     writer.writeAttribute(names[i], names[i], names[i]);
    392                     flag = false;
    393                 }
    394             }
    395         }
    396
    397     }
    398
    399
    400     /**
    401      * <p>Render any attributes on the specified list directly to the
    402      * specified <code>ResponseWriter</code> for which the specified
    403      * <code>UIComponent</code> has a non-<code>null</code> attribute value.
    404      * This method may be used to "pass through" commonly used attribute
    405      * name/value pairs with a minimum of code.</p>
    406      *
    407      * @param context <code>FacesContext</code> for the current request
    408      * @param component <code>EditableValueHolder</code> component whose
    409      *  submitted value is to be stored
    410      * @param writer <code>ResponseWriter</code> to which the element
    411      *  start should be rendered
    412      * @param names List of attribute names to be passed through
    413      *
    414      * @exception IOException if an input/output error occurs
    415      */
    416     protected void renderPassThrough(FacesContext context,
    417                                      UIComponent component,
    418                                      ResponseWriter writer,
    419                                      String names[]) throws IOException {
    420
    421         if (names == null) {
    422             return;
    423         }
    424         Map attributes = component.getAttributes();
    425         Object value;
    426         for (int i = 0; i < names.length; i++) {
    427             value = attributes.get(names[i]);
    428             if (value != null) {
    429                 if (value instanceof String) {
    430                     writer.writeAttribute(names[i], value, names[i]);
    431                 } else {
    432                     writer.writeAttribute(names[i], value.toString(), names[i]);
    433                 }
    434             }
    435         }
    436
    437     }
    438
    439
    440     /**
    441      * <p>Render the element start for the generated markup related to this
    442      * component.  Simple renderers that create a single markup element
    443      * for this component should override this method and include a call
    444      * to <code>startElement()</code> on the specified
    445      * <code>ResponseWriter</code>.</p>
    446      *
    447      * <p>The default implementation does nothing.</p>
    448      *
    449      * @param context <code>FacesContext</code> for the current request
    450      * @param component <code>EditableValueHolder</code> component whose
    451      *  submitted value is to be stored
    452      * @param writer <code>ResponseWriter</code> to which the element
    453      *  start should be rendered
    454      *
    455      * @exception IOException if an input/output error occurs
    456      */
    457     protected void renderStart(FacesContext context, UIComponent component,
    458                                ResponseWriter writer) throws IOException {
    459
    460     }
    461
    462
    463     /**
    464      * <p>If a submitted value was included on this request, store it in the
    465      * component as appropriate.</p>
    466      *
    467      * <p>The default implementation determines whether this component
    468      * implements <code>EditableValueHolder</code>.  If so, it checks for a
    469      * request parameter with the same name as the <code>clientId</code>
    470      * of this <code>UIComponent</code>.  If there is such a parameter, its
    471      * value is passed (as a String) to the <code>setSubmittedValue()</code>
    472      * method on the <code>EditableValueHolder</code> component.</p>
    473      *
    474      * @param context <code>FacesContext</code> for the current request
    475      * @param component <code>EditableValueHolder</code> component whose
    476      *  submitted value is to be stored
    477      */
    478     protected void setSubmittedValue
    479         (FacesContext context, UIComponent component) {
    480
    481         if (!(component instanceof EditableValueHolder)) {
    482             return;
    483         }
    484         String clientId = component.getClientId(context);
    485         Map parameters = context.getExternalContext().getRequestParameterMap();
    486         if (parameters.containsKey(clientId)) {
    487             if (log.isTraceEnabled()) {
    488                 log.trace("setSubmittedValue(" + clientId + "," +
    489                           (String) parameters.get(clientId));
    490             }
    491             component.getAttributes().put("submittedValue",
    492                                           parameters.get(clientId));
    493         }
    494
    495     }
    496
    497
    498     // --------------------------------------------------------- Private Methods
    499
    500
    501     /**
    502      * <p>Decode the current state of the specified UIComponent from the
    503      * request contained in the specified <code>FacesContext</code>, and
    504      * attempt to convert this state information into an object of the
    505      * type equired for this component.</p>
    506      *
    507      * @param context FacesContext for the request we are processing
    508      * @param component UIComponent to be decoded
    509      *
    510      * @exception NullPointerException if context or component is null
    511      */
    512     /*
    513     public void decode(FacesContext context, UIComponent component) {
    514
    515         // Enforce NPE requirements in the Javadocs
    516         if ((context == null) || (component == null)) {
    517             throw new NullPointerException();
    518         }
    519
    520         // Only input components need to be decoded
    521         if (!(component instanceof UIInput)) {
    522             return;
    523         }
    524         UIInput input = (UIInput) component;
    525
    526         // Save the old value for use in generating ValueChangedEvents
    527         Object oldValue = input.getValue();
    528         if (oldValue instanceof String) {
    529             try {
    530                 oldValue = getAsObject(context, component, (String) oldValue);
    531             } catch (ConverterException e) {
    532                 ;
    533             }
    534         }
    535         input.setPrevious(oldValue);
    536
    537         // Decode and convert (if needed) the new value
    538         String clientId = component.getClientId(context);
    539         Map map = context.getExternalContext().getRequestParameterMap();
    540         String newString = (String) map.get(clientId);
    541         Object newValue = null;
    542         try {
    543             newValue = getAsObject(context, component, newString);
    544             input.setValue(newValue);
    545             input.setValid(true);
    546         } catch (ConverterException e) {
    547             input.setValue(newValue);
    548             input.setValid(false);
    549             addConverterMessage(context, component, e.getMessage());
    550         }
    551
    552     }
    553     */
    554
    555
    556     // --------------------------------------------------------- Package Methods
    557
    558
    559     // ------------------------------------------------------- Protected Methods
    560
    561
    562     /**
    563      * <p>Add an error message denoting a conversion failure.</p>
    564      *
    565      * @param context The <code>FacesContext</code> for this request
    566      * @param component The <code>UIComponent</code> that experienced
    567      *  the conversion failure
    568      * @param text The text of the error message
    569      */
    570     /*
    571     protected void addConverterMessage(FacesContext context,
    572                                        UIComponent component,
    573                                        String text) {
    574
    575         String clientId = component.getClientId(context);
    576         FacesMessage message = new FacesMessage
    577             (text,
    578              "Conversion error on component '" + clientId + "'");
    579         context.addMessage(clientId, message);
    580
    581     }
    582     */
    583
    584
    585     /**
    586      * <p>Convert the String representation of this component's value
    587      * to the corresponding Object representation.  The default
    588      * implementation utilizes the <code>getAsObject()</code> method of any
    589      * associated <code>Converter</code>.</p>
    590      *
    591      * @param context The <code>FacesContext</code> for this request
    592      * @param component The <code>UIComponent</code> whose value is
    593      *  being converted
    594      * @param value The String representation to be converted
    595      *
    596      * @exception ConverterException if conversion fails
    597      */
    598     /*
    599     protected Object getAsObject(FacesContext context, UIComponent component,
    600                                  String value) throws ConverterException {
    601
    602         // Identify any Converter associated with this component value
    603         ValueBinding vb = component.getValueBinding("value");
    604         Converter converter = null;
    605         if (component instanceof ValueHolder) {
    606             // Acquire explicitly assigned Converter (if any)
    607             converter = ((ValueHolder) component).getConverter();
    608         }
    609         if ((converter == null) && (vb != null)) {
    610             Class type = vb.getType(context);
    611             if ((type == null) || (type == String.class)) {
    612                 return (value); // No conversion required for Strings
    613             }
    614             // Acquire implicit by-type Converter (if any)
    615             converter = context.getApplication().createConverter(type);
    616         }
    617
    618         // Convert the result if we identified a Converter
    619         if (converter != null) {
    620             return (converter.getAsObject(context, component, value));
    621         } else {
    622             return (value);
    623         }
    624
    625     }
    626     */
    627
    628
    629     /**
    630      * <p>Convert the Object representation of this component's value
    631      * to the corresponding String representation.  The default implementation
    632      * utilizes the <code>getAsString()</code> method of any associated
    633      * <code>Converter</code>.</p>
    634      *
    635      * @param context The <code>FacesContext</code> for this request
    636      * @param component The <code>UIComponent</code> whose value is
    637      *  being converted
    638      * @param value The Object representation to be converted
    639      *
    640      * @exception ConverterException if conversion fails
    641      */
    642     protected String getAsString(FacesContext context, UIComponent component,
    643                                  Object value) throws ConverterException {
    644
    645         // Identify any Converter associated with this component value
    646         ValueBinding vb = component.getValueBinding("value");
    647         Converter converter = null;
    648         if (component instanceof ValueHolder) {
    649             // Acquire explicitly assigned Converter (if any)
    650             converter = ((ValueHolder) component).getConverter();
    651         }
    652         if ((converter == null) && (vb != null)) {
    653             // Acquire implicit by-type Converter (if any)
    654             Class type = vb.getType(context);
    655             if (type != null) {
    656                 converter = context.getApplication().createConverter(type);
    657             }
    658         }
    659
    660         // Convert the result if we identified a Converter
    661         if (converter != null) {
    662             return (converter.getAsString(context, component, value));
    663         } else if (value == null) {
    664             return ("");
    665         } else if (value instanceof String) {
    666             return ((String) value);
    667         } else {
    668             return (value.toString());
    669         }
    670
    671     }
    672
    673
    674 }