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 }