001: package org.gomba;
002:
003: import java.io.ByteArrayInputStream;
004: import java.io.IOException;
005: import java.io.InputStream;
006: import java.sql.ResultSet;
007: import java.sql.SQLException;
008: import java.util.Collections;
009: import java.util.Date;
010: import java.util.Enumeration;
011: import java.util.HashMap;
012: import java.util.Iterator;
013: import java.util.Map;
014: import java.util.Properties;
015:
016: import javax.naming.Context;
017: import javax.naming.InitialContext;
018: import javax.naming.NamingException;
019: import javax.servlet.RequestDispatcher;
020: import javax.servlet.ServletConfig;
021: import javax.servlet.ServletException;
022: import javax.servlet.http.HttpServletRequest;
023: import javax.servlet.http.HttpServletResponse;
024: import javax.sql.DataSource;
025:
026: import org.gomba.utils.servlet.ServletParameterUtils;
027:
028: /**
029: * Base class for Servlets that render data accessed via JDBC.
030: * <p>
031: * Context params:
032: * <dl>
033: * <dt>org.gomba.dataSource</dt>
034: * <dd>The default JNDI data source. If not specified, the data source must be
035: * set in the <code>data-source</code> servlet init-param. (Optional)</dd>
036: * </dl>
037: * <dl>
038: * <dt>org.gomba.debug</dt>
039: * <dd>The default debug mode. To enable debug logging set the value to 'true'.
040: * (Optional)</dd>
041: * </dl>
042: * </p>
043: *
044: * <p>
045: * This servlet inherits the init-params of
046: * {@link org.gomba.TransactorAbstractServlet}
047: * </p>
048: *
049: * </p>
050: * Init params:
051: * <dl>
052: * <dt>data-source</dt>
053: * <dd>The JNDI data source. The default data source may be specified in the
054: * <code>org.gomba.dataSource</code> context-param. (Optional)</dd>
055: * <dt>response-headers</dt>
056: * <dd>The HTTP response headers in Java Properties format. May contain ${}
057: * parameters in the property values. It is highly recommended to set this
058: * init-param, especially for cache headers like "Last-modified", "Expires" etc.
059: * (Optional)</dd>
060: * <dt>http-status</dt>
061: * <dd>The HTTP status code to send when the request is successfully processed.
062: * Defaults to 200 (OK). (Optional)</dd>
063: * <dt>multipart-size-threshold</dt>
064: * <dd>The size threshold beyond which files are written directly to disk.
065: * Defaut: 64K (Optional)</dd>
066: * <dt>multipart-max-size</dt>
067: * <dd>The maximum allowed upload size. If negative, there is no maximum.
068: * Defaut: 128K (Optional)</dd>
069: * <dt>multipart-repository-path</dt>
070: * <dd>The location used to temporarily store files that are larger than the
071: * configured size threshold. Defaut: the system default temp directory, as
072: * returned by System.getProperty("java.io.tmpdir") (Optional)</dd>
073: * <dt>debug</dt>
074: * <dd>Log debug mode. To enable debug logging set the value to 'true'. The
075: * default debug mode may be specified in the <code>org.gomba.debug</code>
076: * context-param. (Optional)</dd>
077: * </dl>
078: * </p>
079: *
080: * @author Flavio Tordini
081: * @version $Id: AbstractServlet.java,v 1.2 2004/06/28 16:12:05 flaviotordini
082: * Exp $
083: */
084: abstract class AbstractServlet extends TransactorAbstractServlet {
085:
086: private final static String CONTEXT_PARAM_DATA_SOURCE = "org.gomba.dataSource";
087:
088: protected final static String CONTEXT_PARAM_DEBUG = "org.gomba.debug";
089:
090: protected final static String INIT_PARAM_DEBUGMODE = "debug";
091:
092: private final static String INIT_PARAM_DATA_SOURCE = "data-source";
093:
094: private final static String INIT_PARAM_RESPONSE_HEADERS = "response-headers";
095:
096: private final static String INIT_PARAM_HTTP_STATUS = "http-status";
097:
098: private final static String INIT_PARAM_MULTIPART_SIZE_THRESHOLD = "multipart-size-threshold";
099:
100: private final static String INIT_PARAM_MULTIPART_MAX_SIZE = "multipart-max-size";
101:
102: private final static String INIT_PARAM_MULTIPART_REPOSITORY_PATH = "multipart-repository-path";
103:
104: private final static String APP_NAME = "Gomba";
105:
106: static {
107:
108: // get version
109: InputStream is = AbstractServlet.class.getClassLoader()
110: .getResourceAsStream("org/gomba/version.properties");
111:
112: Properties version = new Properties();
113: try {
114: version.load(is);
115: is.close();
116: } catch (IOException ioe) {
117: System.err.println(AbstractServlet.class.getName()
118: + ": Cannot load version information.");
119: ioe.printStackTrace();
120: }
121:
122: String major = version.getProperty("major");
123: String minor = version.getProperty("minor");
124: String micro = version.getProperty("micro");
125:
126: System.out.println(APP_NAME + ' ' + major + '.' + minor + '.'
127: + micro + " http://gomba.sourceforge.net/");
128:
129: }
130:
131: /**
132: * <code>true</code> if debug logging is turned on.
133: */
134: private boolean debugMode;
135:
136: /**
137: * The data source to query.
138: */
139: private DataSource dataSource;
140:
141: /**
142: * Map HTTP response header name to an {@link Expression}. May be null.
143: */
144: private Map responseHeaders;
145:
146: /**
147: * The HTTP status code when the request is successfully processed.
148: */
149: private int httpStatusCode = HttpServletResponse.SC_OK;
150:
151: private int multipartSizeThreshold = 1024 * 64;
152:
153: private int multipartMaxSize = 1024 * 128;
154:
155: private String multipartRepositoryPath;
156:
157: /**
158: * @return <code>true</code> if debug is enabled.
159: */
160: protected static boolean getDebugMode(ServletConfig config) {
161: boolean debugMode;
162: String debugStr = config.getInitParameter(INIT_PARAM_DEBUGMODE);
163: if (debugStr != null) {
164: debugMode = Boolean.valueOf(debugStr).booleanValue();
165: } else {
166: debugMode = Boolean.valueOf(
167: config.getServletContext().getInitParameter(
168: CONTEXT_PARAM_DEBUG)).booleanValue();
169: }
170: return debugMode;
171: }
172:
173: /**
174: * @return The JDBC DataSource for this servlet instance.
175: */
176: protected static DataSource getDataSource(ServletConfig config)
177: throws ServletException {
178:
179: DataSource dataSource;
180:
181: String dataSourceName = config
182: .getInitParameter(INIT_PARAM_DATA_SOURCE);
183: if (dataSourceName == null) {
184: dataSourceName = config.getServletContext()
185: .getInitParameter(CONTEXT_PARAM_DATA_SOURCE);
186: }
187: if (dataSourceName == null) {
188: throw new ServletException(
189: "JDBC data source not specified. Please set the '"
190: + CONTEXT_PARAM_DATA_SOURCE
191: + "' context-param or the '"
192: + INIT_PARAM_DATA_SOURCE
193: + "' servlet init-param.");
194: }
195: try {
196: Context initContext = new InitialContext();
197: Context envContext = (Context) initContext
198: .lookup("java:/comp/env");
199: dataSource = (DataSource) envContext.lookup(dataSourceName);
200: } catch (NamingException ne) {
201: throw new ServletException(
202: "Error getting reference to data source.", ne);
203: }
204:
205: return dataSource;
206: }
207:
208: /**
209: * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
210: */
211: public void init(ServletConfig config) throws ServletException {
212: super .init(config);
213:
214: // debug mode
215: this .debugMode = getDebugMode(config);
216:
217: // get the JNDI data source
218: this .dataSource = getDataSource(config);
219:
220: // parse the response header settings
221: try {
222: this .responseHeaders = parseResponseHeaderSettings(config
223: .getInitParameter(INIT_PARAM_RESPONSE_HEADERS));
224: } catch (Exception e) {
225: throw new ServletException(
226: "Error parsing response header settings.", e);
227: }
228:
229: // the HTTP status code to send when everything's alright.
230: try {
231: String httpStatusCodeString = config
232: .getInitParameter(INIT_PARAM_HTTP_STATUS);
233: if (httpStatusCodeString != null) {
234: this .httpStatusCode = Integer
235: .parseInt(httpStatusCodeString);
236: }
237: } catch (NumberFormatException e) {
238: throw new ServletException("Error parsing "
239: + INIT_PARAM_HTTP_STATUS, e);
240: }
241:
242: // the multipart size threshold between memory and disk based storage
243: try {
244: String multipartSizeThresholdString = config
245: .getInitParameter(INIT_PARAM_MULTIPART_SIZE_THRESHOLD);
246: if (multipartSizeThresholdString != null) {
247: this .multipartSizeThreshold = Integer
248: .parseInt(multipartSizeThresholdString);
249: }
250: } catch (NumberFormatException e) {
251: throw new ServletException("Error parsing "
252: + INIT_PARAM_MULTIPART_SIZE_THRESHOLD, e);
253: }
254:
255: // the multipart max size
256: try {
257: String multipartMaxSizeString = config
258: .getInitParameter(INIT_PARAM_MULTIPART_MAX_SIZE);
259: if (multipartMaxSizeString != null) {
260: this .multipartMaxSize = Integer
261: .parseInt(multipartMaxSizeString);
262: }
263: } catch (NumberFormatException e) {
264: throw new ServletException("Error parsing "
265: + INIT_PARAM_MULTIPART_MAX_SIZE, e);
266: }
267:
268: // The multipart repository path
269: this .multipartRepositoryPath = config
270: .getInitParameter(INIT_PARAM_MULTIPART_REPOSITORY_PATH);
271:
272: }
273:
274: /**
275: * Get a Query instance for our QueryDefinition.
276: */
277: protected Query getQuery(QueryDefinition requestQueryDefinition,
278: ParameterResolver parameterResolver)
279: throws ServletException {
280: // build the Query
281: final Query query;
282: try {
283: query = new Query(requestQueryDefinition, parameterResolver);
284: // debug queries
285: if (this .debugMode) {
286: log("Query: " + query);
287: }
288: } catch (Exception e) {
289: throw new ServletException("Error building query.", e);
290: }
291: return query;
292: }
293:
294: /**
295: * Ensure the resultset cursor is positioned on a row.
296: *
297: * @return true if the ResultSet is positioned on a row. if the ResultSet
298: * contains no rows return false.
299: */
300: protected final static boolean maybeMoveCursor(ResultSet resultSet)
301: throws SQLException {
302:
303: if (resultSet.isBeforeFirst()) {
304: // we're before the first row
305: if (!resultSet.next()) {
306: // 0 rows in this result set
307: return false;
308: }
309:
310: } else if (resultSet.getRow() == 0) {
311: // no current row (we should be after the last row)
312: return false;
313: }
314:
315: return true;
316: }
317:
318: /**
319: * Parse the response header settings.
320: *
321: * @param settings
322: * Response header in Java Properties format.
323: * @return Map the response header name to its dynamic value. Mapping is
324: * String to {@link Expression}
325: * @throws Exception
326: */
327: private final static Map parseResponseHeaderSettings(String settings)
328: throws Exception {
329: if (settings == null) {
330: return null;
331: }
332: Properties headers = new Properties();
333: InputStream inputStream = new ByteArrayInputStream(settings
334: .getBytes());
335: try {
336: headers.load(inputStream);
337: } finally {
338: inputStream.close();
339: }
340:
341: final Map responseHeaders = new HashMap(headers.size(), 1);
342:
343: Enumeration headerNames = headers.propertyNames();
344: while (headerNames.hasMoreElements()) {
345: String headerName = (String) headerNames.nextElement();
346: String headerValue = headers.getProperty(headerName);
347: responseHeaders
348: .put(headerName, new Expression(headerValue));
349: }
350:
351: // make responseHeaders immutable
352: return Collections.unmodifiableMap(responseHeaders);
353:
354: }
355:
356: protected final static void setResponseHeader(
357: HttpServletResponse response, String headerName,
358: Object headerValue) {
359: if (headerValue instanceof java.util.Date) {
360: response.setDateHeader(headerName, ((Date) headerValue)
361: .getTime());
362: } else if (headerValue instanceof java.lang.Integer) {
363: response.setIntHeader(headerName, ((Integer) headerValue)
364: .intValue());
365: } else {
366: response.setHeader(headerName, headerValue.toString());
367: }
368: }
369:
370: protected final void setResponseHeaders(
371: HttpServletResponse response,
372: ParameterResolver parameterResolver)
373: throws ServletException {
374: if (this .responseHeaders != null) {
375: try {
376:
377: for (Iterator i = this .responseHeaders.entrySet()
378: .iterator(); i.hasNext();) {
379: Map.Entry mapEntry = (Map.Entry) i.next();
380:
381: String headerName = (String) mapEntry.getKey();
382: Object headerValue = ((Expression) mapEntry
383: .getValue())
384: .replaceParameters(parameterResolver);
385:
386: setResponseHeader(response, headerName, headerValue);
387: }
388:
389: } catch (Exception e) {
390: throw new ServletException(
391: "Error setting response headers.", e);
392: }
393: }
394: }
395:
396: /**
397: * Include a resource (a JSP) and capture its response body.
398: *
399: * @param queryResource
400: * The resource to include
401: * @param request
402: * the HTTP request
403: * @param response
404: * the HTTP response
405: * @return The captured resource response body.
406: */
407: protected final String getDynamicQuery(String queryResource,
408: HttpServletRequest request, HttpServletResponse response)
409: throws ServletException, IOException {
410:
411: // get the dispatcher
412: RequestDispatcher dispatcher = getServletContext()
413: .getRequestDispatcher(queryResource);
414: if (dispatcher == null) {
415: throw new ServletException(
416: "Cannot get a RequestDispatcher for path: "
417: + queryResource);
418: }
419:
420: // set some useful information in the request
421: request.setAttribute("path", ServletParameterUtils
422: .getPathElements(request));
423:
424: // wrap the request in order to supply extra functionality (multiparm
425: // form handling)
426: CustomHttpServletRequestWrapper requestWrapper = new CustomHttpServletRequestWrapper(
427: request, this .multipartSizeThreshold,
428: this .multipartMaxSize, this .multipartRepositoryPath);
429:
430: // wrap our response, so we can capture the body (and the included
431: // resource cannot mess with it)
432: CustomHttpServletResponseWrapper responseWrapper = new CustomHttpServletResponseWrapper(
433: response);
434:
435: // include the resource
436: dispatcher.include(requestWrapper, responseWrapper);
437:
438: // and get the body
439: String sql = responseWrapper.getBody();
440:
441: if (sql == null) {
442: throw new ServletException(
443: "Can't happen! Dynamic query is null. "
444: + queryResource);
445: }
446:
447: // trim SQL to minimize in order to make it more readable
448: sql = sql.trim();
449:
450: return sql;
451: }
452:
453: /**
454: * @return Returns the dataSource.
455: */
456: protected DataSource getDataSource() {
457: return this .dataSource;
458: }
459:
460: /**
461: * @return Returns the responseHeaders.
462: */
463: protected final Map getResponseHeaders() {
464: return this .responseHeaders;
465: }
466:
467: /**
468: * @return Returns the debugMode.
469: */
470: protected final boolean isDebugMode() {
471: return this .debugMode;
472: }
473:
474: /**
475: * @return Returns the httpStatusCode.
476: */
477: protected final int getHttpStatusCode() {
478: return this .httpStatusCode;
479: }
480:
481: /**
482: * Utility method that returns profiling/debugging info for the current
483: * request.
484: *
485: * @param request The current HTTP request
486: * @param startTime The time when the processing has begun.
487: */
488: protected static String getProfilingMessage(
489: HttpServletRequest request, long startTime) {
490: final long processingTime = System.currentTimeMillis()
491: - startTime;
492: StringBuffer msg = new StringBuffer(request.getMethod());
493: msg.append(' ');
494: msg.append(request.getRequestURI());
495: String qs = request.getQueryString();
496: if (qs != null) {
497: msg.append('?');
498: msg.append(qs);
499: }
500: msg.append(" processed in ");
501: msg.append(processingTime);
502: msg.append("ms");
503: return msg.toString();
504: }
505:
506: }
|