View Javadoc

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