View Javadoc

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 org.apache.myfaces.component.html.ext;
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  import java.util.StringTokenizer;
30  
31  import javax.el.ValueExpression;
32  import javax.faces.FacesException;
33  import javax.faces.application.Application;
34  import javax.faces.component.ContextCallback;
35  import javax.faces.component.EditableValueHolder;
36  import javax.faces.component.NamingContainer;
37  import javax.faces.component.UIColumn;
38  import javax.faces.component.UIComponent;
39  import javax.faces.component.UINamingContainer;
40  import javax.faces.context.FacesContext;
41  import javax.faces.el.ValueBinding;
42  import javax.faces.model.DataModel;
43  
44  import org.apache.commons.logging.Log;
45  import org.apache.commons.logging.LogFactory;
46  import org.apache.myfaces.component.NewspaperTable;
47  import org.apache.myfaces.component.UserRoleAware;
48  import org.apache.myfaces.component.UserRoleUtils;
49  import org.apache.myfaces.custom.column.HtmlSimpleColumn;
50  import org.apache.myfaces.custom.crosstable.UIColumns;
51  import org.apache.myfaces.custom.sortheader.HtmlCommandSortHeader;
52  import org.apache.myfaces.renderkit.html.ext.HtmlTableRenderer;
53  import org.apache.myfaces.renderkit.html.util.TableContext;
54  import org.apache.myfaces.shared_tomahawk.renderkit.JSFAttr;
55  import org.apache.myfaces.shared_tomahawk.util.ClassUtils;
56  
57  /**
58   * The MyFacesDataTable extends the standard JSF DataTable by two
59   * important features:
60   * <br/>
61   * <ul>
62   *   <li>Possiblity to save the state of the DataModel.</li>
63   * 
64   *   <li>Support for clickable sort headers (see SortHeader
65   *   component).</li>
66   * </ul>
67   * <br/>
68   * Extended data_table that adds some additional features to the 
69   * standard data_table action: see attribute descriptions for 
70   * preserveDataModel, sortColumn, sortAscending and preserveSort. 
71   * <br/>
72   * Unless otherwise specified, all attributes accept static values or EL expressions.
73   * 
74   * @JSFComponent
75   *   name = "t:dataTable"
76   *   class = "org.apache.myfaces.component.html.ext.HtmlDataTable"
77   *   tagClass = "org.apache.myfaces.generated.taglib.html.ext.HtmlDataTableTag"
78   * @since 1.1.7
79   * @author Thomas Spiegl (latest modification by $Author: lu4242 $)
80   * @author Manfred Geiler
81   * @version $Revision: 986177 $ $Date: 2010-08-16 22:20:38 -0500 (Lun, 16 Ago 2010) $
82   */
83  public abstract class AbstractHtmlDataTable extends HtmlDataTableHack implements UserRoleAware, NewspaperTable
84  {
85      private static final Log log = LogFactory.getLog(AbstractHtmlDataTable.class);
86  
87      private static final int PROCESS_DECODES = 1;
88      private static final int PROCESS_VALIDATORS = 2;
89      private static final int PROCESS_UPDATES = 3;
90  
91      private static final boolean DEFAULT_SORTASCENDING = true;
92      private static final boolean DEFAULT_SORTABLE = false;
93      private static final boolean DEFAULT_EMBEDDED = false;
94      private static final boolean DEFAULT_DETAILSTAMP_EXPANDED = false;
95      private static final Class OBJECT_ARRAY_CLASS = (new Object[0]).getClass();
96  
97      private static final Integer DEFAULT_NEWSPAPER_COLUMNS = new Integer(1);
98      private static final String DEFAULT_NEWSPAPER_ORIENTATION = "vertical";
99      private static final String DEFAULT_DETAILSTAMP_LOCATION = "after";
100 
101     /**
102      * the property names
103      */
104     public static final String NEWSPAPER_COLUMNS_PROPERTY = "newspaperColumns";
105     public static final String SPACER_FACET_NAME = "spacer";
106     public static final String NEWSPAPER_ORIENTATION_PROPERTY = "newspaperOrientation";
107 
108     public static final String DETAIL_STAMP_FACET_NAME = "detailStamp";
109 
110     private _SerializableDataModel _preservedDataModel;
111 
112     private String _forceIdIndexFormula = null;
113     private String _sortColumn = null;
114     private Boolean _sortAscending = null;
115     private String _sortProperty = null;
116     private String _rowStyleClass = null;
117     private String _rowStyle = null;
118     private String _varDetailToggler = null;
119 
120     private int _sortColumnIndex = -1;
121 
122     private boolean _isValidChildren = true;
123 
124     private Map _expandedNodes = new HashMap();
125 
126     //private Map _detailRowStates = new HashMap();
127 
128     private TableContext _tableContext = null;
129 
130     public TableContext getTableContext()
131     {
132         if (_tableContext == null)
133         {
134             _tableContext = new TableContext();
135         }
136         return _tableContext;
137     }
138     
139     public void setDetailStamp(UIComponent facet)
140     {
141         getFacets().put(DETAIL_STAMP_FACET_NAME, facet);
142     }
143 
144     /**
145      * This facet renders an additional row after or before (according
146      * to detailStampLocation value) the current row, usually containing
147      * additional information of the related row. It is toggled usually
148      * using varDetailToggle variable and the method toggleDetail().
149      * 
150      * @JSFFacet name="detailStamp"
151      */
152     public UIComponent getDetailStamp()
153     {
154         return (UIComponent) getFacets().get(DETAIL_STAMP_FACET_NAME);
155     }
156     
157     public String getClientId(FacesContext context)
158     {
159         String standardClientId = super.getClientId(context);
160         int rowIndex = getRowIndex();
161         if (rowIndex == -1)
162         {
163             return standardClientId;
164         }
165 
166         String forcedIdIndex = getForceIdIndexFormula();
167         if (forcedIdIndex == null || forcedIdIndex.length() == 0)
168             return standardClientId;
169 
170         // Trick : Remove the last part starting with NamingContainer.SEPARATOR_CHAR that contains the rowIndex.
171         // It would be best to not resort to String manipulation,
172         // but we can't get super.super.getClientId() :-(
173         int indexLast_ = standardClientId.lastIndexOf(NamingContainer.SEPARATOR_CHAR);
174         if (indexLast_ == -1)
175         {
176             log.info("Could not parse super.getClientId. forcedIdIndex will contain the rowIndex.");
177             return standardClientId + NamingContainer.SEPARATOR_CHAR + forcedIdIndex;
178         }
179 
180         //noinspection UnnecessaryLocalVariable
181         String parsedForcedClientId = standardClientId.substring(0, indexLast_ + 1) + forcedIdIndex;
182         return parsedForcedClientId;
183     }
184 
185     public UIComponent findComponent(String expr)
186     {
187         if (expr.length() > 0 && Character.isDigit(expr.charAt(0)))
188         {
189             int separatorIndex = expr.indexOf(NamingContainer.SEPARATOR_CHAR);
190 
191             String rowIndexStr = expr;
192             String remainingPart = null;
193 
194             if (separatorIndex != -1)
195             {
196                 rowIndexStr = expr.substring(0, separatorIndex);
197                 remainingPart = expr.substring(separatorIndex + 1);
198             }
199 
200             int rowIndex = Integer.valueOf(rowIndexStr).intValue();
201 
202             if (remainingPart == null)
203             {
204                 log.error("Wrong syntax of expression : " + expr +
205                         " rowIndex was provided, but no component name.");
206                 return null;
207             }
208 
209             UIComponent comp = super.findComponent(remainingPart);
210 
211             if (comp == null)
212                 return null;
213 
214             //noinspection UnnecessaryLocalVariable
215             UIComponentPerspective perspective = new UIComponentPerspective(this, comp, rowIndex);
216             return perspective;
217         }
218         else
219         {
220             return super.findComponent(expr);
221         }
222     }
223 
224     @Override
225     public boolean invokeOnComponent(FacesContext context, String clientId,
226             ContextCallback callback) throws FacesException
227     {
228         if (context == null || clientId == null || callback == null)
229         {
230             throw new NullPointerException();
231         }
232 
233         final String baseClientId = getClientId(context);
234         
235         // searching for this component?
236         boolean returnValue = baseClientId.equals(clientId);
237 
238         if (returnValue)
239         {
240             try
241             {
242                 callback.invokeContextCallback(context, this);
243                 return true;
244             }
245             catch (Exception e)
246             {
247                 throw new FacesException(e);
248             }
249         }
250 
251         // Now Look throught facets on this UIComponent
252         for (Iterator<UIComponent> it = this.getFacets().values().iterator(); !returnValue && it.hasNext();)
253         {
254             returnValue = it.next().invokeOnComponent(context, clientId, callback);
255         }
256 
257         if (returnValue)
258         {
259             return returnValue;
260         }
261         
262         // is the component an inner component?
263         if (clientId.startsWith(baseClientId))
264         {
265             // Check if the clientId for the component, which we 
266             // are looking for, has a rowIndex attached
267             char separator = UINamingContainer.SEPARATOR_CHAR;
268                 
269             ValueExpression rowKeyVE = getValueExpression("rowKey");
270             boolean rowKeyFound = false;
271             
272             if (rowKeyVE != null)
273             {
274                 int oldRow = this.getRowIndex();
275                 try
276                 {
277                     // iterate over the rows
278                     int rowsToProcess = getRows();
279                     // if getRows() returns 0, all rows have to be processed
280                     if (rowsToProcess == 0)
281                     {
282                         rowsToProcess = getRowCount();
283                     }
284                     int rowIndex = getFirst();
285                     for (int rowsProcessed = 0; rowsProcessed < rowsToProcess; rowsProcessed++, rowIndex++)
286                     {
287                         setRowIndex(rowIndex);
288                         if (!isRowAvailable())
289                         {
290                             break;
291                         }
292                         
293                         if (clientId.startsWith(getContainerClientId(context)))
294                         {
295                             rowKeyFound = true;
296                             break;
297                         }
298                     }
299                     
300                     if (rowKeyFound)
301                     {
302                         returnValue = invokeOnComponentTraverseRow(context, clientId, callback);
303                     }
304                 }
305                 finally
306                 {
307                     this.setRowIndex(oldRow);
308                 }
309             }
310             if (rowKeyVE == null && clientId.matches(baseClientId + separator+"[0-9]+"+separator+".*"))
311             {
312                 String subId = clientId.substring(baseClientId.length() + 1);
313                 String clientRow = subId.substring(0, subId.indexOf(separator));
314     
315                 //Now we save the current position
316                 int oldRow = this.getRowIndex();
317                 
318                 // try-finally --> make sure, that the old row index is restored
319                 try
320                 {
321                     //The conversion is safe, because its already checked on the
322                     //regular expresion
323                     this.setRowIndex(Integer.parseInt(clientRow));
324                     
325                     // check, if the row is available
326                     if (!isRowAvailable())
327                     {
328                         return false;
329                     }
330         
331                     returnValue = invokeOnComponentTraverseRow(context, clientId, callback);
332                 }
333                 finally
334                 {
335                     //Restore the old position. Doing this prevent
336                     //side effects.
337                     this.setRowIndex(oldRow);
338                 }
339             }
340             else
341             {
342                 // MYFACES-2370: search the component in the childrens' facets too.
343                 // We have to check the childrens' facets here, because in MyFaces
344                 // the rowIndex is not attached to the clientId for the children of
345                 // facets of the UIColumns. However, in RI the rowIndex is 
346                 // attached to the clientId of UIColumns' Facets' children.
347                 for (Iterator<UIComponent> itChildren = this.getChildren().iterator();
348                         !returnValue && itChildren.hasNext();)
349                 {
350                     UIComponent child = itChildren.next();
351                     // This is the only part different to UIData.invokeOnComponent. Since we have
352                     // an auto wrapping on columns feature, it is necessary to check columns ids
353                     // without row for invokeOnComponent, but do not traverse all elements, so
354                     // save/restore algorithm could be able to remove / add them.  
355                     if (child instanceof UIColumn && clientId.equals(child.getClientId(context)))
356                     {
357                         try {
358                             callback.invokeContextCallback(context, child);
359                         } catch (Exception e) {
360                             throw new FacesException(e);
361                         }
362                         returnValue = true;
363                     }
364                     // process the child's facets
365                     for (Iterator<UIComponent> itChildFacets = child.getFacets().values().iterator(); 
366                             !returnValue && itChildFacets.hasNext();)
367                     {
368                         //recursive call to find the component
369                         returnValue = itChildFacets.next().invokeOnComponent(context, clientId, callback);
370                     }
371                 }
372             }
373         }
374 
375         return returnValue;
376     }
377     
378     private boolean invokeOnComponentTraverseRow(FacesContext context, String clientId,
379             ContextCallback callback)
380     {
381         boolean returnValue = false;
382         for (Iterator<UIComponent> it1 = getChildren().iterator(); 
383             !returnValue && it1.hasNext();)
384         {
385             //recursive call to find the component
386             returnValue = it1.next().invokeOnComponent(context, clientId, callback);
387         }
388 
389         if (!returnValue)
390         {
391             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME);
392             if (detailStampFacet != null)
393             {
394                 returnValue = detailStampFacet.invokeOnComponent(context, clientId, callback);
395             }
396         }
397         return returnValue;
398     }
399 
400     public void setRowIndex(int rowIndex)
401     {
402         //FacesContext facesContext = FacesContext.getCurrentInstance();
403 
404         if (rowIndex < -1)
405         {
406             throw new IllegalArgumentException("rowIndex is less than -1");
407         }
408 
409         //UIComponent facet = getFacet(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
410         /*Just for obtaining an iterator which must be passed to saveDescendantComponentStates()*/
411         //Set set = new HashSet();
412         //set.add(facet);
413         //if (getRowIndex() != -1 && facet != null)
414         //{
415         //    _detailRowStates.put(getClientId(facesContext), saveDescendantComponentStates(set.iterator(), false));
416         //}
417 
418         String rowIndexVar = getRowIndexVar();
419         String rowCountVar = getRowCountVar();
420         String previousRowDataVar = getPreviousRowDataVar();
421         if (rowIndexVar != null || rowCountVar != null || previousRowDataVar != null)
422         {
423             Map requestMap = FacesContext.getCurrentInstance().getExternalContext().getRequestMap();
424 
425             if (previousRowDataVar != null && rowIndex >= 0) //we only need to provide the previousRowDataVar for a valid rowIndex
426             {
427                 if (isRowAvailable())
428                 {
429                     //previous row is available
430                     requestMap.put(previousRowDataVar, getRowData());
431                 }
432                 else
433                 {
434                     //no previous row available
435                     requestMap.put(previousRowDataVar, null);
436                 }
437             }
438 
439             super.setRowIndex(rowIndex);
440 
441             if (rowIndex >= 0)
442             {
443                 //regular row index, update request scope variables
444                 if (rowIndexVar != null)
445                 {
446                     requestMap.put(rowIndexVar, new Integer(rowIndex));
447                 }
448 
449                 if (rowCountVar != null)
450                 {
451                     requestMap.put(rowCountVar, new Integer(getRowCount()));
452                 }
453             }
454             else
455             {
456                 //rowIndex == -1 means end of loop --> remove request scope variables
457                 if (rowIndexVar != null)
458                 {
459                     requestMap.remove(rowIndexVar);
460                 }
461 
462                 if (rowCountVar != null)
463                 {
464                     requestMap.remove(rowCountVar);
465                 }
466 
467                 if (previousRowDataVar != null)
468                 {
469                     requestMap.remove(previousRowDataVar);
470                 }
471             }
472         }
473         else
474         {
475             // no extended var attributes defined, no special treatment
476             super.setRowIndex(rowIndex);
477         }
478 
479         //if (rowIndex != -1 && facet != null)
480         //{
481         //    Object rowState = _detailRowStates.get(getClientId(facesContext));
482 
483         //    restoreDescendantComponentStates(set.iterator(),
484         //            rowState, false);
485 
486         //}
487 
488         if (_varDetailToggler != null)
489         {
490             Map requestMap = getFacesContext().getExternalContext().getRequestMap();
491             requestMap.put(_varDetailToggler, this);
492         }
493     }
494 
495     protected Object saveDescendantComponentStates()
496     {
497         if (!getFacets().isEmpty())
498         {
499             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
500             if (detailStampFacet != null)
501             {
502                 return saveDescendantComponentStates(new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), false);
503             }
504         }
505         return super.saveDescendantComponentStates();
506     }
507     
508     protected void restoreDescendantComponentStates(Object state)
509     {
510         if (!getFacets().isEmpty())
511         {
512             UIComponent detailStampFacet = getFacet(DETAIL_STAMP_FACET_NAME); 
513             if (detailStampFacet != null)
514             {
515                 restoreDescendantComponentStates(new _DetailStampFacetAndChildrenIterator(detailStampFacet, getChildren()), state, false);
516                 return;
517             }
518         }
519         super.restoreDescendantComponentStates(state);
520     }
521 
522     public void processDecodes(FacesContext context)
523     {
524         if (!isRendered())
525         {
526             return;
527         }
528 
529         // We must remove and then replace the facet so that
530         // it is not processed by default facet handling code
531         //
532         //Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
533         //super.processDecodes(context);
534         //if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME, (UIComponent)facet);
535         setRowIndex(-1);
536         processFacets(context, PROCESS_DECODES);
537         processColumnFacets(context, PROCESS_DECODES);
538         processColumnChildren(context, PROCESS_DECODES);
539         setRowIndex(-1);
540         try
541         {
542             decode(context);
543         }
544         catch (RuntimeException e)
545         {
546             context.renderResponse();
547             throw e;
548         }
549 
550         setRowIndex(-1);
551         processColumns(context, PROCESS_DECODES);
552         setRowIndex(-1);
553         processDetails(context, PROCESS_DECODES);
554         setRowIndex(-1);
555     }
556     
557     private void processFacets(FacesContext context, int processAction)
558     {
559         for (Iterator it = getFacets().entrySet().iterator(); it.hasNext(); )
560         {
561             Map.Entry entry = (Map.Entry) it.next();
562             if (!DETAIL_STAMP_FACET_NAME.equals((String)entry.getKey()))
563             {
564                 process(context, (UIComponent) entry.getValue(), processAction);
565             }
566         }
567     }
568 
569     /**
570      * Invoke the specified phase on all facets of all UIColumn children of this component. Note that no methods are
571      * called on the UIColumn child objects themselves.
572      * 
573      * @param context
574      *            is the current faces context.
575      * @param processAction
576      *            specifies a JSF phase: decode, validate or update.
577      */
578     private void processColumnFacets(FacesContext context, int processAction)
579     {
580         for (Iterator childIter = getChildren().iterator(); childIter.hasNext();)
581         {
582             UIComponent child = (UIComponent) childIter.next();
583             if (child instanceof UIColumn)
584             {
585                 if (!child.isRendered())
586                 {
587                     //Column is not visible
588                     continue;
589                 }
590                 for (Iterator facetsIter = child.getFacets().values()
591                         .iterator(); facetsIter.hasNext();)
592                 {
593                     UIComponent facet = (UIComponent) facetsIter.next();
594                     process(context, facet, processAction);
595                 }
596             }
597         }
598     }
599 
600     /**
601      * Invoke the specified phase on all non-facet children of all UIColumn children of this component. Note that no
602      * methods are called on the UIColumn child objects themselves.
603      * 
604      * @param context
605      *            is the current faces context.
606      * @param processAction
607      *            specifies a JSF phase: decode, validate or update.
608      */
609     private void processColumnChildren(FacesContext context, int processAction)
610     {
611         int first = getFirst();
612         int rows = getRows();
613         int last;
614         if (rows == 0)
615         {
616             last = getRowCount();
617         }
618         else
619         {
620             last = first + rows;
621         }
622         for (int rowIndex = first; last==-1 || rowIndex < last; rowIndex++)
623         {
624             setRowIndex(rowIndex);
625 
626             //scrolled past the last row
627             if (!isRowAvailable())
628                 break;
629 
630             for (Iterator it = getChildren().iterator(); it.hasNext();)
631             {
632                 UIComponent child = (UIComponent) it.next();
633                 if (child instanceof UIColumn)
634                 {
635                     if (!child.isRendered())
636                     {
637                         //Column is not visible
638                         continue;
639                     }
640                     for (Iterator columnChildIter = child.getChildren()
641                             .iterator(); columnChildIter.hasNext();)
642                     {
643                         UIComponent columnChild = (UIComponent) columnChildIter
644                                 .next();
645                         process(context, columnChild, processAction);
646                     }
647                 }
648             }
649         }
650     }
651 
652     /**
653      * @param context
654      * @param processAction
655      */
656     private void processDetails(FacesContext context, int processAction)
657     {
658         UIComponent facet = getFacet(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
659 
660         if (facet != null)
661         {
662             int first = getFirst();
663             int rows = getRows();
664             int last;
665             if (rows == 0)
666             {
667                 last = getRowCount();
668             }
669             else
670             {
671                 last = first + rows;
672             }
673             for (int rowIndex = first; last == -1 || rowIndex < last; rowIndex++)
674             {
675                 setRowIndex(rowIndex);
676 
677                 //scrolled past the last row
678                 if (!isRowAvailable())
679                 {
680                     break;
681                 }
682 
683                 if (!isCurrentDetailExpanded())
684                 {
685                     continue;
686                 }
687 
688                 // If we are in the decode phase, the values restored into our
689                 // facet in setRowIndex() may be incorrect. This will happen
690                 // if some input fields are rendered in some rows, but not
691                 // rendered in others. In this case the input field components
692                 // will still contain the _submittedValue from the previous row
693                 // that had that input field and _submittedValue will not be set to
694                 // null by the process() method if there was no value submitted.
695                 // Thus, an invalid component state for that row will be saved in
696                 // _detailRowStates. The validation phase will not put a null into
697                 // _sumbittedValue either, b/c the component is not rendered, so
698                 // validation code doesn't run. This erroneous value will propagate all the way
699                 // to the render phase, and result in all rows on the current page being
700                 // rendered with the "stuck" _submittedValue, rather than evaluating the
701                 // value to render for every row.
702                 //
703                 // We can fix this by initializing _submittedValue of all input fields in the facet
704                 // to null before calling the process() method below during the decode phase.
705                 //
706                 if (PROCESS_DECODES == processAction)
707                 {
708                     resetAllSubmittedValues(facet);
709                 }
710 
711                 process(context, facet, processAction);
712 
713                 // This code comes from TOMAHAWK-493, but really the problem was caused by
714                 // TOMAHAWK-1534, by a bad comparison of rowIndex. The solution proposed is override
715                 // the default iterator for save/restore on HtmlDataTableHack.setRowIndex(), to
716                 // include the detailStamp. In this way, we'll be sure that the state is correctly
717                 // saved and the component clientId is reset for all components that requires it
718                 //if ( rowIndex == (last - 1) )
719                 //{
720                 //    Set set = new HashSet();
721                 //    set.add(facet);
722                 //    _detailRowStates.put(
723                 //            getClientId(FacesContext.getCurrentInstance()),
724                 //                saveDescendantComponentStates(set.iterator(),false));
725                 //}
726             }
727         }
728     }
729 
730     private void resetAllSubmittedValues(UIComponent component)
731     {
732         if (component instanceof EditableValueHolder)
733         {
734             ((EditableValueHolder) component).setSubmittedValue(null);
735         }
736 
737         for (Iterator it = component.getFacetsAndChildren(); it.hasNext();)
738         {
739             resetAllSubmittedValues((UIComponent) it.next());
740         }
741     }
742 
743     /**
744      * @param context
745      * @param processAction
746      */
747     private void processColumns(FacesContext context, int processAction)
748     {
749         for (Iterator it = getChildren().iterator(); it.hasNext();)
750         {
751             UIComponent child = (UIComponent) it.next();
752             if (child instanceof UIColumns)
753             {
754                 process(context, child, processAction);
755             }
756         }
757     }
758 
759     private void process(FacesContext context, UIComponent component, int processAction)
760     {
761         switch (processAction)
762         {
763             case PROCESS_DECODES:
764                 component.processDecodes(context);
765                 break;
766             case PROCESS_VALIDATORS:
767                 component.processValidators(context);
768                 break;
769             case PROCESS_UPDATES:
770                 component.processUpdates(context);
771                 break;
772         }
773     }
774 
775     public void processValidators(FacesContext context)
776     {
777         if (!isRendered())
778         {
779             return;
780         }
781         // We must remove and then replace the facet so that
782         // it is not processed by default facet handling code
783         //
784         //Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
785         //super.processValidators(context);
786         //if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME,(UIComponent) facet);
787         setRowIndex(-1);
788         processFacets(context, PROCESS_VALIDATORS);
789         processColumnFacets(context, PROCESS_VALIDATORS);
790         processColumnChildren(context, PROCESS_VALIDATORS);
791         setRowIndex(-1);
792 
793         processColumns(context, PROCESS_VALIDATORS);
794         setRowIndex(-1);
795         processDetails(context, PROCESS_VALIDATORS);
796         setRowIndex(-1);
797 
798         if (context.getRenderResponse())
799         {
800             _isValidChildren = false;
801         }
802     }
803 
804     public void processUpdates(FacesContext context)
805     {
806         if (!isRendered())
807         {
808             return;
809         }
810 
811         // We must remove and then replace the facet so that
812         // it is not processed by default facet handling code
813         //
814         //Object facet = getFacets().remove(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME);
815         //super.processUpdates(context);
816         //if ( facet != null ) getFacets().put(HtmlTableRenderer.DETAIL_STAMP_FACET_NAME,(UIComponent) facet);
817         
818         setRowIndex(-1);
819         processFacets(context, PROCESS_UPDATES);
820         processColumnFacets(context, PROCESS_UPDATES);
821         processColumnChildren(context, PROCESS_UPDATES);
822         setRowIndex(-1);
823 
824         processColumns(context, PROCESS_UPDATES);
825         setRowIndex(-1);
826         processDetails(context, PROCESS_UPDATES);
827         setRowIndex(-1);
828 
829         if (isPreserveDataModel())
830         {
831             updateModelFromPreservedDataModel(context);
832         }
833 
834         if (context.getRenderResponse())
835         {
836             _isValidChildren = false;
837         }
838     }
839 
840     private void updateModelFromPreservedDataModel(FacesContext context)
841     {
842         ValueBinding vb = getValueBinding("value");
843         if (vb != null && !vb.isReadOnly(context))
844         {
845             _SerializableDataModel dm = (_SerializableDataModel) getDataModel();
846             Class type = (getValueType() == null) ? 
847                     vb.getType(context) : 
848                         ClassUtils.simpleClassForName(getValueType());
849             Class dmType = dm.getClass();
850             if (DataModel.class.isAssignableFrom(type))
851             {
852                 vb.setValue(context, dm);
853             }
854             else if (List.class.isAssignableFrom(type) || _SerializableListDataModel.class.isAssignableFrom(dmType))
855             {
856                 vb.setValue(context, dm.getWrappedData());
857             }
858             else if (OBJECT_ARRAY_CLASS.isAssignableFrom(type))
859             {
860                 List lst = (List) dm.getWrappedData();
861                 vb.setValue(context, lst.toArray(new Object[lst.size()]));
862             }
863             else if (ResultSet.class.isAssignableFrom(type))
864             {
865                 throw new UnsupportedOperationException(this.getClass().getName()
866                         + " UnsupportedOperationException");
867             }
868             else
869             {
870                 //Assume scalar data model
871                 List lst = (List) dm.getWrappedData();
872                 if (lst!= null && lst.size() > 0)
873                 {
874                     vb.setValue(context, lst.get(0));
875                 }
876                 else
877                 {
878                     vb.setValue(context, null);
879                 }
880             }
881         }
882         _preservedDataModel = null;
883     }
884 
885     public void encodeBegin(FacesContext context) throws IOException
886     {
887         if (!isRendered())
888             return;
889 
890         if (_isValidChildren && !hasErrorMessages(context))
891         {
892             _preservedDataModel = null;
893         }
894 
895         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
896         {
897             UIComponent component = (UIComponent) iter.next();
898             if (component instanceof UIColumns)
899             {
900                 // Merge the columns from the tomahawk dynamic component
901                 // into this object.
902                 ((UIColumns) component).encodeTableBegin(context);
903             }
904         }
905 
906         //replace facet header content component of the columns, with a new command sort header component
907         //if sortable=true, replace it for all, if not just for the columns marked as sortable
908         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
909         {
910             UIComponent component = (UIComponent) iter.next();
911             if (component instanceof UIColumn)
912             {
913                 UIColumn aColumn = (UIColumn) component;
914                 UIComponent headerFacet = aColumn.getHeader();
915 
916                 boolean replaceHeaderFacets = isSortable(); //if the table is sortable, all
917                 //header facets can be changed if needed
918                 String columnName = null;
919                 String propertyName = null;
920                 boolean defaultSorted = false;
921 
922                 if (aColumn instanceof HtmlSimpleColumn)
923                 {
924                     HtmlSimpleColumn asColumn = (HtmlSimpleColumn) aColumn;
925                     propertyName = asColumn.getSortPropertyName();
926                     defaultSorted = asColumn.isDefaultSorted();
927 
928                     if (asColumn.isSortable())
929                         replaceHeaderFacets = true;
930                 }
931 
932                 //replace header facet with a sortable header component if needed
933                 if (replaceHeaderFacets && isSortHeaderNeeded(aColumn, headerFacet))
934                 {
935                     propertyName = propertyName != null ? propertyName : getSortPropertyFromEL(aColumn);
936                     if (propertyName == null)
937                         log.warn("Couldn't determine sort property for column [" + aColumn.getId() + "].");
938 
939                     if (headerFacet != null)
940                     {
941                         HtmlCommandSortHeader sortHeader = createSortHeaderComponent(context, aColumn, headerFacet, propertyName);
942                         columnName = sortHeader.getColumnName();
943 
944                         aColumn.setHeader(sortHeader);
945                         sortHeader.setParent(aColumn);
946                     }
947                 }
948                 else if (headerFacet instanceof HtmlCommandSortHeader)
949                 {
950                     //command sort headers are already in place, just store the column name and sort property name
951                     HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) headerFacet;
952                     columnName = sortHeader.getColumnName();
953                     propertyName = sortHeader.getPropertyName();
954 
955                     //if the command sort header component doesn't specify a sort property, determine it
956                     if (propertyName == null)
957                     {
958                         propertyName = getSortPropertyFromEL(aColumn);
959                         sortHeader.setPropertyName(propertyName);
960                     }
961 
962                     if (propertyName == null)
963                         log.warn("Couldn't determine sort property for column [" + aColumn.getId() + "].");
964                 }
965 
966                 //make the column marked as default sorted be the current sorted column
967                 if (defaultSorted && getSortColumn() == null)
968                 {
969                     setSortColumn(columnName);
970                     setSortProperty(propertyName);
971                 }
972             }
973         }
974 
975         // Now invoke the superclass encodeBegin, which will eventually
976         // execute the encodeBegin for the associated renderer.
977         super.encodeBegin(context);
978     }
979 
980     /**
981      *
982      */
983     protected boolean isSortHeaderNeeded(UIColumn parentColumn, UIComponent headerFacet)
984     {
985         return !(headerFacet instanceof HtmlCommandSortHeader);
986     }
987 
988     /**
989      *
990      */
991     protected HtmlCommandSortHeader createSortHeaderComponent(FacesContext context, UIColumn parentColumn, UIComponent initialHeaderFacet, String propertyName)
992     {
993         Application application = context.getApplication();
994 
995         HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) application.createComponent(HtmlCommandSortHeader.COMPONENT_TYPE);
996         String id = context.getViewRoot().createUniqueId();
997         sortHeader.setId(id);
998         sortHeader.setColumnName(id);
999         sortHeader.setPropertyName(propertyName);
1000         sortHeader.setArrow(true);
1001         sortHeader.setImmediate(true); //needed to work when dataScroller is present
1002         sortHeader.getChildren().add(initialHeaderFacet);
1003         initialHeaderFacet.setParent(sortHeader);
1004 
1005         return sortHeader;
1006     }
1007 
1008     /**
1009      *
1010      */
1011     protected String getSortPropertyFromEL(UIComponent component)
1012     {
1013         if (getVar() == null)
1014         {
1015             log.warn("There is no 'var' specified on the dataTable, sort properties cannot be determined automaticaly.");
1016             return null;
1017         }
1018 
1019         for (Iterator iter = component.getChildren().iterator(); iter.hasNext();)
1020         {
1021             UIComponent aChild = (UIComponent) iter.next();
1022             if (aChild.isRendered())
1023             {
1024                 ValueBinding vb = aChild.getValueBinding("value");
1025                 if (vb != null)
1026                 {
1027                     String expressionString = vb.getExpressionString();
1028 
1029                     int varIndex = expressionString.indexOf(getVar() + ".");
1030                     if (varIndex != -1)
1031                     {
1032                         int varEndIndex = varIndex + getVar().length();
1033                         String tempProp = expressionString.substring(varEndIndex + 1, expressionString.length());
1034 
1035                         StringTokenizer tokenizer = new StringTokenizer(tempProp, " +[]{}-/*|?:&<>!=()%");
1036                         if (tokenizer.hasMoreTokens())
1037                             return tokenizer.nextToken();
1038                     }
1039                 }
1040                 else
1041                 {
1042                     String sortProperty = getSortPropertyFromEL(aChild);
1043                     if (sortProperty != null)
1044                         return sortProperty;
1045                 }
1046             }
1047         }
1048 
1049         return null;
1050     }
1051 
1052     /**
1053      * @return the index coresponding to the given column name.
1054      */
1055     protected int columnNameToIndex(String columnName)
1056     {
1057         int index = 0;
1058         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
1059         {
1060             UIComponent aChild = (UIComponent) iter.next();
1061             if (aChild instanceof UIColumn)
1062             {
1063                 UIComponent headerFacet = ((UIColumn) aChild).getHeader();
1064                 if (headerFacet != null && headerFacet instanceof HtmlCommandSortHeader)
1065                 {
1066                     HtmlCommandSortHeader sortHeader = (HtmlCommandSortHeader) headerFacet;
1067                     if (columnName != null && columnName.equals(sortHeader.getColumnName()))
1068                         return index;
1069                 }
1070             }
1071 
1072             index += 1;
1073         }
1074 
1075         return -1;
1076     }
1077 
1078     /**
1079      * @see javax.faces.component.UIData#encodeEnd(javax.faces.context.FacesContext)
1080      */
1081     public void encodeEnd(FacesContext context) throws IOException
1082     {
1083         super.encodeEnd(context);
1084         for (Iterator iter = getChildren().iterator(); iter.hasNext();)
1085         {
1086             UIComponent component = (UIComponent) iter.next();
1087             if (component instanceof UIColumns)
1088             {
1089                 ((UIColumns) component).encodeTableEnd(context);
1090             }
1091         }
1092     }
1093 
1094     /**
1095      * The index of the first row to be displayed, where 0 is the first row.
1096      * 
1097      * @JSFProperty
1098      */
1099     public int getFirst()
1100     {
1101         if (_preservedDataModel != null)
1102         {
1103             //Rather get the currently restored DataModel attribute
1104             return _preservedDataModel.getFirst();
1105         }
1106         else
1107         {
1108             return super.getFirst();
1109         }
1110     }
1111 
1112     public void setFirst(int first)
1113     {
1114         if (_preservedDataModel != null)
1115         {
1116             //Also change the currently restored DataModel attribute
1117             _preservedDataModel.setFirst(first);
1118         }
1119         super.setFirst(first);
1120     }
1121 
1122     /**
1123      *  The number of rows to be displayed. Specify zero for all remaining rows in the table.
1124      * 
1125      * @JSFProperty
1126      */
1127     public int getRows()
1128     {
1129         if (_preservedDataModel != null)
1130         {
1131             //Rather get the currently restored DataModel attribute
1132             return _preservedDataModel.getRows();
1133         }
1134         else
1135         {
1136             return super.getRows();
1137         }
1138     }
1139 
1140     public void setRows(int rows)
1141     {
1142         if (_preservedDataModel != null)
1143         {
1144             //Also change the currently restored DataModel attribute
1145             _preservedDataModel.setRows(rows);
1146         }
1147         super.setRows(rows);
1148     }
1149 
1150     public Object saveState(FacesContext context)
1151     {
1152         boolean preserveSort = isPreserveSort();
1153 
1154         Object[] values = new Object[15];
1155         values[0] = super.saveState(context);
1156         values[1] = _preserveDataModel;
1157 
1158         if (isPreserveDataModel())
1159         {
1160             _preservedDataModel = getSerializableDataModel();
1161             values[2] = saveAttachedState(context, _preservedDataModel);
1162         }
1163         else
1164         {
1165             values[2] = null;
1166         }
1167         values[3] = _preserveSort;
1168         values[4] = _forceIdIndexFormula;
1169         values[5] = _sortColumn;
1170         values[6] = _sortAscending;
1171         values[7] = _sortProperty;
1172 
1173         values[8] = _rowStyleClass;
1174         values[9] = _rowStyle;
1175 
1176         values[10] = preserveSort ? getSortColumn() : null;
1177         values[11] = preserveSort ? Boolean.valueOf(isSortAscending()) : null;
1178 
1179         values[12] = _varDetailToggler;
1180         values[13] = _expandedNodes;
1181         values[14] = new Integer(_sortColumnIndex);
1182 
1183         return values;
1184     }
1185 
1186     /**
1187      * @see org.apache.myfaces.component.html.ext.HtmlDataTableHack#getDataModel()
1188      */
1189     protected DataModel getDataModel()
1190     {
1191         if (_preservedDataModel != null)
1192         {
1193             setDataModel(_preservedDataModel);
1194             _preservedDataModel = null;
1195         }
1196 
1197         return super.getDataModel();
1198     }
1199 
1200     /**
1201      * @see org.apache.myfaces.component.html.ext.HtmlDataTableHack#createDataModel()
1202      */
1203     protected DataModel createDataModel()
1204     {
1205         DataModel dataModel = super.createDataModel();
1206 
1207         boolean isSortable = isSortable();
1208 
1209         if (!(dataModel instanceof SortableModel))
1210         {
1211             //if sortable=true make each column sortable
1212             //if sortable=false, check to see if at least one column sortable case in which
1213             //the current model needs to be wrapped by a sortable one.
1214             for (Iterator iter = getChildren().iterator(); iter.hasNext();)
1215             {
1216                 UIComponent component = (UIComponent) iter.next();
1217                 if (component instanceof HtmlSimpleColumn)
1218                 {
1219                     HtmlSimpleColumn aColumn = (HtmlSimpleColumn) component;
1220                     if (isSortable())
1221                         aColumn.setSortable(true);
1222 
1223                     if (aColumn.isSortable())
1224                         isSortable = true;
1225 
1226                     if (aColumn.isDefaultSorted() && getSortColumn() == null)
1227                         setSortProperty(aColumn.getSortPropertyName());
1228                 }
1229             }
1230 
1231             if (isSortable)
1232                 dataModel = new SortableModel(dataModel);
1233         }
1234 
1235         if (isSortable && getSortProperty() != null)
1236         {
1237             SortCriterion criterion = new SortCriterion(getSortProperty(), isSortAscending());
1238             List criteria = new ArrayList();
1239             criteria.add(criterion);
1240 
1241             ((SortableModel) dataModel).setSortCriteria(criteria);
1242         }
1243 
1244         return dataModel;
1245     }
1246 
1247     public void restoreState(FacesContext context, Object state)
1248     {
1249         Object[] values = (Object[]) state;
1250         super.restoreState(context, values[0]);
1251         _preserveDataModel = (Boolean) values[1];
1252         if (isPreserveDataModel())
1253         {
1254             _preservedDataModel = (_SerializableDataModel) restoreAttachedState(context, values[2]);
1255         }
1256         else
1257         {
1258             _preservedDataModel = null;
1259         }
1260         _preserveSort = (Boolean) values[3];
1261         _forceIdIndexFormula = (String) values[4];
1262         _sortColumn = (String) values[5];
1263         _sortAscending = (Boolean) values[6];
1264         _sortProperty = (String) values[7];
1265 
1266         _rowStyleClass = (String) values[8];
1267         _rowStyle = (String) values[9];
1268 
1269         if (isPreserveSort())
1270         {
1271             String sortColumn = (String) values[10];
1272             Boolean sortAscending = (Boolean) values[11];
1273             if (sortColumn != null && sortAscending != null)
1274             {
1275                 ValueBinding vb = getValueBinding("sortColumn");
1276                 if (vb != null && !vb.isReadOnly(context))
1277                 {
1278                     vb.setValue(context, sortColumn);
1279                 }
1280 
1281                 vb = getValueBinding("sortAscending");
1282                 if (vb != null && !vb.isReadOnly(context))
1283                 {
1284                     vb.setValue(context, sortAscending);
1285                 }
1286             }
1287         }
1288 
1289         _varDetailToggler = (String) values[12];
1290         _expandedNodes = (Map) values[13];
1291         _sortColumnIndex = values[14] != null ? ((Integer) values[14]).intValue() : -1;
1292     }
1293 
1294     public _SerializableDataModel getSerializableDataModel()
1295     {
1296         DataModel dm = getDataModel();
1297         if (dm instanceof _SerializableDataModel)
1298         {
1299             return (_SerializableDataModel) dm;
1300         }
1301         return createSerializableDataModel();
1302     }
1303 
1304     /**
1305      * @return _SerializableDataModel
1306      */
1307     private _SerializableDataModel createSerializableDataModel()
1308     {
1309         Object value = getValue();
1310         if (value == null)
1311         {
1312             return null;
1313         }
1314         else if (value instanceof DataModel)
1315         {
1316             return new _SerializableDataModel(getFirst(), getRows(), (DataModel) value);
1317         }
1318         else if (value instanceof List)
1319         {
1320             return new _SerializableListDataModel(getFirst(), getRows(), (List) value);
1321         }
1322         // accept a Collection is not supported in the Spec
1323         else if (value instanceof Collection)
1324         {
1325             return new _SerializableListDataModel(getFirst(), getRows(), new ArrayList((Collection) value));
1326         }
1327         else if (OBJECT_ARRAY_CLASS.isAssignableFrom(value.getClass()))
1328         {
1329             return new _SerializableArrayDataModel(getFirst(), getRows(), (Object[]) value);
1330         }
1331         else if (value instanceof ResultSet)
1332         {
1333             return new _SerializableResultSetDataModel(getFirst(), getRows(), (ResultSet) value);
1334         }
1335         else if (value instanceof javax.servlet.jsp.jstl.sql.Result)
1336         {
1337             return new _SerializableResultDataModel(getFirst(), getRows(),
1338                     (javax.servlet.jsp.jstl.sql.Result) value);
1339         }
1340         else
1341         {
1342             return new _SerializableScalarDataModel(getFirst(), getRows(), value);
1343         }
1344     }
1345 
1346     public boolean isRendered()
1347     {
1348         if (!UserRoleUtils.isVisibleOnUserRole(this))
1349             return false;
1350         return super.isRendered();
1351     }
1352 
1353     public void setForceIdIndexFormula(String forceIdIndexFormula)
1354     {
1355         _forceIdIndexFormula = forceIdIndexFormula;
1356         ValueBinding vb = getValueBinding("forceIdIndexFormula");
1357         if (vb != null)
1358         {
1359             vb.setValue(getFacesContext(), _forceIdIndexFormula);
1360             _forceIdIndexFormula = null;
1361         }
1362     }
1363 
1364     /**
1365      * A formula that overrides the default row index in the 
1366      * construction of table's body components. 
1367      * 
1368      * Example : #{myRowVar.key} Warning, the EL should 
1369      * evaluate to a unique value for each row !
1370      * 
1371      * @JSFProperty
1372      */
1373     public String getForceIdIndexFormula()
1374     {
1375         if (_forceIdIndexFormula != null)
1376             return _forceIdIndexFormula;
1377         ValueBinding vb = getValueBinding("forceIdIndexFormula");
1378         if (vb == null)
1379             return null;
1380         Object eval = vb.getValue(getFacesContext());
1381         return eval == null ? null : eval.toString();
1382     }
1383 
1384     /**
1385      * Specify what column the data should be sorted on.
1386      * <p/>
1387      * Note that calling this method <i>immediately</i> stores the value
1388      * via any value-binding with name "sortColumn". This is done because
1389      * this method is called by the HtmlCommandSortHeader component when
1390      * the user has clicked on a column's sort header. In this case, the
1391      * the model getter method mapped for name "value" needs to read this
1392      * value in able to return the data in the desired order - but the
1393      * HtmlCommandSortHeader component is usually "immediate" in order to
1394      * avoid validating the enclosing form. Yes, this is rather hacky -
1395      * but it works.
1396      */
1397     public void setSortColumn(String sortColumn)
1398     {
1399         _sortColumn = sortColumn;
1400         // update model is necessary here, because processUpdates is never called
1401         // reason: HtmlCommandSortHeader.isImmediate() == true
1402         ValueBinding vb = getValueBinding("sortColumn");
1403         if (vb != null)
1404         {
1405             vb.setValue(getFacesContext(), _sortColumn);
1406             _sortColumn = null;
1407         }
1408 
1409         setSortColumnIndex(columnNameToIndex(sortColumn));
1410     }
1411 
1412     /**
1413      * Value reference to a model property that gives the current 
1414      * sort column name. The target String property is set to 
1415      * the "columnName" of whichever column has been chosen 
1416      * to sort by, and the method which is bound to the "value" 
1417      * attribute of this table (ie which provides the DataModel used) 
1418      * is expected to use this property to determine how to sort 
1419      * the DataModel's contents.
1420      * 
1421      * @JSFProperty
1422      */
1423     public String getSortColumn()
1424     {
1425         if (_sortColumn != null) return _sortColumn;
1426         ValueBinding vb = getValueBinding("sortColumn");
1427         return vb != null ? (String) vb.getValue(getFacesContext()) : null;
1428     }
1429 
1430     public void setSortAscending(boolean sortAscending)
1431     {
1432         _sortAscending = Boolean.valueOf(sortAscending);
1433         // update model is necessary here, because processUpdates is never called
1434         // reason: HtmlCommandSortHeader.isImmediate() == true
1435         ValueBinding vb = getValueBinding("sortAscending");
1436         if (vb != null)
1437         {
1438             vb.setValue(getFacesContext(), _sortAscending);
1439             _sortAscending = null;
1440         }
1441     }
1442 
1443     /**
1444      * Value reference to a model property that gives the current 
1445      * sort direction. The target Boolean property is set to true 
1446      * when the selected sortColumn should be sorted in ascending 
1447      * order, and false otherwise. The method which is bound to 
1448      * the "value" attribute of this table (ie which provides the 
1449      * DataModel used) is expected to use this property to 
1450      * determine how to sort the DataModel's contents.
1451      * 
1452      * @JSFProperty
1453      *   defaultValue = "true"
1454      */
1455     public boolean isSortAscending()
1456     {
1457         if (_sortAscending != null)
1458             return _sortAscending.booleanValue();
1459         ValueBinding vb = getValueBinding("sortAscending");
1460         Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
1461         return v != null ? v.booleanValue() : DEFAULT_SORTASCENDING;
1462     }
1463 
1464     /**
1465      * The name of a javabean property on which the table is sorted.
1466      * <p>
1467      * The datamodel should contain objects that have this property;
1468      * reflection will be used to sort the datamodel on that property
1469      * using the default comparator for that type. 
1470      * <p>
1471      * This value is part of the component state. However it is not
1472      * directly settable by users; instead it is set by other components
1473      * such as a CommandSortHeader.
1474      */
1475     public void setSortProperty(String sortProperty)
1476     {
1477         _sortProperty = sortProperty;
1478     }
1479 
1480     /**
1481      * @JSFProperty
1482      *   literalOnly="true"
1483      *   tagExcluded="true"
1484      */
1485     public String getSortProperty()
1486     {
1487         return _sortProperty;
1488     }
1489 
1490     /**
1491      * Define if the table is sortable or not
1492      * 
1493      * @JSFProperty
1494      *   defaultValue="false"
1495      */
1496     public abstract boolean isSortable();
1497 
1498     /**
1499      * Avoids rendering the html table tags, thus, giving you a 
1500      * table rendering just rows. You can use this together 
1501      * with the detailStamp faces of the parent datatable 
1502      * to render child-tables using the same layout as the parent. 
1503      * 
1504      * Notice: You have to ensure both tables do have the same 
1505      * number of columns. Using the colspan attribute of the 
1506      * column tag might help alot.
1507      * 
1508      * @JSFProperty
1509      *   defaultValue="false"
1510      */
1511     public abstract boolean isEmbedded();
1512 
1513     /**
1514      * true|false - true if the detailStamp should be expanded by default. default: false
1515      * 
1516      * @JSFProperty
1517      *   defaultValue="false"
1518      */
1519     public abstract boolean isDetailStampExpandedDefault();
1520 
1521     /**
1522      * before|after - where to render the detailStamp, before the 
1523      * actual row or after it. default: after
1524      * 
1525      * @JSFProperty
1526      *   defaultValue="after"
1527      */
1528     public abstract String getDetailStampLocation();
1529 
1530     /**
1531      * Defines a JavaScript onmouseover event handler for each table row
1532      * 
1533      * @JSFProperty
1534      */
1535     public abstract String getRowOnMouseOver();
1536 
1537     /**
1538      * Defines a JavaScript onmouseout event handler for each table row
1539      * 
1540      * @JSFProperty
1541      */
1542     public abstract String getRowOnMouseOut();
1543 
1544     /**
1545      * Defines a JavaScript onclick event handler for each table row
1546      * 
1547      * @JSFProperty
1548      */
1549     public abstract String getRowOnClick();
1550 
1551     /**
1552      * Defines a JavaScript ondblclick event handler for each table row
1553      * 
1554      * @JSFProperty
1555      */
1556     public abstract String getRowOnDblClick();
1557 
1558     /**
1559      * Defines a JavaScript onkeydown event handler for each table row
1560      * 
1561      * @JSFProperty
1562      */
1563     public abstract String getRowOnKeyDown();
1564 
1565     /**
1566      * Defines a JavaScript onkeypress event handler for each table row
1567      * 
1568      * @JSFProperty
1569      */
1570     public abstract String getRowOnKeyPress();
1571 
1572     /**
1573      * Defines a JavaScript onkeyup event handler for each table row
1574      * 
1575      * @JSFProperty
1576      */
1577     public abstract String getRowOnKeyUp();
1578 
1579     /**
1580      * Corresponds to the HTML class attribute for the row tr tag.
1581      * 
1582      * @JSFProperty
1583      */
1584     public String getRowStyleClass()
1585     {
1586     if (_rowStyleClass != null)
1587         return _rowStyleClass;
1588 
1589     // TODO: temporarily support fully-qualified ext. dataTable attribute names.
1590     ValueBinding vb = getValueBinding("org.apache.myfaces.dataTable.ROW_STYLECLASS");
1591     if (vb != null)
1592         log.warn("org.apache.myfaces.dataTable.ROW_STYLECLASS is deprecated. Please use rowStyleClass instead.");
1593     else
1594         vb = getValueBinding(JSFAttr.ROW_STYLECLASS_ATTR);
1595     if(vb == null)
1596         return null;
1597     String bindingValue = (String) vb.getValue(getFacesContext());
1598     if(bindingValue == "")
1599         return null;  // Fix for JSF 1.2 EL coercing nulls to empty string
1600     return bindingValue;
1601     }
1602 
1603     public void setRowStyleClass(String rowStyleClass)
1604     {
1605         _rowStyleClass = rowStyleClass;
1606     }
1607 
1608     /**
1609      * Corresponds to the HTML style attribute for the row tr tag.
1610      * 
1611      * @JSFProperty
1612      */
1613     public String getRowStyle()
1614     {
1615         if (_rowStyle != null)
1616             return _rowStyle;
1617 
1618     // TODO: temporarily support fully-qualified ext. dataTable attribute names.
1619         ValueBinding vb = getValueBinding("org.apache.myfaces.dataTable.ROW_STYLE");
1620     if (vb != null)
1621         log.warn("org.apache.myfaces.dataTable.ROW_STYLE is deprecated. Please use rowStyle instead.");
1622     else
1623         vb = getValueBinding(JSFAttr.ROW_STYLE_ATTR);
1624     if(vb == null)
1625         return null;
1626     String bindingValue = (String) vb.getValue(getFacesContext());
1627     if(bindingValue == "")
1628         return null;  // Fix for JSF 1.2 EL coercing nulls to empty string
1629     return bindingValue;
1630     }
1631 
1632     public void setRowStyle(String rowStyle)
1633     {
1634         _rowStyle = rowStyle;
1635     }
1636 
1637     /**
1638      * Defines a JavaScript onmpusedown event handler for each table row
1639      * 
1640      * @JSFProperty
1641      */
1642     public abstract String getRowOnMouseDown();
1643 
1644     /**
1645      * Defines a JavaScript onmousemove event handler for each table row
1646      * 
1647      * @JSFProperty
1648      */
1649     public abstract String getRowOnMouseMove();
1650 
1651     /**
1652      * Defines a JavaScript onmouseup event handler for each table row
1653      * 
1654      * @JSFProperty
1655      */
1656     public abstract String getRowOnMouseUp();
1657 
1658     /**
1659      * @JSFProperty
1660      *   tagExcluded = "true"
1661      */
1662     protected boolean isValidChildren()
1663     {
1664         return _isValidChildren;
1665     }
1666 
1667     protected void setIsValidChildren(boolean isValidChildren)
1668     {
1669         _isValidChildren = isValidChildren;
1670     }
1671 
1672     protected _SerializableDataModel getPreservedDataModel()
1673     {
1674         return _preservedDataModel;
1675     }
1676 
1677     protected void setPreservedDataModel(_SerializableDataModel preservedDataModel)
1678     {
1679         _preservedDataModel = preservedDataModel;
1680     }
1681 
1682 
1683     public boolean isCurrentDetailExpanded()
1684     {
1685         Boolean expanded = (Boolean) _expandedNodes.get(getClientId(getFacesContext()));
1686         if (expanded != null)
1687         {
1688             return expanded.booleanValue();
1689         }
1690 
1691         return isDetailStampExpandedDefault();
1692     }
1693 
1694     public void setVarDetailToggler(String varDetailToggler)
1695     {
1696         _varDetailToggler = varDetailToggler;
1697     }
1698 
1699     /**
1700      *  This variable has the boolean property "currentdetailExpanded" 
1701      *  which is true if the current detail row is expanded and the 
1702      *  action method "toggleDetail" which expand/collapse the current 
1703      *  detail row.
1704      * 
1705      * @JSFProperty
1706      */
1707     public String getVarDetailToggler()
1708     {
1709         if (_varDetailToggler != null)
1710             return _varDetailToggler;
1711         ValueBinding vb = getValueBinding("varDetailToggler");
1712         return vb != null ? (String) vb.getValue(getFacesContext()) : null;
1713     }
1714 
1715     /**
1716      * Corresponds to the HTML style attribute for grouped rows.
1717      *  
1718      * @JSFProperty
1719      */
1720     public abstract String getRowGroupStyle();
1721 
1722     /**
1723      * StyleClass for grouped rows.
1724      * 
1725      * @JSFProperty
1726      */
1727     public abstract String getRowGroupStyleClass();
1728     
1729     /**
1730      * Corresponds to the HTML style attribute for the table body tag
1731      * 
1732      * @JSFProperty
1733      */
1734     public abstract String getBodyStyle();
1735 
1736     /**
1737      * Corresponds to the HTML class attribute for the table body tag.
1738      * 
1739      * @JSFProperty
1740      */
1741     public abstract String getBodyStyleClass();
1742 
1743     public AbstractHtmlDataTable()
1744     {
1745         setRendererType(DEFAULT_RENDERER_TYPE);
1746     }
1747 
1748     /**
1749      * Change the status of the current detail row from collapsed to expanded or
1750      * viceversa.
1751      */
1752     public void toggleDetail()
1753     {
1754         String derivedRowKey = getClientId(getFacesContext());
1755 
1756         // get the current expanded state of the row
1757         boolean expanded = isDetailExpanded();
1758         if (expanded)
1759         {
1760             // toggle to "collapsed"
1761 
1762             if (isDetailStampExpandedDefault())
1763             {
1764                 // if default is expanded we have to override with FALSE here
1765                 _expandedNodes.put(derivedRowKey, Boolean.FALSE);
1766             }
1767             else
1768             {
1769                 // if default is collapsed we can fallback to this default
1770                 _expandedNodes.remove(derivedRowKey);
1771             }
1772         }
1773         else
1774         {
1775             // toggle to "expanded"
1776 
1777             if (isDetailStampExpandedDefault())
1778             {
1779                 // if default is expanded we can fallback to this default
1780                 _expandedNodes.remove(derivedRowKey);
1781             }
1782             else
1783             {
1784                 // if default is collapsed we have to override with TRUE
1785                 _expandedNodes.put(derivedRowKey, Boolean.TRUE);
1786             }
1787         }
1788     }
1789 
1790     /**
1791      * Return true if the current detail row is expanded.
1792      *
1793      * @return true if the current detail row is expanded.
1794      */
1795     public boolean isDetailExpanded()
1796     {
1797         Boolean expanded = (Boolean) _expandedNodes.get(getClientId(getFacesContext()));
1798         if (expanded == null)
1799         {
1800             return isDetailStampExpandedDefault();
1801         }
1802 
1803         return expanded.booleanValue();
1804     }
1805 
1806     public int getSortColumnIndex()
1807     {
1808         if (_sortColumnIndex == -1)
1809             _sortColumnIndex = columnNameToIndex(getSortColumn());
1810 
1811         return _sortColumnIndex;
1812     }
1813 
1814     public void setSortColumnIndex(int sortColumnIndex)
1815     {
1816         _sortColumnIndex = sortColumnIndex;
1817     }
1818 
1819     /**
1820      * The number of columns to wrap the table over. Default: 1
1821      * 
1822      * Set the number of columns the table will be divided over.
1823      * 
1824      * @JSFProperty
1825      *   defaultValue="1"
1826      */
1827     public abstract int getNewspaperColumns();
1828 
1829     /**
1830      * The orientation of the newspaper columns in the newspaper 
1831      * table - "horizontal" or "vertical". Default: vertical
1832      * 
1833      * @JSFProperty
1834      *   defaultValue = "vertical"
1835      */
1836     public abstract String getNewspaperOrientation();
1837 
1838     /**
1839      * Gets the spacer facet, between each pair of newspaper columns.
1840      * 
1841      * @JSFFacet
1842      *   name="spacer"
1843      */
1844     public UIComponent getSpacer()
1845     {
1846         return (UIComponent) getFacets().get(SPACER_FACET_NAME);
1847     }
1848 
1849     public void setSpacer(UIComponent spacer)
1850     {
1851         getFacets().put(SPACER_FACET_NAME, spacer);
1852     }
1853 
1854     /**
1855      * Expand all details
1856      */
1857     public void expandAllDetails()
1858     {
1859         int rowCount = getRowCount();
1860 
1861         _expandedNodes.clear();
1862         
1863         if (getRowKey() != null)
1864         {
1865             int oldRow = getRowIndex();
1866             try
1867             {
1868                 for (int row = 0; row < rowCount; row++)
1869                 {
1870                     setRowIndex(row);
1871                     _expandedNodes.put(getClientId(getFacesContext()), Boolean.TRUE);
1872                 }
1873             }
1874             finally
1875             {
1876                 setRowIndex(oldRow);
1877             }
1878         }
1879         else
1880         {
1881             for (int row = 0; row < rowCount; row++)
1882             {
1883                 _expandedNodes.put(new Integer(row).toString(), Boolean.TRUE);
1884             }
1885         }
1886     }
1887 
1888     /**
1889      * Collapse all details
1890      */
1891     public void collapseAllDetails()
1892     {
1893         _expandedNodes.clear();
1894     }
1895 
1896     /**
1897      * @return true is any of the details is expanded
1898      */
1899     public boolean isExpandedEmpty()
1900     {
1901         boolean expandedEmpty = true;
1902         if (_expandedNodes != null)
1903         {
1904             expandedEmpty = _expandedNodes.isEmpty();
1905         }
1906         return expandedEmpty;
1907     }
1908 
1909     /**
1910      * Clears expanded nodes set if expandedEmpty is true
1911      *
1912      * @param expandedEmpty
1913      */
1914     public void setExpandedEmpty(boolean expandedEmpty)
1915     {
1916         if (expandedEmpty)
1917         {
1918             if (_expandedNodes != null)
1919             {
1920                 _expandedNodes.clear();
1921             }
1922         }
1923     }
1924 
1925     //------------------ GENERATED CODE BEGIN (do not modify!) --------------------
1926 
1927     public static final String COMPONENT_TYPE = "org.apache.myfaces.HtmlDataTable";
1928     public static final String DEFAULT_RENDERER_TYPE = "org.apache.myfaces.Table";
1929 
1930     private static final boolean DEFAULT_PRESERVEDATAMODEL = false;
1931     private static final boolean DEFAULT_PRESERVESORT = true;
1932     private static final boolean DEFAULT_RENDEREDIFEMPTY = true;
1933 
1934     private Boolean _preserveDataModel = null;
1935     private Boolean _preserveSort = null;
1936 
1937     public void setPreserveDataModel(boolean preserveDataModel)
1938     {
1939         _preserveDataModel = Boolean.valueOf(preserveDataModel);
1940     }
1941 
1942     /**
1943      * Indicates whether the state of the whole DataModel should 
1944      * be saved and restored. When set to false, the value-binding 
1945      * for the "value" attribute of this table is executed each 
1946      * time the page is rendered. When set to true, that 
1947      * value-binding is only executed when the component is first 
1948      * created, and the DataModel state is thereafter saved/restored 
1949      * automatically by the component. When column sorting is 
1950      * used for a table this property needs to be false so that 
1951      * the DataModel can be updated to reflect any changes in the 
1952      * sort criteria. Default: false
1953      * 
1954      * @JSFProperty
1955      */
1956     public boolean isPreserveDataModel()
1957     {
1958         if (_preserveDataModel != null)
1959             return _preserveDataModel.booleanValue();
1960         ValueBinding vb = getValueBinding("preserveDataModel");
1961         Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
1962         return v != null ? v.booleanValue() : DEFAULT_PRESERVEDATAMODEL;
1963     }
1964 
1965     public void setPreserveSort(boolean preserveSort)
1966     {
1967         _preserveSort = Boolean.valueOf(preserveSort);
1968     }
1969     
1970     /**
1971      * Indicates whether the state of the sortColumn and sortAscending 
1972      * attribute should be saved and restored and written back to the 
1973      * model during the update model phase. Default: true
1974      * 
1975      * @JSFProperty
1976      *   defaultValue = "true"
1977      */
1978     public boolean isPreserveSort()
1979     {
1980         if (_preserveSort != null)
1981             return _preserveSort.booleanValue();
1982         ValueBinding vb = getValueBinding("preserveSort");
1983         Boolean v = vb != null ? (Boolean) vb.getValue(getFacesContext()) : null;
1984         return v != null ? v.booleanValue() : DEFAULT_PRESERVESORT;
1985     }
1986 
1987     /**
1988      * Indicates whether this table should be rendered if the 
1989      * underlying DataModel is empty. You could as well use 
1990      * rendered="#{not empty bean.list}", but this one causes 
1991      * the getList method of your model bean beeing called up 
1992      * to five times per request, which is not optimal when 
1993      * the list is backed by a DB table. Using 
1994      * renderedIfEmpty="false" solves this problem, because 
1995      * the MyFaces extended HtmlDataTable automatically caches 
1996      * the DataModel and calles the model getter only once 
1997      * per request. Default: true
1998      * 
1999      * @JSFProperty
2000      *   defaultValue = "true"
2001      */
2002     public abstract boolean isRenderedIfEmpty();
2003 
2004     /**
2005      * A parameter name, under which the current rowIndex is set 
2006      * in request scope similar to the var parameter.
2007      * 
2008      * @JSFProperty
2009      */
2010     public abstract String getRowIndexVar();
2011     
2012     /**
2013      * A parameter name, under which the rowCount is set in 
2014      * request scope similar to the var parameter.
2015      * 
2016      * @JSFProperty
2017      */
2018     public abstract String getRowCountVar();
2019 
2020     /**
2021      * A parameter name, under which the previous RowData Object 
2022      * is set in request scope similar to the rowIndexVar and 
2023      * rowCountVar parameters. Mind that the value of this 
2024      * request scope attribute is null in the first row or 
2025      * when isRowAvailable returns false for the previous row.
2026      * 
2027      * @JSFProperty
2028      */
2029     public abstract String getPreviousRowDataVar();
2030 
2031     /**
2032      * A parameter name, under which the a boolean is set in request 
2033      * scope similar to the var parameter. TRUE for the column that 
2034      * is currently sorted, FALSE otherwise.
2035      * 
2036      * @JSFProperty
2037      */
2038     public abstract String getSortedColumnVar();
2039     
2040     /**
2041      * HTML: Specifies the horizontal alignment of this element. 
2042      * Deprecated in HTML 4.01.
2043      * 
2044      * @JSFProperty
2045      */
2046     public abstract String getAlign();
2047 
2048     /**
2049      * The id to use for
2050      * 
2051      * @JSFProperty
2052      */
2053     public abstract String getRowId();
2054         
2055     /**
2056      * Reserved for future use.
2057      * 
2058      * @JSFProperty
2059      */
2060     public abstract String getDatafld();
2061     
2062     /**
2063      * Reserved for future use.
2064      * 
2065      * @JSFProperty
2066      */
2067     public abstract String getDatasrc();
2068     
2069     /**
2070      * Reserved for future use.
2071      * 
2072      * @JSFProperty
2073      */
2074     public abstract String getDataformatas();
2075 
2076     /**
2077      * Indicate the expected type of the EL expression pointed
2078      * by value property. It is useful when vb.getType() cannot
2079      * found the type, like when a map value is resolved on 
2080      * the expression.
2081      * 
2082      * @JSFProperty
2083      */
2084     public abstract String getValueType(); 
2085 
2086 }