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.util.ArrayList;
15 import java.util.List;
16
17 import javax.servlet.http.HttpServletRequest;
18 import javax.servlet.http.HttpServletResponse;
19 import javax.servlet.jsp.JspException;
20 import javax.servlet.jsp.tagext.BodyTagSupport;
21
22 import org.apache.commons.lang.StringUtils;
23 import org.apache.commons.lang.builder.ToStringBuilder;
24 import org.apache.commons.logging.Log;
25 import org.apache.commons.logging.LogFactory;
26 import org.displaytag.decorator.DecoratorFactory;
27 import org.displaytag.exception.DecoratorInstantiationException;
28 import org.displaytag.exception.ObjectLookupException;
29 import org.displaytag.exception.TagStructureException;
30 import org.displaytag.model.Cell;
31 import org.displaytag.model.HeaderCell;
32 import org.displaytag.properties.MediaTypeEnum;
33 import org.displaytag.util.Href;
34 import org.displaytag.util.HtmlAttributeMap;
35 import org.displaytag.util.MultipleHtmlAttribute;
36 import org.displaytag.util.ShortToStringStyle;
37 import org.displaytag.util.TagConstants;
38
39
40 /***
41 * <p>
42 * This tag works hand in hand with the TableTag to display a list of objects. This describes a column of data in the
43 * TableTag. There can be any number of columns that make up the list.
44 * </p>
45 * <p>
46 * This tag does no work itself, it is simply a container of information. The TableTag does all the work based on the
47 * information provided in the attributes of this tag.
48 * <p>
49 * @author mraible
50 * @author Fabrizio Giustina
51 * @version $Revision: 1.48 $ ($Author: fgiust $)
52 */
53 public class ColumnTag extends BodyTagSupport
54 {
55
56 /***
57 * D1597A17A6.
58 */
59 private static final long serialVersionUID = 899149338534L;
60
61 /***
62 * logger.
63 */
64 private static Log log = LogFactory.getLog(ColumnTag.class);
65
66 /***
67 * html pass-through attributes for cells.
68 */
69 private HtmlAttributeMap attributeMap = new HtmlAttributeMap();
70
71 /***
72 * html pass-through attributes for cell headers.
73 */
74 private HtmlAttributeMap headerAttributeMap = new HtmlAttributeMap();
75
76 /***
77 * the property method that is called to retrieve the information to be displayed in this column. This method is
78 * called on the current object in the iteration for the given row. The property format is in typical struts format
79 * for properties (required)
80 */
81 private String property;
82
83 /***
84 * the title displayed for this column. if this is omitted then the property name is used for the title of the
85 * column (optional).
86 */
87 private String title;
88
89 /***
90 * by default, null values don't appear in the list, by setting viewNulls to 'true', then null values will appear as
91 * "null" in the list (mostly useful for debugging) (optional).
92 */
93 private boolean nulls;
94
95 /***
96 * is the column sortable?
97 */
98 private boolean sortable;
99
100 /***
101 * if set to true, then any email addresses and URLs found in the content of the column are automatically converted
102 * into a hypertext link.
103 */
104 private boolean autolink;
105
106 /***
107 * the grouping level (starting at 1 and incrementing) of this column (indicates if successive contain the same
108 * values, then they should not be displayed). The level indicates that if a lower level no longer matches, then the
109 * matching for this higher level should start over as well. If this attribute is not included, then no grouping is
110 * performed. (optional)
111 */
112 private int group = -1;
113
114 /***
115 * if this attribute is provided, then the data that is shown for this column is wrapped inside a <a href> tag
116 * with the url provided through this attribute. Typically you would use this attribute along with one of the
117 * struts-like param attributes below to create a dynamic link so that each row creates a different URL based on the
118 * data that is being viewed. (optional)
119 */
120 private Href href;
121
122 /***
123 * The name of the request parameter that will be dynamically added to the generated href URL. The corresponding
124 * value is defined by the paramProperty and (optional) paramName attributes, optionally scoped by the paramScope
125 * attribute. (optional)
126 */
127 private String paramId;
128
129 /***
130 * The name of a JSP bean that is a String containing the value for the request parameter named by paramId (if
131 * paramProperty is not specified), or a JSP bean whose property getter is called to return a String (if
132 * paramProperty is specified). The JSP bean is constrained to the bean scope specified by the paramScope property,
133 * if it is specified. If paramName is omitted, then it is assumed that the current object being iterated on is the
134 * target bean. (optional)
135 */
136 private String paramName;
137
138 /***
139 * The name of a property of the bean specified by the paramName attribute (or the current object being iterated on
140 * if paramName is not provided), whose return value must be a String containing the value of the request parameter
141 * (named by the paramId attribute) that will be dynamically added to this href URL. (optional)
142 * @deprecated use Expressions in paramName
143 */
144 private String paramProperty;
145
146 /***
147 * The scope within which to search for the bean specified by the paramName attribute. If not specified, all scopes
148 * are searched. If paramName is not provided, then the current object being iterated on is assumed to be the target
149 * bean. (optional)
150 * @deprecated use Expressions in paramName
151 */
152 private String paramScope;
153
154 /***
155 * If this attribute is provided, then the column's displayed is limited to this number of characters. An elipse
156 * (...) is appended to the end if this column is linked, and the user can mouseover the elipse to get the full
157 * text. (optional)
158 */
159 private int maxLength;
160
161 /***
162 * If this attribute is provided, then the column's displayed is limited to this number of words. An elipse (...) is
163 * appended to the end if this column is linked, and the user can mouseover the elipse to get the full text.
164 * (optional)
165 */
166 private int maxWords;
167
168 /***
169 * a class that should be used to "decorate" the underlying object being displayed. If a decorator is specified for
170 * the entire table, then this decorator will decorate that decorator. (optional)
171 */
172 private String decorator;
173
174 /***
175 * is the column already sorted?
176 */
177 private boolean alreadySorted;
178
179 /***
180 * The media supported attribute.
181 */
182 private List supportedMedia;
183
184 /***
185 * Property in a resource bundle to be used as the title for the column.
186 */
187 private String titleKey;
188
189 /***
190 * The name of the bean property if a decorator is used and sorting need to be still on on the property itself.
191 * Useful for displaying data with links but sorting on original value.
192 */
193 private String sortProperty;
194
195 /***
196 * setter for the "property" tag attribute.
197 * @param value attribute value
198 */
199 public void setProperty(String value)
200 {
201 this.property = value;
202 }
203
204 /***
205 * setter for the "title" tag attribute.
206 * @param value attribute value
207 */
208 public void setTitle(String value)
209 {
210 this.title = value;
211 }
212
213 /***
214 * setter for the "nulls" tag attribute.
215 * @param value attribute value
216 */
217 public void setNulls(boolean value)
218 {
219 this.nulls = value;
220 }
221
222 /***
223 * setter for the "sortable" tag attribute.
224 * @param value attribute value
225 */
226 public void setSortable(boolean value)
227 {
228 this.sortable = value;
229 }
230
231 /***
232 * setter for the "autolink" tag attribute.
233 * @param value attribute value
234 */
235 public void setAutolink(boolean value)
236 {
237 this.autolink = value;
238 }
239
240 /***
241 * setter for the "group" tag attribute.
242 * @param value attribute value
243 */
244 public void setGroup(int value)
245 {
246 this.group = value;
247 }
248
249 /***
250 * setter for the "titleKey" tag attribute.
251 * @param value property name
252 */
253 public void setTitleKey(String value)
254 {
255 this.titleKey = value;
256 }
257
258 /***
259 * setter for the "href" tag attribute.
260 * @param value attribute value
261 */
262 public void setHref(String value)
263 {
264
265 String encodedHref = ((HttpServletResponse) this.pageContext.getResponse()).encodeURL(StringUtils
266 .defaultString(value));
267 this.href = new Href(encodedHref);
268 }
269
270 /***
271 * setter for the "url" tag attribute. This has the same meaning of href, but prepends the context path to the given
272 * URI.
273 * @param value attribute value
274 */
275 public void setUrl(String value)
276 {
277 HttpServletRequest req = (HttpServletRequest) pageContext.getRequest();
278
279 String encodedHref = ((HttpServletResponse) this.pageContext.getResponse()).encodeURL(StringUtils
280 .defaultString(req.getContextPath() + value));
281 this.href = new Href(encodedHref);
282 }
283
284 /***
285 * setter for the "paramId" tag attribute.
286 * @param value attribute value
287 */
288 public void setParamId(String value)
289 {
290 this.paramId = value;
291 }
292
293 /***
294 * setter for the "paramName" tag attribute.
295 * @param value attribute value
296 */
297 public void setParamName(String value)
298 {
299 this.paramName = value;
300 }
301
302 /***
303 * setter for the "paramProperty" tag attribute.
304 * @param value attribute value
305 */
306 public void setParamProperty(String value)
307 {
308 this.paramProperty = value;
309 }
310
311 /***
312 * setter for the "paramScope" tag attribute.
313 * @param value attribute value
314 */
315 public void setParamScope(String value)
316 {
317 this.paramScope = value;
318 }
319
320 /***
321 * setter for the "maxLength" tag attribute.
322 * @param value attribute value
323 */
324 public void setMaxLength(int value)
325 {
326 this.maxLength = value;
327 }
328
329 /***
330 * setter for the "maxWords" tag attribute.
331 * @param value attribute value
332 */
333 public void setMaxWords(int value)
334 {
335 this.maxWords = value;
336 }
337
338 /***
339 * setter for the "width" tag attribute.
340 * @param value attribute value
341 * @deprecated use css in "class" or "style"
342 */
343 public void setWidth(String value)
344 {
345 this.attributeMap.put(TagConstants.ATTRIBUTE_WIDTH, value);
346 this.headerAttributeMap.put(TagConstants.ATTRIBUTE_WIDTH, value);
347 }
348
349 /***
350 * setter for the "align" tag attribute.
351 * @param value attribute value
352 * @deprecated use css in "class" or "style"
353 */
354 public void setAlign(String value)
355 {
356 this.attributeMap.put(TagConstants.ATTRIBUTE_ALIGN, value);
357 this.headerAttributeMap.put(TagConstants.ATTRIBUTE_ALIGN, value);
358 }
359
360 /***
361 * setter for the "background" tag attribute.
362 * @param value attribute value
363 * @deprecated use css in "class" or "style"
364 */
365 public void setBackground(String value)
366 {
367 this.attributeMap.put(TagConstants.ATTRIBUTE_BACKGROUND, value);
368 }
369
370 /***
371 * setter for the "bgcolor" tag attribute.
372 * @param value attribute value
373 * @deprecated use css in "class" or "style"
374 */
375 public void setBgcolor(String value)
376 {
377 this.attributeMap.put(TagConstants.ATTRIBUTE_BGCOLOR, value);
378 }
379
380 /***
381 * setter for the "height" tag attribute.
382 * @param value attribute value
383 * @deprecated use css in "class" or "style"
384 */
385 public void setHeight(String value)
386 {
387 this.attributeMap.put(TagConstants.ATTRIBUTE_HEIGHT, value);
388 }
389
390 /***
391 * setter for the "nowrap" tag attribute.
392 * @param value attribute value
393 * @deprecated use css in "class" or "style"
394 */
395 public void setNowrap(String value)
396 {
397 this.attributeMap.put(TagConstants.ATTRIBUTE_NOWRAP, "nowrap");
398 }
399
400 /***
401 * setter for the "valign" tag attribute.
402 * @param value attribute value
403 * @deprecated use css in "class" or "style"
404 */
405 public void setValign(String value)
406 {
407 this.attributeMap.put(TagConstants.ATTRIBUTE_VALIGN, value);
408 }
409
410 /***
411 * setter for the "style" tag attribute.
412 * @param value attribute value
413 */
414 public void setStyle(String value)
415 {
416 this.attributeMap.put(TagConstants.ATTRIBUTE_STYLE, value);
417 }
418
419 /***
420 * setter for the "class" tag attribute.
421 * @param value attribute value
422 */
423 public void setClass(String value)
424 {
425 this.attributeMap.put(TagConstants.ATTRIBUTE_CLASS, new MultipleHtmlAttribute(value));
426 }
427
428 /***
429 * Adds a css class to the class attribute (html class suports multiple values).
430 * @param value attribute value
431 */
432 public void addClass(String value)
433 {
434 Object classAttributes = this.attributeMap.get(TagConstants.ATTRIBUTE_CLASS);
435
436 if (classAttributes == null)
437 {
438 this.attributeMap.put(TagConstants.ATTRIBUTE_CLASS, new MultipleHtmlAttribute(value));
439 }
440 else
441 {
442 ((MultipleHtmlAttribute) classAttributes).addAttributeValue(value);
443 }
444 }
445
446 /***
447 * setter for the "headerClass" tag attribute.
448 * @param value attribute value
449 */
450 public void setHeaderClass(String value)
451 {
452 this.headerAttributeMap.put(TagConstants.ATTRIBUTE_CLASS, new MultipleHtmlAttribute(value));
453 }
454
455 /***
456 * setter for the "decorator" tag attribute.
457 * @param value attribute value
458 */
459 public void setDecorator(String value)
460 {
461 this.decorator = value;
462 }
463
464 /***
465 * setter for the "sortProperty" tag attribute.
466 * @param value attribute value
467 */
468 public void setSortProperty(String value)
469 {
470 this.sortProperty = value;
471 }
472
473 /***
474 * Is this column configured for the media type?
475 * @param mediaType the currentMedia type
476 * @return true if the column should be displayed for this request
477 */
478 public boolean availableForMedia(MediaTypeEnum mediaType)
479 {
480 if (supportedMedia == null)
481 {
482 return true;
483 }
484
485 return this.supportedMedia.contains(mediaType);
486 }
487
488 /***
489 * Tag setter.
490 * @param media the space delimited list of supported types
491 */
492 public void setMedia(String media)
493 {
494 if (StringUtils.isBlank(media) || media.toLowerCase().indexOf("all") > -1)
495 {
496 this.supportedMedia = null;
497 return;
498 }
499 this.supportedMedia = new ArrayList();
500 String[] values = StringUtils.split(media);
501 for (int i = 0; i < values.length; i++)
502 {
503 String value = values[i];
504 if (!StringUtils.isBlank(value))
505 {
506 MediaTypeEnum type = MediaTypeEnum.fromName(value.toLowerCase());
507 if (type == null)
508 {
509 log.warn("Unrecognized value for attribute \"media\" value=\"" + value + "\"");
510 }
511 else
512 {
513 this.supportedMedia.add(type);
514 }
515 }
516 }
517 }
518
519 /***
520 * Passes attribute information up to the parent TableTag.
521 * <p>
522 * When we hit the end of the tag, we simply let our parent (which better be a TableTag) know what the user wants to
523 * do with this column. We do that by simple registering this tag with the parent. This tag's only job is to hold
524 * the configuration information to describe this particular column. The TableTag does all the work.
525 * </p>
526 * @return int
527 * @throws JspException if this tag is being used outside of a <display:list...> tag.
528 * @see javax.servlet.jsp.tagext.Tag#doEndTag()
529 */
530 public int doEndTag() throws JspException
531 {
532 TableTag tableTag = (TableTag) findAncestorWithClass(this, TableTag.class);
533
534 MediaTypeEnum currentMediaType = (MediaTypeEnum) this.pageContext.findAttribute(TableTag.PAGE_ATTRIBUTE_MEDIA);
535 if (currentMediaType != null && !availableForMedia(currentMediaType))
536 {
537 if (log.isDebugEnabled())
538 {
539 log.debug("skipping column body, currentMediaType=" + currentMediaType);
540 }
541 return SKIP_BODY;
542 }
543
544
545 if (tableTag.isFirstIteration())
546 {
547 addHeaderToTable(tableTag);
548 }
549
550 if (!tableTag.isIncludedRow())
551 {
552 return super.doEndTag();
553 }
554
555 Cell cell;
556 if (this.property == null)
557 {
558
559 Object cellValue;
560
561 if (this.bodyContent != null)
562 {
563 String value = this.bodyContent.getString();
564
565 if (value == null && this.nulls)
566 {
567 value = TagConstants.EMPTY_STRING;
568 }
569
570 cellValue = value;
571 }
572
573 else
574 {
575 cellValue = Cell.EMPTY_CELL;
576 }
577 cell = new Cell(cellValue);
578
579 }
580 else
581 {
582 cell = Cell.EMPTY_CELL;
583 }
584
585 tableTag.addCell(cell);
586
587
588 this.alreadySorted = false;
589
590 return super.doEndTag();
591 }
592
593 /***
594 * Adds the current header to the table model calling addColumn in the parent table tag. This method should be
595 * called only at first iteration.
596 * @param tableTag parent table tag
597 * @throws DecoratorInstantiationException for error during column decorator instantiation
598 * @throws ObjectLookupException for errors in looking up values
599 */
600 private void addHeaderToTable(TableTag tableTag) throws DecoratorInstantiationException, ObjectLookupException
601 {
602
603 String evalTitle = this.title;
604
605
606 if (evalTitle == null && (this.titleKey != null || this.property != null))
607 {
608
609 evalTitle = tableTag.getProperties().geResourceProvider().getResource(
610 this.titleKey,
611 this.property,
612 tableTag,
613 this.pageContext);
614 }
615
616 HeaderCell headerCell = new HeaderCell();
617 headerCell.setHeaderAttributes((HtmlAttributeMap) this.headerAttributeMap.clone());
618 headerCell.setHtmlAttributes((HtmlAttributeMap) this.attributeMap.clone());
619 headerCell.setTitle(evalTitle);
620 headerCell.setSortable(this.sortable);
621 headerCell.setColumnDecorator(DecoratorFactory.loadColumnDecorator(this.decorator));
622 headerCell.setBeanPropertyName(this.property);
623 headerCell.setShowNulls(this.nulls);
624 headerCell.setMaxLength(this.maxLength);
625 headerCell.setMaxWords(this.maxWords);
626 headerCell.setAutoLink(this.autolink);
627 headerCell.setGroup(this.group);
628 headerCell.setSortProperty(this.sortProperty);
629
630
631 if (this.href != null)
632 {
633 Href colHref;
634
635
636 if (StringUtils.isEmpty(this.href.getBaseUrl()))
637 {
638 colHref = new Href(tableTag.getBaseHref());
639 }
640 else
641 {
642 colHref = new Href(this.href);
643 }
644
645 if (this.paramId != null)
646 {
647
648 if (this.paramName != null || this.paramScope != null)
649 {
650
651
652 StringBuffer expression = new StringBuffer();
653
654
655 if (StringUtils.isNotBlank(this.paramScope))
656 {
657 expression.append(this.paramScope).append("Scope.");
658 }
659
660
661 if (this.paramId != null)
662 {
663 expression.append(this.paramName);
664 }
665 else
666 {
667 expression.append(tableTag.getName());
668 }
669
670
671 if (StringUtils.isNotBlank(this.paramProperty))
672 {
673 expression.append('.').append(this.paramProperty);
674 }
675
676
677
678
679 Object paramValue = tableTag.evaluateExpression(expression.toString());
680
681
682 colHref.addParameter(this.paramId, paramValue);
683 }
684 else
685 {
686
687 headerCell.setParamName(this.paramId);
688
689
690 headerCell.setParamProperty(this.paramProperty);
691 }
692 }
693
694
695 headerCell.setHref(colHref);
696
697 }
698
699 tableTag.addColumn(headerCell);
700
701 if (log.isDebugEnabled())
702 {
703 log.debug("columnTag.addHeaderToTable() :: first iteration - adding header " + headerCell);
704 }
705 }
706
707 /***
708 * @see javax.servlet.jsp.tagext.Tag#release()
709 */
710 public void release()
711 {
712 super.release();
713 this.attributeMap.clear();
714 this.autolink = false;
715 this.decorator = null;
716 this.group = -1;
717 this.headerAttributeMap.clear();
718 this.href = null;
719 this.maxLength = 0;
720 this.maxWords = 0;
721 this.nulls = false;
722 this.paramId = null;
723 this.paramName = null;
724 this.paramProperty = null;
725 this.paramScope = null;
726 this.property = null;
727 this.sortable = false;
728 this.supportedMedia = null;
729 this.title = null;
730 this.titleKey = null;
731 this.sortProperty = null;
732 }
733
734 /***
735 * @see javax.servlet.jsp.tagext.Tag#doStartTag()
736 */
737 public int doStartTag() throws JspException
738 {
739 TableTag tableTag = (TableTag) findAncestorWithClass(this, TableTag.class);
740 if (tableTag == null)
741 {
742 throw new TagStructureException(getClass(), "column", "table");
743 }
744
745
746 if (tableTag.isEmpty() || !tableTag.isIncludedRow())
747 {
748 return SKIP_BODY;
749 }
750
751 MediaTypeEnum currentMediaType = (MediaTypeEnum) this.pageContext.findAttribute(TableTag.PAGE_ATTRIBUTE_MEDIA);
752 if (!availableForMedia(currentMediaType))
753 {
754 return SKIP_BODY;
755 }
756
757 return super.doStartTag();
758 }
759
760 /***
761 * @see java.lang.Object#toString()
762 */
763 public String toString()
764 {
765 return new ToStringBuilder(this, ShortToStringStyle.SHORT_STYLE)
766 .append("bodyContent", this.bodyContent)
767 .append("group", this.group)
768 .append("maxLength", this.maxLength)
769 .append("decorator", this.decorator)
770 .append("href", this.href)
771 .append("title", this.title)
772 .append("paramScope", this.paramScope)
773 .append("property", this.property)
774 .append("paramProperty", this.paramProperty)
775 .append("headerAttributeMap", this.headerAttributeMap)
776 .append("paramName", this.paramName)
777 .append("autolink", this.autolink)
778 .append("nulls", this.nulls)
779 .append("maxWords", this.maxWords)
780 .append("attributeMap", this.attributeMap)
781 .append("sortable", this.sortable)
782 .append("paramId", this.paramId)
783 .append("alreadySorted", this.alreadySorted)
784 .append("sortProperty", this.sortProperty)
785 .toString();
786 }
787 }