1 /*
2 * $Id: HtmlBasicRenderer.java,v 1.121.4.3 2007/11/29 00:35:49 youngm Exp $
3 */
4
5 /*
6 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
7 *
8 * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
9 *
10 * The contents of this file are subject to the terms of either the GNU
11 * General Public License Version 2 only ("GPL") or the Common Development
12 * and Distribution License("CDDL") (collectively, the "License"). You
13 * may not use this file except in compliance with the License. You can obtain
14 * a copy of the License at https://glassfish.dev.java.net/public/CDDL+GPL.html
15 * or glassfish/bootstrap/legal/LICENSE.txt. See the License for the specific
16 * language governing permissions and limitations under the License.
17 *
18 * When distributing the software, include this License Header Notice in each
19 * file and include the License file at glassfish/bootstrap/legal/LICENSE.txt.
20 * Sun designates this particular file as subject to the "Classpath" exception
21 * as provided by Sun in the GPL Version 2 section of the License file that
22 * accompanied this code. If applicable, add the following below the License
23 * Header, with the fields enclosed by brackets [] replaced by your own
24 * identifying information: "Portions Copyrighted [year]
25 * [name of copyright owner]"
26 *
27 * Contributor(s):
28 *
29 * If you wish your version of this file to be governed by only the CDDL or
30 * only the GPL Version 2, indicate your decision by adding "[Contributor]
31 * elects to include this software in this distribution under the [CDDL or GPL
32 * Version 2] license." If you don't indicate a single choice of license, a
33 * recipient has the option to distribute your version of this file under
34 * either the CDDL, the GPL Version 2 or to extend the choice of license to
35 * its licensees as provided above. However, if you add GPL Version 2 code
36 * and therefore, elected the GPL Version 2 license, then the option applies
37 * only if the new code is made subject to such option by the copyright
38 * holder.
39 */
40
41 // HtmlBasicRenderer.java
42
43 package com.sun.faces.renderkit.html_basic;
44
45 import java.io.IOException;
46 import java.util.ArrayList;
47 import java.util.Collections;
48 import java.util.Iterator;
49 import java.util.List;
50 import java.util.Map;
51 import java.util.logging.Level;
52 import java.util.logging.Logger;
53
54 import javax.faces.component.NamingContainer;
55 import javax.faces.component.UIComponent;
56 import javax.faces.component.UIInput;
57 import javax.faces.component.UIParameter;
58 import javax.faces.component.UIViewRoot;
59 import javax.faces.component.ValueHolder;
60 import javax.faces.context.FacesContext;
61 import javax.faces.context.ResponseWriter;
62 import javax.faces.convert.Converter;
63 import javax.faces.convert.ConverterException;
64 import javax.faces.render.Renderer;
65
66 import com.sun.faces.util.FacesLogger;
67 import com.sun.faces.util.MessageUtils;
68 import com.sun.faces.util.Util;
69
70 /**
71 * <B>HtmlBasicRenderer</B> is a base class for implementing renderers
72 * for HtmlBasicRenderKit.
73 */
74
75 public abstract class HtmlBasicRenderer extends Renderer {
76
77
78 // Log instance for this class
79 protected static final Logger logger = FacesLogger.RENDERKIT.getLogger();
80
81 private static final Param[] EMPTY_PARAMS = new Param[0];
82
83 // ------------------------------------------------------------ Constructors
84
85
86 public HtmlBasicRenderer() {
87
88 super();
89
90 }
91
92 // ---------------------------------------------------------- Public Methods
93
94
95 @Override
96 public String convertClientId(FacesContext context, String clientId) {
97
98 return clientId;
99
100 }
101
102
103 @Override
104 public void decode(FacesContext context, UIComponent component) {
105
106 rendererParamsNotNull(context, component);
107
108 if (!(component instanceof UIInput)) {
109 // decode needs to be invoked only for components that are
110 // instances or subclasses of UIInput.
111 if (logger.isLoggable(Level.FINE)) {
112 logger.log(Level.FINE,
113 "No decoding necessary since the component {0} is not an instance or a sub class of UIInput",
114 component.getId());
115 }
116 return;
117 }
118
119 if (!shouldDecode(component)) {
120 return;
121 }
122
123 String clientId = component.getClientId(context);
124 assert(clientId != null);
125 Map<String, String> requestMap =
126 context.getExternalContext().getRequestParameterMap();
127 // Don't overwrite the value unless you have to!
128 String newValue = requestMap.get(clientId);
129 if (newValue != null) {
130 setSubmittedValue(component, newValue);
131 if (logger.isLoggable(Level.FINE)) {
132 logger.log(Level.FINE,
133 "new value after decoding {0}",
134 newValue);
135 }
136 }
137
138 }
139
140
141 @Override
142 public void encodeEnd(FacesContext context, UIComponent component)
143 throws IOException {
144
145 rendererParamsNotNull(context, component);
146
147 if (!shouldEncode(component)) {
148 return;
149 }
150
151 ResponseWriter writer = context.getResponseWriter();
152 assert(writer != null);
153
154 String currentValue = getCurrentValue(context, component);
155 if (logger.isLoggable(Level.FINE)) {
156 logger.log(Level.FINE,
157 "Value to be rendered {0}",
158 currentValue);
159 }
160 getEndTextToRender(context, component, currentValue);
161
162 }
163
164
165 @Override
166 public boolean getRendersChildren() {
167
168 return true;
169
170 }
171
172 // ------------------------------------------------------- Protected Methods
173
174
175 /**
176 * <p>Conditionally augment an id-reference value.</p>
177 * <p>If the <code>forValue</code> doesn't already include a generated
178 * suffix, but the id of the <code>fromComponent</code> does include a
179 * generated suffix, then append the suffix from the
180 * <code>fromComponent</code> to the <code>forValue</code>.
181 * Otherwise just return the <code>forValue</code> as is.</p>
182 *
183 * @param forValue - the basic id-reference value.
184 * @param fromComponent - the component that holds the
185 * code>forValue</code>.
186 *
187 * @return the (possibly augmented) <code>forValue<code>.
188 */
189 protected String augmentIdReference(String forValue,
190 UIComponent fromComponent) {
191
192 int forSuffix = forValue.lastIndexOf(UIViewRoot.UNIQUE_ID_PREFIX);
193 if (forSuffix <= 0) {
194 // if the for-value doesn't already have a suffix present
195 String id = fromComponent.getId();
196 int idSuffix = id.lastIndexOf(UIViewRoot.UNIQUE_ID_PREFIX);
197 if (idSuffix > 0) {
198 // but the component's own id does have a suffix
199 if (logger.isLoggable(Level.FINE)) {
200 logger.fine("Augmenting for attribute with " +
201 id.substring(idSuffix) +
202 " suffix from Id attribute");
203 }
204 forValue += id.substring(idSuffix);
205 }
206 }
207 return forValue;
208
209 }
210
211
212 /**
213 * <p>Render nested child components by invoking the encode methods
214 * on those components, but only when the <code>rendered</code>
215 * property is <code>true</code>.</p>
216 *
217 * @param context FacesContext for the current request
218 * @param component the component to recursively encode
219 *
220 * @throws IOException if an error occurrs during the encode process
221 */
222 protected void encodeRecursive(FacesContext context, UIComponent component)
223 throws IOException {
224
225 // suppress rendering if "rendered" property on the component is
226 // false.
227 if (!component.isRendered()) {
228 return;
229 }
230
231 // Render this component and its children recursively
232 component.encodeBegin(context);
233 if (component.getRendersChildren()) {
234 component.encodeChildren(context);
235 } else {
236 Iterator<UIComponent> kids = getChildren(component);
237 while (kids.hasNext()) {
238 UIComponent kid = kids.next();
239 encodeRecursive(context, kid);
240 }
241 }
242 component.encodeEnd(context);
243
244 }
245
246
247 /**
248 * @param component <code>UIComponent</code> for which to extract children
249 *
250 * @return an Iterator over the children of the specified
251 * component, selecting only those that have a
252 * <code>rendered</code> property of <code>true</code>.
253 */
254 protected Iterator<UIComponent> getChildren(UIComponent component) {
255
256 int childCount = component.getChildCount();
257 if (childCount > 0) {
258 return component.getChildren().iterator();
259 } else {
260 return Collections.<UIComponent>emptyList().iterator();
261 }
262
263 }
264
265
266 /**
267 * @param context the FacesContext for the current request
268 * @param component the UIComponent whose value we're interested in
269 *
270 * @return the value to be rendered and formats it if required. Sets to
271 * empty string if value is null.
272 */
273 protected String getCurrentValue(FacesContext context,
274 UIComponent component) {
275
276 if (component instanceof UIInput) {
277 Object submittedValue = ((UIInput) component).getSubmittedValue();
278 if (submittedValue != null) {
279 return (String) submittedValue;
280 }
281 }
282
283 String currentValue = null;
284 Object currentObj = getValue(component);
285 if (currentObj != null) {
286 currentValue = getFormattedValue(context, component, currentObj);
287 }
288 return currentValue;
289
290 }
291
292
293 /**
294 * Renderers override this method to write appropriate HTML content into
295 * the buffer.
296 *
297 * @param context the FacesContext for the current request
298 * @param component the UIComponent of interest
299 * @param currentValue <code>component</code>'s current value
300 *
301 * @throws IOException if an error occurs rendering the text
302 */
303 protected void getEndTextToRender(FacesContext context,
304 UIComponent component,
305 String currentValue) throws IOException {
306
307 // no-op unless overridden
308
309 }
310
311
312 /**
313 * @param component Component from which to return a facet
314 * @param name Name of the desired facet
315 *
316 * @return the specified facet from the specified component, but
317 * <strong>only</strong> if its <code>rendered</code> property is
318 * set to <code>true</code>.
319 */
320 protected UIComponent getFacet(UIComponent component, String name) {
321
322 UIComponent facet = null;
323 if (component.getFacetCount() > 0) {
324 facet = component.getFacet(name);
325 if ((facet != null) && !facet.isRendered()) {
326 facet = null;
327 }
328 }
329 return (facet);
330
331 }
332
333
334 /**
335 * Locates the component identified by <code>forComponent</code>
336 *
337 * @param context the FacesContext for the current request
338 * @param forComponent - the component to search for
339 * @param component - the starting point in which to begin the search
340 *
341 * @return the component with the the <code>id</code that matches
342 * <code>forComponent</code> otheriwse null if no match is found.
343 */
344 protected UIComponent getForComponent(FacesContext context,
345 String forComponent,
346 UIComponent component) {
347
348 if (null == forComponent || forComponent.length() == 0) {
349 return null;
350 }
351
352 UIComponent result = null;
353 UIComponent currentParent = component;
354 try {
355 // Check the naming container of the current
356 // component for component identified by
357 // 'forComponent'
358 while (currentParent != null) {
359 // If the current component is a NamingContainer,
360 // see if it contains what we're looking for.
361 result = currentParent.findComponent(forComponent);
362 if (result != null) {
363 break;
364 }
365 // if not, start checking further up in the view
366 currentParent = currentParent.getParent();
367 }
368
369 // no hit from above, scan for a NamingContainer
370 // that contains the component we're looking for from the root.
371 if (result == null) {
372 result =
373 findUIComponentBelow(context.getViewRoot(), forComponent);
374 }
375 } catch (Exception e) {
376 // ignore - log the warning
377 }
378 // log a message if we were unable to find the specified
379 // component (probably a misconfigured 'for' attribute
380 if (result == null) {
381 if (logger.isLoggable(Level.WARNING)) {
382 logger.warning(MessageUtils.getExceptionMessageString(
383 MessageUtils.COMPONENT_NOT_FOUND_IN_VIEW_WARNING_ID,
384 forComponent));
385 }
386 }
387 return result;
388
389 }
390
391 /**
392 * Overloads getFormattedValue to take a advantage of a previously
393 * obtained converter.
394 * @param context the FacesContext for the current request
395 * @param component UIComponent of interest
396 * @param currentValue the current value of <code>component</code>
397 * @param converter the component's converter
398 * @return the currentValue after any associated Converter has been
399 * applied
400 *
401 * @throws ConverterException if the value cannot be converted
402 */
403 protected String getFormattedValue(FacesContext context,
404 UIComponent component,
405 Object currentValue,
406 Converter converter)
407 throws ConverterException {
408
409 // formatting is supported only for components that support
410 // converting value attributes.
411 if (!(component instanceof ValueHolder)) {
412 if (currentValue != null) {
413 return currentValue.toString();
414 }
415 return null;
416 }
417
418 if (converter == null) {
419 // If there is a converter attribute, use it to to ask application
420 // instance for a converter with this identifer.
421 converter = ((ValueHolder) component).getConverter();
422 }
423
424 if (converter == null) {
425 // if value is null and no converter attribute is specified, then
426 // return a zero length String.
427 if(currentValue == null) {
428 return "";
429 }
430 // Do not look for "by-type" converters for Strings
431 if (currentValue instanceof String) {
432 return (String) currentValue;
433 }
434
435 // if converter attribute set, try to acquire a converter
436 // using its class type.
437
438 Class converterType = currentValue.getClass();
439 converter = Util.getConverterForClass(converterType, context);
440
441 // if there is no default converter available for this identifier,
442 // assume the model type to be String.
443 if (converter == null) {
444 return currentValue.toString();
445 }
446 }
447
448 return converter.getAsString(context, component, currentValue);
449 }
450
451
452 /**
453 * @param context the FacesContext for the current request
454 * @param component UIComponent of interest
455 * @param currentValue the current value of <code>component</code>
456 *
457 * @return the currentValue after any associated Converter has been
458 * applied
459 *
460 * @throws ConverterException if the value cannot be converted
461 */
462 protected String getFormattedValue(FacesContext context,
463 UIComponent component,
464 Object currentValue)
465 throws ConverterException {
466
467 return getFormattedValue(context, component, currentValue, null);
468
469 }
470
471
472 protected Iterator getMessageIter(FacesContext context,
473 String forComponent,
474 UIComponent component) {
475
476 Iterator messageIter;
477 // Attempt to use the "for" attribute to locate
478 // messages. Three possible scenarios here:
479 // 1. valid "for" attribute - messages returned
480 // for valid component identified by "for" expression.
481 // 2. zero length "for" expression - global errors
482 // not associated with any component returned
483 // 3. no "for" expression - all messages returned.
484 if (null != forComponent) {
485 if (forComponent.length() == 0) {
486 messageIter = context.getMessages(null);
487 } else {
488 UIComponent result = getForComponent(context, forComponent,
489 component);
490 if (result == null) {
491 messageIter = Collections.EMPTY_LIST.iterator();
492 } else {
493 messageIter =
494 context.getMessages(result.getClientId(context));
495 }
496 }
497 } else {
498 messageIter = context.getMessages();
499 }
500 return messageIter;
501
502 }
503
504
505 /**
506 * @param command the command which may have parameters
507 *
508 * @return an array of parameters
509 */
510 protected Param[] getParamList(UIComponent command) {
511
512 if (command.getChildCount() > 0) {
513 ArrayList<Param> parameterList = new ArrayList<Param>();
514
515 for (UIComponent kid : command.getChildren()) {
516 if (kid instanceof UIParameter) {
517 UIParameter uiParam = (UIParameter) kid;
518 Object value = uiParam.getValue();
519 Param param = new Param(uiParam.getName(),
520 (value == null ? null :
521 value.toString()));
522 parameterList.add(param);
523 }
524 }
525 return parameterList.toArray(new Param[parameterList.size()]);
526 } else {
527 return EMPTY_PARAMS;
528 }
529
530
531 }
532
533
534 protected Object getValue(UIComponent component) {
535
536 // Make sure this method isn't being called except
537 // from subclasses that override getValue()!
538 throw new UnsupportedOperationException();
539
540 }
541
542
543 /**
544 * Renderers override this method to store the previous value
545 * of the associated component.
546 *
547 * @param component the target component to which the submitted value
548 * will be set
549 * @param value the value to set
550 */
551 protected void setSubmittedValue(UIComponent component, Object value) {
552
553 // no-op unless overridden
554
555 }
556
557
558 /**
559 * @param component the component of interest
560 *
561 * @return true if this renderer should render an id attribute.
562 */
563 protected boolean shouldWriteIdAttribute(UIComponent component) {
564
565 String id;
566 return (null != (id = component.getId()) &&
567 !id.startsWith(UIViewRoot.UNIQUE_ID_PREFIX));
568
569 }
570
571
572 protected String writeIdAttributeIfNecessary(FacesContext context,
573 ResponseWriter writer,
574 UIComponent component) {
575
576 String id = null;
577 if (shouldWriteIdAttribute(component)) {
578 try {
579 writer.writeAttribute("id", id = component.getClientId(context),
580 "id");
581 } catch (IOException e) {
582 if (logger.isLoggable(Level.WARNING)) {
583 String message = MessageUtils.getExceptionMessageString
584 (MessageUtils.CANT_WRITE_ID_ATTRIBUTE_ERROR_MESSAGE_ID,
585 e.getMessage());
586 logger.warning(message);
587 }
588 }
589 }
590 return id;
591
592 }
593
594 protected void rendererParamsNotNull(FacesContext context,
595 UIComponent component) {
596
597 Util.notNull("context", context);
598 Util.notNull("component", component);
599
600 }
601
602
603 protected boolean shouldEncode(UIComponent component) {
604
605 // suppress rendering if "rendered" property on the component is
606 // false.
607 if (!component.isRendered()) {
608 if (logger.isLoggable(Level.FINE)) {
609 logger.log(Level.FINE,
610 "End encoding component {0} since rendered attribute is set to false",
611 component.getId());
612 }
613 return false;
614 }
615 return true;
616
617 }
618
619
620 protected boolean shouldDecode(UIComponent component) {
621
622 if (Util.componentIsDisabledOrReadonly(component)) {
623 if (logger.isLoggable(Level.FINE)) {
624 logger.log(Level.FINE,
625 "No decoding necessary since the component {0} is disabled or read-only",
626 component.getId());
627 }
628 return false;
629 }
630 return true;
631
632 }
633
634 protected boolean shouldEncodeChildren(UIComponent component) {
635
636 // suppress rendering if "rendered" property on the component is
637 // false.
638 if (!component.isRendered()) {
639 if (logger.isLoggable(Level.FINE)) {
640 logger.log(Level.FINE,
641 "Children of component {0} will not be encoded since this component's rendered attribute is false",
642 component.getId());
643 }
644 return false;
645 }
646 return true;
647
648 }
649
650
651 // --------------------------------------------------------- Private Methods
652
653
654 /**
655 * <p>Recursively searches for {@link NamingContainer}s from the
656 * given start point looking for the component with the <code>id</code>
657 * specified by <code>forComponent</code>.
658 *
659 * @param startPoint - the starting point in which to begin the search
660 * @param forComponent - the component to search for
661 *
662 * @return the component with the the <code>id</code that matches
663 * <code>forComponent</code> otheriwse null if no match is found.
664 */
665 private static UIComponent findUIComponentBelow(UIComponent startPoint,
666 String forComponent) {
667
668 UIComponent retComp = null;
669 if (startPoint.getChildCount() > 0) {
670 List<UIComponent> children = startPoint.getChildren();
671 for (int i = 0, size = children.size(); i < size; i++) {
672 UIComponent comp = children.get(i);
673
674 if (comp instanceof NamingContainer) {
675 try {
676 retComp = comp.findComponent(forComponent);
677 } catch (IllegalArgumentException iae) {
678 continue;
679 }
680 }
681
682 if (retComp == null) {
683 if (comp.getChildCount() > 0) {
684 retComp = findUIComponentBelow(comp, forComponent);
685 }
686 }
687
688 if (retComp != null) {
689 break;
690 }
691 }
692 }
693 return retComp;
694
695 }
696
697
698 // ----------------------------------------------------------- Inner Classes
699
700
701 /**
702 * <p>Simple class to encapsulate the name and value of a
703 * <code>UIParameeter</code>.
704 */
705 public static class Param {
706
707
708 public String name;
709 public String value;
710
711
712 // -------------------------------------------------------- Constructors
713
714
715 public Param(String name, String value) {
716
717 this.name = name;
718 this.value = value;
719
720 }
721
722 }
723
724 } // end of class HtmlBasicRenderer