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 }