View Javadoc

1   /**
2    * Licensed under the Artistic License; you may not use this file
3    * except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    *      http://displaytag.sourceforge.net/license.html
7    *
8    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
9    * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10   * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11   */
12  package org.displaytag.tags;
13  
14  import java.io.ByteArrayOutputStream;
15  import java.io.IOException;
16  import java.io.StringWriter;
17  import java.io.Writer;
18  import java.util.Collection;
19  import java.util.HashMap;
20  import java.util.Iterator;
21  import java.util.List;
22  import java.util.Map;
23  
24  import javax.servlet.http.HttpServletRequest;
25  import javax.servlet.http.HttpServletResponse;
26  import javax.servlet.jsp.JspException;
27  import javax.servlet.jsp.JspTagException;
28  import javax.servlet.jsp.JspWriter;
29  
30  import org.apache.commons.beanutils.BeanUtils;
31  import org.apache.commons.collections.IteratorUtils;
32  import org.apache.commons.lang.ObjectUtils;
33  import org.apache.commons.lang.StringUtils;
34  import org.apache.commons.lang.math.LongRange;
35  import org.apache.commons.lang.math.NumberUtils;
36  import org.apache.commons.lang.math.Range;
37  import org.apache.commons.logging.Log;
38  import org.apache.commons.logging.LogFactory;
39  import org.displaytag.Messages;
40  import org.displaytag.decorator.TableDecorator;
41  import org.displaytag.exception.ExportException;
42  import org.displaytag.exception.FactoryInstantiationException;
43  import org.displaytag.exception.InvalidTagAttributeValueException;
44  import org.displaytag.exception.WrappedRuntimeException;
45  import org.displaytag.export.BinaryExportView;
46  import org.displaytag.export.ExportView;
47  import org.displaytag.export.ExportViewFactory;
48  import org.displaytag.export.TextExportView;
49  import org.displaytag.model.Cell;
50  import org.displaytag.model.Column;
51  import org.displaytag.model.HeaderCell;
52  import org.displaytag.model.Row;
53  import org.displaytag.model.TableModel;
54  import org.displaytag.pagination.PaginatedList;
55  import org.displaytag.pagination.PaginatedListSmartListHelper;
56  import org.displaytag.pagination.SmartListHelper;
57  import org.displaytag.properties.MediaTypeEnum;
58  import org.displaytag.properties.SortOrderEnum;
59  import org.displaytag.properties.TableProperties;
60  import org.displaytag.render.HtmlTableWriter;
61  import org.displaytag.util.CollectionUtil;
62  import org.displaytag.util.DependencyChecker;
63  import org.displaytag.util.Href;
64  import org.displaytag.util.ParamEncoder;
65  import org.displaytag.util.RequestHelper;
66  import org.displaytag.util.RequestHelperFactory;
67  import org.displaytag.util.TagConstants;
68  
69  
70  /**
71   * This tag takes a list of objects and creates a table to display those objects. With the help of column tags, you
72   * simply provide the name of properties (get Methods) that are called against the objects in your list that gets
73   * displayed. This tag works very much like the struts iterator tag, most of the attributes have the same name and
74   * functionality as the struts tag.
75   * @author mraible
76   * @author Fabrizio Giustina
77   * @version $Revision: 1081 $ ($Author: fgiust $)
78   */
79  public class TableTag extends HtmlTableTag
80  {
81  
82      /**
83       * name of the attribute added to page scope when exporting, containing an MediaTypeEnum this can be used in column
84       * content to detect the output type and to return different data when exporting.
85       */
86      public static final String PAGE_ATTRIBUTE_MEDIA = "mediaType"; //$NON-NLS-1$
87  
88      /**
89       * If this variable is found in the request, assume the export filter is enabled.
90       */
91      public static final String FILTER_CONTENT_OVERRIDE_BODY = //
92      "org.displaytag.filter.ResponseOverrideFilter.CONTENT_OVERRIDE_BODY"; //$NON-NLS-1$
93  
94      /**
95       * D1597A17A6.
96       */
97      private static final long serialVersionUID = 899149338534L;
98  
99      /**
100      * logger.
101      */
102     private static Log log = LogFactory.getLog(TableTag.class);
103 
104     /**
105      * RequestHelperFactory instance used for link generation.
106      */
107     private static RequestHelperFactory rhf;
108 
109     /**
110      * Object (collection, list) on which the table is based. This is not set directly using a tag attribute and can be
111      * cleaned.
112      */
113     protected Object list;
114 
115     // -- start tag attributes --
116 
117     /**
118      * Object (collection, list) on which the table is based. Set directly using the "list" attribute or evaluated from
119      * expression.
120      */
121     protected Object listAttribute;
122 
123     /**
124      * actual row number, updated during iteration.
125      */
126     private int rowNumber = 1;
127 
128     /**
129      * name of the object to use for iteration. Can contain expressions.
130      */
131     private String name;
132 
133     /**
134      * length of list to display.
135      */
136     private int length;
137 
138     /**
139      * table decorator class name.
140      */
141     private String decoratorName;
142 
143     /**
144      * page size.
145      */
146     private int pagesize;
147 
148     /**
149      * list contains only viewable data.
150      */
151     private boolean partialList;
152 
153     /**
154      * add export links.
155      */
156     private boolean export;
157 
158     /**
159      * list offset.
160      */
161     private int offset;
162 
163     /**
164      * Integer containing total size of the data displaytag is paginating
165      */
166     private Object size;
167 
168     /**
169      * Name of the Integer in some scope containing the size of the data displaytag is paginating
170      */
171     private String sizeObjectName;
172 
173     /**
174      * sort the full list?
175      */
176     private Boolean sortFullTable;
177 
178     /**
179      * are we doing any local sorting? (defaults to True)
180      */
181     private boolean localSort = true;
182 
183     /**
184      * Request uri.
185      */
186     private String requestUri;
187 
188     /**
189      * Prepend application context to generated links.
190      */
191     private boolean dontAppendContext;
192 
193     /**
194      * the index of the column sorted by default.
195      */
196     private int defaultSortedColumn = -1;
197 
198     /**
199      * the sorting order for the sorted column.
200      */
201     private SortOrderEnum defaultSortOrder;
202 
203     /**
204      * Name of parameter which should not be forwarded during sorting or pagination.
205      */
206     private String excludedParams;
207 
208     /**
209      * Unique table id.
210      */
211     private String uid;
212 
213     /**
214      * The variable name to store totals in.
215      */
216     private String varTotals;
217 
218     // -- end tag attributes --
219 
220     /**
221      * table model - initialized in doStartTag().
222      */
223     private TableModel tableModel;
224 
225     /**
226      * current row.
227      */
228     private Row currentRow;
229 
230     /**
231      * next row.
232      */
233 
234     /**
235      * Used by various functions when the person wants to do paging - cleaned in doEndTag().
236      */
237     private SmartListHelper listHelper;
238 
239     /**
240      * base href used for links - set in initParameters().
241      */
242     private Href baseHref;
243 
244     /**
245      * table properties - set in doStartTag().
246      */
247     private TableProperties properties;
248 
249     /**
250      * page number - set in initParameters().
251      */
252     private int pageNumber = 1;
253 
254     /**
255      * Iterator on collection.
256      */
257     private Iterator tableIterator;
258 
259     /**
260      * export type - set in initParameters().
261      */
262     private MediaTypeEnum currentMediaType;
263 
264     /**
265      * daAfterBody() has been executed at least once?
266      */
267     private boolean doAfterBodyExecuted;
268 
269     /**
270      * The param encoder used to generate unique parameter names. Initialized at the first use of encodeParameter().
271      */
272     private ParamEncoder paramEncoder;
273 
274     /**
275      * Static footer added using the footer tag.
276      */
277     private String footer;
278 
279     /**
280      * Is this the last iteration we will be performing? We only output the footer on the last iteration.
281      */
282     private boolean lastIteration;
283 
284     /**
285      * Static caption added using the footer tag.
286      */
287     private String caption;
288 
289     /**
290      * Child caption tag.
291      */
292     private CaptionTag captionTag;
293 
294     /**
295      * Included row range. If no rows can be skipped the range is from 0 to Long.MAX_VALUE. Range check should be always
296      * done using containsLong(). This is an instance of org.apache.commons.lang.math.Range, but it's declared as Object
297      * to avoid runtime errors while Jasper tries to compile the page and commons lang 2.0 is not available. Commons
298      * lang version will be checked in the doStartTag() method in order to provide a more user friendly message.
299      */
300     private Object filteredRows;
301 
302     /**
303      * The paginated list containing the external pagination and sort parameters The presence of this paginated list is
304      * what determines if external pagination and sorting is used or not.
305      */
306     private PaginatedList paginatedList;
307 
308     /**
309      * Is this the last iteration?
310      * @return boolean <code>true</code> if this is the last iteration
311      */
312     protected boolean isLastIteration()
313     {
314         return this.lastIteration;
315     }
316 
317     /**
318      * Sets the list of parameter which should not be forwarded during sorting or pagination.
319      * @param value whitespace separated list of parameters which should not be included (* matches all parameters)
320      */
321     public void setExcludedParams(String value)
322     {
323         this.excludedParams = value;
324     }
325 
326     /**
327      * Sets the content of the footer. Called by a nested footer tag.
328      * @param string footer content
329      */
330     public void setFooter(String string)
331     {
332         this.footer = string;
333         this.tableModel.setFooter(this.footer);
334     }
335 
336     /**
337      * Sets the content of the caption. Called by a nested caption tag.
338      * @param string caption content
339      */
340     public void setCaption(String string)
341     {
342         this.caption = string;
343         this.tableModel.setCaption(this.caption);
344     }
345 
346     /**
347      * Set the child caption tag.
348      * @param captionTag Child caption tag
349      */
350     public void setCaptionTag(CaptionTag captionTag)
351     {
352         this.captionTag = captionTag;
353     }
354 
355     /**
356      * Obtain the child caption tag.
357      * @return The child caption tag
358      */
359     public CaptionTag getCaptionTag()
360     {
361         return this.captionTag;
362     }
363 
364     /**
365      * Is the current row empty?
366      * @return true if the current row is empty
367      */
368     protected boolean isEmpty()
369     {
370         return this.currentRow == null;
371     }
372 
373     /**
374      * set the Integer containing the total size of the data displaytag is paginating
375      * @param size Integer containing the total size of the data
376      */
377     public void setSize(Object size)
378     {
379         if (size instanceof String)
380         {
381             this.sizeObjectName = (String) size;
382         }
383         else
384         {
385             this.size = size;
386         }
387     }
388 
389     /**
390      * set the name of the Integer in some scope containing the total size of the data to be paginated
391      * @param sizeObjectName name of the Integer containing the total size of the data to be paginated
392      */
393     public void setSizeObjectName(String sizeObjectName)
394     {
395         this.sizeObjectName = sizeObjectName;
396     }
397 
398     /**
399      * setter for the "sort" attribute.
400      * @param value "page" (sort a single page) or "list" (sort the full list)
401      * @throws InvalidTagAttributeValueException if value is not "page" or "list"
402      */
403     public void setSort(String value) throws InvalidTagAttributeValueException
404     {
405         if (TableTagParameters.SORT_AMOUNT_PAGE.equals(value))
406         {
407             this.sortFullTable = Boolean.FALSE;
408         }
409         else if (TableTagParameters.SORT_AMOUNT_LIST.equals(value))
410         {
411             this.sortFullTable = Boolean.TRUE;
412         }
413         else if (TableTagParameters.SORT_AMOUNT_EXTERNAL.equals(value))
414         {
415             this.localSort = false;
416         }
417         else
418         {
419             throw new InvalidTagAttributeValueException(getClass(), "sort", value); //$NON-NLS-1$
420         }
421     }
422 
423     /**
424      * setter for the "requestURI" attribute. Context path is automatically added to path starting with "/".
425      * @param value base URI for creating links
426      */
427     public void setRequestURI(String value)
428     {
429         this.requestUri = value;
430     }
431 
432     /**
433      * Setter for the "requestURIcontext" attribute.
434      * @param value base URI for creating links
435      */
436     public void setRequestURIcontext(boolean value)
437     {
438         this.dontAppendContext = !value;
439     }
440 
441     /**
442      * Used to directly set a list (or any object you can iterate on).
443      * @param value Object
444      * @deprecated use setName() to get the object from the page or request scope instead of setting it directly here
445      */
446     public void setList(Object value)
447     {
448         this.listAttribute = value;
449     }
450 
451     /**
452      * Sets the name of the object to use for iteration.
453      * @param value name of the object to use for iteration (can contain expression). It also supports direct setting of
454      * a list, for jsp 2.0 containers where users can set up a data source here using EL expressions.
455      */
456     public void setName(Object value)
457     {
458         if (value instanceof String)
459         {
460             // ok, assuming this is the name of the object
461             this.name = (String) value;
462         }
463         else
464         {
465             // is this the list?
466             this.list = value;
467         }
468     }
469 
470     /**
471      * Sets the name of the object to use for iteration. This setter is needed for jsp 1.1 container which doesn't
472      * support the String - Object conversion. The bean info class will swith to this setter.
473      * @param value name of the object
474      */
475     public void setNameString(String value)
476     {
477         this.name = value;
478     }
479 
480     /**
481      * sets the sorting order for the sorted column.
482      * @param value "ascending" or "descending"
483      * @throws InvalidTagAttributeValueException if value is not one of "ascending" or "descending"
484      */
485     public void setDefaultorder(String value) throws InvalidTagAttributeValueException
486     {
487         this.defaultSortOrder = SortOrderEnum.fromName(value);
488         if (this.defaultSortOrder == null)
489         {
490             throw new InvalidTagAttributeValueException(getClass(), "defaultorder", value); //$NON-NLS-1$
491         }
492     }
493 
494     /**
495      * Setter for the decorator class name.
496      * @param decorator fully qualified name of the table decorator to use
497      */
498     public void setDecorator(String decorator)
499     {
500         this.decoratorName = decorator;
501     }
502 
503     /**
504      * Is export enabled?
505      * @param value <code>true</code> if export should be enabled
506      */
507     public void setExport(boolean value)
508     {
509         this.export = value;
510     }
511 
512     /**
513      * The variable name in which the totals map is stored.
514      * @param varTotalsName the value
515      */
516     public void setVarTotals(String varTotalsName)
517     {
518         this.varTotals = varTotalsName;
519     }
520 
521     /**
522      * Get the name that the totals should be stored under.
523      * @return the var name in pageContext
524      */
525     public String getVarTotals()
526     {
527         return this.varTotals;
528     }
529 
530     /**
531      * sets the number of items to be displayed in the page.
532      * @param value number of items to display in a page
533      */
534     public void setLength(int value)
535     {
536         this.length = value;
537     }
538 
539     /**
540      * sets the index of the default sorted column.
541      * @param value index of the column to sort
542      */
543     public void setDefaultsort(int value)
544     {
545         // subtract one (internal index is 0 based)
546         this.defaultSortedColumn = value - 1;
547     }
548 
549     /**
550      * sets the number of items that should be displayed for a single page.
551      * @param value number of items that should be displayed for a single page
552      */
553     public void setPagesize(int value)
554     {
555         this.pagesize = value;
556     }
557 
558     /**
559      * tells display tag that the values contained in the list are the viewable data only, there may be more results not
560      * given to displaytag
561      * @param partialList boolean value telling us there may be more data not given to displaytag
562      */
563     public void setPartialList(boolean partialList)
564     {
565         this.partialList = partialList;
566     }
567 
568     /**
569      * Setter for the list offset attribute.
570      * @param value String
571      */
572     public void setOffset(int value)
573     {
574         if (value < 1)
575         {
576             // negative values has no meaning, simply treat them as 0
577             this.offset = 0;
578         }
579         else
580         {
581             this.offset = value - 1;
582         }
583     }
584 
585     /**
586      * Sets the unique id used to identify for this table.
587      * @param value String
588      */
589     public void setUid(String value)
590     {
591         this.uid = value;
592     }
593 
594     /**
595      * Returns the unique id used to identify for this table.
596      * @return id for this table
597      */
598     public String getUid()
599     {
600         return this.uid;
601     }
602 
603     /**
604      * Returns the properties.
605      * @return TableProperties
606      */
607     protected TableProperties getProperties()
608     {
609         return this.properties;
610     }
611 
612     /**
613      * Returns the base href with parameters. This is the instance used for links, need to be cloned before being
614      * modified.
615      * @return base Href with parameters
616      */
617     protected Href getBaseHref()
618     {
619         return this.baseHref;
620     }
621 
622     /**
623      * Called by interior column tags to help this tag figure out how it is supposed to display the information in the
624      * List it is supposed to display.
625      * @param column an internal tag describing a column in this tableview
626      */
627     public void addColumn(HeaderCell column)
628     {
629         if (log.isDebugEnabled())
630         {
631             log.debug("[" + getUid() + "] addColumn " + column);
632         }
633 
634         if ((this.paginatedList != null) && (column.getSortable()))
635         {
636             String sortCriterion = paginatedList.getSortCriterion();
637 
638             String sortProperty = column.getSortProperty();
639             if (sortProperty == null)
640             {
641                 sortProperty = column.getBeanPropertyName();
642             }
643 
644             if ((sortCriterion != null) && sortCriterion.equals(sortProperty))
645             {
646                 this.tableModel.setSortedColumnNumber(this.tableModel.getNumberOfColumns());
647                 column.setAlreadySorted();
648             }
649         }
650 
651         this.tableModel.addColumnHeader(column);
652     }
653 
654     /**
655      * Adds a cell to the current row. This method is usually called by a contained ColumnTag
656      * @param cell Cell to add to the current row
657      */
658     public void addCell(Cell cell)
659     {
660         // check if null: could be null if list is empty, we don't need to fill rows
661         if (this.currentRow != null)
662         {
663             int columnNumber = this.currentRow.getCellList().size();
664             this.currentRow.addCell(cell);
665 
666             // just be sure that the number of columns has not been altered by conditionally including column tags in
667             // different rows. This is not supported, but better avoid IndexOutOfBounds...
668             if (columnNumber < tableModel.getHeaderCellList().size())
669             {
670                 HeaderCell header = (HeaderCell) tableModel.getHeaderCellList().get(columnNumber);
671                 header.addCell(new Column(header, cell, currentRow));
672             }
673         }
674     }
675 
676     /**
677      * Is this the first iteration?
678      * @return boolean <code>true</code> if this is the first iteration
679      */
680     protected boolean isFirstIteration()
681     {
682         if (log.isDebugEnabled())
683         {
684             log.debug("["
685                 + getUid()
686                 + "] first iteration="
687                 + (this.rowNumber == 1)
688                 + " (row number="
689                 + this.rowNumber
690                 + ")");
691         }
692         // in first iteration this.rowNumber is 1
693         // (this.rowNumber is incremented in doAfterBody)
694         return this.rowNumber == 1;
695     }
696 
697     /**
698      * When the tag starts, we just initialize some of our variables, and do a little bit of error checking to make sure
699      * that the user is not trying to give us parameters that we don't expect.
700      * @return int
701      * @throws JspException generic exception
702      * @see javax.servlet.jsp.tagext.Tag#doStartTag()
703      */
704     public int doStartTag() throws JspException
705     {
706         DependencyChecker.check();
707 
708         // needed before column processing, elsewhere registered views will not be added
709         ExportViewFactory.getInstance();
710 
711         if (log.isDebugEnabled())
712         {
713             log.debug("[" + getUid() + "] doStartTag called");
714         }
715 
716         this.properties = TableProperties.getInstance((HttpServletRequest) pageContext.getRequest());
717         this.tableModel = new TableModel(this.properties, pageContext.getResponse().getCharacterEncoding(), pageContext);
718 
719         // copying id to the table model for logging
720         this.tableModel.setId(getUid());
721 
722         initParameters();
723 
724         this.tableModel.setMedia(this.currentMediaType);
725 
726         Object previousMediaType = this.pageContext.getAttribute(PAGE_ATTRIBUTE_MEDIA);
727         // set the PAGE_ATTRIBUTE_MEDIA attribute in the page scope
728         if (previousMediaType == null || MediaTypeEnum.HTML.equals(previousMediaType))
729         {
730             if (log.isDebugEnabled())
731             {
732                 log.debug("[" + getUid() + "] setting media [" + this.currentMediaType + "] in this.pageContext");
733             }
734             this.pageContext.setAttribute(PAGE_ATTRIBUTE_MEDIA, this.currentMediaType);
735         }
736 
737         doIteration();
738 
739         // always return EVAL_BODY_TAG to get column headers also if the table is empty
740         // using int to avoid deprecation error in compilation using j2ee 1.3
741         return 2;
742     }
743 
744     /**
745      * @see javax.servlet.jsp.tagext.BodyTag#doAfterBody()
746      */
747     public int doAfterBody()
748     {
749         // doAfterBody() has been called, body is not empty
750         this.doAfterBodyExecuted = true;
751 
752         if (log.isDebugEnabled())
753         {
754             log.debug("[" + getUid() + "] doAfterBody called - iterating on row " + this.rowNumber);
755         }
756 
757         // increment this.rowNumber
758         this.rowNumber++;
759 
760         // Call doIteration() to do the common work
761         return doIteration();
762     }
763 
764     /**
765      * Utility method that is used by both doStartTag() and doAfterBody() to perform an iteration.
766      * @return <code>int</code> either EVAL_BODY_TAG or SKIP_BODY depending on whether another iteration is desired.
767      */
768     protected int doIteration()
769     {
770 
771         if (log.isDebugEnabled())
772         {
773             log.debug("[" + getUid() + "] doIteration called");
774         }
775 
776         // Row already filled?
777         if (this.currentRow != null)
778         {
779             // if yes add to table model and remove
780             this.tableModel.addRow(this.currentRow);
781             this.currentRow = null;
782         }
783 
784         if (this.tableIterator.hasNext())
785         {
786 
787             Object iteratedObject = this.tableIterator.next();
788             if (getUid() != null)
789             {
790                 if ((iteratedObject != null))
791                 {
792                     // set object into this.pageContext
793                     if (log.isDebugEnabled())
794                     {
795                         log.debug("[" + getUid() + "] setting attribute \"" + getUid() + "\" in pageContext");
796                     }
797                     this.pageContext.setAttribute(getUid(), iteratedObject);
798 
799                 }
800                 else
801                 {
802                     // if row is null remove previous object
803                     this.pageContext.removeAttribute(getUid());
804                 }
805                 // set the current row number into this.pageContext
806                 this.pageContext.setAttribute(getUid() + TableTagExtraInfo.ROWNUM_SUFFIX, new Integer(this.rowNumber));
807             }
808 
809             // Row object for Cell values
810             this.currentRow = new Row(iteratedObject, this.rowNumber);
811 
812             this.lastIteration = !this.tableIterator.hasNext();
813 
814             // new iteration
815             // using int to avoid deprecation error in compilation using j2ee 1.3
816             return 2;
817         }
818         this.lastIteration = true;
819 
820         if (log.isDebugEnabled())
821         {
822             log.debug("[" + getUid() + "] doIteration() - iterator ended after " + (this.rowNumber - 1) + " rows");
823         }
824 
825         // end iteration
826         return SKIP_BODY;
827     }
828 
829     /**
830      * Reads parameters from the request and initialize all the needed table model attributes.
831      * @throws FactoryInstantiationException for problems in instantiating a RequestHelperFactory
832      */
833     private void initParameters() throws JspTagException, FactoryInstantiationException
834     {
835 
836         if (rhf == null)
837         {
838             // first time initialization
839             rhf = this.properties.getRequestHelperFactoryInstance();
840         }
841 
842         String fullName = getFullObjectName();
843 
844         // only evaluate if needed, else use list attribute
845         if (fullName != null)
846         {
847             this.list = evaluateExpression(fullName);
848         }
849         else if (this.list == null)
850         {
851             // needed to allow removing the collection of objects if not set directly
852             this.list = this.listAttribute;
853         }
854 
855         if (this.list instanceof PaginatedList)
856         {
857             this.paginatedList = (PaginatedList) this.list;
858             this.list = this.paginatedList.getList();
859         }
860 
861         // set the table model to perform in memory local sorting
862         this.tableModel.setLocalSort(this.localSort && (this.paginatedList == null));
863 
864         RequestHelper requestHelper = rhf.getRequestHelperInstance(this.pageContext);
865 
866         initHref(requestHelper);
867 
868         Integer pageNumberParameter = requestHelper.getIntParameter(encodeParameter(TableTagParameters.PARAMETER_PAGE));
869         this.pageNumber = (pageNumberParameter == null) ? 1 : pageNumberParameter.intValue();
870 
871         int sortColumn = -1;
872         if (!this.tableModel.isLocalSort())
873         {
874             // our sort column parameter may be a string, check that first
875             String sortColumnName = requestHelper.getParameter(encodeParameter(TableTagParameters.PARAMETER_SORT));
876 
877             // if usename is not null, sortColumnName is the name, if not is the column index
878             String usename = requestHelper.getParameter(encodeParameter(TableTagParameters.PARAMETER_SORTUSINGNAME));
879 
880             if (sortColumnName == null)
881             {
882                 this.tableModel.setSortedColumnNumber(this.defaultSortedColumn);
883             }
884             else
885             {
886                 if (usename != null)
887                 {
888 
889                     this.tableModel.setSortedColumnName(sortColumnName); // its a string, set as string
890                 }
891                 else if (NumberUtils.isNumber(sortColumnName))
892                 {
893                     sortColumn = Integer.parseInt(sortColumnName);
894                     this.tableModel.setSortedColumnNumber(sortColumn); // its an int set as normal
895                 }
896             }
897         }
898         else if (this.paginatedList == null)
899         {
900             Integer sortColumnParameter = requestHelper
901                 .getIntParameter(encodeParameter(TableTagParameters.PARAMETER_SORT));
902             sortColumn = (sortColumnParameter == null) ? this.defaultSortedColumn : sortColumnParameter.intValue();
903             this.tableModel.setSortedColumnNumber(sortColumn);
904         }
905         else
906         {
907             sortColumn = defaultSortedColumn;
908         }
909 
910         // default value
911         boolean finalSortFull = this.properties.getSortFullList();
912 
913         // user value for this single table
914         if (this.sortFullTable != null)
915         {
916             finalSortFull = this.sortFullTable.booleanValue();
917         }
918 
919         // if a partial list is used and sort="list" is specified, assume the partial list is already sorted
920         if (!this.partialList || !finalSortFull)
921         {
922             this.tableModel.setSortFullTable(finalSortFull);
923         }
924 
925         if (this.paginatedList == null)
926         {
927             SortOrderEnum paramOrder = SortOrderEnum.fromCode(requestHelper
928                 .getIntParameter(encodeParameter(TableTagParameters.PARAMETER_ORDER)));
929 
930             // if no order parameter is set use default
931             if (paramOrder == null)
932             {
933                 paramOrder = this.defaultSortOrder;
934             }
935 
936             boolean order = SortOrderEnum.DESCENDING != paramOrder;
937             this.tableModel.setSortOrderAscending(order);
938         }
939         else
940         {
941             SortOrderEnum direction = paginatedList.getSortDirection();
942             this.tableModel.setSortOrderAscending(direction == SortOrderEnum.ASCENDING);
943         }
944 
945         Integer exportTypeParameter = requestHelper
946             .getIntParameter(encodeParameter(TableTagParameters.PARAMETER_EXPORTTYPE));
947 
948         this.currentMediaType = (MediaTypeEnum) ObjectUtils.defaultIfNull(
949             MediaTypeEnum.fromCode(exportTypeParameter),
950             MediaTypeEnum.HTML);
951 
952         // if we are doing partialLists then ensure we have our size object
953         if (this.partialList)
954         {
955             if ((this.sizeObjectName == null) && (this.size == null))
956             {
957                 // ?
958             }
959             if (this.sizeObjectName != null)
960             {
961                 // retrieve the object from scope
962                 this.size = evaluateExpression(this.sizeObjectName);
963             }
964             if (size == null)
965             {
966                 throw new JspTagException(Messages.getString("MissingAttributeException.msg", new Object[]{"size"}));
967             }
968             else if (!(size instanceof Integer))
969             {
970                 throw new JspTagException(Messages.getString(
971                     "InvalidTypeException.msg",
972                     new Object[]{"size", "Integer"}));
973             }
974         }
975 
976         // do we really need to skip any row?
977         boolean wishOptimizedIteration = ((this.pagesize > 0 // we are paging
978             || this.offset > 0 // or we are skipping some records using offset
979         || this.length > 0 // or we are limiting the records using length
980         ) && !partialList); // only optimize if we have the full list
981 
982         // can we actually skip any row?
983         if (wishOptimizedIteration && (this.list instanceof Collection) // we need to know the size
984             && ((sortColumn == -1 // and we are not sorting
985             || !finalSortFull // or we are sorting with the "page" behaviour
986             ) && (this.currentMediaType == MediaTypeEnum.HTML // and we are not exporting
987             || !this.properties.getExportFullList()) // or we are exporting a single page
988             ))
989         {
990             int start = 0;
991             int end = 0;
992             if (this.offset > 0)
993             {
994                 start = this.offset;
995             }
996             if (length > 0)
997             {
998                 end = start + this.length;
999             }
1000 
1001             if (this.pagesize > 0)
1002             {
1003                 int fullSize = ((Collection) this.list).size();
1004                 start = (this.pageNumber - 1) * this.pagesize;
1005 
1006                 // invalid page requested, go back to last page
1007                 if (start > fullSize)
1008                 {
1009                     int div = fullSize / this.pagesize;
1010                     start = (fullSize % this.pagesize == 0) ? div : div + 1;
1011                 }
1012 
1013                 end = start + this.pagesize;
1014             }
1015 
1016             // rowNumber starts from 1
1017             filteredRows = new LongRange(start + 1, end);
1018         }
1019         else
1020         {
1021             filteredRows = new LongRange(1, Long.MAX_VALUE);
1022         }
1023 
1024         this.tableIterator = IteratorUtils.getIterator(this.list);
1025     }
1026 
1027     /**
1028      * Is the current row included in the "to-be-evaluated" range? Called by nested ColumnTags. If <code>false</code>
1029      * column body is skipped.
1030      * @return <code>true</code> if the current row must be evaluated because is included in output or because is
1031      * included in sorting.
1032      */
1033     protected boolean isIncludedRow()
1034     {
1035         return ((Range) filteredRows).containsLong(this.rowNumber);
1036     }
1037 
1038     /**
1039      * Create a complete string for compatibility with previous version before expression evaluation. This approach is
1040      * optimized for new expressions, not for previous property/scope parameters.
1041      * @return Expression composed by scope + name + property
1042      */
1043     private String getFullObjectName()
1044     {
1045         // only evaluate if needed, else preserve original list
1046         if (this.name == null)
1047         {
1048             return null;
1049         }
1050 
1051         return this.name;
1052     }
1053 
1054     /**
1055      * init the href object used to generate all the links for pagination, sorting, exporting.
1056      * @param requestHelper request helper used to extract the base Href
1057      */
1058     protected void initHref(RequestHelper requestHelper)
1059     {
1060         // get the href for this request
1061         this.baseHref = requestHelper.getHref();
1062 
1063         if (this.excludedParams != null)
1064         {
1065             String[] splittedExcludedParams = StringUtils.split(this.excludedParams);
1066 
1067             // handle * keyword
1068             if (splittedExcludedParams.length == 1 && "*".equals(splittedExcludedParams[0]))
1069             {
1070                 // @todo cleanup: paramEncoder initialization should not be done here
1071                 if (this.paramEncoder == null)
1072                 {
1073                     this.paramEncoder = new ParamEncoder(getUid());
1074                 }
1075 
1076                 Iterator paramsIterator = baseHref.getParameterMap().keySet().iterator();
1077                 while (paramsIterator.hasNext())
1078                 {
1079                     String key = (String) paramsIterator.next();
1080 
1081                     // don't remove parameters added by the table tag
1082                     if (!this.paramEncoder.isParameterEncoded(key))
1083                     {
1084                         baseHref.removeParameter(key);
1085                     }
1086                 }
1087             }
1088             else
1089             {
1090                 for (int j = 0; j < splittedExcludedParams.length; j++)
1091                 {
1092                     baseHref.removeParameter(splittedExcludedParams[j]);
1093                 }
1094             }
1095         }
1096 
1097         if (this.requestUri != null)
1098         {
1099             // if user has added a requestURI create a new href
1100             String fullURI = requestUri;
1101             if (!this.dontAppendContext)
1102             {
1103                 String contextPath = ((HttpServletRequest) this.pageContext.getRequest()).getContextPath();
1104 
1105                 // prepend the context path if any.
1106                 // actually checks if context path is already there for people which manually add it
1107                 if (!StringUtils.isEmpty(contextPath)
1108                     && requestUri != null
1109                     && requestUri.startsWith("/")
1110                     && !requestUri.startsWith(contextPath))
1111                 {
1112                     fullURI = contextPath + this.requestUri;
1113                 }
1114             }
1115 
1116             // call encodeURL to preserve session id when cookies are disabled
1117             fullURI = ((HttpServletResponse) this.pageContext.getResponse()).encodeURL(fullURI);
1118 
1119             baseHref.setFullUrl(fullURI);
1120 
1121             // // ... and copy parameters from the current request
1122             // Map parameterMap = normalHref.getParameterMap();
1123             // this.baseHref.addParameterMap(parameterMap);
1124         }
1125 
1126     }
1127 
1128     /**
1129      * Draw the table. This is where everything happens, we figure out what values we are supposed to be showing, we
1130      * figure out how we are supposed to be showing them, then we draw them.
1131      * @return int
1132      * @throws JspException generic exception
1133      * @see javax.servlet.jsp.tagext.Tag#doEndTag()
1134      */
1135     public int doEndTag() throws JspException
1136     {
1137 
1138         if (log.isDebugEnabled())
1139         {
1140             log.debug("[" + getUid() + "] doEndTag called");
1141         }
1142 
1143         if (!this.doAfterBodyExecuted)
1144         {
1145             if (log.isDebugEnabled())
1146             {
1147                 log.debug("[" + getUid() + "] tag body is empty.");
1148             }
1149 
1150             // first row (created in doStartTag)
1151             if (this.currentRow != null)
1152             {
1153                 // if yes add to table model and remove
1154                 this.tableModel.addRow(this.currentRow);
1155             }
1156 
1157             // other rows
1158             while (this.tableIterator.hasNext())
1159             {
1160                 Object iteratedObject = this.tableIterator.next();
1161                 this.rowNumber++;
1162 
1163                 // Row object for Cell values
1164                 this.currentRow = new Row(iteratedObject, this.rowNumber);
1165 
1166                 this.tableModel.addRow(this.currentRow);
1167             }
1168         }
1169 
1170         // if no rows are defined automatically get all properties from bean
1171         if (this.tableModel.isEmpty())
1172         {
1173             describeEmptyTable();
1174         }
1175 
1176         TableDecorator tableDecorator = this.properties.getDecoratorFactoryInstance().
1177                                         loadTableDecorator(this.pageContext, getConfiguredDecoratorName());
1178 
1179         if (tableDecorator != null)
1180         {
1181             tableDecorator.init(this.pageContext, this.list, this.tableModel);
1182             this.tableModel.setTableDecorator(tableDecorator);
1183         }
1184 
1185         setupViewableData();
1186 
1187         // Figure out how we should sort this data, typically we just sort
1188         // the data being shown, but the programmer can override this behavior
1189         if (this.paginatedList == null && this.tableModel.isLocalSort())
1190         {
1191             if (!this.tableModel.isSortFullTable())
1192             {
1193                 this.tableModel.sortPageList();
1194             }
1195         }
1196 
1197         // Get the data back in the representation that the user is after, do they want HTML/XML/CSV/EXCEL/etc...
1198         int returnValue = EVAL_PAGE;
1199 
1200         // check for nested tables
1201         // Object previousMediaType = this.pageContext.getAttribute(PAGE_ATTRIBUTE_MEDIA);
1202         Object previousMediaType = this.pageContext.getAttribute(PAGE_ATTRIBUTE_MEDIA);
1203         if (MediaTypeEnum.HTML.equals(this.currentMediaType)
1204             && (previousMediaType == null || MediaTypeEnum.HTML.equals(previousMediaType)))
1205         {
1206             writeHTMLData();
1207         }
1208         else if (!MediaTypeEnum.HTML.equals(this.currentMediaType))
1209         {
1210             if (log.isDebugEnabled())
1211             {
1212                 log.debug("[" + getUid() + "] doEndTag - exporting");
1213             }
1214 
1215             returnValue = doExport();
1216         }
1217 
1218         // do not remove media attribute! if the table is nested in other tables this is still needed
1219         // this.pageContext.removeAttribute(PAGE_ATTRIBUTE_MEDIA);
1220 
1221         if (log.isDebugEnabled())
1222         {
1223             log.debug("[" + getUid() + "] doEndTag - end");
1224         }
1225 
1226         cleanUp();
1227         return returnValue;
1228     }
1229 
1230     /**
1231      * Returns the name of the table decorator that should be applied to this table,
1232      * which is either the decorator configured in the property "decorator", or if
1233      * none is configured in said property, a decorator configured with the
1234      * "decorator.media.[media type]" property, or null if none is configured.  
1235      * 
1236      * @return Name of the table decorator that should be applied to this table.
1237      */
1238 	private String getConfiguredDecoratorName()
1239     {
1240         String
1241         tableDecoratorName = (this.decoratorName == null) ?
1242                                    this.properties.getMediaTypeDecoratorName(this.currentMediaType) :
1243                                    this.decoratorName;
1244         tableDecoratorName = (tableDecoratorName == null) ?
1245                                    this.properties.getExportDecoratorName(this.currentMediaType) :
1246                                    tableDecoratorName;
1247         return tableDecoratorName;
1248 	}
1249 
1250     /**
1251      * clean up instance variables, but not the ones representing tag attributes.
1252      */
1253     private void cleanUp()
1254     {
1255         // reset instance variables (non attributes)
1256         this.currentMediaType = null;
1257         this.baseHref = null;
1258         this.caption = null;
1259         this.captionTag = null;
1260         this.currentRow = null;
1261         this.doAfterBodyExecuted = false;
1262         this.footer = null;
1263         this.listHelper = null;
1264         this.pageNumber = 0;
1265         this.paramEncoder = null;
1266         this.properties = null;
1267         this.rowNumber = 1;
1268         this.tableIterator = null;
1269         this.tableModel = null;
1270         this.list = null;
1271     }
1272 
1273     /**
1274      * If no columns are provided, automatically add them from bean properties. Get the first object in the list and get
1275      * all the properties (except the "class" property which is automatically skipped). Of course this isn't possible
1276      * for empty lists.
1277      */
1278     private void describeEmptyTable()
1279     {
1280         this.tableIterator = IteratorUtils.getIterator(this.list);
1281 
1282         if (this.tableIterator.hasNext())
1283         {
1284             Object iteratedObject = this.tableIterator.next();
1285             Map objectProperties = new HashMap();
1286 
1287             // if it's a String don't add the "Bytes" column
1288             if (iteratedObject instanceof String)
1289             {
1290                 return;
1291             }
1292             // if it's a map already use key names for column headers
1293             if (iteratedObject instanceof Map)
1294             {
1295                 objectProperties = (Map) iteratedObject;
1296             }
1297             else
1298             {
1299                 try
1300                 {
1301                     objectProperties = BeanUtils.describe(iteratedObject);
1302                 }
1303                 catch (Exception e)
1304                 {
1305                     log.warn("Unable to automatically add columns: " + e.getMessage(), e);
1306                 }
1307             }
1308 
1309             // iterator on properties names
1310             Iterator propertiesIterator = objectProperties.keySet().iterator();
1311 
1312             while (propertiesIterator.hasNext())
1313             {
1314                 // get the property name
1315                 String propertyName = (String) propertiesIterator.next();
1316 
1317                 // dont't want to add the standard "class" property
1318                 if (!"class".equals(propertyName)) //$NON-NLS-1$
1319                 {
1320                     // creates a new header and add to the table model
1321                     HeaderCell headerCell = new HeaderCell();
1322                     headerCell.setBeanPropertyName(propertyName);
1323 
1324                     // handle title i18n
1325                     headerCell.setTitle(this.properties.geResourceProvider().getResource(
1326                         null,
1327                         propertyName,
1328                         this,
1329                         this.pageContext));
1330 
1331                     this.tableModel.addColumnHeader(headerCell);
1332                 }
1333             }
1334         }
1335     }
1336 
1337     /**
1338      * Called when data are not displayed in a html page but should be exported.
1339      * @return int SKIP_PAGE
1340      * @throws JspException generic exception
1341      */
1342     protected int doExport() throws JspException
1343     {
1344 
1345         boolean exportFullList = this.properties.getExportFullList();
1346 
1347         if (log.isDebugEnabled())
1348         {
1349             log.debug("[" + getUid() + "] currentMediaType=" + this.currentMediaType);
1350         }
1351 
1352         boolean exportHeader = this.properties.getExportHeader(this.currentMediaType);
1353         boolean exportDecorated = this.properties.getExportDecorated();
1354 
1355         ExportView exportView = ExportViewFactory.getInstance().getView(
1356             this.currentMediaType,
1357             this.tableModel,
1358             exportFullList,
1359             exportHeader,
1360             exportDecorated);
1361 
1362         try
1363         {
1364             writeExport(exportView);
1365         }
1366         catch (IOException e)
1367         {
1368             throw new WrappedRuntimeException(getClass(), e);
1369         }
1370 
1371         return SKIP_PAGE;
1372     }
1373 
1374     /**
1375      * Will write the export. The default behavior is to write directly to the response. If the ResponseOverrideFilter
1376      * is configured for this request, will instead write the exported content to a map in the Request object.
1377      * @param exportView export view
1378      * @throws JspException for problem in clearing the response or for invalid export views
1379      * @throws IOException exception thrown when writing content to the response
1380      */
1381     protected void writeExport(ExportView exportView) throws IOException, JspException
1382     {
1383         String filename = properties.getExportFileName(this.currentMediaType);
1384 
1385         HttpServletResponse response = (HttpServletResponse) this.pageContext.getResponse();
1386         HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest();
1387 
1388         Map bean = (Map) request.getAttribute(FILTER_CONTENT_OVERRIDE_BODY);
1389         boolean usingFilter = bean != null;
1390 
1391         String mimeType = exportView.getMimeType();
1392         // original encoding, be sure to add it back after reset()
1393         String characterEncoding = response.getCharacterEncoding();
1394 
1395         if (usingFilter)
1396         {
1397             if (!bean.containsKey(TableTagParameters.BEAN_BUFFER))
1398             {
1399                 // We are running under the export filter, call it
1400                 log.debug("Exportfilter enabled in unbuffered mode, setting headers");
1401                 response.addHeader(TableTagParameters.PARAMETER_EXPORTING, TagConstants.EMPTY_STRING);
1402             }
1403             else
1404             {
1405                 // We are running under the export filter in buffered mode
1406                 bean.put(TableTagParameters.BEAN_CONTENTTYPE, mimeType);
1407                 bean.put(TableTagParameters.BEAN_FILENAME, filename);
1408 
1409                 if (exportView instanceof TextExportView)
1410                 {
1411                     StringWriter writer = new StringWriter();
1412                     ((TextExportView) exportView).doExport(writer);
1413                     bean.put(TableTagParameters.BEAN_BODY, writer.toString());
1414                 }
1415                 else if (exportView instanceof BinaryExportView)
1416                 {
1417                     ByteArrayOutputStream stream = new ByteArrayOutputStream();
1418                     ((BinaryExportView) exportView).doExport(stream);
1419                     bean.put(TableTagParameters.BEAN_BODY, stream.toByteArray());
1420 
1421                 }
1422                 else
1423                 {
1424                     throw new JspTagException("Export view "
1425                         + exportView.getClass().getName()
1426                         + " must implement TextExportView or BinaryExportView");
1427                 }
1428 
1429                 return;
1430             }
1431         }
1432         else
1433         {
1434             log.debug("Exportfilter NOT enabled");
1435             // response can't be already committed at this time
1436             if (response.isCommitted())
1437             {
1438                 throw new ExportException(getClass());
1439             }
1440 
1441             try
1442             {
1443                 response.reset();
1444                 pageContext.getOut().clearBuffer();
1445             }
1446             catch (Exception e)
1447             {
1448                 throw new ExportException(getClass());
1449             }
1450         }
1451 
1452         if (!usingFilter && characterEncoding != null && mimeType.indexOf("charset") == -1) //$NON-NLS-1$
1453         {
1454             mimeType += "; charset=" + characterEncoding; //$NON-NLS-1$
1455         }
1456 
1457         response.setContentType(mimeType);
1458 
1459         if (StringUtils.isNotEmpty(filename))
1460         {
1461             response.setHeader("Content-Disposition", //$NON-NLS-1$
1462                 "attachment; filename=\"" + filename + "\""); //$NON-NLS-1$ //$NON-NLS-2$
1463         }
1464 
1465         if (exportView instanceof TextExportView)
1466         {
1467             Writer writer;
1468             if (usingFilter)
1469             {
1470                 writer = response.getWriter();
1471             }
1472             else
1473             {
1474                 writer = pageContext.getOut();
1475             }
1476 
1477             ((TextExportView) exportView).doExport(writer);
1478         }
1479         else if (exportView instanceof BinaryExportView)
1480         {
1481             // dealing with binary content
1482             // note that this is not assured to work on any application server if the filter is not enabled. According
1483             // to the jsp specs response.getOutputStream() should no be called in jsps.
1484             ((BinaryExportView) exportView).doExport(response.getOutputStream());
1485         }
1486         else
1487         {
1488             throw new JspTagException("Export view "
1489                 + exportView.getClass().getName()
1490                 + " must implement TextExportView or BinaryExportView");
1491         }
1492 
1493         log.debug("Export completed");
1494 
1495     }
1496 
1497     /**
1498      * This sets the list of all of the data that will be displayed on the page via the table tag. This might include
1499      * just a subset of the total data in the list due to to paging being active, or the user asking us to just show a
1500      * subset, etc...
1501      */
1502     protected void setupViewableData()
1503     {
1504 
1505         // If the user has changed the way our default behavior works, then we need to look for it now, and resort
1506         // things if needed before we ask for the viewable part. (this is a bad place for this, this should be
1507         // refactored and moved somewhere else).
1508 
1509         if (this.paginatedList == null || this.tableModel.isLocalSort())
1510         {
1511             if (this.tableModel.isSortFullTable())
1512             {
1513                 // Sort the total list...
1514                 this.tableModel.sortFullList();
1515             }
1516         }
1517 
1518         Object originalData = this.tableModel.getRowListFull();
1519 
1520         // If they have asked for a subset of the list via the length
1521         // attribute, then only fetch those items out of the master list.
1522         List fullList = CollectionUtil.getListFromObject(originalData, this.offset, this.length);
1523 
1524         int pageOffset = this.offset;
1525         // If they have asked for just a page of the data, then use the
1526         // SmartListHelper to figure out what page they are after, etc...
1527         if (this.paginatedList == null && this.pagesize > 0)
1528         {
1529             this.listHelper = new SmartListHelper(fullList, (this.partialList) ? ((Integer) size).intValue() : fullList
1530                 .size(), this.pagesize, this.properties, this.partialList);
1531             this.listHelper.setCurrentPage(this.pageNumber);
1532             pageOffset = this.listHelper.getFirstIndexForCurrentPage();
1533             fullList = this.listHelper.getListForCurrentPage();
1534         }
1535         else if (this.paginatedList != null)
1536         {
1537             this.listHelper = new PaginatedListSmartListHelper(this.paginatedList, this.properties);
1538         }
1539         this.tableModel.setRowListPage(fullList);
1540         this.tableModel.setPageOffset(pageOffset);
1541     }
1542 
1543     /**
1544      * Uses HtmlTableWriter to write table called when data have to be displayed in a html page.
1545      * @throws JspException generic exception
1546      */
1547     private void writeHTMLData() throws JspException
1548     {
1549         JspWriter out = this.pageContext.getOut();
1550 
1551         String css = this.properties.getCssTable();
1552         if (StringUtils.isNotBlank(css))
1553         {
1554             this.addClass(css);
1555         }
1556         // use HtmlTableWriter to write table
1557         new HtmlTableWriter(
1558             this.tableModel,
1559             this.properties,
1560             this.baseHref,
1561             this.export,
1562             out,
1563             getCaptionTag(),
1564             this.paginatedList,
1565             this.listHelper,
1566             this.pagesize,
1567             getAttributeMap(),
1568             this.uid).writeTable(this.tableModel, this.getUid());
1569 
1570         if (this.varTotals != null)
1571         {
1572             pageContext.setAttribute(this.varTotals, getTotals());
1573         }
1574     }
1575 
1576     /**
1577      * Get the column totals Map. If there is no varTotals defined, there are no totals.
1578      * @return a Map of totals where the key is the column number and the value is the total for that column
1579      */
1580     public Map getTotals()
1581     {
1582         Map totalsMap = new HashMap();
1583         if (this.varTotals != null)
1584         {
1585             List headers = this.tableModel.getHeaderCellList();
1586             for (Iterator iterator = headers.iterator(); iterator.hasNext();)
1587             {
1588                 HeaderCell headerCell = (HeaderCell) iterator.next();
1589                 if (headerCell.isTotaled())
1590                 {
1591                     totalsMap.put("column" + (headerCell.getColumnNumber() + 1), new Double(headerCell.getTotal()));
1592                 }
1593             }
1594         }
1595         return totalsMap;
1596     }
1597 
1598     /**
1599      * Get the table model for this tag. Sometimes required by local tags that cooperate with DT. USE THIS METHOD WITH
1600      * EXTREME CAUTION; IT PROVIDES ACCESS TO THE INTERNALS OF DISPLAYTAG, WHICH ARE NOT TO BE CONSIDERED STABLE PUBLIC
1601      * INTERFACES.
1602      * @return the TableModel
1603      */
1604     public TableModel getTableModel()
1605     {
1606         return this.tableModel;
1607     }
1608 
1609     /**
1610      * Called by the setProperty tag to override some default behavior or text String.
1611      * @param propertyName String property name
1612      * @param propertyValue String property value
1613      */
1614     public void setProperty(String propertyName, String propertyValue)
1615     {
1616         this.properties.setProperty(propertyName, propertyValue);
1617     }
1618 
1619     /**
1620      * @see javax.servlet.jsp.tagext.Tag#release()
1621      */
1622     public void release()
1623     {
1624         if (log.isDebugEnabled())
1625         {
1626             log.debug("[" + getUid() + "] release() called");
1627         }
1628 
1629         super.release();
1630 
1631         // tag attributes
1632         this.decoratorName = null;
1633         this.defaultSortedColumn = -1;
1634         this.defaultSortOrder = null;
1635         this.export = false;
1636         this.length = 0;
1637         this.listAttribute = null;
1638         this.localSort = true;
1639         this.name = null;
1640         this.offset = 0;
1641         this.pagesize = 0;
1642         this.partialList = false;
1643         this.requestUri = null;
1644         this.dontAppendContext = false;
1645         this.sortFullTable = null;
1646         this.excludedParams = null;
1647         this.filteredRows = null;
1648         this.uid = null;
1649         this.paginatedList = null;
1650     }
1651 
1652     /**
1653      * Returns the name.
1654      * @return String
1655      */
1656     protected String getName()
1657     {
1658         return this.name;
1659     }
1660 
1661     /**
1662      * encode a parameter name to be unique in the page using ParamEncoder.
1663      * @param parameterName parameter name to encode
1664      * @return String encoded parameter name
1665      */
1666     private String encodeParameter(String parameterName)
1667     {
1668         // paramEncoder has been already instantiated?
1669         if (this.paramEncoder == null)
1670         {
1671             // use the id attribute to get the unique identifier
1672             this.paramEncoder = new ParamEncoder(getUid());
1673         }
1674 
1675         return this.paramEncoder.encodeParameterName(parameterName);
1676     }
1677 
1678 }