View Javadoc

1   /***
2    * Licensed under the Artistic License; you may not use this file
3    * except in compliance with the License.
4    * You may obtain a copy of the License at
5    *
6    *      http://displaytag.sourceforge.net/license.html
7    *
8    * THIS PACKAGE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
9    * IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
10   * WARRANTIES OF MERCHANTIBILITY AND FITNESS FOR A PARTICULAR PURPOSE.
11   */
12  package org.displaytag.tags;
13  
14  import java.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 &lt;a href&gt; 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         // call encodeURL to preserve session id when cookies are disabled
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         // call encodeURL to preserve session id when cookies are disabled
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 &lt;display:list...&gt; 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         // add column header only once
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             // BodyContent will be null if the body was not eval'd, eg an empty list.
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         // cleanup non-attribute variables
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         // don't modify "title" directly
603         String evalTitle = this.title;
604 
605         // title has precedence over titleKey
606         if (evalTitle == null && (this.titleKey != null || this.property != null))
607         {
608             // handle title i18n
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         // href and parameter, create link
631         if (this.href != null)
632         {
633             Href colHref;
634 
635             // empty base url, use href with parameters from parent table
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                 // parameter value is in a different object than the iterated one
648                 if (this.paramName != null || this.paramScope != null)
649                 {
650                     // create a complete string for compatibility with previous version before expression evaluation.
651                     // this approach is optimized for new expressions, not for previous property/scope parameters
652                     StringBuffer expression = new StringBuffer();
653 
654                     // append scope
655                     if (StringUtils.isNotBlank(this.paramScope))
656                     {
657                         expression.append(this.paramScope).append("Scope.");
658                     }
659 
660                     // base bean name
661                     if (this.paramId != null)
662                     {
663                         expression.append(this.paramName);
664                     }
665                     else
666                     {
667                         expression.append(tableTag.getName());
668                     }
669 
670                     // append property
671                     if (StringUtils.isNotBlank(this.paramProperty))
672                     {
673                         expression.append('.').append(this.paramProperty);
674                     }
675 
676                     // evaluate expression.
677                     // note the value is fixed, not based on any object created during iteration
678                     // this is here for compatibility with the old version mainly
679                     Object paramValue = tableTag.evaluateExpression(expression.toString());
680 
681                     // add parameter
682                     colHref.addParameter(this.paramId, paramValue);
683                 }
684                 else
685                 {
686                     // set id
687                     headerCell.setParamName(this.paramId);
688 
689                     // set property
690                     headerCell.setParamProperty(this.paramProperty);
691                 }
692             }
693 
694             // sets the base href
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         // If the list is empty, do not execute the body; may result in NPE
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) //$NON-NLS-1$
767             .append("group", this.group) //$NON-NLS-1$
768             .append("maxLength", this.maxLength) //$NON-NLS-1$
769             .append("decorator", this.decorator) //$NON-NLS-1$
770             .append("href", this.href) //$NON-NLS-1$
771             .append("title", this.title) //$NON-NLS-1$
772             .append("paramScope", this.paramScope) //$NON-NLS-1$
773             .append("property", this.property) //$NON-NLS-1$
774             .append("paramProperty", this.paramProperty) //$NON-NLS-1$
775             .append("headerAttributeMap", this.headerAttributeMap) //$NON-NLS-1$
776             .append("paramName", this.paramName) //$NON-NLS-1$
777             .append("autolink", this.autolink) //$NON-NLS-1$
778             .append("nulls", this.nulls) //$NON-NLS-1$
779             .append("maxWords", this.maxWords) //$NON-NLS-1$
780             .append("attributeMap", this.attributeMap) //$NON-NLS-1$
781             .append("sortable", this.sortable) //$NON-NLS-1$
782             .append("paramId", this.paramId) //$NON-NLS-1$
783             .append("alreadySorted", this.alreadySorted) //$NON-NLS-1$
784             .append("sortProperty", this.sortProperty) //$NON-NLS-1$
785             .toString();
786     }
787 }