• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

hazendaz / displaytag / 1753

12 Feb 2026 03:17AM UTC coverage: 77.321% (-0.01%) from 77.334%
1753

push

github

web-flow
Merge pull request #1102 from hazendaz/renovate/javax-support-logback-monorepo

Update dependency ch.qos.logback:logback-classic to v1.5.29 (javax-support)

1438 of 2003 branches covered (71.79%)

Branch coverage included in aggregate %.

4034 of 5074 relevant lines covered (79.5%)

0.8 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.01
/displaytag/src/main/java/org/displaytag/tags/TableTag.java
1
/*
2
 * SPDX-License-Identifier: MIT
3
 * See LICENSE file for details.
4
 *
5
 * Copyright 2002-2026 Fabrizio Giustina, the Displaytag team
6
 */
7
package org.displaytag.tags;
8

9
import java.io.ByteArrayOutputStream;
10
import java.io.IOException;
11
import java.io.StringWriter;
12
import java.io.Writer;
13
import java.util.Collection;
14
import java.util.HashMap;
15
import java.util.Iterator;
16
import java.util.List;
17
import java.util.Map;
18

19
import javax.servlet.http.HttpServletRequest;
20
import javax.servlet.http.HttpServletResponse;
21
import javax.servlet.http.HttpSession;
22
import javax.servlet.jsp.JspException;
23
import javax.servlet.jsp.JspTagException;
24
import javax.servlet.jsp.JspWriter;
25
import javax.servlet.jsp.tagext.Tag;
26

27
import org.apache.commons.beanutils2.BeanUtils;
28
import org.apache.commons.collections4.IteratorUtils;
29
import org.apache.commons.lang3.ObjectUtils;
30
import org.apache.commons.lang3.Range;
31
import org.apache.commons.lang3.StringUtils;
32
import org.apache.commons.lang3.Strings;
33
import org.apache.commons.lang3.math.NumberUtils;
34
import org.displaytag.Messages;
35
import org.displaytag.decorator.TableDecorator;
36
import org.displaytag.exception.ExportException;
37
import org.displaytag.exception.InvalidTagAttributeValueException;
38
import org.displaytag.exception.WrappedRuntimeException;
39
import org.displaytag.export.BinaryExportView;
40
import org.displaytag.export.ExportView;
41
import org.displaytag.export.ExportViewFactory;
42
import org.displaytag.export.TextExportView;
43
import org.displaytag.model.Cell;
44
import org.displaytag.model.Column;
45
import org.displaytag.model.HeaderCell;
46
import org.displaytag.model.Row;
47
import org.displaytag.model.TableModel;
48
import org.displaytag.pagination.PaginatedList;
49
import org.displaytag.pagination.PaginatedListSmartListHelper;
50
import org.displaytag.pagination.SmartListHelper;
51
import org.displaytag.properties.MediaTypeEnum;
52
import org.displaytag.properties.SortOrderEnum;
53
import org.displaytag.properties.TableProperties;
54
import org.displaytag.render.HtmlTableWriter;
55
import org.displaytag.render.TableTotaler;
56
import org.displaytag.util.CollectionUtil;
57
import org.displaytag.util.Href;
58
import org.displaytag.util.ParamEncoder;
59
import org.displaytag.util.RequestHelper;
60
import org.displaytag.util.RequestHelperFactory;
61
import org.displaytag.util.TagConstants;
62
import org.slf4j.Logger;
63
import org.slf4j.LoggerFactory;
64

65
/**
66
 * This tag takes a list of objects and creates a table to display those objects. With the help of column tags, you
67
 * simply provide the name of properties (get Methods) that are called against the objects in your list that gets
68
 * displayed. This tag works very much like the struts iterator tag, most of the attributes have the same name and
69
 * functionality as the struts tag.
70
 */
