package hep.aida.web.taglib;
import hep.aida.IAnalysisFactory;
import hep.aida.ITree;
import hep.aida.ITreeFactory;
import hep.aida.ITuple;
import hep.aida.ITupleFactory;
import hep.aida.web.taglib.util.LogUtils;
import hep.aida.web.taglib.util.PlotUtils;
import java.math.BigDecimal;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import javax.servlet.jsp.JspException;
import javax.servlet.jsp.PageContext;
import javax.servlet.jsp.jstl.sql.Result;
import javax.servlet.jsp.jstl.sql.ResultSupport;
import org.apache.commons.beanutils.DynaBean;
import org.apache.commons.beanutils.DynaProperty;
import org.apache.commons.beanutils.RowSetDynaClass;
/**
* The implementation class for all {@link TupleTag}classes.
*
* @author The AIDA Team @ SLAC
*
*/
public class TupleTagSupport implements TupleTag {
private String var;
private String scope = "page";
private Object query;
private ITuple tuple;
private IAnalysisFactory analysisFactory = IAnalysisFactory.create();
private ITreeFactory treeFactory = analysisFactory.createTreeFactory();
// Classes supported by ITuple.
private static final Class[] supportedClassArray = { Boolean.class,
Byte.class, Character.class, Short.class, Integer.class,
Long.class, Float.class, Double.class, String.class, Object.class,
BigDecimal.class };
// Types supported by ITuple.
private static final Class[] supportedTypeArray = { Boolean.TYPE,
Byte.TYPE, Character.TYPE, Short.TYPE, Integer.TYPE, Long.TYPE,
Float.TYPE, Double.TYPE, String.class, Object.class, Double.TYPE };
private static Map classTypeMap = Collections
.synchronizedMap(new HashMap());
static {
for (int i = 0; i < supportedTypeArray.length; ++i) {
classTypeMap.put(supportedClassArray[i], supportedTypeArray[i]);
}
}
public void doStartTag() throws JspException {
if (var == null || var.length() == 0) {
throw new JspException("var must not be null");
}
if (query == null) {
throw new JspException("query must not be null");
}
}
public void doEndTag(PageContext pageContext) throws JspException {
String scopeName = getScope();
if (scopeName == null) {
scopeName = "page";
}
int scope = PlotUtils.getScope(scopeName);
Result result = null;
if (query instanceof ResultSet) {
ResultSet resultSet = (ResultSet) query;
result = ResultSupport.toResult(resultSet);
} else if (query instanceof Result) {
result = (Result) query;
} else if (query instanceof String) {
// If query is a string, then search all JSP scopes for either a
// ResultSet or a Result with that name.
String attributeName = (String) query;
ResultSet resultSet = findResultSet(attributeName, pageContext);
if (resultSet != null) {
result = ResultSupport.toResult(resultSet);
} else {
result = findResult(attributeName, pageContext);
}
if (result == null) {
throw new JspException("don't know how to handle query "
+ query);
}
} else {
// We don't know how to handle objects of this type.
throw new JspException("don't know how to handle query " + query);
}
String tuplePath = getVar();
String tupleTitle = getVar();
ITuple tuple = toTuple(result, tuplePath, tupleTitle);
// Store the ITuple in a JSP scope.
pageContext.setAttribute(getVar(), tuple, scope);
}
private Class getColumnType(SortedMap[] rows, String columnName) {
for ( int i = 0; i < rows.length; i++ ) {
SortedMap row = rows[i];
if ( row != null ) {
Object rowValue = row.get(columnName);
if ( rowValue != null )
return rowValue.getClass();
}
}
return null;
}
ITuple toTuple(Result result, String tuplePath, String tupleTitle)
throws JspException {
// Convert the ResultsSet to a Result.
final int rowCount = result.getRowCount();
if (rowCount < 0) {
throw new JspException("query has no rows - can not continue");
}
List columnNameList = new ArrayList();
List columnClassList = new ArrayList();
// Loop over all of the columns from the query and silently ignore types
// that aren't supported by ITuple.
SortedMap[] rows = result.getRows();
final String[] resultColumnNames = result.getColumnNames();
for (int columnIndex = 0; columnIndex < resultColumnNames.length; ++columnIndex) {
String columnName = resultColumnNames[columnIndex];
Class clazz = getColumnType(rows,columnName);
if ( clazz == null )
continue;
// Results broker in Classes. ITuples broker in primitive types
// (except for String and Object). Therefore, we use the
// classTypeMap which maps Classes (the keys) to primitive types
// (the values).
if (classTypeMap.containsKey(clazz)) {
if (LogUtils.log().isDebugEnabled()) {
String message = "Found supported class "+clazz.getName();
LogUtils.log().debug(message);
}
columnNameList.add(columnName);
columnClassList.add(classTypeMap.get(clazz));
} else if (Date.class.isAssignableFrom(clazz)) {
// Special case to handle dates.
if (LogUtils.log().isDebugEnabled()) {
String message = "Found supported class "+clazz.getName()+" (but flagged as type Long.TYPE)";
LogUtils.log().debug(message);
}
columnNameList.add(columnName);
// Tony says to use doubles instead of longs due
// to AIDA limitations.
//columnClassList.add(Long.TYPE);
columnClassList.add(Double.TYPE);
} else {
LogUtils.log().debug("Ignore unsupported type " + clazz.getName());
}
}
String[] columnNames = (String[]) columnNameList.toArray(new String[0]);
Class[] columnClasses = (Class[]) columnClassList.toArray(new Class[0]);
// Create an ITree to store the ITuple in. TODO the TupleTag should be
// nested in a TreeTag and search its ancestry for the TreeTag to get at
// the ITree.
ITree tree = treeFactory.create();
ITupleFactory tupleFactory = analysisFactory.createTupleFactory(tree);
ITuple tuple = tupleFactory.create(tuplePath, tupleTitle, columnNames,
columnClasses);
// Loop over the rows of the query.
for (int rowIndex = 0; rowIndex < rows.length; ++rowIndex) {
SortedMap row = rows[rowIndex];
// Loop over the columns, filling the columns with the values from
// the query.
for (int columnIndex = 0; columnIndex < columnNames.length; ++columnIndex) {
String columnName = columnNames[columnIndex];
Object columnValue = row.get(columnName);
if ( columnValue == null && tuple.column(columnIndex).type() != Double.TYPE )
throw new JspException("A Query cannot contain a null value for a column that is not a double : "+tuple.column(columnIndex).type());
Class clazz = Double.class;
if ( columnValue != null )
clazz = columnValue.getClass();
if (Boolean.class.equals(clazz)) {
Boolean value = (Boolean) columnValue;
tuple.fill(columnIndex, value.booleanValue());
} else if (Byte.class.equals(clazz)) {
Byte value = (Byte) columnValue;
tuple.fill(columnIndex, value.byteValue());
} else if (Character.class.equals(clazz)) {
Character value = (Character) columnValue;
tuple.fill(columnIndex, value.charValue());
} else if (Short.class.equals(clazz)) {
Short value = (Short) columnValue;
tuple.fill(columnIndex, value.shortValue());
} else if (Integer.class.equals(clazz)) {
Integer value = (Integer) columnValue;
tuple.fill(columnIndex, value.intValue());
} else if (Long.class.equals(clazz)) {
Long value = (Long) columnValue;
tuple.fill(columnIndex, value.longValue());
} else if (Float.class.equals(clazz)) {
Float value = (Float) columnValue;
tuple.fill(columnIndex, value.floatValue());
} else if (Double.class.equals(clazz)) {
if ( columnValue != null ) {
Double value = (Double) columnValue;
tuple.fill(columnIndex, value.doubleValue());
} else
tuple.fill(columnIndex, Double.NaN);
} else if (BigDecimal.class.equals(clazz)) {
BigDecimal value = (BigDecimal) columnValue;
tuple.fill(columnIndex, value.doubleValue());
} else if (String.class.equals(clazz)) {
String value = (String) columnValue;
tuple.fill(columnIndex, value);
} else if (Date.class.isAssignableFrom(clazz)) {
// Convert Dates to longs.
Date dateTime = (Date) columnValue;
// Tony says to use doubles instead of longs due
// to AIDA limitations, and to divide by 1,000 since AIDA
// expects seconds instead of milliseconds.
//tuple.fill(columnIndex, dateTime.getTime());
tuple.fill(columnIndex, dateTime.getTime() / 1000.0);
} else {
// Fill with Object.
tuple.fill(columnIndex, columnValue);
}
}
tuple.addRow();
}
return tuple;
}
/**
* This is an old implementaion that uses {@link RowSetDynaClass}from
* commons-beanutils.
*/
ITuple toTuple(ResultSet resultSet, String tuplePath, String tupleTitle)
throws SQLException {
// Convert the ResultsSet to a RowSetDynaClass.
RowSetDynaClass rsdc = null;
rsdc = new RowSetDynaClass(resultSet);
List columnNameList = new ArrayList();
List columnClassList = new ArrayList();
// Loop over all of the columns from the query and silently ignore types
// that aren't supported by ITuple.
DynaProperty[] properties = rsdc.getDynaProperties();
for (int columnIndex = 0; columnIndex < properties.length; ++columnIndex) {
DynaProperty property = properties[columnIndex];
String name = property.getName();
Class clazz = property.getType();
// ResultSets broker in Classes. ITuples broker in primitive types
// (except for String and Object). Therefore, we use the
// classTypeMap which maps Classes (the keys) to primitive types
// (the values).
if (classTypeMap.containsKey(clazz)) {
if (LogUtils.log().isDebugEnabled())
LogUtils.log().debug("Found supported class "+clazz.getName());
columnNameList.add(name);
columnClassList.add(classTypeMap.get(clazz));
} else if (Date.class.isAssignableFrom(clazz)) {
// Special case to handle dates.
if (LogUtils.log().isDebugEnabled())
LogUtils.log().debug("Found supported class "+clazz.getName()+" (but flagged as type Long.TYPE)");
columnNameList.add(name);
columnClassList.add(Long.TYPE);
} else {
LogUtils.log().debug("Ignore unsupported type " + clazz.getName());
}
}
String[] columnNames = (String[]) columnNameList.toArray(new String[0]);
Class[] columnClasses = (Class[]) columnClassList.toArray(new Class[0]);
// Create an ITree to store the ITuple in. TODO the TupleTag should be
// nested in a TreeTag and search its ancestry for the TreeTag to get at
// the ITree.
ITree tree = treeFactory.create();
ITupleFactory tupleFactory = analysisFactory.createTupleFactory(tree);
ITuple tuple = tupleFactory.create(tuplePath, tupleTitle, columnNames,
columnClasses);
// Loop over the rows of the query.
Iterator iterator = rsdc.getRows().iterator();
while (iterator.hasNext()) {
DynaBean dynaBean = (DynaBean) iterator.next();
// Loop over the columns, filling the columns with the values from
// the query.
for (int columnIndex = 0; columnIndex < columnNames.length; ++columnIndex) {
String propertyName = columnNames[columnIndex];
Class clazz = dynaBean.getDynaClass().getDynaProperty(
propertyName).getType();
// Convert Dates to longs.
if (Boolean.class.equals(clazz)) {
Boolean value = (Boolean) dynaBean.get(propertyName);
tuple.fill(columnIndex, value.booleanValue());
} else if (Byte.class.equals(clazz)) {
Byte value = (Byte) dynaBean.get(propertyName);
tuple.fill(columnIndex, value.byteValue());
} else if (Character.class.equals(clazz)) {
Character value = (Character) dynaBean.get(propertyName);
tuple.fill(columnIndex, value.charValue());
} else if (Short.class.equals(clazz)) {
Short value = (Short) dynaBean.get(propertyName);
tuple.fill(columnIndex, value.shortValue());
} else if (Integer.class.equals(clazz)) {
Integer value = (Integer) dynaBean.get(propertyName);
tuple.fill(columnIndex, value.intValue());
} else if (Long.class.equals(clazz)) {
Long value = (Long) dynaBean.get(propertyName);
tuple.fill(columnIndex, value.longValue());
} else if (Float.class.equals(clazz)) {
Float value = (Float) dynaBean.get(propertyName);
tuple.fill(columnIndex, value.floatValue());
} else if (Double.class.equals(clazz)) {
Double value = (Double) dynaBean.get(propertyName);
tuple.fill(columnIndex, value.doubleValue());
} else if (String.class.equals(clazz)) {
String value = (String) dynaBean.get(propertyName);
tuple.fill(columnIndex, value);
} else if (Date.class.isAssignableFrom(clazz)) {
Date dateTime = (Date) dynaBean.get(propertyName);
tuple.fill(columnIndex, dateTime.getTime());
} else {
// Fill with Object.
tuple.fill(columnIndex, dynaBean.get(propertyName));
}
}
tuple.addRow();
}
return tuple;
}
/*
* (non-Javadoc)
*
* @see hep.aida.web.taglib.TupleTag#setVar(java.lang.String)
*/
public void setVar(String var) {
this.var = var;
}
public String getVar() {
return var;
}
/*
* (non-Javadoc)
*
* @see hep.aida.web.taglib.TupleTag#setScope(java.lang.String)
*/
public void setScope(String scope) {
this.scope = scope;
}
public String getScope() {
return scope;
}
/*
* (non-Javadoc)
*
* @see hep.aida.web.taglib.TupleTag#setQuery(java.lang.Object)
*/
public void setQuery(Object query) {
this.query = query;
}
public Object getQuery() {
return query;
}
/**
* Find a {@link ResultSet}in a JSP scope under the given attribute name.
* If nothing is found then return null.
*
* @param attributeName
* the name of the {@link ResultSet}in a JSP scope
* @return the {@link ResultSet}if it is found, otherwise null
*/
private ResultSet findResultSet(String attributeName,
PageContext pageContext) {
ResultSet resultSet = null;
// There is a bug in ColdFusion MX 6.1 on JRun4 whereby a
// request scope attribute exists but its value is always null.
// Therefore, we simply search the scopes ourselves.
// resultSet = (ResultSet)
// pageContext.findAttribute(attributeName);
int[] scope = { PageContext.PAGE_SCOPE, PageContext.REQUEST_SCOPE,
PageContext.SESSION_SCOPE, PageContext.APPLICATION_SCOPE };
for (int i = 0; i < scope.length; ++i) {
resultSet = (ResultSet) pageContext.getAttribute(attributeName,
scope[i]);
if (resultSet != null) {
break;
}
}
return resultSet;
}
/**
* Find a {@link Result}in a JSP scope under the given attribute name. If
* nothing is found then return null.
*
* @param attributeName
* the name of the {@link Result}in a JSP scope
* @return the {@link Result}if it is found, otherwise null
*/
private Result findResult(String attributeName, PageContext pageContext) {
Result result = null;
// There is a bug in ColdFusion MX 6.1 on JRun4 whereby a
// request scope attribute exists but its value is always null.
// Therefore, we simply search the scopes ourselves.
// resultSet = (ResultSet)
// pageContext.findAttribute(attributeName);
int[] scope = { PageContext.PAGE_SCOPE, PageContext.REQUEST_SCOPE,
PageContext.SESSION_SCOPE, PageContext.APPLICATION_SCOPE };
for (int i = 0; i < scope.length; ++i) {
result = (Result) pageContext.getAttribute(attributeName, scope[i]);
if (result != null) {
break;
}
}
return result;
}
}