/*
* 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;
}
}