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 }