/* * RHQ Management Platform * Copyright (C) 2005-2008 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ package org.rhq.enterprise.gui.legacy.taglib.display; import java.io.UnsupportedEncodingException; import java.lang.reflect.InvocationTargetException; import java.net.URLEncoder; import java.text.MessageFormat; import java.util.ArrayList; import java.util.Hashtable; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Properties; import java.util.StringTokenizer; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletResponse; import javax.servlet.jsp.JspException; import javax.servlet.jsp.JspTagException; import javax.servlet.jsp.JspWriter; import javax.servlet.jsp.PageContext; import org.apache.commons.beanutils.PropertyUtils; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.struts.util.RequestUtils; import org.apache.taglibs.standard.tag.common.core.NullAttributeException; import org.apache.taglibs.standard.tag.el.core.ExpressionUtil; import org.rhq.core.domain.util.OrderingField; import org.rhq.core.domain.util.PageControl; import org.rhq.core.domain.util.PageList; import org.rhq.core.domain.util.PageOrdering; import org.rhq.enterprise.gui.legacy.AttrConstants; import org.rhq.enterprise.gui.legacy.Constants; import org.rhq.enterprise.gui.legacy.DefaultConstants; import org.rhq.enterprise.gui.legacy.ParamConstants; import org.rhq.enterprise.gui.legacy.util.TaglibUtils; /** * This tag takes a list of objects and creates a table to display those objects. With the help of column tags, you * simply provide the name of properties (get Methods) that are called against the objects in your list that gets * displayed [[reword that...]] * * <p/>This tag works very much like the struts iterator tag, most of the attributes have the same name and * functionality as the struts tag. * * <p/>Simple Usage: * * <p> * <p/><display:table name="list" > <display:column property="title" /> <display:column property="code" /> * <display:column property="dean" /> </display:table> * * <p/>More Complete Usage: * * <p> * <p/><display:table name="list" pagesize="100"> <display:column property="title" title="College Title" width="60%" * sort="true" href="/display/pubs/college/edit.page" paramId="OID" paramProperty="OID" /> <display:column * property="code" width="10%" sort="true"/> <display:column property="primaryOfficer.name" title="Dean" width="30%" /> * <display:column property="active" sort="true" /> </display:table> * * <p/> * <p/>Attributes: * * <p> * <p/>name property scope length offset pageSize decorator * * <p/> * <p/>HTML Pass-through Attributes * * <p/>There are a number of additional attributes that just get passed through to the underlying HTML table * declaration. With the exception of the following few default values, if these attributes are not provided, they will * not be displayed as part of the * * <table ...>tag. * * <p/>width - defaults to "100%" if not provided border - defaults to "0" if not provided cellspacing - defaults to * "0" if not provided cellpadding - defaults to "2" if not provided align nowrapHeader background bgcolor frame * height hspace rules summary vspace */ public class TableTag extends TablePropertyTag { private List<ColumnTag> columns = new ArrayList<ColumnTag>(); private int currentNumCols = 0; private int numCols = 0; private Decorator dec = null; private static Properties prop = null; private int pageNumber = DefaultConstants.PAGENUM_DEFAULT; private int pageSize = DefaultConstants.PAGESIZE_DEFAULT; private int offSet; private PageList masterList; /** * This is appended to the parameter names for sort and paging attributes */ private String postfix = ""; private List viewableList; private Iterator iterator; private HttpServletResponse res; private JspWriter out; private HttpServletRequest req; private StringBuilder buf = new StringBuilder(8192); // variables to hold the previous row columns values. protected Hashtable previousRow = new Hashtable(10); protected Hashtable nextRow = new Hashtable(10); private ColumnDecorator[] colDecorators; /** * Current value representing a row of data */ private Object row; /** * If set only include rows where this property is set to true */ private String onlyForProperty; private boolean started = false; int rowcnt = 0; String tableUid = Long.toString(System.currentTimeMillis()); /** * static footer added using the footer tag. */ private String footer; private final Log log = LogFactory.getLog(TableTag.class.getName()); //------------------------------------------------------ support methods @Override public void release() { log.debug("releasing values"); super.release(); columns = new ArrayList<ColumnTag>(10); currentNumCols = 0; numCols = 0; pageNumber = DefaultConstants.PAGENUM_DEFAULT; pageSize = DefaultConstants.PAGESIZE_DEFAULT; buf = new StringBuilder(8192); // variables to hold the previous row columns values. previousRow = new Hashtable(10); nextRow = new Hashtable(10); colDecorators = null; started = false; rowcnt = 0; tableUid = Long.toString(System.currentTimeMillis()); this.footer = null; this.postfix = ""; } // ---------------------------------------- Communication with interior tags /** * Called by interior column tags to help this tag figure out how it is supposed to display the information in the * List it is supposed to display * * @param obj an internal tag describing a column in this tableview */ public void addColumn(ColumnTag obj) { columns.add(obj); if (!started) { currentNumCols++; } } private void resetColumns() { columns = new ArrayList<ColumnTag>(10); currentNumCols = 0; } // --------------------------------------------------------- Tag API methods /** * When the tag starts, we just initialize some of our variables, and do a little bit of error checking to make sure * that the user is not trying to give us parameters that we don't expect. * * @return value returned by super.doStartTag() */ @Override public int doStartTag() throws JspException { log.debug("beginning of doStartTag()"); prop = (Properties) pageContext.getServletContext().getAttribute(AttrConstants.PROPS_TAGLIB_NAME); req = (HttpServletRequest) this.pageContext.getRequest(); res = (HttpServletResponse) this.pageContext.getResponse(); out = pageContext.getOut(); evaluateAttributes(); // Load our table decorator if it is requested this.dec = this.loadDecorator(); if (this.dec != null) { log.debug("table decorator found: " + this.dec); this.dec.init(this.pageContext, viewableList); } return super.doStartTag(); } /** * Make the next collection element available and loop, or finish the iterations if there are no more elements. * * @throws JspException if a JSP exception has occurred */ @Override public int doAfterBody() throws JspException { if (!started) { // First pass, build the headers. numCols = currentNumCols; // build an array of column decorator objects - 1 for each column tag colDecorators = new ColumnDecorator[currentNumCols]; for (int c = 0; c < currentNumCols; c++) { ColumnTag tmpTag = columns.get(c); ColumnDecorator coldec = tmpTag.getDecorator(); colDecorators[c] = coldec; if (colDecorators[c] != null) { if (log.isDebugEnabled()) log.debug("adding column decorator: " + colDecorators[c]); colDecorators[c].setColumnTag(tmpTag); colDecorators[c].init(this.pageContext, masterList); } } viewableList = getViewableData(); buf.append(this.getTableHeader()); iterator = viewableList.iterator(); started = true; if (iterator.hasNext()) { // Get data for first row this.row = iterator.next(); String tmpvar = getVar(); if (tmpvar != null) { /* put this in a var in the page scope so that the user can have access to it * in jstl expression language. */ pageContext.setAttribute(tmpvar, this.row); TaglibUtils.setScopedVariable(pageContext, "request", tmpvar, this.row); } rowcnt++; resetColumns(); return (EVAL_BODY_AGAIN); } } else { // We're at the end of a row... generate it and see if there are more buf.append(generateRow(this.row, rowcnt)); rowcnt++; resetColumns(); if (iterator.hasNext()) { // Passes for rows this.row = iterator.next(); String tmpvar = getVar(); if (tmpvar != null) { /* put this in a var in the page scope so that the user can have access to it * in jstl expression language. */ pageContext.setAttribute(tmpvar, row); TaglibUtils.setScopedVariable(pageContext, "request", tmpvar, row); } resetColumns(); return (EVAL_BODY_AGAIN); } } // End of data resetColumns(); return (SKIP_BODY); } /** * Draw the table. This is where everything happens, we figure out what values we are supposed to be showing, we * figure out how we are supposed to be showing them, then we draw them. */ @Override public int doEndTag() throws JspException { buf.append(this.getTableFooter()); buf.append("</table>\n"); write(buf); // a little clean up. started = false; buf = new StringBuilder(8192); rowcnt = 0; release(); return EVAL_PAGE; } /** * This returns a list of all of the data that will be displayed on the page via the table tag. This might include * 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 * subset, etc... * * <p> * <p/>The list that is returned from here is not the original list, but it does contain references to the same * objects in the original list, so that means that we can sort and reorder the list, but we can't mess with the * data objects in the list. */ public List getViewableData() throws JspException { //display the entinre list if thats what the user wants if (pageSize == Constants.PAGESIZE_ALL) { return masterList; } //just return the list return masterList; // TODO: Shouldn't this return a subset? (ips, 04/11/07) } /** * Format the row as HTML. * * @param row The list object to format as HTML. * * @return The object formatted as HTML. */ protected StringBuffer generateRow(Object row, int rowcnt) throws JspException { log.trace("generating row with count: " + rowcnt); /* * Check if the onlyForProperty of the table tag was filled. If so, check if the property given exists and if it * is empty of false just return an empty StringBuffer. Otherwise continue processing. This is to selectively * hide rows that are contained in the List given, but which should not shown anyway (e.g. because the list is * generated for different targets, that have different requirements). * * TODO el-ify .. */ if (onlyForProperty != null) { try { Object o = PropertyUtils.getProperty(row, onlyForProperty); if (o instanceof Boolean) { Boolean shouldShow = (Boolean) o; if ((shouldShow == null) || (shouldShow == false)) { return new StringBuffer(); } } else { throw new JspException("Property " + onlyForProperty + " is not boolean"); } } catch (Exception e) { // well, we did not find the property. So complain throw new JspException("Error when accssing property " + onlyForProperty + ": " + e.getMessage()); } } StringBuffer buf = new StringBuffer(8192); if (this.dec != null) { String rt = this.dec.initRow(row, rowcnt, rowcnt + offSet); if (rt != null) { buf.append(rt); } } try { for (int c = 0; c < numCols; c++) { if (colDecorators[c] != null) { log.trace("initializing decorator: " + colDecorators[c]); colDecorators[c].initRow(row, rowcnt, rowcnt + (pageSize * (this.pageNumber - 1))); } } } catch (Throwable t) { throw new JspException(t); } pageContext.setAttribute("smartRow", row); // Start building the row to be displayed... buf.append("<tr"); if ((rowcnt % 2) == 0) { buf.append(" class=\"tableRowOdd\""); } else { buf.append(" class=\"tableRowEven\""); } buf.append(">\n"); if (isLeftSidebar()) { buf.append("<td class=\"ListCellLine\"><img src=\""); buf.append(spacerImg()); buf.append("\" width=\"5\" height=\"1\" border=\"0\"></td>\n"); } // Create ID for hidden fields String showHideDivId = tableUid + rowcnt; this.pageContext.setAttribute("showHideDivId", showHideDivId); // Bounce through our columns and pull out the data from this object // that we are currently focused on (lives in "smartRow"). for (int i = 0; i < numCols; i++) { ColumnTag tag = this.columns.get(i); ColumnDecorator colDecorator = colDecorators[i]; // Get the hidden value to be displayed as part of this column Object showHideValue = null; if (tag.getShowHideProperty() != null) { String showHideProperty = tag.getShowHideProperty(); if ((showHideProperty != null) && !showHideProperty.equals("null")) { showHideValue = this.lookup(pageContext, "smartRow", showHideProperty, null, true); } } // Special handling for the show/hide column if (tag.getIsShowHideColumn() && (showHideValue != null) && !showHideValue.equals("")) { buf.append("<td "); buf.append(tag.getCellAttributes()); buf.append(">"); buf.append("<div onClick=\"expandcontent(this, '"); buf.append(showHideDivId); buf.append("')\" class=\"showHideSwitch\"><span class=\"showstate\"></span></div></td>"); continue; } buf.append("<td "); buf.append(tag.getCellAttributes()); buf.append(">"); // Get the value to be displayed for the column Object value; if (tag.getValue() != null) { try { value = tag.getValue(); value = applyDecorator(colDecorator, value); } catch (NullAttributeException ne) { throw new JspException("bean " + tag.getValue() + " not found"); } } else { if (tag.getProperty() == null) { // Neither property nor value set... user wants the entire row value = pageContext.getAttribute(getVar()); value = applyDecorator(colDecorator, value); } else if ("ff".equals(tag.getProperty())) { value = String.valueOf(rowcnt); } else if (tag.getProperty().equals("null")) { value = ""; /* user doesn't want output, using c:set or something */ } else { try { value = this.lookup(pageContext, "smartRow", tag.getProperty(), null, true); value = applyDecorator(colDecorator, value); } catch (Throwable t) { log.warn("Exception during processing of TableTag: " + t); value = null; } } } // By default, we show null values as empty strings, unless the // user tells us otherwise. if ((value == null) || "".equals(value.toString().trim())) { if ((tag.getNulls() == null) && (prop.getProperty("basic.htmlNullValue") != null)) { value = prop.getProperty("basic.htmlNullValue"); } else { value = tag.getNulls(); } } // String to hold what's left over after value is chopped String leftover = ""; boolean chopped = false; String tempValue = ""; if (value != null) { tempValue = value.toString(); } // trim the string if a maxLength or maxWords is defined if ((tag.getMaxLength() > 0) && (tempValue.length() > tag.getMaxLength())) { leftover = "..." + tempValue.substring(tag.getMaxLength(), tempValue.length()); value = tempValue.substring(0, tag.getMaxLength()) + "..."; chopped = true; } else if (tag.getMaxWords() > 0) { StringBuffer tmpBuffer = new StringBuffer(); StringTokenizer st = new StringTokenizer(tempValue); int numTokens = st.countTokens(); if (numTokens > tag.getMaxWords()) { int x = 0; while (st.hasMoreTokens() && (x < tag.getMaxWords())) { tmpBuffer.append(st.nextToken() + " "); x++; } leftover = "..." + tempValue.substring(tmpBuffer.length(), tempValue.length()); tmpBuffer.append("..."); value = tmpBuffer; chopped = true; } } // set up a link to the data being displayed in this column if requested if ((tag.getAutolink() != null) && tag.getAutolink().equals("true")) { value = this.autoLink(value.toString()); } // set up a link if href="" property is defined String href = null; if (tag.getHref() != null) { try { href = (String) evalAttr("href", tag.getHref(), String.class); } catch (NullAttributeException ne) { throw new JspException("bean " + tag.getHref() + " not found"); } if (tag.getParamId() != null) { String name = tag.getParamName(); if (name == null) { name = "smartRow"; } Object param = this.lookup(pageContext, name, tag.getParamProperty(), tag.getParamScope(), true); // URL escape params // PR: 7709 String paramId; String paramVal; String tmp = (param instanceof String) ? (String) param : param.toString(); try { paramId = URLEncoder.encode(tag.getParamId(), "UTF-8"); paramVal = URLEncoder.encode(tmp, "UTF-8"); } catch (UnsupportedEncodingException e) { throw new JspException( "could not encode ActionForward path parameters because the JVM does not support UTF-8!?", e); } // flag to determine if we should use a ? or a & int index = href.indexOf('?'); String separator = (index == -1) ? "?" : "&"; // if value has been chopped, add leftover as title if (chopped) { value = "<a href=\"" + href + separator + paramId + "=" + paramVal + "\" title=\"" + leftover + "\">" + value + "</a>"; } else { value = "<a href=\"" + href + separator + paramId + "=" + paramVal + "\">" + value + "</a>"; } } else /* tag.getParamId() == null */ { // if value has been chopped, add leftover as title if (chopped) { value = "<a href=\"" + href + "\" title=\"" + leftover + "\">" + value + "</a>"; } else { value = "<a href=\"" + href + "\">" + value + "</a>"; } } } if (chopped && (href == null)) { buf.append(value.toString().substring(0, value.toString().length() - 3)); buf.append("<a style=\"cursor: help;\" title=\"" + leftover + "\">"); buf.append(value.toString().substring(value.toString().length() - 3, value.toString().length()) + "</a>"); } else { buf.append(value); } // Append the hidden text to the text in this cell // NOTE: This assumes the class of switchcontent, however in the future the need // may arise to make this variable. if ((showHideValue != null) && !showHideValue.equals("")) { showHideValue = applyDecorator(colDecorator, showHideValue); buf.append("<div id=\""); buf.append(showHideDivId); buf.append("\" class=\"switchcontent\">"); buf.append(showHideValue.toString()); buf.append("</div>"); } buf.append("</td>\n"); } // special case, if they didn't provide any columns. if (numCols == 0) { buf.append("<td class=\"tableCell\">"); buf.append(row.toString()); buf.append("</td>"); } if (isRightSidebar()) { buf.append("<td class=\"ListCellLine\"><img src=\""); buf.append(spacerImg()); buf.append("\" width=\"5\" height=\"1\" border=\"0\"></td>\n"); } buf.append("</tr>\n"); if (this.dec != null) { String rt = this.dec.finishRow(); if (rt != null) { buf.append(rt); } } for (int c = 0; c < numCols; c++) { if (colDecorators[c] != null) { colDecorators[c].finishRow(); } } if (this.dec != null) { this.dec.finish(); } this.dec = null; for (int c = 0; c < numCols; c++) { if (colDecorators[c] != null) { colDecorators[c].finish(); } } return buf; } private Object applyDecorator(ColumnDecorator colDecorator, Object value) throws JspException { if (colDecorator != null) { try { value = colDecorator.decorate(value); } catch (RuntimeException e) { log.warn("Exception decorating column", e); throw new JspException("Decorator " + colDecorator + " encountered a problem: ", e); } catch (Exception e) { throw new JspException("Decorator " + colDecorator + " encountered a problem: ", e); } } return value; } // -------------------------------------------------------- Utility Methods private String spacerImg() { return req.getContextPath() + "/images/spacer.gif"; } private String makeUrl(boolean makeQueryStringOpenEnded, boolean removePageControlParams) throws JspException { HttpServletRequest request = (HttpServletRequest) this.pageContext.getRequest(); String url = (getAction() != null) ? getAction() : request.getRequestURI(); Map params = RequestUtils.computeParameters(pageContext, getParamId(), getParamName(), getParamProperty(), getParamScope(), null, null, null, false); try { url = RequestUtils.computeURL(pageContext, null, url, null, null, params, null, false); } catch (Exception e) { throw new JspException("Couldn't compute URL:" + e); } if (removePageControlParams) { url = removePageControlParams(url); } if (makeQueryStringOpenEnded) { url = makeQueryStringOpenEnded(url); } return url; } private String removePageControlParams(String url) { int index = url.indexOf('?'); if (index != -1) { String base = url.substring(0, index); StringBuilder buf = new StringBuilder(base); String queryString = url.substring(index + 1); Map<String, Object> params = new java.util.HashMap<String, Object>();//HttpUtils.parseQueryString(queryString); params.remove(ParamConstants.SORTCOL_PARAM); params.remove(ParamConstants.SORTORDER_PARAM); params.remove(ParamConstants.PAGESIZE_PARAM); params.remove(ParamConstants.PAGENUM_PARAM); if (!params.isEmpty()) { buf.append('?').append(convertMapToQueryString(params)); } url = buf.toString(); } return url; } private String makeQueryStringOpenEnded(String url) { if (url.indexOf('?') == -1) { url += '?'; } else if (!url.endsWith("&")) { url += '&'; } return url; } /** * Generates the table header, including the first row of the table which displays the titles of the various * columns. * * @return Table header in HTML format */ protected String getTableHeader() throws JspException { log.trace("generating table header"); StringBuilder buf = new StringBuilder(1024); HttpServletRequest req = (HttpServletRequest) this.pageContext.getRequest(); String url = makeUrl(true, true); buf.append("<table "); buf.append(this.getTableAttributes()); buf.append(">\n"); // If they don't want the header shown for some reason, then stop here. if ((prop.getProperty("basic.show.header") != null) && !prop.getProperty("basic.show.header").equals("true")) { return buf.toString(); } buf.append("<tr class=\"tableRowHeader\">\n"); if (isLeftSidebar()) { buf.append("<td class=\"ListCellLineEmpty\"><img src=\""); buf.append(spacerImg()); buf.append("\" width=\"5\" height=\"1\" border=\"0\"></td>\n"); } // if one of the columns declares a colspan, we'll set this to // the number of subsequent columns to skip int colsToSkip = 0; for (int i = 0; i < currentNumCols; i++) { LocalizedColumnTag tag = (LocalizedColumnTag) this.columns.get(i); if (colsToSkip > 0) { // we're in the middle of a colspan, so skip this // column colsToSkip--; continue; } else if (tag.getHeaderColspan() != null) { Integer colspan; try { colspan = (Integer) evalAttr("headerColspan", tag.getHeaderColspan(), Integer.class); } catch (NullAttributeException ne) { throw new JspException("bean " + tag.getHeaderColspan() + " not found"); } // start the colspan colsToSkip = colspan; } String sortAttr = tag.getSortAttr(); buf.append("<th"); if (tag.getWidth() != null) { buf.append(" width=\"" + tag.getWidth() + "\""); } if (tag.getAlign() != null) { buf.append(" align=\"" + tag.getAlign() + "\""); } // if nowrapHeader if (getNowrapHeader() != null) { buf.append(" nowrap=\"true\""); } if (colsToSkip > 0) { buf.append(" colspan=\"" + colsToSkip + "\""); // decrement colsToSkip to account for the declaring // column colsToSkip--; } String header; try { header = (String) evalAttr("title", tag.getTitle(), String.class); } catch (NullAttributeException ne) { throw new JspException("bean " + tag.getTitle() + " not found"); } if (header == null) { if (tag.getIsLocalizedTitle()) { header = StringUtil.toUpperCaseAt(tag.getProperty(), 0); } else { header = "<img src=\"" + spacerImg() + "\" width=\"1\" height=\"1\" border=\"0\"/>"; } } buf.append(" class=\"tableRowInactive\">"); /* * start multi-column sort rendering */ PageControl pc = masterList.getPageControl(); int sortIndex = 0; String sortimg = null; String newSortOrder = Constants.SORTORDER_ASC; // default boolean wasSortedOn = false; if (sortAttr != null) { for (OrderingField field : pc.getOrderingFields()) { sortIndex++; if (field.getField().equals(sortAttr)) { if (field.getOrdering().equals(PageOrdering.ASC)) { newSortOrder = Constants.SORTORDER_DEC; if (sortIndex == 1) { sortimg = "/images/tb_sortup.gif"; } else { sortimg = "/images/tb_sortup_inactive.gif"; } } else { newSortOrder = Constants.SORTORDER_ASC; if (sortIndex == 1) { sortimg = "/images/tb_sortdown.gif"; } else { sortimg = "/images/tb_sortdown_inactive.gif"; } } wasSortedOn = true; break; } } buf.append("<a href=\""); buf.append(url); buf.append(ParamConstants.SORTORDER_PARAM).append(this.postfix); buf.append("=").append(newSortOrder); buf.append("&"); buf.append(ParamConstants.SORTCOL_PARAM).append(this.postfix); buf.append("=").append(sortAttr); buf.append("&"); buf.append(ParamConstants.PAGESIZE_PARAM).append(this.postfix); buf.append("=").append(masterList.getPageControl().getPageSize()); buf.append("&"); buf.append(ParamConstants.PAGENUM_PARAM).append(this.postfix); buf.append("=").append(masterList.getPageControl().getPageNumber()); buf.append("\">"); buf.append(header); if (sortimg != null) { buf.append("<img border=\"0\" src=\""); buf.append(req.getContextPath()); buf.append(sortimg); buf.append("\" >"); } if (wasSortedOn) { buf.append(" " + String.valueOf(sortIndex)); } buf.append("</a>"); } else { buf.append(header); } buf.append("</th>\n"); } // Special case, if they don't provide any columns. if (currentNumCols == 0) { buf.append("<td><b>" + prop.getProperty("error.msg.no_column_tags") + "</b></td>"); } if (isRightSidebar()) { buf.append("<td class=\"ListCellLineEmpty\"><img src=\""); buf.append(spacerImg()); buf.append("\" width=\"5\" height=\"1\" border=\"0\"></td>\n"); } buf.append("</tr>\n"); if (this.footer != null) { buf.append("<tfoot>"); buf.append(this.footer); buf.append("</tfoot>"); // reset footer this.footer = null; } String ret = buf.toString(); return ret; } /** * Generates table footer with links for export commands. * * @return HTML formatted table footer */ protected String getTableFooter() throws JspException { log.trace("generating footer"); StringBuilder buf = new StringBuilder(1024); String url = makeUrl(true, false); if (getExport() != null) { buf.append("<tr><td align=\"left\" width=\"100%\" colspan=\"").append(currentNumCols).append("\">"); buf.append("<table width=\"100%\" border=\"0\" cellspacing=\"0\" "); buf.append("cellpadding=0><tr class=\"tableRowAction\">"); buf.append("<td align=\"left\" valign=\"bottom\" class=\""); buf.append("tableCellAction\">"); // Figure out what formats they want to export, make up a little string String formats = ""; if ((prop.getProperty("export.csv") != null) && prop.getProperty("export.csv").equals("true")) { formats += "<a href=\"" + url + "exportType=1\">" + prop.getProperty("export.csv.label") + "</a>\n"; } if ((prop.getProperty("export.excel") != null) && prop.getProperty("export.excel").equals("true")) { if (!formats.equals("")) { formats += prop.getProperty("export.banner.sepchar"); } formats += "<a href=\"" + url + "exportType=2\">" + prop.getProperty("export.excel.label") + "</a>\n"; } if ((prop.getProperty("export.xml") != null) && prop.getProperty("export.xml").equals("true")) { if (!formats.equals("")) { formats += prop.getProperty("export.banner.sepchar"); } formats += "<a href=\"" + url + "exportType=3\">" + prop.getProperty("export.xml.label") + "</a>\n"; } Object[] objs = { formats }; if (prop.getProperty("export.banner") != null) { buf.append(MessageFormat.format(prop.getProperty("export.banner"), objs)); } buf.append("</td></tr>"); buf.append("</table>\n"); buf.append("</td></tr>"); } String tmpEmptyMsg = getEmptyMsg(); if (isPadRows()) { int tableSize = this.pageSize; if (tableSize < 1) { tableSize = Constants.PAGESIZE_DEFAULT; } if (tableSize > rowcnt) { String src = spacerImg(); for (int i = rowcnt; i < tableSize; i++) { buf.append("<tr class=\"ListRow\">\n"); if (isLeftSidebar()) { buf.append("<td class=\"ListCellLine\"><img src=\""); buf.append(src); buf.append("\" width=\"5\" height=\"1\" border=\"0\"></td>\n"); } for (int j = 0; j < numCols; j++) { buf.append("<td class=\"ListCell\"> </td>\n"); } if (isRightSidebar()) { buf.append("<td class=\"ListCellLine\"><img src=\""); buf.append(src); buf.append("\" width=\"5\" height=\"1\" border=\"0\"></td>\n"); } buf.append("</tr>\n"); } } } else if ((tmpEmptyMsg != null) && (rowcnt == 0)) { // there is a message to display when there are no rows String src = spacerImg(); buf.append("<tr class=\"ListRow\">\n"); if (isLeftSidebar()) { buf.append("<td class=\"ListCellLine\"><img src=\""); buf.append(src); buf.append("\" width=\"5\" height=\"1\" border=\"0\"></td>\n"); } for (int j = 0; j < numCols; j++) { buf.append("<td class=\"ListCell\""); if (j == 1) { buf.append(" nowrap=\"true\"><i>").append(tmpEmptyMsg).append("</i>"); } else { buf.append("> "); } buf.append("</td>\n"); } if (isRightSidebar()) { buf.append("<td class=\"ListCellLine\"><img src=\""); buf.append(src); buf.append("\" width=\"5\" height=\"1\" border=\"0\"></td>\n"); } buf.append("</tr>\n"); } String tmpstr = buf.toString(); return tmpstr; } /** * This takes a cloumn value and grouping index as the argument. It then groups the column and returns the * appropriate string back to the caller. */ protected String group(String value, int group) { if ((group == 1) && (this.nextRow.size() > 0)) { // we are at the begining of the next row so copy the contents from . // nextRow to the previousRow. this.previousRow.clear(); this.previousRow.putAll(nextRow); this.nextRow.clear(); } if (!this.nextRow.containsKey(new Integer(group))) { // Key not found in the nextRow so adding this key now... remember all the old values. this.nextRow.put(group, value); } /** * Start comparing the value we received, along with the grouping index. * if no matching value is found in the previous row then return the value. * if a matching value is found then this value should not get printed out * so reuturn "" **/ if (this.previousRow.containsKey(new Integer(group))) { for (int x = 1; x <= group; x++) { if (!(this.previousRow.get(new Integer(x))).equals((this.nextRow.get(new Integer(x))))) { // no match found so return this value back to the caller. return value; } } } /** * This is used, for when there is no data in the previous row, * It gets used only the firt time. **/ if (this.previousRow.size() == 0) { return value; } // There is corresponding value in the previous row so this value need not be printed, return "" return "<!-- returning from table tag -->"; // we are done !. } /* XXX not used? * protected void clearHashForColumGroupings() { // clear the hashes so that the hash does not have any reside from * previous reports and not cause any // problems. this.previousRow.clear(); this.nextRow.clear(); } */ /** * Takes all the table pass-through arguments and bundles them up as a string that gets tacked on to the end of the * table tag declaration. * * <p> * <p/>Note that we override some default behavior, specifically: * * <p> * <p/>width defaults to 100% if not provided border defaults to 0 if not provided cellspacing defaults to 1 if not * provided cellpadding defaults to 2 if not provided */ protected String getTableAttributes() { StringBuffer results = new StringBuffer(); if (getStyleClass() != null) { results.append(" class=\""); results.append(getStyleClass()); results.append("\""); } else { results.append(" class=\"table\""); } if (getStyleId() != null) { results.append(" id=\""); results.append(getStyleId()); results.append("\""); } else { results.append(" class=\"table\""); } if (getWidth() != null) { results.append(" width=\""); results.append(getWidth()); results.append("\""); } else { results.append(" width=\"100%\""); } if (getBorder() != null) { results.append(" border=\""); results.append(getBorder()); results.append("\""); } else { results.append(" border=\"0\""); } if (getCellspacing() != null) { results.append(" cellspacing=\""); results.append(getCellspacing()); results.append("\""); } else { results.append(" cellspacing=\"1\""); } if (getCellpadding() != null) { results.append(" cellpadding=\""); results.append(getCellpadding()); results.append("\""); } else { results.append(" cellpadding=\"2\""); } if (getAlign() != null) { results.append(" align=\""); results.append(getAlign()); results.append("\""); } if (getBackground() != null) { results.append(" background=\""); results.append(getBackground()); results.append("\""); } if (getBgcolor() != null) { results.append(" bgcolor=\""); results.append(getBgcolor()); results.append("\""); } if (getFrame() != null) { results.append(" frame=\""); results.append(getFrame()); results.append("\""); } if (getHeight() != null) { results.append(" height=\""); results.append(getHeight()); results.append("\""); } if (getHspace() != null) { results.append(" hspace=\""); results.append(getHspace()); results.append("\""); } if (getRules() != null) { results.append(" rules=\""); results.append(getRules()); results.append("\""); } if (getSummary() != null) { results.append(" summary=\""); results.append(getSummary()); results.append("\""); } if (getVspace() != null) { results.append(" vspace=\""); results.append(getVspace()); results.append("\""); } return results.toString(); } /** * This functionality is borrowed from struts, but I've removed some struts specific features so that this tag can * be used both in a struts application, and outside of one. * * <p/>Locate and return the specified bean, from an optionally specified scope, in the specified page context. If * no such bean is found, return <code>null</code> instead. * * @param pageContext Page context to be searched * @param name Name of the bean to be retrieved * @param scope Scope to be searched (page, request, session, application) or <code>null</code> to use <code> * findAttribute()</code> instead * * @throws JspException if an invalid scope name is requested */ public Object lookup(PageContext pageContext, String name, String scope) throws JspException { log.trace("looking up: " + name + " in scope: " + scope); Object bean; if (scope == null) { bean = pageContext.findAttribute(name); } else if (scope.equalsIgnoreCase("page")) { bean = pageContext.getAttribute(name, PageContext.PAGE_SCOPE); } else if (scope.equalsIgnoreCase("request")) { bean = pageContext.getAttribute(name, PageContext.REQUEST_SCOPE); } else if (scope.equalsIgnoreCase("session")) { bean = pageContext.getAttribute(name, PageContext.SESSION_SCOPE); } else if (scope.equalsIgnoreCase("application")) { bean = pageContext.getAttribute(name, PageContext.APPLICATION_SCOPE); } else { Object[] objs = { name, scope }; if (prop.getProperty("error.msg.cant_find_bean") != null) { String msg = MessageFormat.format(prop.getProperty("error.msg.cant_find_bean"), objs); throw new JspException(msg); } else { throw new JspException("Could not find " + name + " in scope " + scope); } } return (bean); } /** * This functionality is borrowed from struts, but I've removed some struts specific features so that this tag can * be used both in a struts application, and outside of one. * * <p/>Locate and return the specified property of the specified bean, from an optionally specified scope, in the * specified page context. * * @param pageContext Page context to be searched * @param name Name of the bean to be retrieved * @param property Name of the property to be retrieved, or <code>null</code> to retrieve the bean itself * @param scope Scope to be searched (page, request, session, application) or <code>null</code> to use <code> * findAttribute()</code> instead * * @throws JspException if an invalid scope name is requested * @throws JspException if the specified bean is not found * @throws JspException if accessing this property causes an IllegalAccessException, IllegalArgumentException, * InvocationTargetException, or NoSuchMethodException */ public Object lookup(PageContext pageContext, String name, String property, String scope, boolean useDecorator) throws JspException { log.trace("looking up: " + name + ":" + property + " in scope: " + scope); if (useDecorator && (this.dec != null)) { // First check the decorator, and if it doesn't return a value // then check the inner object... try { if (property == null) { return this.dec; } return (PropertyUtils.getProperty(this.dec, property)); } catch (IllegalAccessException e) { log.debug("bean access failed:", e); Object[] objs = { name, this.dec }; if (prop.getProperty("error.msg.illegal_access_exception") != null) { throw new JspException(MessageFormat.format(prop.getProperty("error.msg.illegal_access_exception"), objs)); } else { throw new JspException("IllegalAccessException trying to fetch " + "property " + name + " from bean " + dec); } } catch (InvocationTargetException e) { log.debug("bean invocation failed:", e); Object[] objs = { name, this.dec }; if (prop.getProperty("error.msg.invocation_target_exception") != null) { throw new JspException(MessageFormat.format(prop .getProperty("error.msg.invocation_target_exception"), objs)); } else { throw new JspException("InvocationTargetException trying to fetch " + "property " + name + " from bean " + dec); } } catch (NoSuchMethodException e) { log.debug("bean getter property access failed:", e); throw new JspException(" bean property getter not found"); } } // Look up the requested bean, and return if requested Object bean = this.lookup(pageContext, name, scope); if (property == null) { return (bean); } if (bean == null) { log.debug("expected bean was null"); Object[] objs = { name, scope }; if (prop.getProperty("error.msg.cant_find_bean") != null) { throw new JspException(MessageFormat.format(prop.getProperty("error.msg.cant_find_bean"), objs)); } else { throw new JspException("Could not find bean " + name + "in scope " + scope); } } // Locate and return the specified property try { return (PropertyUtils.getProperty(bean, property)); } catch (IllegalAccessException e) { Object[] objs = { property, name }; log.debug("bean access failed:", e); if (prop.getProperty("error.msg.illegal_access_exception") != null) { throw new JspException(MessageFormat.format(prop.getProperty("error.msg.illegal_access_exception"), objs)); } else { throw new JspException("IllegalAccessException trying to fetch " + "property " + property + " from bean " + name); } } catch (InvocationTargetException e) { Object[] objs = { property, name }; if (prop.getProperty("error.msg.invocation_target_exception") != null) { throw new JspException(MessageFormat.format(prop.getProperty("error.msg.invocation_target_exception"), objs)); } else { throw new JspException("InvocationTargetException trying to fetch " + "property " + name + " from bean " + dec); } } catch (NoSuchMethodException e) { log.debug("bean getter property access failed:", e); throw new JspException(" bean getter for property " + property + " not found in bean " + name); } } /** * If the user has specified a decorator, then this method takes care of creating the decorator (and checking to * make sure it is a subclass of the TableDecorator object). If there are any problems loading the decorator then * this will throw a JspException which will get propogated up the page. */ protected Decorator loadDecorator() throws JspException { log.trace("loading decorator"); if ((getDecorator() == null) || (getDecorator().length() == 0)) { return null; } try { Class c = Class.forName(getDecorator()); if (!Class.forName("org.rhq.enterprise.gui.legacy.taglib.display.Decorator").isAssignableFrom(c)) { throw new JspException("invalid decorator"); } Decorator d = (Decorator) c.newInstance(); log.debug("found decorator: " + d); return d; } catch (Exception e) { log.debug("loading and instantiating decorator failed: ", e); throw new JspException("failure loading and instanting decorator " + e.toString()); } } /** * This takes the string that is passed in, and "auto-links" it, it turns email addresses into hyperlinks, and also * turns things that looks like URLs into hyperlinks as well. The rules are currently very basic, In Perl regex * lingo... * * <p/>Email: \b\S+\@[^\@\s]+\b URL: (http|https|ftp)://\S+\b * * <p/>I'm doing this via brute-force since I don't want to be dependent on a third party regex package. */ protected String autoLink(String data) { String work = data; int index; String results = ""; if ((data == null) || (data.length() == 0)) { return data; } // First check for email addresses. while ((index = work.indexOf("@")) != -1) { int start = 0; int end = work.length() - 1; // scan backwards... for (int i = index; i >= 0; i--) { if (Character.isWhitespace(work.charAt(i))) { start = i + 1; break; } } // scan forwards... for (int i = index; i <= end; i++) { if (Character.isWhitespace(work.charAt(i))) { end = i - 1; break; } } String email = work.substring(start, (end - start + 1)); results = results + work.substring(0, start) + "<a href=\"mailto:" + email + "\">" + email + "</a>"; if (end == work.length()) { work = ""; } else { work = work.substring(end + 1); } } work = results + work; results = ""; // Now check for urls... while ((index = work.indexOf("http://")) != -1) { int end = work.length() - 1; // scan forwards... for (int i = index; i <= end; i++) { if (Character.isWhitespace(work.charAt(i))) { end = i - 1; break; } } String url = work.substring(index, (end - index + 1)); results = results + work.substring(0, index) + "<a href=\"" + url + "\">" + url + "</a>"; if (end == work.length()) { work = ""; } else { work = work.substring(end + 1); } } results += work; return results; } /** * Called by the setProperty tag to override some default behavior or text string. */ public void setProperty(String name, String value) { prop.setProperty(name, value); } /** * Sets the content of the footer. Called by a nested footer tag. * * @param string footer content */ public void setFooter(String string) { this.footer = string; } /** * Is this the first iteration? * * @return boolean <code>true</code> if this is the first iteration */ protected boolean isFirstIteration() { if (log.isDebugEnabled()) { log.debug("[" + getId() + "] first iteration=" + (this.rowcnt == 0) + " (row number=" + this.rowcnt + ")"); } // in first iteration rowcnt is 0 // (rowcnt is incremented in doAfterBody) return this.rowcnt == 0; } /** * Use the jstl expression expression language to evaluate a field. * * @param name * @param value * @param type The Class type of the object you expect. * * @return The object found * * @throws NullAttributeException Thrown if the value is null. */ private Object evalAttr(String name, String value, Class type) throws JspTagException { try { return ExpressionUtil.evalNotNull("display", name, value, type, this, pageContext); } catch (NullAttributeException ne) { throw new JspTagException("Attribute " + name + " not found in TableTag"); } catch (JspException je) { throw new JspTagException(je.toString()); } } protected void evaluateAttributes() throws JspTagException { masterList = getItems(); offSet = (Integer) evalAttr("offset", getOffset(), Integer.class); pageNumber = this.masterList.getPageControl().getPageNumber(); this.pageSize = this.masterList.getPageControl().getPageSize(); } public String getPostfix() { return postfix; } public void setPostfix(String postfix) { this.postfix = postfix; } /** * Converts a map containing query string parameters into an HTTP query string. * * @param params a map containing the query string parameters; the values may be stored as either String Arrays * (for multi-value parameters) or non-Array Objects (for single-value parameters); null values are * allowed; non-String Array values are not allowed * * @return an HTTP query string */ private static String convertMapToQueryString(Map<String, Object> params) { if ((params == null) || params.isEmpty()) { return ""; } StringBuilder buf = new StringBuilder(); for (String paramName : params.keySet()) { Object paramValueObj = params.get(paramName); if (paramValueObj instanceof String[]) { String[] paramValues = (String[]) paramValueObj; for (String paramValue : paramValues) { buf.append(paramName).append('=').append(paramValue).append('&'); } } else { buf.append(paramName).append('='); if ((paramValueObj != null) && !paramValueObj.getClass().isArray()) { buf.append(paramValueObj); } buf.append('&'); } } buf.deleteCharAt(buf.length() - 1); return buf.toString(); } /** * @return the onlyForProperty */ public String getOnlyForProperty() { return onlyForProperty; } /** * @param onlyForProperty the onlyForProperty to set */ public void setOnlyForProperty(String onlyForProperty) { this.onlyForProperty = onlyForProperty; } }