1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one
3 * or more contributor license agreements. See the NOTICE file
4 * distributed with this work for additional information
5 * regarding copyright ownership. The ASF licenses this file
6 * to you under the Apache License, Version 2.0 (the
7 * "License"); you may not use this file except in compliance
8 * with the License. You may obtain a copy of the License at
9 *
10 * http://www.apache.org/licenses/LICENSE-2.0
11 *
12 * Unless required by applicable law or agreed to in writing,
13 * software distributed under the License is distributed on an
14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15 * KIND, either express or implied. See the License for the
16 * specific language governing permissions and limitations
17 * under the License.
18 */
19 package javax.faces.component;
20
21 import java.io.IOException;
22 import java.sql.ResultSet;
23 import java.util.ArrayList;
24 import java.util.Collection;
25 import java.util.HashMap;
26 import java.util.Iterator;
27 import java.util.List;
28 import java.util.Map;
29
30 import javax.faces.application.FacesMessage;
31 import javax.faces.context.FacesContext;
32 import javax.faces.el.ValueBinding;
33 import javax.faces.event.AbortProcessingException;
34 import javax.faces.event.FacesEvent;
35 import javax.faces.event.FacesListener;
36 import javax.faces.event.PhaseId;
37 import javax.faces.model.ArrayDataModel;
38 import javax.faces.model.DataModel;
39 import javax.faces.model.ListDataModel;
40 import javax.faces.model.ResultDataModel;
41 import javax.faces.model.ResultSetDataModel;
42 import javax.faces.model.ScalarDataModel;
43 import javax.servlet.jsp.jstl.sql.Result;
44
45 /**
46 * Represents a component which has multiple "rows" of data.
47 * <p>
48 * The children of this component are expected to be UIColumn components.
49 * <p>
50 * Note that the same set of child components are reused to implement each
51 * row of the table in turn during such phases as apply-request-values and
52 * render-response. Altering any of the members of these components therefore
53 * affects the attribute for every row, except for the following members:
54 * <ul>
55 * <li>submittedValue
56 * <li>value (where no EL binding is used)
57 * <li>valid
58 * </ul>
59 * <p>
60 * This reuse of the child components also means that it is not possible
61 * to save a reference to a component during table processing, then access
62 * it later and expect it to still represent the same row of the table.
63 * <h1>
64 * Implementation Notes
65 * </h1>
66 * <p>
67 * Each of the UIColumn children of this component has a few component
68 * children of its own to render the contents of the table cell. However
69 * there can be a very large number of rows in a table, so it isn't
70 * efficient for the UIColumn and all its child objects to be duplicated
71 * for each row in the table. Instead the "flyweight" pattern is used
72 * where a serialized state is held for each row. When setRowIndex is
73 * invoked, the UIColumn objects and their children serialize their
74 * current state then reinitialise themselves from the appropriate saved
75 * state. This allows a single set of real objects to represent multiple
76 * objects which have the same types but potentially different internal
77 * state. When a row is selected for the first time, its state is set to
78 * a clean "initial" state. Transient components (including any read-only
79 * component) do not save their state; they are just reinitialised as required.
80 * The state saved/restored when changing rows is not the complete
81 * component state, just the fields that are expected to vary between
82 * rows: "submittedValue", "value", "isValid".
83 * </p>
84 * <p>
85 * Note that a table is a "naming container", so that components
86 * within the table have their ids prefixed with the id of the
87 * table. Actually, when setRowIndex has been called on a table with
88 * id of "zzz" the table pretends to its children that its ID is
89 * "zzz_n" where n is the row index. This means that renderers for
90 * child components which call component.getClientId automatically
91 * get ids of form "zzz_n:childId" thus ensuring that components in
92 * different rows of the table get different ids.
93 * </p>
94 * <p>
95 * When decoding a submitted page, this class iterates over all
96 * its possible rowIndex values, restoring the appropriate serialized
97 * row state then calling processDecodes on the child components. Because
98 * the child components (or their renderers) use getClientId to get the
99 * request key to look for parameter data, and because this object pretends
100 * to have a different id per row ("zzz_n") a single child component can
101 * decode data from each table row in turn without being aware that it is
102 * within a table. The table's data model is updated before each call to
103 * child.processDecodes, so the child decode method can assume that the
104 * data model's rowData points to the model object associated with the
105 * row currently being decoded. Exactly the same process applies for
106 * the later validation and updateModel phases.
107 * </p>
108 * <p>
109 * When the data model for the table is bound to a backing bean property,
110 * and no validation errors have occured during processing of a postback,
111 * the data model is refetched at the start of the rendering phase
112 * (ie after the update model phase) so that the contents of the data model
113 * can be changed as a result of the latest form submission. Because the
114 * saved row state must correspond to the elements within the data model,
115 * the row state must be discarded whenever a new data model is fetched;
116 * not doing this would cause all sorts of inconsistency issues. This does
117 * imply that changing the state of any of the members "submittedValue",
118 * "value" or "valid" of a component within the table during the
119 * invokeApplication phase has no effect on the rendering of the table.
120 * When a validation error has occurred, a new DataModel is <i>not</i>
121 * fetched, and the saved state of the child components is <i>not</i>
122 * discarded.
123 * </p>
124 * see Javadoc of <a href="http://java.sun.com/j2ee/javaserverfaces/1.1_01/docs/api/index.html">JSF Specification</a> for more.
125 *
126 * @JSFComponent
127 * type = "javax.faces.Data"
128 * family = "javax.faces.Data"
129 * desc = "UIData"
130 *
131 * @author Manfred Geiler (latest modification by $Author: lu4242 $)
132 * @version $Revision: 949325 $ $Date: 2010-05-28 19:38:36 -0500 (Fri, 28 May 2010) $
133 */
134 public class UIData extends UIComponentBase implements NamingContainer
135 {
136 public static final String COMPONENT_TYPE = "javax.faces.Data";
137 public static final String COMPONENT_FAMILY = "javax.faces.Data";
138 private static final String DEFAULT_RENDERER_TYPE = "javax.faces.Table";
139 private static final int DEFAULT_FIRST = 0;
140 private static final int DEFAULT_ROWS = 0;
141
142 private static final int STATE_SIZE = 5;
143 private static final int SUPER_STATE_INDEX = 0;
144 private static final int FIRST_STATE_INDEX = 1;
145 private static final int ROWS_STATE_INDEX = 2;
146 private static final int VALUE_STATE_INDEX = 3;
147 private static final int VAR_STATE_INDEX = 4;
148
149 private static final String FOOTER_FACET_NAME = "footer";
150 private static final String HEADER_FACET_NAME = "header";
151 private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
152 private static final int PROCESS_DECODES = 1;
153 private static final int PROCESS_VALIDATORS = 2;
154 private static final int PROCESS_UPDATES = 3;
155
156 private Integer _first = null;
157 private Integer _rows = null;
158 private Object _value = null;
159
160 private int _rowIndex = -1;
161 private String _var = null;
162
163 // Holds for each row the states of the child components of this UIData.
164 // Note that only "partial" component state is saved: the component fields
165 // that are expected to vary between rows.
166 private Map _rowStates = new HashMap();
167
168 /**
169 * Handle case where this table is nested inside another table.
170 * See method getDataModel for more details.
171 * <p>
172 * Key: parentClientId (aka rowId when nested within a parent table)
173 * Value: DataModel
174 */
175 private Map _dataModelMap = new HashMap();
176
177 // will be set to false if the data should not be refreshed at the beginning of the encode phase
178 private boolean _isValidChilds = true;
179
180 private Object _initialDescendantComponentState = null;
181
182 public void setFooter(UIComponent footer)
183 {
184 getFacets().put(FOOTER_FACET_NAME, footer);
185 }
186
187 /**
188 * @JSFFacet
189 */
190 public UIComponent getFooter()
191 {
192 return (UIComponent) getFacets().get(FOOTER_FACET_NAME);
193 }
194
195 public void setHeader(UIComponent header)
196 {
197 getFacets().put(HEADER_FACET_NAME, header);
198 }
199
200 /**
201 * @JSFFacet
202 */
203 public UIComponent getHeader()
204 {
205 return (UIComponent) getFacets().get(HEADER_FACET_NAME);
206 }
207
208 public boolean isRowAvailable()
209 {
210 return getDataModel().isRowAvailable();
211 }
212
213 public int getRowCount()
214 {
215 return getDataModel().getRowCount();
216 }
217
218 public Object getRowData()
219 {
220 return getDataModel().getRowData();
221 }
222
223 public int getRowIndex()
224 {
225 return _rowIndex;
226 }
227
228 /**
229 * Set the current row index that methods like getRowData use.
230 * <p>
231 * Param rowIndex can be -1, meaning "no row".
232 * <p>
233 * @param rowIndex
234 */
235 public void setRowIndex(int rowIndex)
236 {
237 if (rowIndex < -1)
238 {
239 throw new IllegalArgumentException("rowIndex is less than -1");
240 }
241
242 if (_rowIndex == rowIndex)
243 {
244 return;
245 }
246
247 FacesContext facesContext = getFacesContext();
248
249 if (_rowIndex == -1)
250 {
251 if (_initialDescendantComponentState == null)
252 {
253 // Create a template that can be used to initialise any row
254 // that we haven't visited before, ie a "saved state" that can
255 // be pushed to the "restoreState" method of all the child
256 // components to set them up to represent a clean row.
257 _initialDescendantComponentState = saveDescendantComponentStates(
258 getChildren().iterator(), false);
259 }
260 }
261 else
262 {
263 // We are currently positioned on some row, and are about to
264 // move off it, so save the (partial) state of the components
265 // representing the current row. Later if this row is revisited
266 // then we can restore this state.
267 _rowStates.put(getClientId(facesContext),
268 saveDescendantComponentStates(getChildren().iterator(),
269 false));
270 }
271
272 _rowIndex = rowIndex;
273
274 DataModel dataModel = getDataModel();
275 dataModel.setRowIndex(rowIndex);
276
277 String var = getVar();
278 if (rowIndex == -1)
279 {
280 if (var != null)
281 {
282 facesContext.getExternalContext().getRequestMap().remove(var);
283 }
284 }
285 else
286 {
287 if (var != null)
288 {
289 if (isRowAvailable())
290 {
291 Object rowData = dataModel.getRowData();
292 facesContext.getExternalContext().getRequestMap().put(var,
293 rowData);
294 }
295 else
296 {
297 facesContext.getExternalContext().getRequestMap().remove(
298 var);
299 }
300 }
301 }
302
303 if (_rowIndex == -1)
304 {
305 // reset components to initial state
306 restoreDescendantComponentStates(getChildren().iterator(),
307 _initialDescendantComponentState, false);
308 }
309 else
310 {
311 Object rowState = _rowStates.get(getClientId(facesContext));
312 if (rowState == null)
313 {
314 // We haven't been positioned on this row before, so just
315 // configure the child components of this component with
316 // the standard "initial" state
317 restoreDescendantComponentStates(getChildren().iterator(),
318 _initialDescendantComponentState, false);
319 }
320 else
321 {
322 // We have been positioned on this row before, so configure
323 // the child components of this component with the (partial)
324 // state that was previously saved. Fields not in the
325 // partial saved state are left with their original values.
326 restoreDescendantComponentStates(getChildren().iterator(),
327 rowState, false);
328 }
329 }
330 }
331
332 /**
333 * Overwrite the state of the child components of this component
334 * with data previously saved by method saveDescendantComponentStates.
335 * <p>
336 * The saved state info only covers those fields that are expected to
337 * vary between rows of a table. Other fields are not modified.
338 */
339 private void restoreDescendantComponentStates(Iterator childIterator,
340 Object state, boolean restoreChildFacets)
341 {
342 Iterator descendantStateIterator = null;
343 while (childIterator.hasNext())
344 {
345 if (descendantStateIterator == null && state != null)
346 {
347 descendantStateIterator = ((Collection) state).iterator();
348 }
349 UIComponent component = (UIComponent) childIterator.next();
350
351 // reset the client id (see spec 3.1.6)
352 component.setId(component.getId());
353 if(!component.isTransient())
354 {
355 Object childState = null;
356 Object descendantState = null;
357 if (descendantStateIterator != null
358 && descendantStateIterator.hasNext())
359 {
360 Object[] object = (Object[]) descendantStateIterator.next();
361 childState = object[0];
362 descendantState = object[1];
363 }
364 if (component instanceof EditableValueHolder)
365 {
366 ((EditableValueHolderState) childState)
367 .restoreState((EditableValueHolder) component);
368 }
369 Iterator childsIterator;
370 if (restoreChildFacets)
371 {
372 childsIterator = component.getFacetsAndChildren();
373 }
374 else
375 {
376 childsIterator = component.getChildren().iterator();
377 }
378 restoreDescendantComponentStates(childsIterator, descendantState,
379 true);
380 }
381 }
382 }
383
384 /**
385 * Walk the tree of child components of this UIData, saving the parts of
386 * their state that can vary between rows.
387 * <p>
388 * This is very similar to the process that occurs for normal components
389 * when the view is serialized. Transient components are skipped (no
390 * state is saved for them).
391 * <p>
392 * If there are no children then null is returned. If there are one or
393 * more children, and all children are transient then an empty collection
394 * is returned; this will happen whenever a table contains only read-only
395 * components.
396 * <p>
397 * Otherwise a collection is returned which contains an object for every
398 * non-transient child component; that object may itself contain a collection
399 * of the state of that child's child components.
400 */
401 private Object saveDescendantComponentStates(Iterator childIterator,
402 boolean saveChildFacets)
403 {
404 Collection childStates = null;
405 while (childIterator.hasNext())
406 {
407 if (childStates == null)
408 {
409 childStates = new ArrayList();
410 }
411 UIComponent child = (UIComponent) childIterator.next();
412 if(!child.isTransient())
413 {
414 // Add an entry to the collection, being an array of two
415 // elements. The first element is the state of the children
416 // of this component; the second is the state of the current
417 // child itself.
418
419 Iterator childsIterator;
420 if (saveChildFacets)
421 {
422 childsIterator = child.getFacetsAndChildren();
423 }
424 else
425 {
426 childsIterator = child.getChildren().iterator();
427 }
428 Object descendantState = saveDescendantComponentStates(
429 childsIterator, true);
430 Object state = null;
431 if (child instanceof EditableValueHolder)
432 {
433 state = new EditableValueHolderState(
434 (EditableValueHolder) child);
435 }
436 childStates.add(new Object[] { state, descendantState });
437 }
438 }
439 return childStates;
440 }
441
442 /**
443 * Set the maximum number of rows displayed in the table.
444 */
445 public void setRows(int rows)
446 {
447 _rows = new Integer(rows);
448 if (rows < 0)
449 throw new IllegalArgumentException("rows: " + rows);
450 }
451
452 /**
453 * Set the name of the temporary variable that will be exposed to
454 * child components of the table to tell them what the "rowData"
455 * object for the current row is. This value must be a literal
456 * string (EL expression not permitted).
457 */
458 public void setVar(String var)
459 {
460 _var = var;
461 }
462
463 /**
464 * Defines the name of the request-scope variable that will hold the current row during iteration. This value must be a static value.
465 *
466 * @JSFProperty
467 * literalOnly = "true"
468 * required = "true"
469 */
470 public String getVar()
471 {
472 return _var;
473 }
474
475 public void setValueBinding(String name, ValueBinding binding)
476 {
477 if (name == null)
478 {
479 throw new NullPointerException("name");
480 }
481 else if (name.equals("value"))
482 {
483 _dataModelMap.clear();
484 }
485 else if (name.equals("var") || name.equals("rowIndex"))
486 {
487 throw new IllegalArgumentException(
488 "You can never set the 'rowIndex' or the 'var' attribute as a value-binding. Set the property directly instead. Name " + name);
489 }
490 super.setValueBinding(name, binding);
491 }
492
493 public String getClientId(FacesContext context)
494 {
495 String clientId = super.getClientId(context);
496 int rowIndex = getRowIndex();
497 if (rowIndex == -1)
498 {
499 return clientId;
500 }
501 return clientId + NamingContainer.SEPARATOR_CHAR + rowIndex;
502 }
503
504 /**
505 * Modify events queued for any child components so that the
506 * UIData state will be correctly configured before the event's
507 * listeners are executed.
508 * <p>
509 * Child components or their renderers may register events against
510 * those child components. When the listener for that event is
511 * eventually invoked, it may expect the uidata's rowData and
512 * rowIndex to be referring to the same object that caused the
513 * event to fire.
514 * <p>
515 * The original queueEvent call against the child component has been
516 * forwarded up the chain of ancestors in the standard way, making
517 * it possible here to wrap the event in a new event whose source
518 * is <i>this</i> component, not the original one. When the event
519 * finally is executed, this component's broadcast method is invoked,
520 * which ensures that the UIData is set to be at the correct row
521 * before executing the original event.
522 */
523 public void queueEvent(FacesEvent event)
524 {
525 super.queueEvent(new FacesEventWrapper(event, getRowIndex(), this));
526 }
527
528 /**
529 * Ensure that before the event's listeners are invoked this UIData
530 * component's "current row" is set to the row associated with the event.
531 * <p>
532 * See queueEvent for more details.
533 */
534 public void broadcast(FacesEvent event) throws AbortProcessingException
535 {
536 if (event instanceof FacesEventWrapper)
537 {
538 FacesEvent originalEvent = ((FacesEventWrapper) event)
539 .getWrappedFacesEvent();
540 int eventRowIndex = ((FacesEventWrapper) event).getRowIndex();
541 int currentRowIndex = getRowIndex();
542 setRowIndex(eventRowIndex);
543 try
544 {
545 originalEvent.getComponent().broadcast(originalEvent);
546 }
547 finally
548 {
549 setRowIndex(currentRowIndex);
550 }
551 }
552 else
553 {
554 super.broadcast(event);
555 }
556 }
557
558 /**
559 * Perform necessary actions when rendering of this component starts,
560 * before delegating to the inherited implementation which calls the
561 * associated renderer's encodeBegin method.
562 */
563 public void encodeBegin(FacesContext context) throws IOException
564 {
565 _initialDescendantComponentState = null;
566 if (_isValidChilds && !hasErrorMessages(context))
567 {
568 // Clear the data model so that when rendering code calls
569 // getDataModel a fresh model is fetched from the backing
570 // bean via the value-binding.
571 _dataModelMap.clear();
572
573 // When the data model is cleared it is also necessary to
574 // clear the saved row state, as there is an implicit 1:1
575 // relation between objects in the _rowStates and the
576 // corresponding DataModel element.
577 _rowStates.clear();
578 }
579 super.encodeBegin(context);
580 }
581
582 private boolean hasErrorMessages(FacesContext context)
583 {
584 for(Iterator iter = context.getMessages(); iter.hasNext();)
585 {
586 FacesMessage message = (FacesMessage) iter.next();
587 if(FacesMessage.SEVERITY_ERROR.compareTo(message.getSeverity()) <= 0)
588 {
589 return true;
590 }
591 }
592 return false;
593 }
594
595 /**
596 * @see javax.faces.component.UIComponentBase#encodeEnd(javax.faces.context.FacesContext)
597 */
598 public void encodeEnd(FacesContext context) throws IOException
599 {
600 try
601 {
602 setCachedFacesContext(context);
603 setRowIndex(-1);
604 }
605 finally
606 {
607 setCachedFacesContext(null);
608 }
609 super.encodeEnd(context);
610 }
611
612 public void processDecodes(FacesContext context)
613 {
614 if (context == null)
615 throw new NullPointerException("context");
616 try
617 {
618 setCachedFacesContext(context);
619 if (!isRendered())
620 return;
621 setRowIndex(-1);
622 processFacets(context, PROCESS_DECODES);
623 processColumnFacets(context, PROCESS_DECODES);
624 processColumnChildren(context, PROCESS_DECODES);
625 setRowIndex(-1);
626 try
627 {
628 decode(context);
629 }
630 catch (RuntimeException e)
631 {
632 context.renderResponse();
633 throw e;
634 }
635 }
636 finally
637 {
638 setCachedFacesContext(null);
639 }
640 }
641
642 public void processValidators(FacesContext context)
643 {
644 if (context == null)
645 throw new NullPointerException("context");
646 try
647 {
648 setCachedFacesContext(context);
649 if (!isRendered())
650 return;
651 setRowIndex(-1);
652 processFacets(context, PROCESS_VALIDATORS);
653 processColumnFacets(context, PROCESS_VALIDATORS);
654 processColumnChildren(context, PROCESS_VALIDATORS);
655 setRowIndex(-1);
656
657 // check if an validation error forces the render response for our data
658 if (context.getRenderResponse())
659 {
660 _isValidChilds = false;
661 }
662 }
663 finally
664 {
665 setCachedFacesContext(null);
666 }
667 }
668
669 public void processUpdates(FacesContext context)
670 {
671 if (context == null)
672 throw new NullPointerException("context");
673 try
674 {
675 setCachedFacesContext(context);
676 if (!isRendered())
677 return;
678 setRowIndex(-1);
679 processFacets(context, PROCESS_UPDATES);
680 processColumnFacets(context, PROCESS_UPDATES);
681 processColumnChildren(context, PROCESS_UPDATES);
682 setRowIndex(-1);
683
684 if (context.getRenderResponse())
685 {
686 _isValidChilds = false;
687 }
688 }
689 finally
690 {
691 setCachedFacesContext(null);
692 }
693 }
694
695 private void processFacets(FacesContext context, int processAction)
696 {
697 for (Iterator it = getFacets().values().iterator(); it.hasNext();)
698 {
699 UIComponent facet = (UIComponent) it.next();
700 process(context, facet, processAction);
701 }
702 }
703
704 /**
705 * Invoke the specified phase on all facets of all UIColumn children
706 * of this component. Note that no methods are called on the UIColumn
707 * child objects themselves.
708 *
709 * @param context is the current faces context.
710 * @param processAction specifies a JSF phase: decode, validate or update.
711 */
712 private void processColumnFacets(FacesContext context, int processAction)
713 {
714 for (Iterator childIter = getChildren().iterator(); childIter.hasNext();)
715 {
716 UIComponent child = (UIComponent) childIter.next();
717 if (child instanceof UIColumn)
718 {
719 if (!child.isRendered())
720 {
721 //Column is not visible
722 continue;
723 }
724 for (Iterator facetsIter = child.getFacets().values()
725 .iterator(); facetsIter.hasNext();)
726 {
727 UIComponent facet = (UIComponent) facetsIter.next();
728 process(context, facet, processAction);
729 }
730 }
731 }
732 }
733
734 /**
735 * Invoke the specified phase on all non-facet children of all UIColumn
736 * children of this component. Note that no methods are called on the
737 * UIColumn child objects themselves.
738 *
739 * @param context is the current faces context.
740 * @param processAction specifies a JSF phase: decode, validate or update.
741 */
742 private void processColumnChildren(FacesContext context, int processAction)
743 {
744 int first = getFirst();
745 int rows = getRows();
746 int last;
747 if (rows == 0)
748 {
749 last = getRowCount();
750 }
751 else
752 {
753 last = first + rows;
754 }
755 for (int rowIndex = first; last==-1 || rowIndex < last; rowIndex++)
756 {
757 setRowIndex(rowIndex);
758
759 //scrolled past the last row
760 if (!isRowAvailable())
761 break;
762
763 for (Iterator it = getChildren().iterator(); it.hasNext();)
764 {
765 UIComponent child = (UIComponent) it.next();
766 if (child instanceof UIColumn)
767 {
768 if (!child.isRendered())
769 {
770 //Column is not visible
771 continue;
772 }
773 for (Iterator columnChildIter = child.getChildren()
774 .iterator(); columnChildIter.hasNext();)
775 {
776 UIComponent columnChild = (UIComponent) columnChildIter
777 .next();
778 process(context, columnChild, processAction);
779 }
780 }
781 }
782 }
783 }
784
785 private void process(FacesContext context, UIComponent component,
786 int processAction)
787 {
788 switch (processAction)
789 {
790 case PROCESS_DECODES:
791 component.processDecodes(context);
792 break;
793 case PROCESS_VALIDATORS:
794 component.processValidators(context);
795 break;
796 case PROCESS_UPDATES:
797 component.processUpdates(context);
798 break;
799 }
800 }
801
802 /**
803 * Return the datamodel for this table, potentially fetching the data from
804 * a backing bean via a value-binding if this is the first time this method
805 * has been called.
806 * <p>
807 * This is complicated by the fact that this table may be nested within
808 * another table. In this case a different datamodel should be fetched
809 * for each row. When nested within a parent table, the parent reference
810 * won't change but parent.getClientId() will, as the suffix changes
811 * depending upon the current row index. A map object on this component
812 * is therefore used to cache the datamodel for each row of the table.
813 * In the normal case where this table is not nested inside a component
814 * that changes its id (like a table does) then this map only ever has
815 * one entry.
816 */
817 private DataModel getDataModel()
818 {
819 DataModel dataModel = null;
820 String clientID = "";
821
822 UIComponent parent = getParent();
823 if (parent != null) {
824 clientID = parent.getClientId(getFacesContext());
825 }
826 dataModel = (DataModel) _dataModelMap.get(clientID);
827 if (dataModel == null)
828 {
829 dataModel = createDataModel();
830 _dataModelMap.put(clientID, dataModel);
831 }
832 return dataModel;
833 }
834
835 /**
836 * Evaluate this object's value property and convert the result into a
837 * DataModel. Normally this object's value property will be a value-binding
838 * which will cause the value to be fetched from some backing bean.
839 * <p>
840 * The result of fetching the value may be a DataModel object, in which
841 * case that object is returned directly. If the value is of type
842 * List, Array, ResultSet, Result, other object or null then an appropriate
843 * wrapper is created and returned.
844 * <p>
845 * Null is never returned by this method.
846 */
847 private DataModel createDataModel()
848 {
849 Object value = getValue();
850 if (value == null)
851 {
852 return EMPTY_DATA_MODEL;
853 }
854 else if (value instanceof DataModel)
855 {
856 return (DataModel) value;
857 }
858 else if (value instanceof List)
859 {
860 return new ListDataModel((List) value);
861 }
862 else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
863 {
864 return new ArrayDataModel((Object[]) value);
865 }
866 else if (value instanceof ResultSet)
867 {
868 return new ResultSetDataModel((ResultSet) value);
869 }
870 else if (value instanceof Result)
871 {
872 return new ResultDataModel((Result) value);
873 }
874 else
875 {
876 return new ScalarDataModel(value);
877 }
878 }
879
880 private static class FacesEventWrapper extends FacesEvent
881 {
882 private static final long serialVersionUID = 6648047974065628773L;
883 private FacesEvent _wrappedFacesEvent;
884 private int _rowIndex;
885
886 public FacesEventWrapper(FacesEvent facesEvent, int rowIndex,
887 UIData redirectComponent)
888 {
889 super(redirectComponent);
890 _wrappedFacesEvent = facesEvent;
891 _rowIndex = rowIndex;
892 }
893
894 public PhaseId getPhaseId()
895 {
896 return _wrappedFacesEvent.getPhaseId();
897 }
898
899 public void setPhaseId(PhaseId phaseId)
900 {
901 _wrappedFacesEvent.setPhaseId(phaseId);
902 }
903
904 public void queue()
905 {
906 _wrappedFacesEvent.queue();
907 }
908
909 public String toString()
910 {
911 return _wrappedFacesEvent.toString();
912 }
913
914 public boolean isAppropriateListener(FacesListener faceslistener)
915 {
916 return _wrappedFacesEvent.isAppropriateListener(faceslistener);
917 }
918
919 public void processListener(FacesListener faceslistener)
920 {
921 _wrappedFacesEvent.processListener(faceslistener);
922 }
923
924 public FacesEvent getWrappedFacesEvent()
925 {
926 return _wrappedFacesEvent;
927 }
928
929 public int getRowIndex()
930 {
931 return _rowIndex;
932 }
933 }
934
935 private static final DataModel EMPTY_DATA_MODEL = new DataModel()
936 {
937 public boolean isRowAvailable()
938 {
939 return false;
940 }
941
942 public int getRowCount()
943 {
944 return 0;
945 }
946
947 public Object getRowData()
948 {
949 throw new IllegalArgumentException();
950 }
951
952 public int getRowIndex()
953 {
954 return -1;
955 }
956
957 public void setRowIndex(int i)
958 {
959 if (i < -1)
960 throw new IllegalArgumentException();
961 }
962
963 public Object getWrappedData()
964 {
965 return null;
966 }
967
968 public void setWrappedData(Object obj)
969 {
970 if (obj == null)
971 return; //Clearing is allowed
972 throw new UnsupportedOperationException(this.getClass().getName()
973 + " UnsupportedOperationException");
974 }
975 };
976
977 public void setValue(Object value)
978 {
979 _value = value;
980 _dataModelMap.clear();
981 _rowStates.clear();
982 _isValidChilds = true;
983 }
984
985 public Object saveState(FacesContext context)
986 {
987 Object[] values = new Object[STATE_SIZE];
988 values[SUPER_STATE_INDEX] = super.saveState(context);
989 values[FIRST_STATE_INDEX] = _first;
990 values[ROWS_STATE_INDEX] = _rows;
991 values[VALUE_STATE_INDEX] = _value;
992 values[VAR_STATE_INDEX] = _var;
993 return values;
994 }
995
996 public void restoreState(FacesContext context, Object state)
997 {
998 Object[] values = (Object[]) state;
999 super.restoreState(context, values[0]);
1000 _first = (Integer) values[FIRST_STATE_INDEX];
1001 _rows = (Integer) values[ROWS_STATE_INDEX];
1002 _value = values[VALUE_STATE_INDEX];
1003 _var = (String) values[VAR_STATE_INDEX];
1004 }
1005
1006 private class EditableValueHolderState
1007 {
1008 private final Object _value;
1009 private final boolean _localValueSet;
1010 private final boolean _valid;
1011 private final Object _submittedValue;
1012
1013 public EditableValueHolderState(EditableValueHolder evh)
1014 {
1015 _value = evh.getLocalValue();
1016 _localValueSet = evh.isLocalValueSet();
1017 _valid = evh.isValid();
1018 _submittedValue = evh.getSubmittedValue();
1019 }
1020
1021 public void restoreState(EditableValueHolder evh)
1022 {
1023 evh.setValue(_value);
1024 evh.setLocalValueSet(_localValueSet);
1025 evh.setValid(_valid);
1026 evh.setSubmittedValue(_submittedValue);
1027 }
1028 }
1029
1030 public UIData()
1031 {
1032 setRendererType(DEFAULT_RENDERER_TYPE);
1033 }
1034
1035 public String getFamily()
1036 {
1037 return COMPONENT_FAMILY;
1038 }
1039
1040 public void setFirst(int first)
1041 {
1042 if (first < 0)
1043 {
1044 throw new IllegalArgumentException("Illegal value for first row: " + first);
1045 }
1046 _first = new Integer(first);
1047 }
1048
1049 /**
1050 * The index of the first row to be displayed, where 0 is the first row.
1051 *
1052 * @JSFProperty
1053 */
1054 public int getFirst()
1055 {
1056 if (_first != null)
1057 {
1058 return _first.intValue();
1059 }
1060 ValueBinding vb = getValueBinding("first");
1061 Number v = vb != null ? (Number) vb.getValue(getFacesContext()) : null;
1062 return v != null ? v.intValue() : DEFAULT_FIRST;
1063 }
1064
1065 /**
1066 * The number of rows to be displayed. Specify zero for all remaining rows in the table.
1067 *
1068 * @JSFProperty
1069 */
1070 public int getRows()
1071 {
1072 if (_rows != null)
1073 {
1074 return _rows.intValue();
1075 }
1076 ValueBinding vb = getValueBinding("rows");
1077 Number v = vb != null ? (Number) vb.getValue(getFacesContext()) : null;
1078 return v != null ? v.intValue() : DEFAULT_ROWS;
1079 }
1080
1081 /**
1082 * An EL expression that specifies the data model that backs this table. The value can be of any type.
1083 *
1084 * A value of type DataModel is used directly. Array-like parameters of type java.util.List, array of Object,
1085 * java.sql.ResultSet, or javax.servlet.jsp.jstl.sql.Result are wrapped in a DataModel.
1086 *
1087 * Other values are wrapped in a DataModel as a single row.
1088 * @JSFProperty
1089 */
1090 public Object getValue()
1091 {
1092 if (_value != null)
1093 return _value;
1094 ValueBinding vb = getValueBinding("value");
1095 return vb != null ? vb.getValue(getFacesContext()) : null;
1096 }
1097 }