/*
* NOTE: This copyright does *not* cover user programs that use HQ
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004, 2005, 2006], Hyperic, Inc.
* This file is part of HQ.
*
* HQ is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.ui.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.Collections;
import java.util.HashMap;
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.http.HttpSession;
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.lang.StringEscapeUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.struts2.views.jsp.TagUtils;
import org.apache.struts2.views.util.DefaultUrlHelper;
import org.apache.struts2.views.util.UrlHelper;
import org.hyperic.hq.ui.Constants;
import org.hyperic.hq.ui.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...]]
*
* This tag works very much like the struts iterator tag, most of the attributes
* have the same name and functionality as the struts tag.
*
* Simple Usage:
* <p>
*
* <display:table name="list" > <display:column property="title" />
* <display:column property="code" /> <display:column property="dean" />
* </display:table>
*
* More Complete Usage:
* <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>
*
*
* Attributes:
* <p>
*
* name property scope length offset pageSize decorator
*
*
* HTML Pass-through Attributes
*
* 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.
*
* 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 Integer itemCount = null;
private Decorator dec = null;
private static Properties prop = null;
// Used by various functions when the person wants to do paging
private SmartListHelper helper = null;
// User parameters that control the behavior of paging and sorting
private int sortColumn = Constants.SORTCOL_DEFAULT.intValue();
private int pageNumber = Constants.PAGENUM_DEFAULT.intValue();
private int pageSize = Constants.PAGESIZE_DEFAULT.intValue();
private String sortOrder = Constants.SORTORDER_ASC;
private int offSet;
private int length;
private List masterList;
private List viewableList;
private Iterator iterator;
private HttpServletResponse res;
private JspWriter out;
private HttpServletRequest req;
private StringBuffer buf = new StringBuffer(8192);
// variables to hold the previous row columns values.
protected Hashtable previousRow = new Hashtable(10);
protected Hashtable nextRow = new Hashtable(10);
private ColumnDecorator[] colDecorators;
private Object row;
private boolean started = false;
int rowcnt = 0;
private UrlHelper urlHelper = new DefaultUrlHelper();
/**
* static footer added using the footer tag.
*/
private String footer;
private static Log log = LogFactory.getLog(TableTag.class.getName());
public void release() {
super.release();
columns = new ArrayList(10);
currentNumCols = 0;
numCols = 0;
sortColumn = Constants.SORTCOL_DEFAULT.intValue();
pageNumber = Constants.PAGENUM_DEFAULT.intValue();
pageSize = Constants.PAGESIZE_DEFAULT.intValue();
sortOrder = Constants.SORTORDER_ASC;
buf = new StringBuffer(8192);
// variables to hold the previous row columns values.
previousRow = new Hashtable(10);
nextRow = new Hashtable(10);
colDecorators = null;
started = false;
rowcnt = 0;
this.footer = null;
}
/**
* 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);
currentNumCols++;
}
private void resetColumns() {
columns = new ArrayList<ColumnTag>(10);
currentNumCols = 0;
}
/**
* 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()
**/
public int doStartTag() throws JspException {
prop = (Properties) pageContext.getServletContext().getAttribute(
Constants.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) {
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.
*
* @exception JspException
* if a JSP exception has occurred
*/
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].init(this.pageContext, masterList);
}
}
viewableList = getViewableData();
buf.append(this.getTableHeader());
iterator = viewableList.iterator();
started = true;
if (iterator.hasNext()) {
// Get data for first row
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);
}
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(row, rowcnt));
rowcnt++;
resetColumns();
if (iterator.hasNext()) {
// Passes for rows
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.
*/
public int doEndTag() throws JspException {
buf.append(this.getTableFooter());
buf.append("</table>\n");
write(buf);
// a little clean up.
started = false;
buf = new StringBuffer(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>
*
* 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.intValue())
return masterList;
// just return the list
helper = new SmartListHelper(masterList, pageSize, prop, itemCount);
return masterList;
}
/**
* This method will sort the data in either ascending or decending order
* based on the user clicking on the column headers.
*
* @param viewableData
* The list passed into this mehtod will be sorted.
*/
protected void sortDataIfNeeded(List viewableData) {
// At this point we have all the objects that are supposed to be shown
// sitting in our internal list ready to be shown, so if they have
// clicked
// on one of the titles, then sort the list in either ascending or
// decending order...
ColumnTag tag = (ColumnTag) this.columns.get(this.sortColumn);
// If it is an explicit value, then sort by that, otherwise sort by
// the property...
if (tag.getValue() != null) {
// Todo, figure out how to sort this better....
// Collections.sort( (List)collection, tag.getValue() );
} else {
Collections.sort(viewableData, new BeanSorter(tag.getProperty(),
this.dec));
}
if ((Constants.SORTORDER_DEC).equals(sortOrder)) {
Collections.reverse(viewableData);
}
}
/**
* 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 {
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 < currentNumCols; c++) {
if (colDecorators[c] != null) {
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");
}
// 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 < currentNumCols; i++) {
ColumnTag tag = (ColumnTag) this.columns.get(i);
buf.append("<td ");
buf.append(tag.getCellAttributes());
buf.append(">");
// Get the value to be displayed for the column
Object value = null;
if (tag.getValue() != null) {
value = tag.getValue();
} else {
if (tag.getProperty().equals("ff")) {
value = String.valueOf(rowcnt);
} else if (tag.getProperty().equals("null")) {
value = ""; /*
* user doesn't want output, using c:set or
* something
*/
} else {
value = this.lookup(pageContext, "smartRow", tag
.getProperty(), null, true);
if (value instanceof String) {
// ...escape any HTML/XML text...
value = StringEscapeUtils.escapeHtml((String) value);
}
if (colDecorators[i] != null) {
try {
value = colDecorators[i].decorate(value);
} catch (RuntimeException e) {
throw new JspException("Decorator "
+ colDecorators[i]
+ " encountered a problem: ", e);
} catch (Exception e) {
throw new JspException("Decorator "
+ colDecorators[i]
+ " encountered a problem: ", e);
}
}
}
}
// 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());
}
String href = null;
if (value != null && value.toString().length() > 0) {
// set up a link if href="" property is defined
if (tag.getHref() != null) {
href = tag.getHref();
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 = null;
String paramVal = null;
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 = "";
if (index == -1) {
separator = "?";
} else {
separator = "&";
}
// if value has been chopped, add leftover as title
String url = res.encodeURL(href + separator + paramId + "=" + paramVal);
if (chopped) {
value = "<a href=\"" + url + "\" title=\"" + leftover
+ "\">" + value + "</a>";
} else {
value = "<a href=\"" + url + "\">" + value + "</a>";
}
} else /* tag.getParamId() == null */{
// if value has been chopped, add leftover as title
String url = res.encodeURL(href);
if (chopped) {
value = "<a href=\"" + url + "\" title=\""
+ leftover + "\">" + value + "</a>";
} else {
value = "<a href=\"" + url + "\">" + 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);
}
buf.append("</td>\n");
}
// special case, if they didn't provide any columns.
if (currentNumCols == 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 < currentNumCols; c++) {
if (colDecorators[c] != null) {
colDecorators[c].finishRow();
}
}
if (this.dec != null) {
this.dec.finish();
}
this.dec = null;
for (int c = 0; c < currentNumCols; c++) {
if (colDecorators[c] != null)
colDecorators[c].finish();
}
return buf;
}
private String spacerImg() {
return res.encodeURL(req.getContextPath() + "/images/spacer.gif");
}
private String makeUrl(boolean qs) throws JspException {
HttpServletRequest req = (HttpServletRequest) this.pageContext
.getRequest();
String url = getAction();
if (url == null) {
url = req.getRequestURI();
}
Map params=null;
if (getParamId() != null && getParamName() != null ) {
params = this.computeParameters(pageContext,
getParamId(), getParamName(), getParamProperty(),
getParamScope(), null, null, null, false);;
}
try {
url = urlHelper.buildUrl(url, req, res, params);
// url = TagUtils.getInstance().computeURL(pageContext, null, url, null, null, null, params, null, false);
} catch (Exception e) {
throw new JspException("couldn't compute URL" + e);
}
if (qs) {
// flag to determine if we should use a ? or a &
int index = url.indexOf('?');
String separator = index == -1 ? "?" : "&";
String qString = req.getQueryString();
if (qString != null && !qString.equals("")) {
url += separator + qString + "&";
} else {
url += separator;
}
}
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 {
StringBuffer buf = new StringBuffer(1024);
HttpServletRequest req = (HttpServletRequest) this.pageContext
.getRequest();
String url = makeUrl(false);
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 = tag.getHeaderColspan();
// start the colspan
colsToSkip = colspan.intValue();
}
boolean isDefaultSort = tag.isDefaultSort();
int sortAttr = -1;
// bizapp requires a specific constant to tell it
// which object type and attribute to sort on
Integer sortAttrInt = tag.getSortAttr();
if (sortAttrInt != null) {
sortAttr = sortAttrInt.intValue();
}
if (sortAttr == -1) {
sortAttr = i;
}
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--;
}
// table header class stuff
if (sortColumn == -1 && isDefaultSort) {
buf.append(" class=\"tableRowSorted\">");
} else if (sortColumn == sortAttr) {
buf.append(" class=\"tableRowSorted\">");
} else if (tag.getHeaderStyleClass() != null) {
buf.append(" class=\"" + tag.getHeaderStyleClass() + "\">");
} else if ("true".equals(tag.getSort()))
buf.append(" class=\"tableCellHeader\">");
else {
buf.append(" class=\"tableRowInactive\">");
}
String header = tag.getTitle();
if (header == null) {
if (tag.getIsLocalizedTitle()) {
header = StringUtil.toUpperCaseAt(tag.getProperty(), 0);
} else {
header = "<img src=\"" + spacerImg()
+ "\" width=\"1\" height=\"1\" border=\"0\"/>";
}
}
if (tag.getSort() != null) {
String sortimg = null;
int index = url.indexOf('?');
String separator = index == -1 ? "?" : "&";
if ((Constants.SORTORDER_ASC).equals(sortOrder)) {
buf.append("<a href=\"");
buf.append(url);
buf.append(separator);
buf.append(getOrderValue());
buf.append("=");
buf.append(Constants.SORTORDER_DEC);
buf.append("&");
buf.append(getSortValue());
buf.append("=");
buf.append(sortAttr);
buf.append("\">");
if ((sortColumn == -1 && isDefaultSort)
|| (sortColumn == sortAttr))
sortimg = "/images/tb_sortup.gif"; /*
* XXX make this a
* property
*/
} else {
buf.append("<a href=\"" + url + separator + getOrderValue()
+ "=" + Constants.SORTORDER_ASC + "&"
+ getSortValue() + "=" + sortAttr + "\">");
if ((sortColumn == -1 && isDefaultSort)
|| (sortColumn == sortAttr)) {
sortimg = "/images/tb_sortdown.gif"; /*
* XXX make this a
* property
*/
}
}
buf.append(header);
if (sortimg != null && !"".equals(sortimg)) {
buf.append("<img border=\"0\" src=\"");
buf.append(req.getContextPath());
buf.append(sortimg);
buf.append("\" >");
}
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 {
StringBuffer buf = new StringBuffer(1024);
HttpServletRequest req = (HttpServletRequest) this.pageContext
.getRequest();
String url = makeUrl(true);
if (getExport() != null) {
buf.append("<tr><td align=\"left\" width=\"100%\" colspan=\""
+ currentNumCols + "\">");
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=\"" + res.encodeURL(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=\"" + res.encodeURL(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=\"" + res.encodeURL(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.intValue();
}
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 appropritate 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(new Integer(group), new String(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 (!((String) this.previousRow.get(new Integer(x)))
.equals(((String) 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 !.
}
/**
* 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>
*
* Note that we override some default behavior, specifically:
* <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("\"");
}
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.
*
* 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
*
* @exception JspException
* if an invalid scope name is requested
*/
public Object lookup(PageContext pageContext, String name, String scope)
throws JspException {
Object bean = null;
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.
*
* 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
*
* @exception JspException
* if an invalid scope name is requested
* @exception JspException
* if the specified bean is not found
* @exception 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 {
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) {
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) {
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) {
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) {
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 };
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) {
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 {
if (getDecorator() == null || getDecorator().length() == 0) {
return null;
}
try {
Class c = Class.forName(getDecorator());
if (!Class.forName("org.hyperic.hq.ui.taglib.display.Decorator")
.isAssignableFrom(c)) {
throw new JspException("Invalid decorator");
}
Decorator d = (Decorator) c.newInstance();
return d;
} catch (Exception 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...
*
* Email: \b\S+\@[^\@\s]+\b URL: (http|https|ftp)://\S+\b
*
* 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 = new String(data);
int index = -1;
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() {
return this.rowcnt == 0;
}
protected void evaluateAttributes() throws JspTagException {
masterList = getItems();
offSet = getOffset();
length = getLength();
Integer pn = getPage();
if (pn == null || pn.intValue() == 0) {
String pnp = req.getParameter(getPageValue());
if (pnp != null && !pnp.equals("")) {
pn = new Integer(pnp);
}
if (pn == null || pn.intValue() == 0) {
pn = Constants.PAGENUM_DEFAULT;
}
}
pageNumber = pn.intValue();
Integer ps = getPageSize();
if (ps == null || ps.intValue() == 0) {
String psp = req.getParameter(getPageSizeValue());
if (psp != null && !psp.equals("")) {
ps = new Integer(psp);
}
if (ps == null || ps.intValue() == 0) {
ps = Constants.PAGESIZE_DEFAULT;
}
}
pageSize = ps.intValue();
Integer sc = getSort();
if (sc == null || sc.intValue() == 0) {
String scp = req.getParameter(getSortValue());
if (scp != null && !scp.equals("")) {
sc = new Integer(scp);
}
if (sc == null || sc.intValue() == 0) {
sc = new Integer(-1);
}
}
sortColumn = sc.intValue();
String sortOrder = getOrder();
if (sortOrder == null || sortOrder.equals("")) {
sortOrder = req.getParameter(getOrderValue());
if (sortOrder == null || sortOrder.equals("")) {
sortOrder = Constants.SORTORDER_DEFAULT;
}
}
this.sortOrder = sortOrder;
}
private Map computeParameters(PageContext pageContext, String paramId,
String paramName, String paramProperty, String paramScope, String name,
String property, String scope, boolean transaction)
throws JspException {
// Short circuit if no parameters are specified
if ((paramId == null) && (name == null) && !transaction) {
return (null);
}
// Locate the Map containing our multi-value parameters map
Map map = null;
if (name != null) {
map = (Map) TagUtils.getStack(pageContext).findValue( name);
}
// Create a Map to contain our results from the multi-value parameters
Map results = null;
if (map != null) {
results = new HashMap(map);
} else {
results = new HashMap();
}
// Add the single-value parameter (if any)
if ((paramId != null) && (paramName != null)) {
Object paramValue = null;
paramValue = TagUtils.getStack(pageContext).findValue( paramName);
if (paramValue != null) {
String paramString = null;
if (paramValue instanceof String) {
paramString = (String) paramValue;
} else {
paramString = paramValue.toString();
}
Object mapValue = results.get(paramId);
if (mapValue == null) {
results.put(paramId, paramString);
} else if (mapValue instanceof String[]) {
String[] oldValues = (String[]) mapValue;
String[] newValues = new String[oldValues.length + 1];
System.arraycopy(oldValues, 0, newValues, 0,
oldValues.length);
newValues[oldValues.length] = paramString;
results.put(paramId, newValues);
} else {
String[] newValues = new String[2];
newValues[0] = mapValue.toString();
newValues[1] = paramString;
results.put(paramId, newValues);
}
}
}
// Return the completed Map
return (results);
}
}