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