/**
* Copyright (c) 2009--2010 Red Hat, Inc.
*
* This software is licensed to you under the GNU General Public License,
* version 2 (GPLv2). There is NO WARRANTY for this software, express or
* implied, including the implied warranties of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. You should have received a copy of GPLv2
* along with this software; if not, see
* http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt.
*
* Red Hat trademarks are not licensed under GPLv2. No permission is
* granted to use or replicate Red Hat trademarks that are incorporated
* in this software or its documentation.
*/
package com.redhat.rhn.frontend.taglibs;
import com.redhat.rhn.common.localization.LocalizationService;
import com.redhat.rhn.common.util.ServletUtils;
import com.redhat.rhn.frontend.html.HtmlTag;
import com.redhat.rhn.frontend.struts.RequestContext;
import org.apache.commons.lang.StringUtils;
import java.io.IOException;
import java.util.Map;
import java.util.TreeMap;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.JspWriter;
import javax.servlet.jsp.tagext.Tag;
import javax.servlet.jsp.tagext.TagSupport;
/**
* The ColumnTag represents a column of data in a ListView. It must be used
* within a
* {@link com.redhat.rhn.frontend.taglibs.ListDisplayTag ListDisplayTag}.
* It will setup the title of the column using the header attribute, and
* display the body after it has setup the header.
* The column has six main attributes:
* <code>header</code>, <code>style</code>, <code>cssClass</code>,
* <code>url</code>, <code>width</code> and <code>renderUrl</code>.
* <p>
* The <code>header</code> is a <strong>REQUIRED</strong> attribute.
* All other attributes are optional.
* <p>
* You can specify html formatting with the <code>style</code>,
* <code>cssClass</code>, <code>nowrap</code> and <code>width</code> attributes.
* <p>
* Example usage of the ColumnTag:
* <pre>
* <rhn:column header="l10n.jsp.message"
* "text-align: center;
* nowrap="true"
* url="someurl?id=${current.id}">
* ${current.name}
* </rhn:column>
* </pre>
* The <code>renderUrl</code> is a boolean which allows you to turn off the url
* rendering based on some calculated value. This is useful when you want
* the column to show a url for all content except a particular condition.
* Below is an example usage of this boolean and the sample output.
* Assuming ${current.id} is equal to zero (0) the url will render.
* <pre>
* <rhn:column header="l10n.jsp.message"
* url="http://www.somesite.com"
* renderUrl="${current.id == 0}" >
* sometext
* </rhn:column>
* </pre>
* Sample output:
* <pre>
* ...
* <a href="http://www.somesite.com">sometext</a>
* ...
* </pre>
* Otherwise, you simply get sometext.
* @version $Rev$
* @see com.redhat.rhn.frontend.taglibs.ListTag
* @see com.redhat.rhn.frontend.taglibs.ListDisplayTag
* @see com.redhat.rhn.frontend.taglibs.SetTag
*/
public class ColumnTag extends TagSupport {
public static final String DYNAMIC_HEADER = "dynamic";
/** Localization key for header name */
private String header;
/** Localization param for header name */
private String arg0;
/** width of column */
private String width;
/** URL with which to surround the body */
private String url;
/** CSS class used for the column */
private String cssClass;
/** True-false String attribute indicating if or not to wrap the column value */
private String nowrap;
/** The TD tag which surrounds the column */
private HtmlTag td;
/** The a href tag which surrounds the column data when a url is supplied */
private HtmlTag href;
/** Property to sort the list based on */
private String sortProperty;
/** style **/
private String style = "text-align: left;";
/** header colspan attribute **/
private String headerStyle;
/**
* TODO: Eliminate this attribute. This is a dirty dirty
* hack to allow the ColumnTag to know whether to use
* UnpagedListDisplayTag as its parent or ListDisplayTag
* as its parent. When ListDisplayTag is refactored
* this attribute will no longer be needed**/
private boolean usesRefactoredList = false;
/**
* Optional boolean which determines whether the URL should be rendered.
* I know this seems odd, but sometimes we want the URL to be suppressed.
*/
private boolean renderUrl;
/** Set colspan attribute for this column,row */
private String colspan;
/**
* Default constructor
*/
public ColumnTag() {
renderUrl = true;
}
/**
* Copy constructor.
* @param c ColumnTag to copy.
*/
public ColumnTag(ColumnTag c) {
header = c.getHeader();
width = c.getWidth();
style = c.getStyle();
url = c.getUrl();
cssClass = c.getCssClass();
nowrap = c.getNowrap();
renderUrl = c.isRenderUrl();
arg0 = c.getArg0();
style = c.getStyle();
colspan = c.getColspan();
usesRefactoredList = c.isUsesRefactoredList();
}
/**
* {@inheritDoc}
*/
public int doStartTag() throws JspException {
td = new HtmlTag("td");
href = new HtmlTag("a");
try {
JspWriter out = pageContext.getOut();
if (usesRefactoredList) {
UnpagedListDisplayTag parent = findUnpagedListDisplay();
if (showHeader()) {
parent.incrNumberOfColumns();
renderHeader(out, header, arg0);
return SKIP_BODY;
}
renderData(out, parent);
}
else {
ListDisplayTag parent = findListDisplay();
if (showHeader()) {
parent.incrNumberOfColumns();
renderHeader(out, header, arg0);
return SKIP_BODY;
}
renderData(out, parent);
}
return EVAL_BODY_INCLUDE;
}
catch (IOException ioe) {
throw new JspException("IO error writing to JSP file:", ioe);
}
}
/**
* Simple utility method to add an attribute to an HtmlTag.
* If value is null, the attribute is removed from the HtmlTag.
* @param tag HtmlTag to be affected.
* @param name Attribute name.
* @param value Attribute value.
*/
private void setupAttribute(HtmlTag tag, String name, String value) {
if (value != null) {
tag.setAttribute(name, value);
}
else {
tag.removeAttribute(name);
}
}
/**
* Displays the opening of the TD tag and prepares it for
* displaying the body contents.
* @param out JspWriter to write to.
* @param parent Containing JspTag.
* @throws IOException if an error occurs writing to the JspWriter.
*/
protected void renderData(JspWriter out, ListDisplayTag parent)
throws IOException {
setupAttribute(td, "width", getWidth());
if (getNowrap() != null && getNowrap().equals("true")) {
setupAttribute(td, "nowrap", "nowrap");
}
setupAttribute(td, "style", getStyle());
setupAttribute(td, "colspan", getColspan());
if (getColspan() != null) {
parent.setColumnCount(parent.getColumnCount() +
Integer.parseInt(getColspan()) - 1);
}
// Only one column
if (parent.getNumberOfColumns() == 1) {
setupAttribute(td, "class", "first-column last-column");
}
// Are we the first column?
else if (parent.getColumnCount() == 0) {
setupAttribute(td, "class", "first-column");
}
// Are we the last column?
else if (parent.getColumnCount() == parent.getNumberOfColumns() - 1) {
setupAttribute(td, "class", "last-column");
}
// Are we a middle column?
else {
setupAttribute(td, "class", getCssClass());
}
parent.incrColumnCount();
out.print(td.renderOpenTag());
if (showUrl()) {
setupAttribute(href, "href", getUrl());
out.print(href.renderOpenTag());
}
}
/**
* Displays the opening of the TD tag and prepares it for
* displaying the body contents.
* @param out JspWriter to write to.
* @param parent Containing JspTag.
* @throws IOException if an error occurs writing to the JspWriter.
*/
protected void renderData(JspWriter out, UnpagedListDisplayTag parent)
throws IOException {
String nodeIdString = parent.getNodeIdString();
// Deal with structural markup before we get to this <td>
if (parent.getColumnCount() == 0 && parent.getCurrRow() == 0) {
out.println("</thead><tbody>");
}
setupAttribute(td, "width", getWidth());
setupAttribute(td, "colspan", getColspan());
if (getNowrap() != null && getNowrap().equals("true")) {
setupAttribute(td, "nowrap", "nowrap");
}
// Only one column
if (parent.getNumberOfColumns() == 1) {
setupAttribute(td, "class", "first-column last-column");
}
// Are we the first column?
else if (parent.getColumnCount() == 0) {
setupAttribute(td, "class", "first-column");
}
// Are we the last column?
else if (parent.getColumnCount() == parent.getNumberOfColumns() - 1) {
setupAttribute(td, "class", "last-column");
}
// Are we a middle column?
else {
setupAttribute(td, "class", getCssClass());
}
parent.incrColumnCount();
if (parent.getType().equals("treeview") && parent.isChild(nodeIdString)) {
setupAttribute(td, "style", getStyle() + "display: none;");
}
else {
setupAttribute(td, "style", getStyle());
}
out.print(td.renderOpenTag());
if (parent.getType().equals("treeview") &&
parent.isParent(nodeIdString) &&
parent.getColumnCount() == 1) {
out.print("<a onclick=\"toggleRowVisibility('" +
parent.createIdString(nodeIdString) +
"');\" " + "style=\"cursor: pointer;\">" +
"<img name=\"" +
parent.createIdString(nodeIdString) +
"-image\" src=\"/img/list-expand.gif\" alt=\"" +
LocalizationService.getInstance().
getMessage("channels.parentchannel.alt") +
"\"/></a>");
parent.setCurrRow(parent.getCurrRow() + 1);
}
if (showUrl()) {
setupAttribute(href, "href", getUrl());
out.print(href.renderOpenTag());
}
}
/**
* Renders the header element of the table.
* @param out JspWriter to write to.
* @param hdr Header to display.
* @param arg Single argument for header l10n.
* @throws IOException
*/
private void renderHeader(JspWriter out, String hdr, String arg) throws IOException {
HtmlTag th = new HtmlTag("th");
if (usesRefactoredList) {
if (!StringUtils.isEmpty(findUnpagedListDisplay().getTitle())) {
setupAttribute(th, "class", "row-2");
}
if (headerStyle != null) {
setupAttribute(th, "style", headerStyle);
}
else {
setupAttribute(th, "style", getStyle());
}
}
else {
if (!StringUtils.isEmpty(findListDisplay().getTitle())) {
setupAttribute(th, "class", "row-2");
if (headerStyle != null) {
setupAttribute(th, "style", headerStyle);
}
else {
setupAttribute(th, "style", getStyle());
}
}
}
th.addBody(renderHeaderData(hdr, arg));
out.print(th.render());
}
protected String renderHeaderData(String hdr, String arg) {
if (DYNAMIC_HEADER.equals(hdr)) {
return arg;
}
String contents = null;
if (arg != null) {
contents = LocalizationService.getInstance().getMessage(hdr, arg);
}
else {
contents = LocalizationService.getInstance().getMessage(hdr);
}
String retval = null;
if (this.sortProperty != null) {
HttpServletRequest request = (HttpServletRequest) pageContext.getRequest();
String pageUrl;
Map params = new TreeMap(request.getParameterMap());
String sortOrder = request.getParameter(RequestContext.SORT_ORDER);
if (RequestContext.SORT_ASC.equals(sortOrder)) {
params.put(RequestContext.SORT_ORDER, RequestContext.SORT_DESC);
}
else {
params.put(RequestContext.SORT_ORDER, RequestContext.SORT_ASC);
}
params.put(RequestContext.LIST_SORT, sortProperty);
pageUrl = ServletUtils.pathWithParams("",
params);
String title = LocalizationService.getInstance().
getMessage("listdisplay.sortyby");
retval = "<a title=\"" + title + "\" href=\"" + pageUrl + "\">" +
contents + "</a>";
}
else {
retval = contents;
}
return retval;
}
/**
* Returns true if the header needs to be displayed.
* @return true if the header needs to be displayed.
*/
private boolean showHeader() {
return (pageContext.getAttribute("current") == null);
}
/**
* @return Returns the style
*/
public String getStyle() {
return style;
}
/**
* Sets the style
* @param styleIn Style to set
*/
public void setStyle(String styleIn) {
this.style = styleIn;
}
/**
* Returns the header column name.
* @return Returns the header.
*/
public String getHeader() {
return header;
}
/**
* Sets the header column name. Should be a localization key.
* Optionally, you can pass in a value of "dynamic" in conjunction
* with the arg0 attribute which will allow you to set a dynamic
* header.
* @param hdr The header to set.
*/
public void setHeader(String hdr) {
this.header = hdr;
}
/**
* Returns the url.
* @return Returns the url.
*/
public String getUrl() {
return url;
}
/**
* The URL to render around the body. The following example,
* <pre>
* <rhn:column header="foo" url="http://www.hostname.com">
* Data to show.
* </rhn:column>
* </pre>
* would result in the following HTML being generated:
* <pre>
* <td>
* <a href="http://www.hostname.com">Data to show.</a>
* </td>
* </pre>
* @param urlIn The url to set.
*/
public void setUrl(String urlIn) {
this.url = urlIn;
}
/**
* @return Returns the nowrap.
*/
public String getNowrap() {
return nowrap;
}
/**
* @param noWrapIn The nowrap to set.
*/
public void setNowrap(String noWrapIn) {
this.nowrap = noWrapIn;
}
/**
* Sets the CSS class attribute.
* @param css CSS class attribute.
*/
public void setCssClass(String css) {
cssClass = css;
}
/**
* Returns the CSS class attribute.
* @return the CSS class attribute.
*/
public String getCssClass() {
return cssClass;
}
/**
* Returns the column width.
* @return the column width.
*/
public String getWidth() {
return width;
}
/**
* Sets the column width in terms of pixels or percentage.
* @param w The column width.
*/
public void setWidth(String w) {
this.width = w;
}
/**
* Returns flag indicating whether the URL should be rendered.
* @return flag indicating whether the URL should be rendered.
*/
public boolean isRenderUrl() {
return renderUrl;
}
/**
* The flag indicating whether the URL should be rendered.
* @param render flag indicating whether the URL should be rendered.
*/
public void setRenderUrl(boolean render) {
this.renderUrl = render;
}
/**
* @return Returns the arg0.
*/
public String getArg0() {
return arg0;
}
/**
* @param arg0In The arg0 to set.
*/
public void setArg0(String arg0In) {
this.arg0 = arg0In;
}
/**
* @return Returns the sortProperty.
*/
public String getSortProperty() {
return sortProperty;
}
/**
* @param sortPropertyIn The sortProperty to set.
*/
public void setSortProperty(String sortPropertyIn) {
this.sortProperty = sortPropertyIn;
}
/**
* @return Returns the usesRefactoredList.
*/
public boolean isUsesRefactoredList() {
return usesRefactoredList;
}
/**
* @param usesRefactoredListIn The usesRefactoredList to set.
*/
public void setUsesRefactoredList(boolean usesRefactoredListIn) {
this.usesRefactoredList = usesRefactoredListIn;
}
/**
* {@inheritDoc}
*/
public int doEndTag() throws JspException {
JspWriter out = null;
try {
out = pageContext.getOut();
if (!showHeader()) {
if (showUrl()) {
out.print(href.renderCloseTag());
}
out.print(td.renderCloseTag());
}
return Tag.EVAL_BODY_INCLUDE;
}
catch (IOException ioe) {
throw new JspException("IO error writing to JSP file:", ioe);
}
}
private boolean showUrl() {
return ((getUrl() != null) && isRenderUrl());
}
/**
* Returns the ListDisplayTag that serves as the parent tag.
* Returns null if no ListDisplayTag is found.
* @return the parent ListDisplayTag
*/
public UnpagedListDisplayTag findUnpagedListDisplay() {
Tag tagParent = getParent();
while (tagParent != null && !(tagParent instanceof UnpagedListDisplayTag)) {
tagParent = tagParent.getParent();
}
return (UnpagedListDisplayTag) tagParent;
}
/**
* Returns the ListDisplayTag that serves as the parent tag.
* Returns null if no ListDisplayTag is found.
* @return the parent ListDisplayTag
*/
public ListDisplayTag findListDisplay() {
Tag tagParent = getParent();
while (tagParent != null && !(tagParent instanceof ListDisplayTag)) {
tagParent = tagParent.getParent();
}
return (ListDisplayTag) tagParent;
}
/**
* {@inheritDoc}
*/
public boolean equals(Object obj) {
// This method specifically does _NOT_ compare the url portion of
// the ColumnTag when checking for equality. Doing so does not work.
// The problem is that each instance of the ColumnTag can have a
// different URL, because parts of the URL are evaluated by JSTL.
// Take this tag:
// <rhn:column header="assignedgroups.jsp.group"
// style="text-align: center;
// url="SystemGroupDetails.do?sgid=${current.id}">
// The instance used for the header will have the URL
// SystemGroupDetails.do?sgid=
// But every other instance will include an id in the URL. We can't
// check for obj.getUrl().startsWith(this.getUrl()), because that
// breaks the rules for equals, naming obj.equals(this) must ==
// this.equals(obj). So, just remove that test and all works well.
if (obj == null || !(obj instanceof ColumnTag)) {
return false;
}
ColumnTag c = (ColumnTag) obj;
if (header != null) {
if (!header.equals(c.getHeader())) {
return false;
}
}
else if (c.getHeader() != null) {
return false;
}
if (width != null) {
if (!width.equals(c.getWidth())) {
return false;
}
}
else if (c.getWidth() != null) {
return false;
}
if (style != null) {
if (!style.equals(c.getStyle())) {
return false;
}
}
else if (c.getStyle() != null) {
return false;
}
if (nowrap != null) {
if (!nowrap.equals(c.getNowrap())) {
return false;
}
}
else if (c.getNowrap() != null) {
return false;
}
if (cssClass != null) {
if (!cssClass.equals(c.getCssClass())) {
return false;
}
}
else if (c.getCssClass() != null) {
return false;
}
if (arg0 != null) {
if (!arg0.equals(c.getArg0())) {
return false;
}
}
else if (c.getArg0() != null) {
return false;
}
if (renderUrl != c.isRenderUrl()) {
return false;
}
return true;
}
/**
* {@inheritDoc}
*/
public int hashCode() {
int result = 17;
result = 37 * (header == null ? 0 : header.hashCode());
result += 37 * (width == null ? 0 : width.hashCode());
result += 37 * (style == null ? 0 : style.hashCode());
result += 37 * (nowrap == null ? 0 : nowrap.hashCode());
result += 37 * (cssClass == null ? 0 : cssClass.hashCode());
result += 37 * (arg0 == null ? 0 : arg0.hashCode());
return result;
}
/**
* {@inheritDoc}
*/
public void release() {
header = null;
arg0 = null;
width = null;
url = null;
cssClass = null;
nowrap = null;
td = null;
href = null;
sortProperty = null;
style = null;
headerStyle = null;
usesRefactoredList = false;
super.release();
}
/**
* @return Returns the headerStyle.
*/
public String getHeaderStyle() {
return headerStyle;
}
/**
* @param headerStyleIn The headerStyle to set.
*/
public void setHeaderStyle(String headerStyleIn) {
this.headerStyle = headerStyleIn;
}
/**
* @return Returns the colspan.
*/
public String getColspan() {
return colspan;
}
/**
* @param colspanIn The colspan to set.
*/
public void setColspan(String colspanIn) {
this.colspan = colspanIn;
}
}