package hirondelle.web4j.database;
import java.util.*;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.ResultSetMetaData;
import hirondelle.web4j.BuildImpl;
import hirondelle.web4j.request.Formats;
import hirondelle.web4j.security.SafeText;
import hirondelle.web4j.util.Args;
/**
Translates a <tt>ResultSet</tt> row into a <tt>Map</tt>.
<P>The returned {@code Map<String, SafeText>} has :
<ul>
<li>key - column name.
<li>value - column value as {@link SafeText}. The column values may be formatted
using {@link Formats#objectToTextForReport(Object)}, according to which constructor
is called.
</ul>
<P><tt>SafeText</tt> is used to protect the caller against unescaped special characters,
and Cross Site Scripting attacks.
*/
final class ReportBuilder extends ModelBuilder<Map<String, SafeText>> {
/**
Builds a <tt>Map</tt> whose values are simply the <em>raw, unprocessed text</em> of the
underlying <tt>ResultSet</tt>. These values are placed into {@link SafeText}
objects, to allow for various styles of escaping special characters when presenting
the data in the view.
<P>Database formatting functions can be used in the underlying SELECT statement
to format values to the desired form.
*/
ReportBuilder(){
/*
Implementation Note
This is a relatively rare instance where is it makes sense to
declare an empty constructor.
*/
}
/**
Constructor for applying some processing to column values, instead of using the raw text.
<P><tt>aColumnTypes</tt> is used to convert each column into an <tt>Object</tt>.
The supported types are the same as for {@link ConvertColumn}.
<P>After conversion into the desired class,
{@link hirondelle.web4j.request.Formats#objectToTextForReport(Object)} is used to apply
formats to the various objects.
@param aColumnTypes defines the type of each column, in order from left to right, as
they appear in the <tt>ResultSet</tt>; contains <tt>N</tt> class literals,
where <tt>N</tt> is the number of columns in the <tt>ResultSet</tt>; contains only
the classes supported by {@link ConvertColumn}; this constructor will create a defensive
copy of this parameter.
@param aFormats used to apply the
formats specified in <tt>web.xml</tt> and
{@link hirondelle.web4j.request.Formats#objectToTextForReport(Object)},
localized using {@link hirondelle.web4j.request.LocaleSource}.
*/
ReportBuilder(Class<?>[] aColumnTypes, Formats aFormats){
Args.checkForPositive(aColumnTypes.length);
fColumnTypes = defensiveCopy(aColumnTypes);
fFormats = aFormats;
fColumnToObject = BuildImpl.forConvertColumn();
}
/**
Returns an unmodifiable <tt>Map</tt>.
<P>See class description.
*/
Map<String, SafeText> build(ResultSet aRow) throws SQLException {
LinkedHashMap<String, SafeText> result = new LinkedHashMap<String, SafeText>();
SafeText value = null;
ResultSetMetaData metaData = aRow.getMetaData();
for (int columnIdx = 1; columnIdx <= metaData.getColumnCount(); ++columnIdx) {
if ( doNotTranslateToObjects() ) {
value = getSafeText(aRow, columnIdx);
//fLogger.finest("Raw unformatted text value: " + value.getRawString());
}
else {
if ( fColumnTypes.length != metaData.getColumnCount() ) {
throw new IllegalArgumentException(
"Class[] has different number of elements than ResultSet: " + fColumnTypes.length +
" versus " + metaData.getColumnCount () + ", respectively."
);
}
value = getFormattedObject(aRow, columnIdx);
//security risk to log data: fLogger.fine("Formatted object: " + value);
}
result.put(metaData.getColumnName(columnIdx), value);
}
return Collections.unmodifiableMap(result);
}
// PRIVATE //
/** The types to which ResultSet columns will be converted. */
private Class<?>[] fColumnTypes;
/** Applies formatting to objects created from ResultSet columns. */
private Formats fFormats;
/** Translates ResultSet columns into desired Objects. */
private ConvertColumn fColumnToObject;
private Class[] defensiveCopy(Class[] aClassArray){
Class[] result = new Class[aClassArray.length];
System.arraycopy(aClassArray, 0, result, 0, aClassArray.length);
return result;
}
private boolean doNotTranslateToObjects() {
return fColumnTypes == null;
}
private SafeText getSafeText(ResultSet aRow, int aColumn) throws SQLException {
SafeText result = null;
String text = aRow.getString(aColumn);
if ( text == null ) {
result = new SafeText(Formats.getEmptyOrNullText());
}
else {
result = new SafeText(text);
}
return result;
}
private SafeText getFormattedObject(ResultSet aRow, int aColumnIdx) throws SQLException {
Object translatedObject = getUnformattedObject(aRow, aColumnIdx);
return fFormats.objectToTextForReport(translatedObject);
}
private Object getUnformattedObject(ResultSet aRow, int aColumnIdx) throws SQLException {
return fColumnToObject.convert(aRow, aColumnIdx, fColumnTypes[aColumnIdx-1]);
}
}