package org.gomba;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.io.Writer;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.Types;
import javax.servlet.ServletConfig;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.gomba.utils.xml.XMLTextReader;
/**
* Render a single datum accessed via JDBC. The SQL in the <code>query</code>
* init-param should be a SELECT and must return a resultset. The field to
* render must be non-null. This servlet inherits the init-params of
* {@link org.gomba.AbstractServlet}, plus:
* <dl>
* <dt>column</dt>
* <dd>The result set column to render. This init-param is required only if the
* result set contains more than one column. (Optional)</dd>
* <dt>media-type</dt>
* <dd>The resource MIME content type. May contain ${} parameters. (Required)
* </dd>
* </dl>
* <p>
* This servlet can handle the following HTTP methods: GET, HEAD.
* </p>
*
* @author Flavio Tordini
* @version $Id: DatumServlet.java,v 1.1.1.1 2004/06/16 13:15:12 flaviotordini
* Exp $
*/
public class DatumServlet extends SingleQueryServlet {
/**
* The result set column name to render. May be null.
*/
private String columnName;
/**
* The resource MIME content type.
*/
private Expression mediaTypeExpression;
/**
* @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
*/
public void init(ServletConfig config) throws ServletException {
super.init(config);
// name of the column to render
this.columnName = config.getInitParameter("column");
// MIME
String mediaTypeString = config.getInitParameter("media-type");
if (mediaTypeString == null) {
throw new ServletException("Missing init-param: media-type");
}
try {
this.mediaTypeExpression = new Expression(mediaTypeString);
} catch (Exception e) {
throw new ServletException(
"Error parsing media type expression.", e);
}
}
/**
* @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
protected final void doGet(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response, true);
}
/**
* @see javax.servlet.http.HttpServlet#doHead(javax.servlet.http.HttpServletRequest,
* javax.servlet.http.HttpServletResponse)
*/
protected final void doHead(HttpServletRequest request,
HttpServletResponse response) throws ServletException, IOException {
processRequest(request, response, false);
}
/**
* @see org.gomba.AbstractServlet#doOutput(java.sql.ResultSet,
* javax.servlet.http.HttpServletResponse, ParameterResolver)
*/
protected void doOutput(ResultSet resultSet, HttpServletResponse response,
ParameterResolver parameterResolver) throws Exception {
ResultSetMetaData rsmd = resultSet.getMetaData();
if (rsmd.getColumnCount() != 1 && this.columnName == null) {
throw new Exception(
"The result set contains more than one column. "
+ "You need to set the 'column' init-param.");
}
// mime type
final Object mediaType = this.mediaTypeExpression
.replaceParameters(parameterResolver);
if (!(mediaType instanceof java.lang.String)) {
throw new Exception(
"The media-type expression does not evaluate to " + java.lang.String.class);
}
response.setContentType((String) mediaType);
// find out the column index
final int columnIndex;
if (this.columnName != null) {
columnIndex = getColumnIndex(rsmd, this.columnName);
} else {
columnIndex = 1;
}
final int columnType = rsmd.getColumnType(columnIndex);
// render!
switch (columnType) {
case Types.BLOB:
Blob blob = resultSet.getBlob(columnIndex);
if (blob == null) {
throw new Exception("BLOB value is null.");
}
streamBytes(blob.getBinaryStream(), response);
break;
case Types.CLOB:
Clob clob = resultSet.getClob(columnIndex);
if (clob == null) {
throw new Exception("CLOB value is null.");
}
// wrap our reader in order to strip invalid XML chars
Reader reader1 = new XMLTextReader(clob.getCharacterStream());
streamCharacters(reader1, response);
break;
case Types.LONGVARCHAR:
case Types.VARCHAR:
case Types.CHAR:
Reader reader2 = resultSet.getCharacterStream(columnIndex);
if (reader2 == null) {
throw new Exception("LONGVARCHAR value is null.");
}
// wrap our reader in order to strip invalid XML chars
reader2 = new XMLTextReader(reader2);
streamCharacters(reader2, response);
break;
case Types.LONGVARBINARY:
InputStream is = resultSet.getBinaryStream(columnIndex);
if (is == null) {
throw new Exception("LONGVARBINARY value is null.");
}
streamBytes(is, response);
break;
default:
String value = resultSet.getObject(columnIndex).toString();
response.getWriter().write(value);
}
}
/**
* Get the index of a column in a resultset given its name.
*/
protected static int getColumnIndex(ResultSetMetaData rsmd,
String columnName) throws Exception {
final int columnCount = rsmd.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
// The set of columns begins with '1' rather than '0'
final String cn = rsmd.getColumnName(i);
if (cn.equalsIgnoreCase(columnName)) {
return i;
}
}
throw new Exception("Cannot find column named " + columnName);
}
/**
* Stream unicode characters to the response body.
*
* @param reader
* The stream of charachters
* @param response
* The HTTP response
*/
private static void streamCharacters(Reader reader,
HttpServletResponse response) throws IOException {
try {
char[] buffer = new char[response.getBufferSize()];
Writer writer = response.getWriter();
int length;
while ((length = reader.read(buffer)) >= 0) {
writer.write(buffer, 0, length);
}
} finally {
reader.close();
}
}
/**
* Stream bytes to the response body.
*
* @param is
* The stream of bytes
* @param response
* The HTTP response
*/
private static void streamBytes(InputStream is, HttpServletResponse response)
throws IOException {
try {
byte[] buffer = new byte[response.getBufferSize()];
OutputStream os = response.getOutputStream();
int length;
while ((length = is.read(buffer)) >= 0) {
os.write(buffer, 0, length);
}
} finally {
is.close();
}
}
}