001: package org.gomba;
002:
003: import java.io.IOException;
004: import java.sql.Connection;
005: import java.sql.ResultSet;
006: import java.sql.SQLException;
007: import java.util.ArrayList;
008: import java.util.Collections;
009: import java.util.Iterator;
010: import java.util.List;
011: import java.util.StringTokenizer;
012:
013: import javax.servlet.ServletConfig;
014: import javax.servlet.ServletException;
015: import javax.servlet.http.HttpServletRequest;
016: import javax.servlet.http.HttpServletResponse;
017:
018: /**
019: * Perform a write operation on a JDBC data store. the SQL in the
020: * <code>query</code> init-param can be an INSERT, UPDATE or DELETE statement.
021: * This servlet inherits the init-params of {@link org.gomba.AbstractServlet},
022: * plus:
023: * <dl>
024: * <dt>query</dt>
025: * <dd>The SQL update to execute. May contain ${} parameters. This init-param
026: * also accepts a path to a dynamic resource (a JSP) when dynamic SQL generation
027: * is needed. The path must begin with a "/" and is interpreted as relative to
028: * the current context root. (Required)</dd>
029: * <dt>batch</dt>
030: * <dd>true if the SQL contains multiple statements separated by semicolons.
031: * Muliple updates are performed as part of single transaction. (Optional)</dd>
032: * <dt>http-method</dt>
033: * <dd>The value can be POST, PUT or DELETE. (Required)</dd>
034: * </dl>
035: *
036: * Note about HTTP method usage. The POST method is normally used for creation
037: * (INSERT in SQL) operations. The PUT method is normally used for update
038: * (UPDATE in SQL) operations. The DELETE method is obviously used for deletion
039: * (DELETE in SQL) operations.
040: *
041: * @author Flavio Tordini
042: * @version $Id: UpdateServlet.java,v 1.12 2005/10/19 13:00:21 flaviotordini Exp $
043: */
044: public class UpdateServlet extends AbstractServlet {
045:
046: private final static String INIT_PARAM_HTTP_METHOD = "http-method";
047:
048: private final static String INIT_PARAM_QUERY = "query";
049:
050: private final static String INIT_PARAM_BATCH = "batch";
051:
052: /**
053: * <code>true</code> if this servlet supports the POST HTTP method.
054: */
055: private boolean supportPost;
056:
057: /**
058: * <code>true</code> if this servlet supports the PUT HTTP method.
059: */
060: private boolean supportPut;
061:
062: /**
063: * <code>true</code> if this servlet supports the DELETE HTTP method.
064: */
065: private boolean supportDelete;
066:
067: /**
068: * The parsed query definitions. It is null when the query is dynamic, i.e.
069: * a dynamic resource (a JSP) is used to generate the SQL. List of
070: * QueryDefinition.
071: */
072: private List queryDefinitions;
073:
074: /**
075: * The path of a resource that dynamically generates a SQL query.
076: */
077: private String queryResource;
078:
079: private boolean batch;
080:
081: /**
082: * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
083: */
084: public void init(ServletConfig config) throws ServletException {
085: super .init(config);
086:
087: // supported HTTP method
088: String httpMethod = config
089: .getInitParameter(INIT_PARAM_HTTP_METHOD);
090: if (httpMethod == null) {
091: throw new ServletException("Missing init-param: "
092: + INIT_PARAM_HTTP_METHOD);
093: }
094: if (httpMethod.equals("POST")) {
095: this .supportPost = true;
096: } else if (httpMethod.equals("PUT")) {
097: this .supportPut = true;
098: } else if (httpMethod.equals("DELETE")) {
099: this .supportDelete = true;
100: } else {
101: throw new ServletException("Unsupported HTTP method: "
102: + httpMethod);
103: }
104:
105: // is batch?
106: String batchStr = config.getInitParameter(INIT_PARAM_BATCH);
107: this .batch = Boolean.valueOf(batchStr).booleanValue();
108:
109: // parse the query definition(s)
110: try {
111: String query = config.getInitParameter(INIT_PARAM_QUERY);
112: if (!query.startsWith("/")) {
113: this .queryDefinitions = parseQueryDefinitions(query);
114: } else {
115: this .queryResource = query;
116: }
117: } catch (Exception e) {
118: throw new ServletException(
119: "Error parsing query definition(s).", e);
120: }
121:
122: }
123:
124: private List parseBatchQueryDefinitions(String query)
125: throws Exception {
126: StringTokenizer st = new StringTokenizer(query, ";");
127: List queryDefinitions = new ArrayList(st.countTokens());
128: while (st.hasMoreTokens()) {
129: String token = st.nextToken().trim();
130: if (token.length() == 0) {
131: continue;
132: }
133: QueryDefinition queryDefinition = new QueryDefinition(token);
134: queryDefinitions.add(queryDefinition);
135: }
136: return queryDefinitions;
137: }
138:
139: private List parseQueryDefinitions(String query) throws Exception {
140: List queryDefinitions;
141: if (this .batch) {
142: queryDefinitions = parseBatchQueryDefinitions(query);
143: } else {
144: queryDefinitions = Collections
145: .singletonList(new QueryDefinition(query));
146: }
147: return queryDefinitions;
148: }
149:
150: /**
151: * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest,
152: * javax.servlet.http.HttpServletResponse)
153: */
154: protected void doDelete(HttpServletRequest request,
155: HttpServletResponse response) throws ServletException,
156: IOException {
157: if (this .supportDelete) {
158: processRequest(request, response);
159: } else {
160: response
161: .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
162: }
163: }
164:
165: /**
166: * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
167: * javax.servlet.http.HttpServletResponse)
168: */
169: protected void doPost(HttpServletRequest request,
170: HttpServletResponse response) throws ServletException,
171: IOException {
172: if (this .supportPost) {
173: processRequest(request, response);
174: } else {
175: response
176: .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
177: }
178: }
179:
180: /**
181: * @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest,
182: * javax.servlet.http.HttpServletResponse)
183: */
184: protected void doPut(HttpServletRequest request,
185: HttpServletResponse response) throws ServletException,
186: IOException {
187: if (this .supportPut) {
188: processRequest(request, response);
189: } else {
190: response
191: .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
192: }
193: }
194:
195: /**
196: * Get the QueryDefinition, it can be a fixed QueryDefinition created at
197: * init-time. Or a dynamic one created by evaluating a JSP.
198: */
199: private List getQueryDefinitions(HttpServletRequest request,
200: HttpServletResponse response) throws ServletException,
201: IOException {
202: List requestQueryDefinitions;
203: if (this .queryDefinitions == null) {
204: // dynamic query
205: String sql = getDynamicQuery(this .queryResource, request,
206: response);
207: try {
208: requestQueryDefinitions = parseQueryDefinitions(sql);
209: } catch (Exception e) {
210: throw new ServletException(
211: "Error parsing query definition(s).", e);
212: }
213: } else {
214: // fixed query
215: requestQueryDefinitions = this .queryDefinitions;
216: }
217: return requestQueryDefinitions;
218: }
219:
220: /**
221: * The real work is done here.
222: *
223: * @param request
224: * The HTTP request
225: * @param response
226: * The HTTP response
227: */
228: protected final void processRequest(HttpServletRequest request,
229: HttpServletResponse response) throws ServletException,
230: IOException {
231:
232: // get current time for benchmarking purposes
233: final long startTime = System.currentTimeMillis();
234:
235: // create the parameter resolver that will help us throughout this
236: // request
237: final ParameterResolver parameterResolver = new ParameterResolver(
238: request);
239:
240: // get the query definition(s)
241: List requestQueryDefinitions = getQueryDefinitions(request,
242: response);
243: if (requestQueryDefinitions.isEmpty()) {
244: throw new ServletException("Missing query definitions.");
245: }
246:
247: // find out if this request is part of a transaction
248: Transaction transaction = getTransaction(parameterResolver);
249:
250: Connection connection = null;
251: Query.QueryResult queryResult = null;
252:
253: // surround everything in this try/finally to be able to free JDBC
254: // resources even in case of exceptions
255: try {
256:
257: // get a JDBC connection
258: try {
259: if (transaction == null) {
260: connection = getDataSource().getConnection();
261: if (this .batch) {
262: connection.setAutoCommit(false);
263: }
264: } else {
265: if (isDebugMode()) {
266: log("Request is part of transaction: "
267: + transaction.getUri());
268: }
269: connection = transaction.getConnection();
270: }
271: } catch (SQLException e) {
272: throw new ServletException(
273: "Error getting JDBC connection.", e);
274: }
275:
276: // loop through query definitions
277: for (Iterator qdIterator = requestQueryDefinitions
278: .iterator(); qdIterator.hasNext();) {
279:
280: QueryDefinition requestQueryDefinition = (QueryDefinition) qdIterator
281: .next();
282:
283: // build the Query
284: final Query query = getQuery(requestQueryDefinition,
285: parameterResolver);
286:
287: // execute the query
288: Query.QueryResult newQueryResult = query
289: .execute(connection);
290: if (newQueryResult != null) {
291:
292: // now that we've (maybe) used the data from the previous query,
293: // let's free the ResultSet
294: if (queryResult != null) {
295: try {
296: queryResult.close();
297: } catch (Exception e) {
298: throw new ServletException(
299: "Error freeing JDBC resources.", e);
300: }
301: }
302:
303: queryResult = newQueryResult;
304: ResultSet resultSet = queryResult.getResultSet();
305: maybeMoveCursor(resultSet);
306: parameterResolver.setResultSet(resultSet);
307: }
308:
309: }
310:
311: // set the response headers
312: setResponseHeaders(response, parameterResolver);
313:
314: // set the HTTP status code
315: int httpStatus = getHttpStatusCode();
316: if (httpStatus != HttpServletResponse.SC_OK) {
317: response.setStatus(httpStatus);
318: }
319:
320: if (transaction == null && this .batch) {
321: connection.commit();
322: }
323:
324: } catch (Exception e) {
325: if (transaction == null && connection != null && this .batch) {
326: try {
327: connection.rollback();
328: } catch (Exception e2) {
329: log("Error rolling back JDBC connection.", e2);
330: }
331: }
332: if (e instanceof ServletException) {
333: throw (ServletException) e;
334: }
335: if (e instanceof IOException) {
336: throw (IOException) e;
337: }
338: throw new ServletException("Error processing request.", e);
339: } finally {
340:
341: // *always* free the JDBC resources!
342: if (queryResult != null) {
343: try {
344: queryResult.close();
345: } catch (Exception e) {
346: log("Error freeing JDBC resources.", e);
347: }
348: }
349:
350: // close the JDBC connection if this request is not part of a
351: // transaction
352: if (transaction == null && connection != null) {
353: try {
354: if (this .batch) {
355: connection.setAutoCommit(true);
356: }
357: connection.close();
358: } catch (Exception e) {
359: throw new ServletException(
360: "Error closing JDBC connection.", e);
361: }
362: }
363:
364: // processing time
365: if (isDebugMode()) {
366: log(getProfilingMessage(request, startTime));
367: }
368:
369: }
370: }
371: }
|