71
public class TableTag extends HtmlTableTag {
1✔
72

73
    /**
74
     * name of the attribute added to page scope when exporting, containing an MediaTypeEnum this can be used in column
75
     * content to detect the output type and to return different data when exporting.
76
     */
77
    public static final String PAGE_ATTRIBUTE_MEDIA = "mediaType"; //$NON-NLS-1$
78

79
    /**
80
     * If this variable is found in the request, assume the export filter is enabled.
81
     */
82
    public static final String FILTER_CONTENT_OVERRIDE_BODY = //
83
            "org.displaytag.filter.ResponseOverrideFilter.CONTENT_OVERRIDE_BODY"; //$NON-NLS-1$
84

85
    /**
86
     * Serial ID.
87
     */
88
    private static final long serialVersionUID = 899149338534L;
89

90
    /**
91
     * logger.
92
     */
93
    private static Logger log = LoggerFactory.getLogger(TableTag.class);
1✔
94

95
    /**
96
     * RequestHelperFactory instance used for link generation.
97
     */
98
    private static RequestHelperFactory rhf;
99

100
    /**
101
     * Object (collection, list) on which the table is based. This is not set directly using a tag attribute and can be
102
     * cleaned.
103
     */
104
    protected transient Object list;
105

106
    // -- start tag attributes --
107

108
    /**
109
     * Object (collection, list) on which the table is based. Set directly using the "list" attribute or evaluated from
110
     * expression.
111
     */
112
    protected transient Object listAttribute;
113

114
    /**
115
     * actual row number, updated during iteration.
116
     */
117
    private int rowNumber = 1;
1✔
118

119
    /**
120
     * name of the object to use for iteration. Can contain expressions.
121
     */
122
    private String name;
123

124
    /**
125
     * length of list to display.
126
     */
127
    private int length;
128

129
    /**
130
     * table decorator class name.
131
     */
132
    private String decoratorName;
133

134
    /**
135
     * page size.
136
     */
137
    private int pagesize;
138

139
    /**
140
     * list contains only viewable data.
141
     */
142
    private boolean partialList;
143

144
    /**
145
     * add export links.
146
     */
147
    private boolean export;
148

149
    /**
150
     * list offset.
151
     */
152
    private int offset;
153

154
    /** Integer containing total size of the data displaytag is paginating. */
155
    private transient Object size;
156

157
    /** Name of the Integer in some scope containing the size of the data displaytag is paginating. */
158
    private String sizeObjectName;
159

160
    /** sort the full list?. */
161
    private Boolean sortFullTable;
162

163
    /** are we doing any local sorting? (defaults to True). */
164
    private boolean localSort = true;
1✔
165

166
    /**
167
     * Request uri.
168
     */
169
    private String requestUri;
170

171
    /**
172
     * Prepend application context to generated links.
173
     */
174
    private boolean dontAppendContext;
175

176
    /**
177
     * the index of the column sorted by default.
178
     */
179
    private int defaultSortedColumn = -1;
1✔
180

181
    /**
182
     * the sorting order for the sorted column.
183
     */
184
    private SortOrderEnum defaultSortOrder;
185

186
    /**
187
     * Name of parameter which should not be forwarded during sorting or pagination.
188
     */
189
    private String excludedParams;
190

191
    /**
192
     * Unique table id.
193
     */
194
    private String uid;
195

196
    /**
197
     * The variable name to store totals in.
198
     */
199
    private String varTotals;
200

201
    /**
202
     * Preserve the current page and sort.
203
     */
204
    private boolean keepStatus;
205

206
    /**
207
     * Clear the current page and sort status.
208
     */
209
    private boolean clearStatus;
210

211
    /**
212
     * Use form post in paging/sorting links (javascript required).
213
     */
214
    private String form;
215

216
    // -- end tag attributes --
217

218
    /**
219
     * table model - initialized in doStartTag().
220
     */
221
    private transient TableModel tableModel;
222

223
    /**
224
     * current row.
225
     */
226
    private transient Row currentRow;
227

228
    /**
229
     * next row.
230
     */
231

232
    /**
233
     * Used by various functions when the person wants to do paging - cleaned in doEndTag().
234
     */
235
    private transient SmartListHelper listHelper;
236

237
    /**
238
     * base href used for links - set in initParameters().
239
     */
240
    private Href baseHref;
241

242
    /**
243
     * table properties - set in doStartTag().
244
     */
245
    private transient TableProperties properties;
246

247
    /**
248
     * page number - set in initParameters().
249
     */
250
    private int pageNumber = 1;
1✔
251

252
    /**
253
     * Iterator on collection.
254
     */
255
    private transient Iterator<?> tableIterator;
256

257
    /**
258
     * export type - set in initParameters().
259
     */
260
    private transient MediaTypeEnum currentMediaType;
261

262
    /** daAfterBody() has been executed at least once?. */
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
     * Is this the last iteration we will be performing? We only output the footer on the last iteration.
277
     */
278
    private boolean lastIteration;
279

280
    /**
281
     * Static caption added using the footer tag.
282
     */
283
    private String caption;
284

285
    /**
286
     * Child caption tag.
287
     */
288
    private CaptionTag captionTag;
289

290
    /**
291
     * Included row range. If no rows can be skipped the range is from 0 to Integer.MAX_VALUE.
292
     */
293
    private Range<Integer> filteredRows;
294

295
    /**
296
     * The paginated list containing the external pagination and sort parameters The presence of this paginated list is
297
     * what determines if external pagination and sorting is used or not.
298
     */
299
    private transient PaginatedList<Row> paginatedList;
300

301
    /**
302
     * The classname of the totaler.
303
     */
304
    private String totalerName;
305

306
    /**
307
     * Is this the last iteration?.
308
     *
309
     * @return boolean <code>true</code> if this is the last iteration
310
     */
311
    public boolean isLastIteration() {
312
        return this.lastIteration;
1✔
313
    }
314

315
    /**
316
     * Sets the list of parameter which should not be forwarded during sorting or pagination.
317
     *
318
     * @param value
319
     *            whitespace separated list of parameters which should not be included (* matches all parameters)
320
     */
321
    public void setExcludedParams(final String value) {
322
        this.excludedParams = value;
1✔
323
    }
1✔
324

325
    /**
326
     * Sets the content of the footer. Called by a nested footer tag.
327
     *
328
     * @param string
329
     *            footer content
330
     */
331
    public void setFooter(final String string) {
332
        this.footer = string;
1✔
333
        this.tableModel.setFooter(this.footer);
1✔
334
    }
1✔
335

336
    /**
337
     * Sets the content of the caption. Called by a nested caption tag.
338
     *
339
     * @param string
340
     *            caption content
341
     */
342
    public void setCaption(final String string) {
343
        this.caption = string;
1✔
344
        this.tableModel.setCaption(this.caption);
1✔
345
    }
1✔
346

347
    /**
348
     * Set the child caption tag.
349
     *
350
     * @param captionTag
351
     *            Child caption tag
352
     */
353
    public void setCaptionTag(final CaptionTag captionTag) {
354
        this.captionTag = captionTag;
1✔
355
    }
1✔
356

357
    /**
358
     * Obtain the child caption tag.
359
     *
360
     * @return The child caption tag
361
     */
362
    public CaptionTag getCaptionTag() {
363
        return this.captionTag;
1✔
364
    }
365

366
    /**
367
     * Is the current row empty?.
368
     *
369
     * @return true if the current row is empty
370
     */
371
    public boolean isEmpty() {
372
        return this.currentRow == null;
1✔
373
    }
374

375
    /**
376
     * Preserve the current page and sort across session?.
377
     *
378
     * @param keepStatus
379
     *            <code>true</code> to preserve paging and sorting
380
     */
381
    public void setKeepStatus(final boolean keepStatus) {
382
        this.keepStatus = keepStatus;
1✔
383
    }
1✔
384

385
    /**
386
     * Setter for <code>clearStatus</code>.
387
     *
388
     * @param clearStatus
389
     *            The clearStatus to set.
390
     */
391
    public void setClearStatus(final boolean clearStatus) {
392
        this.clearStatus = clearStatus;
1✔
393
    }
1✔
394

395
    /**
396
     * Setter for <code>form</code>.
397
     *
398
     * @param form
399
     *            The form to set.
400
     */
401
    public void setForm(final String form) {
402
        this.form = form;
1✔
403
    }
1✔
404

405
    /**
406
     * set the Integer containing the total size of the data displaytag is paginating.
407
     *
408
     * @param size
409
     *            Integer containing the total size of the data
410
     */
411
    public void setSize(final Object size) {
412
        if (size instanceof String) {
1!
413
            this.sizeObjectName = (String) size;
1✔
414
        } else {
415
            this.size = size;
×
416
        }
417
    }
1✔
418

419
    /**
420
     * set the name of the Integer in some scope containing the total size of the data to be paginated.
421
     *
422
     * @param sizeObjectName
423
     *            name of the Integer containing the total size of the data to be paginated
424
     */
425
    public void setSizeObjectName(final String sizeObjectName) {
426
        this.sizeObjectName = sizeObjectName;
×
427
    }
×
428

429
    /**
430
     * setter for the "sort" attribute.
431
     *
432
     * @param value
433
     *            "page" (sort a single page), "list" (sort the full list), "external" (list already sorted)
434
     *
435
     * @throws InvalidTagAttributeValueException
436
     *             if value is not "page", "list" or "external"
437
     */
438
    public void setSort(final String value) throws InvalidTagAttributeValueException {
439
        if (TableTagParameters.SORT_AMOUNT_PAGE.equals(value)) {
1✔
440
            this.sortFullTable = Boolean.FALSE;
1✔
441
        } else if (TableTagParameters.SORT_AMOUNT_LIST.equals(value)) {
1✔
442
            this.sortFullTable = Boolean.TRUE;
1✔
443
        } else if (TableTagParameters.SORT_AMOUNT_EXTERNAL.equals(value)) {
1✔
444
            this.localSort = false;
1✔
445
        } else {
446
            throw new InvalidTagAttributeValueException(this.getClass(), "sort", value); //$NON-NLS-1$
1✔
447
        }
448
    }
1✔
449

450
    /**
451
     * setter for the "requestURI" attribute. Context path is automatically added to path starting with "/".
452
     *
453
     * @param value
454
     *            base URI for creating links
455
     */
456
    public void setRequestURI(final String value) {
457
        this.requestUri = value;
1✔
458
    }
1✔
459

460
    /**
461
     * Setter for the "requestURIcontext" attribute.
462
     *
463
     * @param value
464
     *            base URI for creating links
465
     */
466
    public void setRequestURIcontext(final boolean value) {
467
        this.dontAppendContext = !value;
1!
468
    }
1✔
469

470
    /**
471
     * Used to directly set a list (or any object you can iterate on).
472
     *
473
     * @param value
474
     *            Object
475
     *
476
     * @deprecated use setItems()
477
     */
478
    @Deprecated
479
    public void setList(final Object value) {
480
        this.listAttribute = value;
1✔
481
    }
1✔
482

483
    /**
484
     * Sets the name of the object to use for iteration.
485
     *
486
     * @param value
487
     *            name of the object to use for iteration (can contain expression). It also supports direct setting of a
488
     *            list, for jsp 2.0 containers where users can set up a data source here using EL expressions.
489
     *
490
     * @deprecated please use setItems()
491
     */
492
    @Deprecated
493
    public void setName(final Object value) {
494
        if (value instanceof String) {
1!
495
            // ok, assuming this is the name of the object
496
            this.name = (String) value;
1✔
497
        } else {
498
            // is this the list?
499
            this.list = value;
×
500
        }
501
    }
1✔
502

503
    /**
504
     * Sets the name of the object to use for iteration.
505
     *
506
     * @param value
507
     *            the object to use for iteration (can contain expression).
508
     */
509
    public void setItems(final Object value) {
510
        this.list = value;
×
511
    }
×
512

513
    /**
514
     * Sets the name of the object to use for iteration. This setter is needed for jsp 1.1 container which doesn't
515
     * support the String - Object conversion. The bean info class will swith to this setter.
516
     *
517
     * @param value
518
     *            name of the object
519
     */
520
    public void setNameString(final String value) {
521
        this.name = value;
×
522
    }
×
523

524
    /**
525
     * sets the sorting order for the sorted column.
526
     *
527
     * @param value
528
     *            "ascending" or "descending"
529
     *
530
     * @throws InvalidTagAttributeValueException
531
     *             if value is not one of "ascending" or "descending"
532
     */
533
    public void setDefaultorder(final String value) throws InvalidTagAttributeValueException {
534
        this.defaultSortOrder = SortOrderEnum.fromName(value);
1✔
535
        if (this.defaultSortOrder == null) {
1!
536
            throw new InvalidTagAttributeValueException(this.getClass(), "defaultorder", value); //$NON-NLS-1$
×
537
        }
538
    }
1✔
539

540
    /**
541
     * Setter for the decorator class name.
542
     *
543
     * @param decorator
544
     *            fully qualified name of the table decorator to use
545
     */
546
    public void setDecorator(final String decorator) {
547
        this.decoratorName = decorator;
1✔
548
    }
1✔
549

550
    /**
551
     * Is export enabled?.
552
     *
553
     * @param value
554
     *            <code>true</code> if export should be enabled
555
     */
556
    public void setExport(final boolean value) {
557
        this.export = value;
1✔
558
    }
1✔
559

560
    /**
561
     * The variable name in which the totals map is stored.
562
     *
563
     * @param varTotalsName
564
     *            the value
565
     */
566
    public void setVarTotals(final String varTotalsName) {
567
        this.varTotals = varTotalsName;
1✔
568
    }
1✔
569

570
    /**
571
     * Get the name that the totals should be stored under.
572
     *
573
     * @return the var name in pageContext
574
     */
575
    public String getVarTotals() {
576
        return this.varTotals;
1✔
577
    }
578

579
    /**
580
     * sets the number of items to be displayed in the page.
581
     *
582
     * @param value
583
     *            number of items to display in a page
584
     */
585
    public void setLength(final int value) {
586
        this.length = value;
1✔
587
    }
1✔
588

589
    /**
590
     * sets the index of the default sorted column.
591
     *
592
     * @param value
593
     *            index of the column to sort
594
     */
595
    public void setDefaultsort(final int value) {
596
        // subtract one (internal index is 0 based)
597
        this.defaultSortedColumn = value - 1;
1✔
598
    }
1✔
599

600
    /**
601
     * sets the number of items that should be displayed for a single page.
602
     *
603
     * @param value
604
     *            number of items that should be displayed for a single page
605
     */
606
    public void setPagesize(final int value) {
607
        this.pagesize = value;
1✔
608
    }
1✔
609

610
    /**
611
     * tells display tag that the values contained in the list are the viewable data only, there may be more results not
612
     * given to displaytag.
613
     *
614
     * @param partialList
615
     *            boolean value telling us there may be more data not given to displaytag
616
     */
617
    public void setPartialList(final boolean partialList) {
618
        this.partialList = partialList;
1✔
619
    }
1✔
620

621
    /**
622
     * Setter for the list offset attribute.
623
     *
624
     * @param value
625
     *            String
626
     */
627
    public void setOffset(final int value) {
628
        if (value < 1) {
1!
629
            // negative values has no meaning, simply treat them as 0
630
            this.offset = 0;
×
631
        } else {
632
            this.offset = value - 1;
1✔
633
        }
634
    }
1✔
635

636
    /**
637
     * Sets the unique id used to identify for this table.
638
     *
639
     * @param value
640
     *            String
641
     */
642
    public void setUid(final String value) {
643
        this.uid = value;
1✔
644
    }
1✔
645

646
    /**
647
     * Returns the unique id used to identify for this table.
648
     *
649
     * @return id for this table
650
     */
651
    public String getUid() {
652
        return this.uid;
1✔
653
    }
654

655
    /**
656
     * Returns the properties.
657
     *
658
     * @return TableProperties
659
     */
660
    public TableProperties getProperties() {
661
        return this.properties;
1✔
662
    }
663

664
    /**
665
     * Returns the base href with parameters. This is the instance used for links, need to be cloned before being
666
     * modified.
667
     *
668
     * @return base Href with parameters
669
     */
670
    protected Href getBaseHref() {
671
        return this.baseHref;
1✔
672
    }
673

674
    /**
675
     * Called by interior column tags to help this tag figure out how it is supposed to display the information in the
676
     * List it is supposed to display.
677
     *
678
     * @param column
679
     *            an internal tag describing a column in this tableview
680
     */
681
    public void addColumn(final HeaderCell column) {
682
        if (TableTag.log.isDebugEnabled()) {
1!
683
            TableTag.log.debug("[{}] addColumn {}", this.getUid(), column);
×
684
        }
685

686
        if (this.paginatedList != null && column.getSortable()) {
1!
687
            final String sortCriterion = this.paginatedList.getSortCriterion();
1✔
688

689
            String sortProperty = column.getSortProperty();
1✔
690
            if (sortProperty == null) {
1!
691
                sortProperty = column.getBeanPropertyName();
1✔
692
            }
693

694
            if (sortCriterion != null && sortCriterion.equals(sortProperty)) {
1!
695
                this.tableModel.setSortedColumnNumber(this.tableModel.getNumberOfColumns());
1✔
696
                column.setAlreadySorted();
1✔
697
            }
698
        }
699

700
        this.tableModel.addColumnHeader(column);
1✔
701
    }
1✔
702

703
    /**
704
     * Adds a cell to the current row. This method is usually called by a contained ColumnTag
705
     *
706
     * @param cell
707
     *            Cell to add to the current row
708
     */
709
    public void addCell(final Cell cell) {
710
        // check if null: could be null if list is empty, we don't need to fill rows
711
        if (this.currentRow != null) {
1✔
712
            final int columnNumber = this.currentRow.getCellList().size();
1✔
713
            this.currentRow.addCell(cell);
1✔
714

715
            // just be sure that the number of columns has not been altered by conditionally including column tags in
716
            // different rows. This is not supported, but better avoid IndexOutOfBounds...
717
            if (columnNumber < this.tableModel.getHeaderCellList().size()) {
1✔
718
                final HeaderCell header = this.tableModel.getHeaderCellList().get(columnNumber);
1✔
719
                header.addCell(new Column(header, cell, this.currentRow));
1✔
720
            }
721
        }
722
    }
1✔
723

724
    /**
725
     * Is this the first iteration?.
726
     *
727
     * @return boolean <code>true</code> if this is the first iteration
728
     */
729
    public boolean isFirstIteration() {
730
        if (TableTag.log.isDebugEnabled()) {
1!
731
            TableTag.log.debug("[{}] first iteration={} (row number={})", this.getUid(), (this.rowNumber == 1),
×
732
                    this.rowNumber);
×
733
        }
734
        // in first iteration this.rowNumber is 1
735
        // (this.rowNumber is incremented in doAfterBody)
736
        return this.rowNumber == 1;
1✔
737
    }
738

739
    /**
740
     * When the tag starts, we just initialize some of our variables, and do a little bit of error checking to make sure
741
     * that the user is not trying to give us parameters that we don't expect.
742
     *
743
     * @return int
744
     *
745
     * @throws JspException
746
     *             generic exception
747
     *
748
     * @see javax.servlet.jsp.tagext.Tag#doStartTag()
749
     */
750
    @Override
751
    public int doStartTag() throws JspException {
752

753
        // needed before column processing, elsewhere registered views will not be added
754
        ExportViewFactory.getInstance();
1✔
755

756
        if (TableTag.log.isDebugEnabled()) {
1!
757
            TableTag.log.debug("[{}] doStartTag called", this.getUid());
×
758
        }
759

760
        this.properties = TableProperties.getInstance(this.pageContext);
1✔
761
        this.tableModel = new TableModel(this.properties, this.pageContext.getResponse().getCharacterEncoding(),
1✔
762
                this.pageContext);
763

764
        // copying id to the table model for logging
765
        this.tableModel.setId(this.getUid());
1✔
766
        this.tableModel.setForm(this.form);
1✔
767

768
        this.initParameters();
1✔
769

770
        this.tableModel.setMedia(this.currentMediaType);
1✔
771

772
        final Object previousMediaType = this.pageContext.getAttribute(TableTag.PAGE_ATTRIBUTE_MEDIA);
1✔
773
        // set the PAGE_ATTRIBUTE_MEDIA attribute in the page scope
774
        if (previousMediaType == null || MediaTypeEnum.HTML.equals(previousMediaType)) {
1✔
775
            if (TableTag.log.isDebugEnabled()) {
1!
776
                TableTag.log.debug("[{}] setting media [{}] in this.pageContext", this.getUid(), this.currentMediaType);
×
777
            }
778
            this.pageContext.setAttribute(TableTag.PAGE_ATTRIBUTE_MEDIA, this.currentMediaType);
1✔
779
        }
780

781
        this.doIteration();
1✔
782

783
        // always return EVAL_BODY_TAG to get column headers also if the table is empty
784
        // using int to avoid deprecation error in compilation using j2ee 1.3
785
        return 2;
1✔
786
    }
787

788
    /**
789
     * Do after body.
790
     *
791
     * @return the int
792
     *
793
     * @see javax.servlet.jsp.tagext.BodyTag#doAfterBody()
794
     */
795
    @Override
796
    public int doAfterBody() {
797
        // doAfterBody() has been called, body is not empty
798
        this.doAfterBodyExecuted = true;
1✔
799

800
        if (TableTag.log.isDebugEnabled()) {
1!
801
            TableTag.log.debug("[{}] doAfterBody called - iterating on row {}", this.getUid(), this.rowNumber);
×
802
        }
803

804
        // increment this.rowNumber
805
        this.rowNumber++;
1✔
806

807
        // Call doIteration() to do the common work
808
        return this.doIteration();
1✔
809
    }
810

811
    /**
812
     * Utility method that is used by both doStartTag() and doAfterBody() to perform an iteration.
813
     *
814
     * @return <code>int</code> either EVAL_BODY_TAG or SKIP_BODY depending on whether another iteration is desired.
815
     */
816
    protected int doIteration() {
817

818
        if (TableTag.log.isDebugEnabled()) {
1!
819
            TableTag.log.debug("[{}] doIteration called", this.getUid());
×
820
        }
821

822
        // Row already filled?
823
        if (this.currentRow != null) {
1✔
824
            // if yes add to table model and remove
825
            this.tableModel.addRow(this.currentRow);
1✔
826
            this.currentRow = null;
1✔
827
        }
828

829
        if (this.tableIterator.hasNext()) {
1✔
830

831
            final Object iteratedObject = this.tableIterator.next();
1✔
832
            if (this.getUid() != null) {
1✔
833
                if (iteratedObject != null) {
1!
834
                    // set object into this.pageContext
835
                    if (TableTag.log.isDebugEnabled()) {
1!
836
                        TableTag.log.debug("[{}] setting attribute \"{}\" in pageContext", this.getUid(),
×
837
                                this.getUid());
×
838
                    }
839
                    this.pageContext.setAttribute(this.getUid(), iteratedObject);
1✔
840

841
                } else {
842
                    // if row is null remove previous object
843
                    this.pageContext.removeAttribute(this.getUid());
×
844
                }
845
                // set the current row number into this.pageContext
846
                this.pageContext.setAttribute(this.getUid() + TableTagExtraInfo.ROWNUM_SUFFIX,
1✔
847
                        Integer.valueOf(this.rowNumber));
1✔
848
            }
849

850
            // Row object for Cell values
851
            this.currentRow = new Row(iteratedObject, this.rowNumber);
1✔
852

853
            this.lastIteration = !this.tableIterator.hasNext();
1✔
854

855
            // new iteration
856
            // using int to avoid deprecation error in compilation using j2ee 1.3
857
            return 2;
1✔
858
        }
859
        this.lastIteration = true;
1✔
860

861
        if (TableTag.log.isDebugEnabled()) {
1!
862
            TableTag.log.debug("[{}] doIteration() - iterator ended after {} rows", this.getUid(), this.rowNumber - 1);
×
863
        }
864

865
        // end iteration
866
        return Tag.SKIP_BODY;
1✔
867
    }
868

869
    /**
870
     * Get the given parameter from the request or, if not available, look for into into the session if keepstatus is
871
     * set. Also takes care of storing an existing parameter into session.
872
     *
873
     * @param request
874
     *            servlet request
875
     * @param requestHelper
876
     *            request helper instance
877
     * @param parameter
878
     *            parameter, will be encoded
879
     *
880
     * @return value value taken from a request parameter or from a session attribute
881
     */
882
    private Integer getFromRequestOrSession(final HttpServletRequest request, final RequestHelper requestHelper,
883
            final String parameter) {
884
        final String encodedParam = this.encodeParameter(parameter);
1✔
885
        Integer result = requestHelper.getIntParameter(encodedParam);
1✔
886

887
        if (this.keepStatus) {
1✔
888
            if (result == null) {
1✔
889
                // get from session
890
                final HttpSession session = request.getSession(false);
1✔
891
                if (session != null) {
1!
892
                    if (this.clearStatus) {
1!
893
                        session.removeAttribute(encodedParam);
×
894
                    } else {
895
                        result = (Integer) session.getAttribute(encodedParam);
1✔
896
                    }
897
                }
898
            } else {
1✔
899
                // set into session
900
                request.getSession(true).setAttribute(encodedParam, result);
1✔
901
            }
902
        }
903
        return result;
1✔
904
    }
905

906
    /**
907
     * Reads parameters from the request and initialize all the needed table model attributes.
908
     *
909
     * @throws JspTagException
910
     *             the jsp tag exception
911
     */
912
    private void initParameters() throws JspTagException {
913

914
        if (TableTag.rhf == null) {
1✔
915
            // first time initialization
916
            TableTag.rhf = this.properties.getRequestHelperFactoryInstance();
1✔
917
        }
918

919
        final String fullName = this.getFullObjectName();
1✔
920

921
        // only evaluate if needed, else use list attribute
922
        if (fullName != null) {
1✔
923
            this.list = this.evaluateExpression(fullName);
1✔
924
        } else if (this.list == null) {
1!
925
            // needed to allow removing the collection of objects if not set directly
926
            this.list = this.listAttribute;
1✔
927
        }
928

929
        if (this.list instanceof PaginatedList) {
1✔
930
            this.paginatedList = (PaginatedList<Row>) this.list;
1✔
931
            this.list = this.paginatedList.getList();
1✔
932
        }
933

934
        // set the table model to perform in memory local sorting
935
        this.tableModel.setLocalSort(this.localSort && this.paginatedList == null);
1✔
936

937
        final HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest();
1✔
938
        final RequestHelper requestHelper = TableTag.rhf.getRequestHelperInstance(this.pageContext);
1✔
939

940
        this.initHref(requestHelper);
1✔
941

942
        final Integer pageNumberParameter = this.getFromRequestOrSession(request, requestHelper,
1✔
943
                TableTagParameters.PARAMETER_PAGE);
944
        this.pageNumber = pageNumberParameter == null ? 1 : pageNumberParameter.intValue();
1✔
945

946
        int sortColumn = -1;
1✔
947
        if (!this.tableModel.isLocalSort()) {
1✔
948
            // our sort column parameter may be a string, check that first
949
            final String sortColumnName = requestHelper
1✔
950
                    .getParameter(this.encodeParameter(TableTagParameters.PARAMETER_SORT));
1✔
951

952
            // if usename is not null, sortColumnName is the name, if not is the column index
953
            final String usename = requestHelper
1✔
954
                    .getParameter(this.encodeParameter(TableTagParameters.PARAMETER_SORTUSINGNAME));
1✔
955

956
            if (sortColumnName == null) {
1✔
957
                this.tableModel.setSortedColumnNumber(this.defaultSortedColumn);
1✔
958
            } else if (usename != null) {
1!
959

960
                this.tableModel.setSortedColumnName(sortColumnName); // its a string, set as string
1✔
961
            } else if (NumberUtils.isCreatable(sortColumnName)) {
×
962
                sortColumn = Integer.parseInt(sortColumnName);
×
963
                this.tableModel.setSortedColumnNumber(sortColumn); // its an int set as normal
×
964
            }
965
        } else if (this.paginatedList == null) {
1!
966
            final Integer sortColumnParameter = this.getFromRequestOrSession(request, requestHelper,
1✔
967
                    TableTagParameters.PARAMETER_SORT);
968
            sortColumn = sortColumnParameter == null ? this.defaultSortedColumn : sortColumnParameter.intValue();
1✔
969
            this.tableModel.setSortedColumnNumber(sortColumn);
1✔
970
        } else {
1✔
971
            sortColumn = this.defaultSortedColumn;
×
972
        }
973

974
        // default value
975
        boolean finalSortFull = this.properties.getSortFullList();
1✔
976

977
        // user value for this single table
978
        if (this.sortFullTable != null) {
1✔
979
            finalSortFull = this.sortFullTable.booleanValue();
1✔
980
        }
981

982
        // if a partial list is used and sort="list" is specified, assume the partial list is already sorted
983
        if (!this.partialList || !finalSortFull) {
1!
984
            this.tableModel.setSortFullTable(finalSortFull);
1✔
985
        }
986

987
        if (this.paginatedList == null) {
1✔
988
            SortOrderEnum paramOrder = SortOrderEnum
1✔
989
                    .fromCode(this.getFromRequestOrSession(request, requestHelper, TableTagParameters.PARAMETER_ORDER));
1✔
990

991
            // if no order parameter is set use default
992
            if (paramOrder == null) {
1✔
993
                paramOrder = this.defaultSortOrder;
1✔
994
            }
995

996
            final boolean order = SortOrderEnum.DESCENDING != paramOrder;
1✔
997
            this.tableModel.setSortOrderAscending(order);
1✔
998
        } else {
1✔
999
            final SortOrderEnum direction = this.paginatedList.getSortDirection();
1✔
1000
            this.tableModel.setSortOrderAscending(direction == SortOrderEnum.ASCENDING);
1!
1001
        }
1002

1003
        final Integer exportTypeParameter = requestHelper
1✔
1004
                .getIntParameter(this.encodeParameter(TableTagParameters.PARAMETER_EXPORTTYPE));
1✔
1005

1006
        this.currentMediaType = ObjectUtils.getIfNull(MediaTypeEnum.fromCode(exportTypeParameter), MediaTypeEnum.HTML);
1✔
1007

1008
        // if we are doing partialLists then ensure we have our size object
1009
        if (this.partialList) {
1✔
1010
            if (this.sizeObjectName == null && this.size == null) {
1!
1011
                // ?
1012
            }
1013
            if (this.sizeObjectName != null) {
1✔
1014
                // retrieve the object from scope
1015
                this.size = this.evaluateExpression(this.sizeObjectName);
1✔
1016
            }
1017
            if (this.size == null) {
1✔
1018
                throw new JspTagException(Messages.getString("MissingAttributeException.msg", new Object[] { "size" }));
1✔
1019
            }
1020
            if (!(this.size instanceof Integer)) {
1!
1021
                throw new JspTagException(
×
1022
                        Messages.getString("InvalidTypeException.msg", new Object[] { "size", "Integer" }));
×
1023
            }
1024
        }
1025
        this.tableIterator = IteratorUtils.getIterator(this.list);
1✔
1026

1027
        // do we really need to skip any row?
1028
        final boolean wishOptimizedIteration = (this.pagesize > 0 // we are paging
1!
1029
                || this.offset > 0 // or we are skipping some records using offset
1030
                || this.length > 0 // or we are limiting the records using length
1031
        ) && !this.partialList; // only optimize if we have the full list
1032

1033
        // can we actually skip any row?
1034
        if (wishOptimizedIteration && this.list instanceof Collection // we need to know the size
1✔
1035
                && (sortColumn == -1 // and we are not sorting
1036
                        || !finalSortFull // or we are sorting with the "page" behaviour
1037
                ) && (this.currentMediaType == MediaTypeEnum.HTML // and we are not exporting
1038
                        || !this.properties.getExportFullList())) // or we are exporting a single page
1!
1039
        {
1040
            int start = 0;
1✔
1041
            int end = 0;
1✔
1042
            if (this.offset > 0) {
1✔
1043
                start = this.offset;
1✔
1044
            }
1045
            if (this.length > 0) {
1✔
1046
                end = start + this.length;
1✔
1047
            }
1048

1049
            if (this.pagesize > 0) {
1✔
1050
                final int fullSize = ((Collection<?>) this.list).size();
1✔
1051
                start = (this.pageNumber - 1) * this.pagesize;
1✔
1052

1053
                // invalid page requested, go back to last page
1054
                if (start >= fullSize) {
1✔
1055

1056
                    final int div = fullSize / this.pagesize;
1✔
1057
                    start = (fullSize % this.pagesize == 0 ? div - 1 : div) * this.pagesize;
1✔
1058
                }
1059
                end = start + this.pagesize;
1✔
1060
            }
1061

1062
            // rowNumber starts from 1
1063
            this.filteredRows = Range.of(start + 1, end, null);
1✔
1064
        } else {
1✔
1065
            this.filteredRows = Range.of(1, Integer.MAX_VALUE, null);
1✔
1066
        }
1067
    }
1✔
1068

1069
    /**
1070
     * Is the current row included in the "to-be-evaluated" range? Called by nested ColumnTags. If <code>false</code>
1071
     * column body is skipped.
1072
     *
1073
     * @return <code>true</code> if the current row must be evaluated because is included in output or because is
1074
     *         included in sorting.
1075
     */
1076
    public boolean isIncludedRow() {
1077
        return this.filteredRows.contains(this.rowNumber);
1✔
1078
    }
1079

1080
    /**
1081
     * Create a complete string for compatibility with previous version before expression evaluation. This approach is
1082
     * optimized for new expressions, not for previous property/scope parameters.
1083
     *
1084
     * @return Expression composed by scope + name + property
1085
     */
1086
    private String getFullObjectName() {
1087
        // only evaluate if needed, else preserve original list
1088
        return this.name;
1✔
1089
    }
1090

1091
    /**
1092
     * init the href object used to generate all the links for pagination, sorting, exporting.
1093
     *
1094
     * @param requestHelper
1095
     *            request helper used to extract the base Href
1096
     */
1097
    protected void initHref(final RequestHelper requestHelper) {
1098
        // get the href for this request
1099
        this.baseHref = requestHelper.getHref();
1✔
1100

1101
        if (this.excludedParams != null) {
1✔
1102
            final String[] splittedExcludedParams = StringUtils.split(this.excludedParams);
1✔
1103

1104
            // handle * keyword
1105
            if (splittedExcludedParams.length == 1 && "*".equals(splittedExcludedParams[0])) {
1✔
1106
                // TODO cleanup: paramEncoder initialization should not be done here
1107
                if (this.paramEncoder == null) {
1!
1108
                    this.paramEncoder = new ParamEncoder(this.getUid());
1✔
1109
                }
1110

1111
                final Iterator<String> paramsIterator = this.baseHref.getParameterMap().keySet().iterator();
1✔
1112
                while (paramsIterator.hasNext()) {
1✔
1113
                    final String key = paramsIterator.next();
1✔
1114

1115
                    // don't remove parameters added by the table tag
1116
                    if (!this.paramEncoder.isParameterEncoded(key)) {
1!
1117
                        this.baseHref.removeParameter(key);
1✔
1118
                    }
1119
                }
1✔
1120
            } else {
1✔
1121
                for (final String splittedExcludedParam : splittedExcludedParams) {
1✔
1122
                    this.baseHref.removeParameter(splittedExcludedParam);
1✔
1123
                }
1124
            }
1125
        }
1126

1127
        if (this.requestUri != null) {
1✔
1128
            // if user has added a requestURI create a new href
1129
            String fullURI = this.requestUri;
1✔
1130
            if (!this.dontAppendContext) {
1✔
1131
                final String contextPath = ((HttpServletRequest) this.pageContext.getRequest()).getContextPath();
1✔
1132

1133
                // prepend the context path if any.
1134
                // actually checks if context path is already there for people which manually add it
1135
                if (!StringUtils.isEmpty(contextPath) && this.requestUri != null && this.requestUri.startsWith("/")
1!
1136
                        && !this.requestUri.startsWith(contextPath)) {
1!
1137
                    fullURI = contextPath + this.requestUri;
1✔
1138
                }
1139
            }
1140

1141
            // call encodeURL to preserve session id when cookies are disabled
1142
            fullURI = ((HttpServletResponse) this.pageContext.getResponse()).encodeURL(fullURI);
1✔
1143

1144
            this.baseHref.setFullUrl(fullURI);
1✔
1145

1146
        }
1147

1148
    }
1✔
1149

1150
    /**
1151
     * Draw the table. This is where everything happens, we figure out what values we are supposed to be showing, we
1152
     * figure out how we are supposed to be showing them, then we draw them.
1153
     *
1154
     * @return int
1155
     *
1156
     * @throws JspException
1157
     *             generic exception
1158
     *
1159
     * @see javax.servlet.jsp.tagext.Tag#doEndTag()
1160
     */
1161
    @Override
1162
    public int doEndTag() throws JspException {
1163

1164
        if (TableTag.log.isDebugEnabled()) {
1!
1165
            TableTag.log.debug("[{}] doEndTag called", this.getUid());
×
1166
        }
1167

1168
        if (!this.doAfterBodyExecuted) {
1✔
1169
            if (TableTag.log.isDebugEnabled()) {
1!
1170
                TableTag.log.debug("[{}] tag body is empty.", this.getUid());
×
1171
            }
1172

1173
            // first row (created in doStartTag)
1174
            if (this.currentRow != null) {
1!
1175
                // if yes add to table model and remove
1176
                this.tableModel.addRow(this.currentRow);
1✔
1177
            }
1178

1179
            // other rows
1180
            while (this.tableIterator.hasNext()) {
1✔
1181
                final Object iteratedObject = this.tableIterator.next();
1✔
1182
                this.rowNumber++;
1✔
1183

1184
                // Row object for Cell values
1185
                this.currentRow = new Row(iteratedObject, this.rowNumber);
1✔
1186

1187
                this.tableModel.addRow(this.currentRow);
1✔
1188
            }
1✔
1189
        }
1190

1191
        // if no rows are defined automatically get all properties from bean
1192
        if (this.tableModel.isEmpty()) {
1✔
1193
            this.describeEmptyTable();
1✔
1194
        }
1195

1196
        final TableDecorator tableDecorator = this.properties.getDecoratorFactoryInstance()
1✔
1197
                .loadTableDecorator(this.pageContext, this.getConfiguredDecoratorName());
1✔
1198

1199
        if (tableDecorator != null) {
1✔
1200
            tableDecorator.init(this.pageContext, this.list, this.tableModel);
1✔
1201
            this.tableModel.setTableDecorator(tableDecorator);
1✔
1202
        }
1203

1204
        final TableTotaler totaler = this.properties.getDecoratorFactoryInstance().loadTableTotaler(this.pageContext,
1✔
1205
                this.getTotalerName());
1✔
1206

1207
        if (totaler != null) {
1!
1208
            totaler.init(this.tableModel);
1✔
1209
            this.tableModel.setTotaler(totaler);
1✔
1210
        }
1211

1212
        this.setupViewableData();
1✔
1213

1214
        // Figure out how we should sort this data, typically we just sort
1215
        // the data being shown, but the programmer can override this behavior
1216
        if ((this.paginatedList == null && this.tableModel.isLocalSort()) && !this.tableModel.isSortFullTable()) {
1✔
1217
            this.tableModel.sortPageList();
1✔
1218
        }
1219

1220
        // Get the data back in the representation that the user is after, do they want HTML/XML/CSV/EXCEL/etc...
1221
        int returnValue = Tag.EVAL_PAGE;
1✔
1222

1223
        // check for nested tables
1224
        final Object previousMediaType = this.pageContext.getAttribute(TableTag.PAGE_ATTRIBUTE_MEDIA);
1✔
1225
        if (MediaTypeEnum.HTML.equals(this.currentMediaType)
1!
1226
                && (previousMediaType == null || MediaTypeEnum.HTML.equals(previousMediaType))) {
1✔
1227
            this.writeHTMLData();
1✔
1228
        } else if (!MediaTypeEnum.HTML.equals(this.currentMediaType)) {
1✔
1229
            if (TableTag.log.isDebugEnabled()) {
1!
1230
                TableTag.log.debug("[{}] doEndTag - exporting", this.getUid());
×
1231
            }
1232

1233
            returnValue = this.doExport();
1✔
1234
        }
1235

1236
        // do not remove media attribute! if the table is nested in other tables this is still needed
1237

1238
        if (TableTag.log.isDebugEnabled()) {
1!
1239
            TableTag.log.debug("[{}] doEndTag - end", this.getUid());
×
1240
        }
1241

1242
        this.cleanUp();
1✔
1243
        return returnValue;
1✔
1244
    }
1245

1246
    /**
1247
     * Returns the name of the table decorator that should be applied to this table, which is either the decorator
1248
     * configured in the property "decorator", or if none is configured in said property, a decorator configured with
1249
     * the "decorator.media.[media type]" property, or null if none is configured.
1250
     *
1251
     * @return Name of the table decorator that should be applied to this table.
1252
     */
1253
    private String getConfiguredDecoratorName() {
1254
        return this.decoratorName == null ? this.properties.getMediaTypeDecoratorName(this.currentMediaType)
1✔
1255
                : this.decoratorName;
1✔
1256
    }
1257

1258
    /**
1259
     * Gets the totaler name.
1260
     *
1261
     * @return the totaler name
1262
     */
1263
    private String getTotalerName() {
1264
        return this.totalerName == null ? this.properties.getTotalerName() : this.totalerName;
1!
1265
    }
1266

1267
    /**
1268
     * clean up instance variables, but not the ones representing tag attributes.
1269
     */
1270
    private void cleanUp() {
1271
        // reset instance variables (non attributes)
1272
        this.currentMediaType = null;
1✔
1273
        this.baseHref = null;
1✔
1274
        this.caption = null;
1✔
1275
        this.captionTag = null;
1✔
1276
        this.currentRow = null;
1✔
1277
        this.doAfterBodyExecuted = false;
1✔
1278
        this.footer = null;
1✔
1279
        this.listHelper = null;
1✔
1280
        this.pageNumber = 0;
1✔
1281
        this.paramEncoder = null;
1✔
1282
        this.properties = null;
1✔
1283
        this.rowNumber = 1;
1✔
1284
        this.tableIterator = null;
1✔
1285
        this.tableModel = null;
1✔
1286
        this.list = null;
1✔
1287
        this.paginatedList = null;
1✔
1288
    }
1✔
1289

1290
    /**
1291
     * If no columns are provided, automatically add them from bean properties. Get the first object in the list and get
1292
     * all the properties (except the "class" property which is automatically skipped). Of course this isn't possible
1293
     * for empty lists.
1294
     */
1295
    private void describeEmptyTable() {
1296
        this.tableIterator = IteratorUtils.getIterator(this.list);
1✔
1297

1298
        if (this.tableIterator.hasNext()) {
1!
1299
            final Object iteratedObject = this.tableIterator.next();
1✔
1300
            Map<String, String> objectProperties = new HashMap<>();
1✔
1301

1302
            // if it's a String don't add the "Bytes" column
1303
            if (iteratedObject instanceof String) {
1✔
1304
                return;
1✔
1305
            }
1306
            // if it's a map already use key names for column headers
1307
            if (iteratedObject instanceof Map) {
1✔
1308
                objectProperties = (Map<String, String>) iteratedObject;
1✔
1309
            } else {
1310
                try {
1311
                    objectProperties = BeanUtils.describe(iteratedObject);
1✔
1312
                } catch (final Exception e) {
×
1313
                    TableTag.log.warn("Unable to automatically add columns: " + e.getMessage(), e);
×
1314
                }
1✔
1315
            }
1316

1317
            // iterator on properties names
1318
            final Iterator<String> propertiesIterator = objectProperties.keySet().iterator();
1✔
1319

1320
            while (propertiesIterator.hasNext()) {
1✔
1321
                // get the property name
1322
                final String propertyName = propertiesIterator.next();
1✔
1323

1324
                // dont't want to add the standard "class" property
1325
                if (!"class".equals(propertyName)) //$NON-NLS-1$
1!
1326
                {
1327
                    // creates a new header and add to the table model
1328
                    final HeaderCell headerCell = new HeaderCell();
1✔
1329
                    headerCell.setBeanPropertyName(propertyName);
1✔
1330

1331
                    // handle title i18n
1332
                    headerCell.setTitle(this.properties.geResourceProvider().getResource(null, propertyName, this,
1✔
1333
                            this.pageContext));
1334

1335
                    this.tableModel.addColumnHeader(headerCell);
1✔
1336
                }
1337
            }
1✔
1338
        }
1339
    }
1✔
1340

1341
    /**
1342
     * Called when data are not displayed in a html page but should be exported.
1343
     *
1344
     * @return int SKIP_PAGE
1345
     *
1346
     * @throws JspException
1347
     *             generic exception
1348
     */
1349
    protected int doExport() throws JspException {
1350

1351
        final boolean exportFullList = this.properties.getExportFullList();
1✔
1352

1353
        if (TableTag.log.isDebugEnabled()) {
1!
1354
            TableTag.log.debug("[{}] currentMediaType={}", this.getUid(), this.currentMediaType);
×
1355
        }
1356

1357
        final boolean exportHeader = this.properties.getExportHeader(this.currentMediaType);
1✔
1358
        final boolean exportDecorated = this.properties.getExportDecorated();
1✔
1359

1360
        final ExportView exportView = ExportViewFactory.getInstance().getView(this.currentMediaType, this.tableModel,
1✔
1361
                exportFullList, exportHeader, exportDecorated);
1362

1363
        try {
1364
            this.writeExport(exportView);
1✔
1365
        } catch (final IOException e) {
×
1366
            throw new WrappedRuntimeException(this.getClass(), e);
×
1367
        }
1✔
1368

1369
        return Tag.SKIP_PAGE;
1✔
1370
    }
1371

1372
    /**
1373
     * Will write the export. The default behavior is to write directly to the response. If the ResponseOverrideFilter
1374
     * is configured for this request, will instead write the exported content to a map in the Request object.
1375
     *
1376
     * @param exportView
1377
     *            export view
1378
     *
1379
     * @throws IOException
1380
     *             exception thrown when writing content to the response
1381
     * @throws JspException
1382
     *             for problem in clearing the response or for invalid export views
1383
     */
1384
    protected void writeExport(final ExportView exportView) throws IOException, JspException {
1385
        final String filename = this.properties.getExportFileName(this.currentMediaType);
1✔
1386

1387
        final HttpServletResponse response = (HttpServletResponse) this.pageContext.getResponse();
1✔
1388
        final HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest();
1✔
1389

1390
        final Map<String, Object> bean = (Map<String, Object>) request
1✔
1391
                .getAttribute(TableTag.FILTER_CONTENT_OVERRIDE_BODY);
1✔
1392
        final boolean usingFilter = bean != null;
1✔
1393

1394
        String mimeType = exportView.getMimeType();
1✔
1395
        // original encoding, be sure to add it back after reset()
1396
        final String characterEncoding = response.getCharacterEncoding();
1✔
1397

1398
        if (usingFilter) {
1✔
1399
            if (bean.containsKey(TableTagParameters.BEAN_BUFFER)) {
1!
1400
                // We are running under the export filter in buffered mode
1401
                bean.put(TableTagParameters.BEAN_CONTENTTYPE, mimeType);
1✔
1402
                bean.put(TableTagParameters.BEAN_FILENAME, filename);
1✔
1403

1404
                if (exportView instanceof TextExportView) {
1✔
1405
                    final StringWriter writer = new StringWriter();
1✔
1406
                    ((TextExportView) exportView).doExport(writer, characterEncoding);
1✔
1407
                    bean.put(TableTagParameters.BEAN_BODY, writer.toString());
1✔
1408
                } else if (exportView instanceof BinaryExportView) {
1!
1409
                    final ByteArrayOutputStream stream = new ByteArrayOutputStream();
1✔
1410
                    ((BinaryExportView) exportView).doExport(stream);
1✔
1411
                    bean.put(TableTagParameters.BEAN_BODY, stream.toByteArray());
1✔
1412

1413
                } else {
1✔
1414
                    throw new JspTagException("Export view " + exportView.getClass().getName()
×
1415
                            + " must implement TextExportView or BinaryExportView");
1416
                }
1417

1418
                return;
1✔
1419
            }
1420
            // We are running under the export filter, call it
1421
            TableTag.log.debug("Exportfilter enabled in unbuffered mode, setting headers");
×
1422
            response.addHeader(TableTagParameters.PARAMETER_EXPORTING, TagConstants.EMPTY_STRING);
×
1423
        } else {
1424
            TableTag.log.debug("Exportfilter NOT enabled");
1✔
1425
            // response can't be already committed at this time
1426
            if (response.isCommitted()) {
1✔
1427
                throw new ExportException(this.getClass());
1✔
1428
            }
1429

1430
            try {
1431
                response.reset();
1✔
1432
                this.pageContext.getOut().clearBuffer();
1✔
1433
            } catch (final Exception e) {
×
1434
                throw new ExportException(this.getClass());
×
1435
            }
1✔
1436
        }
1437

1438
        if (!usingFilter && characterEncoding != null && !Strings.CS.contains(mimeType, "charset") //$NON-NLS-1$
1!
1439
                && exportView instanceof TextExportView) {
1440
            mimeType += "; charset=" + characterEncoding; //$NON-NLS-1$
1✔
1441
        }
1442

1443
        response.setContentType(mimeType);
1✔
1444

1445
        if (StringUtils.isNotEmpty(filename)) {
1✔
1446
            response.setHeader("Content-Disposition", //$NON-NLS-1$
1✔
1447
                    "attachment; filename=\"" + filename + "\""); //$NON-NLS-1$ //$NON-NLS-2$
1448
        }
1449

1450
        if (exportView instanceof TextExportView) {
1✔
1451
            Writer writer;
1452
            if (usingFilter) {
1!
1453
                writer = response.getWriter();
×
1454
            } else {
1455
                writer = this.pageContext.getOut();
1✔
1456
            }
1457

1458
            ((TextExportView) exportView).doExport(writer, characterEncoding);
1✔
1459
        } else if (exportView instanceof BinaryExportView) {
1!
1460
            // dealing with binary content
1461
            // note that this is not assured to work on any application server if the filter is not enabled. According
1462
            // to the jsp specs response.getOutputStream() should no be called in jsps.
1463
            ((BinaryExportView) exportView).doExport(response.getOutputStream());
1✔
1464
        } else {
1465
            throw new JspTagException("Export view " + exportView.getClass().getName()
×
1466
                    + " must implement TextExportView or BinaryExportView");
1467
        }
1468

1469
        TableTag.log.debug("Export completed");
1✔
1470

1471
    }
1✔
1472

1473
    /**
1474
     * This sets the list of all of the data that will be displayed on the page via the table tag. This might include
1475
     * 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
1476
     * subset, etc...
1477
     */
1478
    protected void setupViewableData() {
1479

1480
        // If the user has changed the way our default behavior works, then we need to look for it now, and resort
1481
        // things if needed before we ask for the viewable part. (this is a bad place for this, this should be
1482
        // refactored and moved somewhere else).
1483

1484
        if ((this.paginatedList == null || this.tableModel.isLocalSort()) && this.tableModel.isSortFullTable()) {
1!
1485
            // Sort the total list...
1486
            this.tableModel.sortFullList();
1✔
1487
        }
1488

1489
        final Object originalData = this.tableModel.getRowListFull();
1✔
1490

1491
        // If they have asked for a subset of the list via the length
1492
        // attribute, then only fetch those items out of the master list.
1493
        List<Row> fullList = CollectionUtil.getListFromObject(originalData, this.offset, this.length);
1✔
1494

1495
        int pageOffset = this.offset;
1✔
1496
        // If they have asked for just a page of the data, then use the
1497
        // SmartListHelper to figure out what page they are after, etc...
1498
        if (this.paginatedList == null && this.pagesize > 0) {
1✔
1499
            this.listHelper = new SmartListHelper(fullList,
1✔
1500
                    this.partialList ? ((Integer) this.size).intValue() : fullList.size(), this.pagesize,
1✔
1501
                    this.properties, this.partialList);
1502
            this.listHelper.setCurrentPage(this.pageNumber);
1✔
1503
            pageOffset = this.listHelper.getFirstIndexForCurrentPage();
1✔
1504
            fullList = this.listHelper.getListForCurrentPage();
1✔
1505
        } else if (this.paginatedList != null) {
1✔
1506
            this.listHelper = new PaginatedListSmartListHelper(this.paginatedList, this.properties);
1✔
1507
        }
1508
        this.tableModel.setRowListPage(fullList);
1✔
1509
        this.tableModel.setPageOffset(pageOffset);
1✔
1510
    }
1✔
1511

1512
    /**
1513
     * Uses HtmlTableWriter to write table called when data have to be displayed in a html page.
1514
     *
1515
     * @throws JspException
1516
     *             generic exception
1517
     */
1518
    protected void writeHTMLData() throws JspException {
1519
        final JspWriter out = this.pageContext.getOut();
1✔
1520

1521
        final String css = this.properties.getCssTable();
1✔
1522
        if (StringUtils.isNotBlank(css)) {
1!
1523
            this.addClass(css);
1✔
1524
        }
1525
        // use HtmlTableWriter to write table
1526
        new HtmlTableWriter(this.properties, this.baseHref, this.export, out, this.getCaptionTag(), this.paginatedList,
1✔
1527
                this.listHelper, this.pagesize, this.getAttributeMap(), this.uid).writeTable(this.tableModel,
1✔
1528
                        this.getUid());
1✔
1529

1530
        if (this.varTotals != null) {
1✔
1531
            this.pageContext.setAttribute(this.varTotals, this.getTotals());
1✔
1532
        }
1533
    }
1✔
1534

1535
    /**
1536
     * Get the column totals Map. If there is no varTotals defined, there are no totals.
1537
     *
1538
     * @return a Map of totals where the key is the column number and the value is the total for that column
1539
     */
1540
    public Map<String, Double> getTotals() {
1541
        final Map<String, Double> totalsMap = new HashMap<>();
1✔
1542
        if (this.varTotals != null) {
1!
1543
            final List<HeaderCell> headers = this.tableModel.getHeaderCellList();
1✔
1544
            for (final HeaderCell headerCell : headers) {
1✔
1545
                if (headerCell.isTotaled()) {
1!
1546
                    totalsMap.put("column" + (headerCell.getColumnNumber() + 1), Double.valueOf(headerCell.getTotal()));
1✔
1547
                }
1548
            }
1✔
1549
        }
1550
        return totalsMap;
1✔
1551
    }
1552

1553
    /**
1554
     * Get the table model for this tag. Sometimes required by local tags that cooperate with DT. USE THIS METHOD WITH
1555
     * EXTREME CAUTION; IT PROVIDES ACCESS TO THE INTERNALS OF DISPLAYTAG, WHICH ARE NOT TO BE CONSIDERED STABLE PUBLIC
1556
     * INTERFACES.
1557
     *
1558
     * @return the TableModel
1559
     */
1560
    public TableModel getTableModel() {
1561
        return this.tableModel;
1✔
1562
    }
1563

1564
    /**
1565
     * Called by the setProperty tag to override some default behavior or text String.
1566
     *
1567
     * @param propertyName
1568
     *            String property name
1569
     * @param propertyValue
1570
     *            String property value
1571
     */
1572
    public void setProperty(final String propertyName, final String propertyValue) {
1573
        this.properties.setProperty(propertyName, propertyValue);
1✔
1574
    }
1✔
1575

1576
    /**
1577
     * Release.
1578
     *
1579
     * @see javax.servlet.jsp.tagext.Tag#release()
1580
     */
1581
    @Override
1582
    public void release() {
1583
        if (TableTag.log.isDebugEnabled()) {
1!
1584
            TableTag.log.debug("[{}] release() called", this.getUid());
×
1585
        }
1586

1587
        super.release();
1✔
1588

1589
        // tag attributes
1590
        this.decoratorName = null;
1✔
1591
        this.defaultSortedColumn = -1;
1✔
1592
        this.defaultSortOrder = null;
1✔
1593
        this.export = false;
1✔
1594
        this.length = 0;
1✔
1595
        this.listAttribute = null;
1✔
1596
        this.localSort = true;
1✔
1597
        this.name = null;
1✔
1598
        this.offset = 0;
1✔
1599
        this.pagesize = 0;
1✔
1600
        this.partialList = false;
1✔
1601
        this.requestUri = null;
1✔
1602
        this.dontAppendContext = false;
1✔
1603
        this.sortFullTable = null;
1✔
1604
        this.excludedParams = null;
1✔
1605
        this.filteredRows = null;
1✔
1606
        this.uid = null;
1✔
1607
        this.keepStatus = false;
1✔
1608
        this.clearStatus = false;
1✔
1609
        this.form = null;
1✔
1610
    }
1✔
1611

1612
    /**
1613
     * Returns the name.
1614
     *
1615
     * @return String
1616
     */
1617
    protected String getName() {
1618
        return this.name;
×
1619
    }
1620

1621
    /**
1622
     * encode a parameter name to be unique in the page using ParamEncoder.
1623
     *
1624
     * @param parameterName
1625
     *            parameter name to encode
1626
     *
1627
     * @return String encoded parameter name
1628
     */
1629
    private String encodeParameter(final String parameterName) {
1630
        // paramEncoder has been already instantiated?
1631
        if (this.paramEncoder == null) {
1✔
1632
            // use the id attribute to get the unique identifier
1633
            this.paramEncoder = new ParamEncoder(this.getUid());
1✔
1634
        }
1635

1636
        return this.paramEncoder.encodeParameterName(parameterName);
1✔
1637
    }
1638

1639
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc