2
* Copyright 2002,2004 The Apache Software Foundation.
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
8
* http://www.apache.org/licenses/LICENSE-2.0
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.
17
package org.apache.struts.faces.renderer;
20
import java.io.IOException;
21
import java.util.Iterator;
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;
35
import org.apache.commons.logging.Log;
36
import org.apache.commons.logging.LogFactory;
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>
44
* @version $Revision: 1.8 $ $Date: 2004/06/09 02:28:28 $
47
public abstract class AbstractRenderer extends Renderer {
50
// -------------------------------------------------------- Static Variables
53
private static final Log log =
54
LogFactory.getLog(AbstractRenderer.class);
57
// -------------------------------------------------------- Renderer Methods
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>
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>
69
* @param context <code>FacesContext</code> for the current request
70
* @param component <code>UIComponent</code> to be decoded
72
* @exception NullPointerException if <code>context</code> or
73
* <code>component</code> is <code>null</code>
75
public void decode(FacesContext context, UIComponent component) {
77
// Enforce NPE requirements in the Javadocs
78
if ((context == null) || (component == null)) {
79
throw new NullPointerException();
82
// Disabled or readonly components are not decoded
83
if (isDisabled(component) || isReadOnly(component)) {
87
// Save submitted value on EditableValueHolder components
88
if (component instanceof EditableValueHolder) {
89
setSubmittedValue(context, component);
96
* <p>Render the beginning of the specified <code>UIComponent</code>
97
* to the output stream or writer associated with the response we are
100
* <p>The default implementation calls <code>renderStart()</code> and
101
* <code>renderAttributes()</code>.</p>
103
* @param context <code>FacesContext</code> for the current request
104
* @param component <code>UIComponent</code> to be decoded
106
* @exception NullPointerException if <code>context</code> or
107
* <code>component</code> is <code>null</code>
109
* @exception IOException if an input/output error occurs
111
public void encodeBegin(FacesContext context, UIComponent component)
114
// Enforce NPE requirements in the Javadocs
115
if ((context == null) || (component == null)) {
116
throw new NullPointerException();
119
if (log.isTraceEnabled()) {
120
log.trace("encodeBegin(id=" + component.getId() +
121
", family=" + component.getFamily() +
122
", rendererType=" + component.getRendererType() + ")");
125
// Render the element and attributes for this component
126
ResponseWriter writer = context.getResponseWriter();
127
renderStart(context, component, writer);
128
renderAttributes(context, component, writer);
134
* <p>Render the children of the specified <code>UIComponent</code>
135
* to the output stream or writer associated with the response we are
138
* <p>The default implementation iterates through the children of
139
* this component and renders them.</p>
141
* @param context <code>FacesContext</code> for the current request
142
* @param component <code>UIComponent</code> to be decoded
144
* @exception NullPointerException if <code>context</code> or
145
* <code>component</code> is <code>null</code>
147
* @exception IOException if an input/output error occurs
149
public void encodeChildren(FacesContext context, UIComponent component)
152
if (context == null || component == null) {
153
throw new NullPointerException();
156
if (log.isTraceEnabled()) {
157
log.trace("encodeChildren(id=" + component.getId() +
158
", family=" + component.getFamily() +
159
", rendererType=" + component.getRendererType() + ")");
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);
168
kid.encodeEnd(context);
170
if (log.isTraceEnabled()) {
171
log.trace("encodeChildren(id=" + component.getId() + ") end");
178
* <p>Render the ending of the specified <code>UIComponent</code>
179
* to the output stream or writer associated with the response we are
182
* <p>The default implementation calls <code>renderEnd()</code>.</p>
184
* @param context <code>FacesContext</code> for the current request
185
* @param component <code>UIComponent</code> to be decoded
187
* @exception NullPointerException if <code>context</code> or
188
* <code>component</code> is <code>null</code>
190
* @exception IOException if an input/output error occurs
192
public void encodeEnd(FacesContext context, UIComponent component)
195
// Enforce NPE requirements in the Javadocs
196
if ((context == null) || (component == null)) {
197
throw new NullPointerException();
200
if (log.isTraceEnabled()) {
201
log.trace("encodeEnd(id=" + component.getId() +
202
", family=" + component.getFamily() +
203
", rendererType=" + component.getRendererType() + ")");
206
// Render the element closing for this component
207
ResponseWriter writer = context.getResponseWriter();
208
renderEnd(context, component, writer);
213
// --------------------------------------------------------- Package Methods
216
// ------------------------------------------------------- Protected Methods
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>
224
protected void encodeRecursive(FacesContext context, UIComponent component)
227
// suppress rendering if "rendered" property on the component is
229
if (!component.isRendered()) {
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() +
240
component.encodeBegin(context);
241
if (component.getRendersChildren()) {
242
if (log.isTraceEnabled()) {
243
log.trace("encodeRecursive(id=" + component.getId() +
246
component.encodeChildren(context);
248
if (log.isTraceEnabled()) {
249
log.trace("encodeRecursive(id=" + component.getId() +
252
Iterator kids = component.getChildren().iterator();
253
while (kids.hasNext()) {
254
UIComponent kid = (UIComponent) kids.next();
255
encodeRecursive(context, kid);
258
if (log.isTraceEnabled()) {
259
log.trace("encodeRecursive(id=" + component.getId() + ") encodeEnd");
261
component.encodeEnd(context);
267
* <p>Return <code>true</code> if the specified component is disabled.</p>
269
* @param component <code>UIComponent</code> to be checked
271
protected boolean isDisabled(UIComponent component) {
273
Object disabled = component.getAttributes().get("disabled");
274
if (disabled == null) {
277
if (disabled instanceof String) {
278
return (Boolean.valueOf((String) disabled).booleanValue());
280
return (disabled.equals(Boolean.TRUE));
287
* <p>Return <code>true</code> if the specified component is read only.</p>
289
* @param component <code>UIComponent</code> to be checked
291
protected boolean isReadOnly(UIComponent component) {
293
Object readonly = component.getAttributes().get("readonly");
294
if (readonly == null) {
297
if (readonly instanceof String) {
298
return (Boolean.valueOf((String) readonly).booleanValue());
300
return (readonly.equals(Boolean.TRUE));
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>
313
* <p>The default implementation does nothing.</p>
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
321
* @exception IOException if an input/output error occurs
323
protected void renderAttributes(FacesContext context, UIComponent component,
324
ResponseWriter writer) throws IOException {
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>
336
* <p>The default implementation does nothing.</p>
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
344
* @exception IOException if an input/output error occurs
346
protected void renderEnd(FacesContext context, UIComponent component,
347
ResponseWriter writer) throws IOException {
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>
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
364
* @exception IOException if an input/output error occurs
366
protected void renderBoolean(FacesContext context,
367
UIComponent component,
368
ResponseWriter writer,
369
String names[]) throws IOException {
374
Map attributes = component.getAttributes();
377
for (int i = 0; i < names.length; i++) {
378
value = attributes.get(names[i]);
380
if (value instanceof String) {
381
flag = Boolean.valueOf((String) value).booleanValue();
383
flag = Boolean.valueOf(value.toString()).booleanValue();
386
writer.writeAttribute(names[i], names[i], names[i]);
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>
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
409
* @exception IOException if an input/output error occurs
411
protected void renderPassThrough(FacesContext context,
412
UIComponent component,
413
ResponseWriter writer,
414
String names[]) throws IOException {
419
Map attributes = component.getAttributes();
421
for (int i = 0; i < names.length; i++) {
422
value = attributes.get(names[i]);
424
if (value instanceof String) {
425
writer.writeAttribute(names[i], (String) value, names[i]);
427
writer.writeAttribute(names[i], value.toString(), names[i]);
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>
442
* <p>The default implementation does nothing.</p>
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
450
* @exception IOException if an input/output error occurs
452
protected void renderStart(FacesContext context, UIComponent component,
453
ResponseWriter writer) throws IOException {
459
* <p>If a submitted value was included on this request, store it in the
460
* component as appropriate.</p>
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>
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
473
protected void setSubmittedValue
474
(FacesContext context, UIComponent component) {
476
if (!(component instanceof EditableValueHolder)) {
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));
486
component.getAttributes().put("submittedValue",
487
(String) parameters.get(clientId));
493
// --------------------------------------------------------- Private Methods
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>
502
* @param context FacesContext for the request we are processing
503
* @param component UIComponent to be decoded
505
* @exception NullPointerException if context or component is null
508
public void decode(FacesContext context, UIComponent component) {
510
// Enforce NPE requirements in the Javadocs
511
if ((context == null) || (component == null)) {
512
throw new NullPointerException();
515
// Only input components need to be decoded
516
if (!(component instanceof UIInput)) {
519
UIInput input = (UIInput) component;
521
// Save the old value for use in generating ValueChangedEvents
522
Object oldValue = input.getValue();
523
if (oldValue instanceof String) {
525
oldValue = getAsObject(context, component, (String) oldValue);
526
} catch (ConverterException e) {
530
input.setPrevious(oldValue);
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;
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());
551
// --------------------------------------------------------- Package Methods
554
// ------------------------------------------------------- Protected Methods
558
* <p>Add an error message denoting a conversion failure.</p>
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
566
protected void addConverterMessage(FacesContext context,
567
UIComponent component,
570
String clientId = component.getClientId(context);
571
FacesMessage message = new FacesMessage
573
"Conversion error on component '" + clientId + "'");
574
context.addMessage(clientId, message);
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>
586
* @param context The <code>FacesContext</code> for this request
587
* @param component The <code>UIComponent</code> whose value is
589
* @param value The String representation to be converted
591
* @exception ConverterException if conversion fails
594
protected Object getAsObject(FacesContext context, UIComponent component,
595
String value) throws ConverterException {
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();
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
609
// Acquire implicit by-type Converter (if any)
610
converter = context.getApplication().createConverter(type);
613
// Convert the result if we identified a Converter
614
if (converter != null) {
615
return (converter.getAsObject(context, component, value));
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>
630
* @param context The <code>FacesContext</code> for this request
631
* @param component The <code>UIComponent</code> whose value is
633
* @param value The Object representation to be converted
635
* @exception ConverterException if conversion fails
637
protected String getAsString(FacesContext context, UIComponent component,
638
Object value) throws ConverterException {
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();
647
if ((converter == null) && (vb != null)) {
648
// Acquire implicit by-type Converter (if any)
649
Class type = vb.getType(context);
651
converter = context.getApplication().createConverter(type);
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) {
660
} else if (value instanceof String) {
661
return ((String) value);
663
return (value.toString());