001: package org.gomba;
002:
003: import java.io.CharArrayWriter;
004: import java.io.IOException;
005: import java.io.PrintWriter;
006: import java.io.Writer;
007: import java.util.ArrayList;
008: import java.util.Iterator;
009: import java.util.List;
010: import java.util.StringTokenizer;
011:
012: import javax.servlet.RequestDispatcher;
013: import javax.servlet.ServletConfig;
014: import javax.servlet.ServletContext;
015: import javax.servlet.ServletException;
016: import javax.servlet.ServletOutputStream;
017: import javax.servlet.http.HttpServletRequest;
018: import javax.servlet.http.HttpServletRequestWrapper;
019: import javax.servlet.http.HttpServletResponse;
020: import javax.servlet.http.HttpServletResponseWrapper;
021:
022: import org.gomba.utils.servlet.ServletParameterUtils;
023:
024: /**
025: * This servlets dispatches the same request to mutiple servlets overriding the
026: * original HTTP method and capturing the output of each servlet into the
027: * request scope. The output of the last operation is sent directly the client.
028: *
029: * <p>
030: * This servlet inherits the init-params of
031: * {@link org.gomba.TransactorAbstractServlet}
032: * </p>
033: *
034: * <p>
035: * Init-params:
036: * <dl>
037: * <dt>http-method</dt>
038: * <dd>The value can be GET, POST, PUT or DELETE. (Required)</dd>
039: * <dt>operations</dt>
040: * <dd>A list of operations to execute, one per line. Syntax:
041: * <code>HTTPMethod ResourceNameOrPath [RequestAttributeName]</code>.
042: * Example: <code>GET myServlet</code>. (Required)</dd>
043: * </dl>
044: * </p>
045: *
046: * @author Flavio Tordini
047: * @version $Id: MultiServlet.java,v 1.1 2004/11/26 17:52:58 flaviotordini Exp $
048: */
049: public class MultiServlet extends TransactorAbstractServlet {
050:
051: /**
052: * <code>true</code> if this servlet supports the GET HTTP method.
053: */
054: private boolean supportGet;
055:
056: /**
057: * <code>true</code> if this servlet supports the POST HTTP method.
058: */
059: private boolean supportPost;
060:
061: /**
062: * <code>true</code> if this servlet supports the PUT HTTP method.
063: */
064: private boolean supportPut;
065:
066: /**
067: * <code>true</code> if this servlet supports the DELETE HTTP method.
068: */
069: private boolean supportDelete;
070:
071: /**
072: * List of MultiServlet.Operation.
073: */
074: private List operations;
075:
076: /**
077: * @see javax.servlet.Servlet#init(javax.servlet.ServletConfig)
078: */
079: public void init(ServletConfig config) throws ServletException {
080: super .init(config);
081:
082: // supported HTTP method
083: String httpMethod = config.getInitParameter("http-method");
084: if (httpMethod == null) {
085: throw new ServletException(
086: "Missing init-param: http-method");
087: }
088: if (httpMethod.equals("GET")) {
089: this .supportGet = true;
090: } else if (httpMethod.equals("POST")) {
091: this .supportPost = true;
092: } else if (httpMethod.equals("PUT")) {
093: this .supportPut = true;
094: } else if (httpMethod.equals("DELETE")) {
095: this .supportDelete = true;
096: } else {
097: throw new ServletException("Unsupported HTTP method: "
098: + httpMethod);
099: }
100:
101: // parse operation list
102: String operationStr = config.getInitParameter("operations");
103: if (operationStr == null) {
104: throw new ServletException("Missing init-param: operations");
105: }
106: this .operations = parseOperations(operationStr);
107:
108: }
109:
110: /**
111: * Parse an operation line.
112: */
113: private static Operation parseOperationLine(String line)
114: throws ServletException {
115: StringTokenizer tokenizer = new StringTokenizer(line, " ");
116: String[] tokens = new String[3];
117: int i = 0;
118: for (; i < tokens.length && tokenizer.hasMoreTokens(); i++) {
119: String token = tokenizer.nextToken();
120: tokens[i] = token;
121: }
122: if (i < 1) {
123: throw new ServletException(
124: "Not enough tokens on this line: " + line);
125: }
126: if (tokenizer.hasMoreTokens()) {
127: throw new ServletException("Too many tokens on this line: "
128: + line);
129: }
130:
131: Operation operation = new Operation(tokens[0], tokens[1],
132: tokens[2]);
133:
134: return operation;
135: }
136:
137: /**
138: * Parse operations config: HTTPMethod nameOrPath [varName]
139: */
140: private static List parseOperations(String operations)
141: throws ServletException {
142:
143: List operationList = new ArrayList();
144: int currentPos = 0;
145:
146: do {
147:
148: int nextLineFeed = operations.indexOf('\n', currentPos + 1);
149: String line;
150: if (nextLineFeed != -1) {
151: line = operations.substring(currentPos, nextLineFeed);
152: } else {
153: line = operations.substring(currentPos);
154: }
155:
156: Operation operation = parseOperationLine(line.trim());
157: operationList.add(operation);
158:
159: currentPos = nextLineFeed;
160:
161: } while (currentPos != -1);
162:
163: return operationList;
164: }
165:
166: /**
167: * @see javax.servlet.http.HttpServlet#doGet(javax.servlet.http.HttpServletRequest,
168: * javax.servlet.http.HttpServletResponse)
169: */
170: protected void doGet(HttpServletRequest request,
171: HttpServletResponse response) throws ServletException,
172: IOException {
173: if (this .supportGet) {
174: processRequest(request, response);
175: } else {
176: response
177: .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
178: }
179: }
180:
181: /**
182: * @see javax.servlet.http.HttpServlet#doDelete(javax.servlet.http.HttpServletRequest,
183: * javax.servlet.http.HttpServletResponse)
184: */
185: protected void doDelete(HttpServletRequest request,
186: HttpServletResponse response) throws ServletException,
187: IOException {
188: if (this .supportDelete) {
189: processRequest(request, response);
190: } else {
191: response
192: .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
193: }
194: }
195:
196: /**
197: * @see javax.servlet.http.HttpServlet#doPost(javax.servlet.http.HttpServletRequest,
198: * javax.servlet.http.HttpServletResponse)
199: */
200: protected void doPost(HttpServletRequest request,
201: HttpServletResponse response) throws ServletException,
202: IOException {
203: if (this .supportPost) {
204: processRequest(request, response);
205: } else {
206: response
207: .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
208: }
209: }
210:
211: /**
212: * @see javax.servlet.http.HttpServlet#doPut(javax.servlet.http.HttpServletRequest,
213: * javax.servlet.http.HttpServletResponse)
214: */
215: protected void doPut(HttpServletRequest request,
216: HttpServletResponse response) throws ServletException,
217: IOException {
218: if (this .supportPut) {
219: processRequest(request, response);
220: } else {
221: response
222: .sendError(HttpServletResponse.SC_METHOD_NOT_ALLOWED);
223: }
224: }
225:
226: /**
227: * Do the real work.
228: */
229: private void processRequest(HttpServletRequest request,
230: HttpServletResponse response) throws ServletException {
231:
232: final ServletContext context = getServletContext();
233:
234: // get a TransactionServlet instance
235: TransactionServlet transactionServlet = (TransactionServlet) getServletContext()
236: .getAttribute(
237: TransactionServlet.CONTEXT_ATTRIBUTE_NAME_SERVLET);
238: if (transactionServlet == null) {
239: throw new ServletException("Missing TransactionServlet.");
240: }
241:
242: // create the parameter resolver that will help us throughout this
243: // request
244: final ParameterResolver parameterResolver = new ParameterResolver(
245: request);
246:
247: // this flag is true if we create our own transaction, instead of
248: // using a transaction specified by the client
249: boolean implicitTransaction = false;
250:
251: Transaction transaction = null;
252: try {
253:
254: // start the transaction
255: transaction = getTransaction(parameterResolver);
256: if (transaction == null) {
257: transaction = transactionServlet.createTransaction();
258: implicitTransaction = true;
259: }
260:
261: // put the transaction in the request scope so AbstractServlet knows
262: // that requests are part of the transaction.
263: request.setAttribute(REQUEST_ATTRIBUTE_NAME_TRANSACTION,
264: transaction);
265:
266: // loop through operations
267: for (Iterator i = this .operations.iterator(); i.hasNext();) {
268: Operation operation = (Operation) i.next();
269:
270: // get the dispatcher
271: RequestDispatcher dispatcher = ServletParameterUtils
272: .getRequestDispatcher(context, operation
273: .getResourceNameOrPath());
274:
275: // wrap our request in order to override the HTTP method
276: RequestWrapper requestWrapper = new RequestWrapper(
277: request, operation.getHttpMethod());
278:
279: // if this is the last operation just forward the request
280: if (!i.hasNext()) {
281: dispatcher.forward(requestWrapper, response);
282: break;
283: }
284:
285: // wrap our response in order to capture its body
286: ResponseWrapper responseWrapper = new ResponseWrapper(
287: response);
288:
289: // otherwise process this operation
290: dispatcher.include(requestWrapper, responseWrapper);
291:
292: // check for successful response
293: int responseStatus = responseWrapper.getStatus();
294: if (responseStatus < 200 || responseStatus > 299) {
295: throw new ServletException("Operation "
296: + operation.getResourceNameOrPath()
297: + " returned HTTP status " + responseStatus);
298: }
299:
300: // put the result in request scope
301: String opName = operation.getName();
302: if (opName != null && opName.length() > 0) {
303: String responseBody = responseWrapper.getBody();
304: request.setAttribute(opName, responseBody);
305: }
306:
307: }
308:
309: // commit the transaction
310: if (implicitTransaction) {
311: transaction.commit();
312: }
313:
314: } catch (Exception e) {
315:
316: // rollback the transaction
317: // if the transaction was started by the client, it is the client
318: // responsibility to rollback it.
319: if (transaction != null && implicitTransaction) {
320: try {
321: transaction.rollback();
322: } catch (Exception e2) {
323: log("Error rolling back transaction.", e2);
324: }
325: }
326: throw new ServletException("Error processing request.", e);
327:
328: }
329:
330: }
331:
332: private static class Operation {
333:
334: private final String httpMethod;
335:
336: private final String resourceNameOrPath;
337:
338: private final String name;
339:
340: /**
341: * Constructor.
342: *
343: * @param httpMethod
344: * The HTTP method to use.
345: * @param resourceNameOrPath
346: * a resource to dispatch the request to. May be the resource
347: * name or the resource path.
348: * @param name
349: * The optional request attribute name to hold the response
350: * body.
351: */
352: private Operation(final String httpMethod,
353: final String resourceNameOrPath, final String name) {
354: this .httpMethod = httpMethod;
355: this .resourceNameOrPath = resourceNameOrPath;
356: this .name = name;
357: }
358:
359: /**
360: * @return Returns the httpMethod.
361: */
362: public String getHttpMethod() {
363: return this .httpMethod;
364: }
365:
366: /**
367: * @return Returns the name.
368: */
369: public String getName() {
370: return this .name;
371: }
372:
373: /**
374: * @return Returns the resourceNameOrPath.
375: */
376: public String getResourceNameOrPath() {
377: return this .resourceNameOrPath;
378: }
379: }
380:
381: /**
382: * Wraps a HttpServletRequest and override the HTTP method.
383: */
384: private static class RequestWrapper extends
385: HttpServletRequestWrapper {
386:
387: // TODO add support for in-memory request body
388: // in order to let multiple servlets use the request body.
389:
390: private final String httpMethod;
391:
392: private RequestWrapper(HttpServletRequest request,
393: String httpMethod) {
394: super (request);
395: this .httpMethod = httpMethod;
396: }
397:
398: /**
399: * @see javax.servlet.http.HttpServletRequest#getMethod()
400: */
401: public String getMethod() {
402: // System.out.println("getMethod() " + this.httpMethod);
403: return this .httpMethod;
404: }
405:
406: }
407:
408: /**
409: * Wraps a HttpServletResponse in order to capture its response body.
410: */
411: private static class ResponseWrapper extends
412: HttpServletResponseWrapper {
413:
414: private Writer writer;
415:
416: private int status = SC_OK;
417:
418: /**
419: * Constructor.
420: */
421: private ResponseWrapper(HttpServletResponse response) {
422: super (response);
423:
424: }
425:
426: /**
427: * @see javax.servlet.ServletResponse#getOutputStream()
428: */
429: public ServletOutputStream getOutputStream() throws IOException {
430: // TODO implement
431: throw new UnsupportedOperationException(
432: "This implementation does not yet support getOutputStream().");
433: }
434:
435: /**
436: * @see javax.servlet.ServletResponse#getWriter()
437: */
438: public PrintWriter getWriter() throws IOException {
439: if (this .writer != null) {
440: throw new IOException(
441: "PrintWriter has already been obtained.");
442: }
443: // we could also use a StringWriter...
444: // what's the difference?
445: this .writer = new CharArrayWriter();
446: return new PrintWriter(this .writer);
447: }
448:
449: /**
450: * @return The body of this response.
451: */
452: private String getBody() {
453: return this .writer.toString();
454: }
455:
456: /**
457: * @see javax.servlet.http.HttpServletResponseWrapper#setStatus(int)
458: */
459: public void setStatus(int status) {
460: super .setStatus(status);
461: this .status = status;
462: }
463:
464: /**
465: * @see javax.servlet.http.HttpServletResponse#setStatus(int,
466: * java.lang.String)
467: */
468: public void setStatus(int status, String message) {
469: super .setStatus(status, message);
470: this .status = status;
471: }
472:
473: /**
474: * @see javax.servlet.http.HttpServletResponse#sendError(int,
475: * java.lang.String)
476: */
477: public void sendError(int status, String message)
478: throws IOException {
479: super .sendError(status, message);
480: this .status = status;
481: }
482:
483: /**
484: * @see javax.servlet.http.HttpServletResponse#sendError(int)
485: */
486: public void sendError(int status) throws IOException {
487: super .sendError(status);
488: this .status = status;
489: }
490:
491: /**
492: * @return Returns the status.
493: */
494: protected int getStatus() {
495: return this.status;
496: }
497: }
498:
499: }
|