001: /*
002: License $Id: JoServletHandler.java,v 1.14 2005/05/26 16:36:04 hendriks73 Exp $
003:
004: Copyright (c) 2001-2005 tagtraum industries.
005:
006: LGPL
007: ====
008:
009: jo! is free software; you can redistribute it and/or
010: modify it under the terms of the GNU Lesser General Public
011: License as published by the Free Software Foundation; either
012: version 2.1 of the License, or (at your option) any later version.
013:
014: jo! is distributed in the hope that it will be useful,
015: but WITHOUT ANY WARRANTY; without even the implied warranty of
016: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
017: Lesser General Public License for more details.
018:
019: You should have received a copy of the GNU Lesser General Public
020: License along with this library; if not, write to the Free Software
021: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
022:
023: For LGPL see <http://www.fsf.org/copyleft/lesser.txt>
024:
025:
026: Sun license
027: ===========
028:
029: This release contains software by Sun Microsystems. Therefore
030: the following conditions have to be met, too. They apply to the
031: files
032:
033: - lib/mail.jar
034: - lib/activation.jar
035: - lib/jsse.jar
036: - lib/jcert.jar
037: - lib/jaxp.jar
038: - lib/crimson.jar
039: - lib/servlet.jar
040: - lib/jnet.jar
041: - lib/jaas.jar
042: - lib/jaasmod.jar
043:
044: contained in this release.
045:
046: a. Licensee may not modify the Java Platform
047: Interface (JPI, identified as classes contained within the javax
048: package or any subpackages of the javax package), by creating additional
049: classes within the JPI or otherwise causing the addition to or modification
050: of the classes in the JPI. In the event that Licensee creates any
051: Java-related API and distribute such API to others for applet or
052: application development, you must promptly publish broadly, an accurate
053: specification for such API for free use by all developers of Java-based
054: software.
055:
056: b. Software is confidential copyrighted information of Sun and
057: title to all copies is retained by Sun and/or its licensors. Licensee
058: shall not modify, decompile, disassemble, decrypt, extract, or otherwise
059: reverse engineer Software. Software may not be leased, assigned, or
060: sublicensed, in whole or in part. Software is not designed or intended
061: for use in on-line control of aircraft, air traffic, aircraft navigation
062: or aircraft communications; or in the design, construction, operation or
063: maintenance of any nuclear facility. Licensee warrants that it will not
064: use or redistribute the Software for such purposes.
065:
066: c. Software is provided "AS IS," without a warranty
067: of any kind. ALL EXPRESS OR IMPLIED REPRESENTATIONS AND WARRANTIES,
068: INCLUDING ANY IMPLIED WARRANTY OF MERCHANTABILITY, FITNESS FOR A
069: PARTICULAR PURPOSE OR NON-INFRINGEMENT, ARE HEREBY EXCLUDED.
070:
071: d. This License is effective until terminated. Licensee may
072: terminate this License at any time by destroying all copies of Software.
073: This License will terminate immediately without notice from Sun if Licensee
074: fails to comply with any provision of this License. Upon such termination,
075: Licensee must destroy all copies of Software.
076:
077: e. Software, including technical data, is subject to U.S.
078: export control laws, including the U.S. Export Administration Act and its
079: associated regulations, and may be subject to export or import regulations
080: in other countries. Licensee agrees to comply strictly with all such
081: regulations and acknowledges that it has the responsibility to obtain
082: licenses to export, re-export, or import Software. Software may not be
083: downloaded, or otherwise exported or re-exported (i) into, or to a national
084: or resident of, Cuba, Iraq, Iran, North Korea, Libya, Sudan, Syria or any
085: country to which the U.S. has embargoed goods; or (ii) to anyone on the
086: U.S. Treasury Department's list of Specially Designated Nations or the U.S.
087: Commerce Department's Table of Denial Orders.
088:
089:
090: Feedback
091: ========
092:
093: We encourage your feedback and suggestions and want to use your feedback to
094: improve the Software. Send all such feedback to:
095: <feedback@tagtraum.com>
096:
097: For more information on tagtraum industries and jo!
098: please see <http://www.tagtraum.com/>.
099:
100:
101: */
102: package com.tagtraum.jo;
103:
104: import com.tagtraum.framework.http.C_Http;
105: import com.tagtraum.framework.http.HttpException;
106: import com.tagtraum.framework.http.StatusCodes;
107: import com.tagtraum.framework.log.C_Log;
108: import com.tagtraum.framework.log.Log;
109: import com.tagtraum.framework.log.LogEvent;
110: import com.tagtraum.framework.recycler.Recycler;
111: import com.tagtraum.framework.server.HandlerException;
112: import com.tagtraum.framework.server.I_Service;
113: import com.tagtraum.framework.server.TCPHandler;
114: import com.tagtraum.framework.util.FactoryException;
115:
116: import javax.servlet.UnavailableException;
117: import javax.servlet.http.HttpServletResponse;
118: import java.io.EOFException;
119: import java.io.IOException;
120: import java.io.InterruptedIOException;
121: import java.lang.reflect.Constructor;
122: import java.net.SocketException;
123: import java.util.List;
124: import java.util.ResourceBundle;
125:
126: /**
127: * Initializes {@link I_JoServletRequest} and {@link I_JoServletResponse}
128: * and executes the matching servlet.
129: *
130: * @author <a href="mailto:hs@tagtraum.com">Hendrik Schreiber</a>
131: * @version 1.1beta1 $Id: JoServletHandler.java,v 1.14 2005/05/26 16:36:04 hendriks73 Exp $
132: */
133: public class JoServletHandler extends TCPHandler implements C_Jo,
134: C_Http {
135:
136: /**
137: * Source-Version
138: */
139: public static String vcid = "$Id: JoServletHandler.java,v 1.14 2005/05/26 16:36:04 hendriks73 Exp $";
140: private static ResourceBundle localStrings = ResourceBundle
141: .getBundle("com.tagtraum.jo.localStrings");
142:
143: /**
144: * Current Log. This may be either the service's log or the host's log, depending
145: * on whether the host is known or not.
146: */
147: private Log log;
148:
149: /**
150: * Request.
151: */
152: private I_JoServletRequest request;
153:
154: /**
155: * Response.
156: */
157: private I_JoServletResponse response;
158:
159: /**
160: * Requests No.
161: */
162: private int numberOfRequests;
163:
164: /**
165: * Log arguments.
166: */
167: private Object[] logArgs = new Object[3];
168:
169: /**
170: * Log constructor.
171: */
172: private Constructor logEventConstructor;
173:
174: private RequestInterceptor[] requestInterceptors;
175:
176: private Recycler recycler;
177:
178: /**
179: * ThreadGroup-constructor.
180: */
181: public JoServletHandler(ThreadGroup tg) {
182: super (tg);
183: }
184:
185: /**
186: * Initializes the handler. After calling this method the handler
187: * must be in a state that is exactly the same as if it had been
188: * freshly instantiated. The only difference is the set service.
189: *
190: * @param aService Service, this handler belongs to.
191: * @exception HandlerException if initialization fails.
192: */
193: public void init(I_Service aService) throws HandlerException {
194: super .init(aService);
195: log = Log.getLog(getService().getName());
196: List riList = ((I_JoServletService) aService)
197: .getRequestInterceptorList();
198: requestInterceptors = (RequestInterceptor[]) riList
199: .toArray(new RequestInterceptor[riList.size()]);
200: recycler = Recycler.getNamedRecycler(getService().getName());
201: createRequestObject();
202: createResponseObject();
203: }
204:
205: private Constructor getAccessLogEventConstructor()
206: throws FactoryException {
207: if (logEventConstructor == null) {
208: try {
209: String logEventClassname = recycler
210: .resolveAlias(C_FactoryKey_AccessLogEvent);
211: // hack for equality (rik)
212: if (logEventClassname == C_FactoryKey_AccessLogEvent) {
213: logEventClassname = C_Default_AccessLogEvent;
214: }
215: Class logEventClass = Class.forName(logEventClassname);
216: Class[] types = new Class[] { I_JoServletRequest.class,
217: I_JoServletResponse.class, Object.class };
218: logEventConstructor = logEventClass
219: .getConstructor(types);
220: logArgs[2] = this ;
221: } catch (Exception e) {
222: throw new FactoryException(e);
223: }
224: }
225: return logEventConstructor;
226: }
227:
228: private void createResponseObject() throws HandlerException {
229: if (response == null) {
230: try {
231: response = (I_JoServletResponse) recycler
232: .get(C_FactoryKey_ServletResponse);
233: } catch (FactoryException fe) {
234: throw new HandlerException(localStrings
235: .getString("response_instantiation_failed"), fe);
236: }
237: }
238: }
239:
240: private void createRequestObject() throws HandlerException {
241: if (request == null) {
242: try {
243: request = (I_JoServletRequest) recycler
244: .get(C_FactoryKey_ServletRequest);
245: } catch (FactoryException fe) {
246: throw new HandlerException(localStrings
247: .getString("request_instantiation_failed"), fe);
248: }
249: }
250: }
251:
252: /**
253: * Executes a series of requests on a single connection.
254: */
255: public void service() {
256: try {
257: numberOfRequests = 0;
258: // disable Nagle algo for better performance in short connections
259: getSocket().setTcpNoDelay(true);
260: request.setSocket(getSocket());
261: response.setSocket(getSocket());
262: while (isSocketOpen()
263: && !stopped
264: && ((I_JoServletService) getService())
265: .getMaxRequests() > numberOfRequests) {
266: numberOfRequests++;
267: if (((I_JoServletService) service).getMaxRequests() <= numberOfRequests) {
268: // make suret that we indicate on the HTTP level that the connection
269: // will be closed
270: response.setForceClose(true);
271: }
272: if (readRequest()) {
273: if (executeInterceptors())
274: doRequest();
275: } else {
276: closeSocket();
277: }
278: }
279: } catch (IOException ioe) {
280: if (log.isLog())
281: log.log(ioe);
282: }
283: }
284:
285: /**
286: * Read the request and return true, if we read it successfully.
287: */
288: private boolean readRequest() {
289: log = Log.getLog(getService().getName());
290: try {
291: // initialize
292: response.init();
293: request.init();
294: // point request and response to each other
295: request.setResponse(response);
296: response.setRequest(request);
297: getSocket().setSoTimeout(
298: 1000 * ((I_JoServletService) getService())
299: .getKeepAlive());
300: request.readRequest();
301: // throws InterruptedIOException after 3 min of being idle
302: getSocket().setSoTimeout(1000 * 60 * 3);
303: } catch (InterruptedIOException ioe) {
304: if (Log.isLog(C_Log.METHOD, log.getName()))
305: log.log("Timeout: " + getName());
306: return false;
307: } catch (EOFException eof) {
308: // connection closed by peer
309: return false;
310: } catch (SocketException so) {
311: // something with the connection went wrong
312: return false;
313: } catch (HttpException he) {
314: sendHttpError(null, he);
315: logAccess();
316: return false;
317: } catch (IOException ioe) {
318: if (Log.isLog(C_Log.ERROR, log.getName())) {
319: log.log(ioe);
320: }
321: return false;
322: }
323: return true;
324: }
325:
326: /**
327: * Executes the Request.
328: */
329: public void doRequest() {
330: I_JoHost host = null;
331: JoFilterChain filterChain = null;
332: try {
333: // find Host
334: host = ((I_JoServletService) getService()).getHost(request);
335: if (host != null) {
336: // now that we know the host, we can log to the host
337: log = host.getLog();
338: } else {
339: sendError(null,
340: HttpServletResponse.SC_INTERNAL_SERVER_ERROR,
341: localStrings.getString("host_not_found"));
342: return;
343: }
344:
345: // find ContextPeer
346: String requestURI = request.getRequestURI();
347: String decodedRequestURI = requestURI;
348: try {
349: decodedRequestURI = com.tagtraum.framework.util.URLDecoder
350: .decode(requestURI);
351: } catch (Exception ignore) {
352: if (log.isLog(Log.FORTYTWO))
353: log.log(ignore, Log.VERBOSE);
354: }
355: // get peer
356: I_JoServletContextPeer peer = getServletContextPeer(host,
357: decodedRequestURI);
358: // Register the peer's classloader as ThreadContext classloader. If jo!
359: // runs inside an appserver (JBoss) that uses the threadcontext classloader as
360: // ID in JNDI, this can be useful.
361: // TODO: Do we still need this?
362: setContextClassLoader(peer.getClassLoader());
363: request.setServletContextPeer(peer);
364: response.setServletContextPeer(peer);
365: //I_JoServletModel model = peer.getServletModel(request, decodedRequestURI);
366: filterChain = peer.getFilterChain(request,
367: decodedRequestURI);
368: // authentification/authorization
369: peer.authentificate(request, response);
370: // check if an unauthorized or something else was sent
371: if (response.getStatus() == 200) {
372: // still the same ServletModel? - ==, because that's faster and does the same job
373: if (requestURI != request.getRequestURI()) {
374: //model = peer.getServletModel(request);
375: filterChain = peer.getFilterChain(request, request
376: .getRequestURI());
377: }
378: // errormessage, if no model was found
379: //if (model == null) {
380: if (filterChain == null) {
381: sendError(host, I_JoServletResponse.SC_NOT_FOUND,
382: localStrings
383: .getString("resource_not_found"));
384: } else {
385: // execute the model
386: //model.service(request, response);
387: filterChain.execute(request, response);
388: }
389: }
390: } catch (UnavailableException ue) {
391: sendUnavailableError(host, ue);
392: } catch (HttpException he) {
393: sendHttpError(host, he);
394: } catch (SocketException se) {
395: // something with the connection went wrong, therefore we will close it.
396: // as we don't have any socket, we cannot send an error to the client
397: closeSocket();
398: } catch (InterruptedIOException ioe) {
399: if (log.isLog(C_Log.METHOD))
400: log.log("Timeout: " + getName());
401: closeSocket();
402: // the connection timed out, therefore we don't need to deal with
403: // the access log or flush out unwritten headers
404: return;
405: } catch (Exception e) {
406: // some unknown thing went wrong
407: String servletName = null;
408: if (filterChain != null
409: && filterChain.getJoServletModel() != null)
410: servletName = filterChain.getJoServletModel().getName();
411: sendExceptionError(e, servletName);
412: } finally {
413: // cleaning up etc.
414: closeRequestAndResponse();
415: // conditions for Keep-Alive or pers. connections
416: if ("close".equals(response.getHeader(C_HTTP_Connection)))
417: closeSocket();
418: logAccess(host);
419: }
420: }
421:
422: private void closeRequestAndResponse() {
423: try {
424: if (log.isLog(C_Log.METHOD)) {
425: log.log(localStrings
426: .getString("closing_request_and_response")
427: + getName());
428: }
429: response.close();
430: request.close();
431: } catch (IOException ioe) {
432: // ignore any IO releate stuff for the log as this could simply be the client
433: // having closed the connection before us.
434: if (log.isLog(C_Log.FORTYTWO))
435: log.log(ioe);
436: closeSocket();
437: } catch (Exception e) {
438: if (log.isLog(C_Log.ERROR))
439: log.log(e);
440: closeSocket();
441: }
442: }
443:
444: private void sendExceptionError(Exception e, String servletName) {
445: if (log.isLog())
446: log.log(e);
447: try {
448: response.sendError(e, servletName);
449: } catch (Exception ioe) {
450: // ignore
451: }
452: // whatever it was - we didn't expect it so we close the connection
453: closeSocket();
454: }
455:
456: private void sendHttpError(I_JoHost host, HttpException he) {
457: if (log.isLog(C_Log.VERBOSE))
458: log.log(he, C_Log.VERBOSE);
459: try {
460: sendError(host, he.getStatusCode(), he.getMessage());
461: } catch (Exception ioe) {
462: if (log.isLog()) {
463: log.log(localStrings
464: .getString("send_errormessage_failed")
465: + he);
466: log.log(ioe);
467: }
468: closeSocket();
469: }
470: }
471:
472: private void sendUnavailableError(I_JoHost host,
473: UnavailableException ue) {
474: if (log.isLog())
475: log.log(ue);
476: try {
477: if (ue.isPermanent()) {
478: sendError(host, StatusCodes.SC_SERVICE_UNAVAILABLE, ue
479: .getMessage());
480: } else {
481: response.setIntHeader(C_Http.C_HTTP_RetryAfter, ue
482: .getUnavailableSeconds());
483: sendError(host, StatusCodes.SC_SERVICE_UNAVAILABLE, ue
484: .getMessage());
485: }
486: } catch (Exception e) {
487: if (log.isLog()) {
488: log.log(localStrings
489: .getString("send_errormessage_failed")
490: + ue);
491: log.log(e);
492: }
493: closeSocket();
494: }
495: }
496:
497: private void logAccess() {
498: logAccess(((I_JoServletService) getService()).getHost(request));
499: }
500:
501: private void logAccess(I_JoHost host) {
502: Log accessLog = null;
503: LogEvent logEvent = null;
504: if (host != null) {
505: accessLog = host.getAccessLog();
506: logEvent = null;
507: if (accessLog.isLog(C_Log.ERROR)) {
508: logArgs[0] = request;
509: logArgs[1] = response;
510: try {
511: logEvent = (LogEvent) Recycler.getNamedRecycler(
512: getService().getName()).get(
513: getAccessLogEventConstructor(), logArgs);
514: } catch (FactoryException fe) {
515: if (log.isLog(C_Log.ERROR)) {
516: log
517: .log(localStrings
518: .getString("accesslogevent_instantiation_failed")
519: + fe.toString());
520: if (log.isLog(C_Log.METHOD)) {
521: log.log(fe.getThrowable());
522: }
523: }
524: }
525: }
526: }
527: if (logEvent != null && accessLog != null) {
528: accessLog.log(logEvent);
529: } else {
530: Log.getLog(getService().getName()).log(
531: "Failed to log to access log. host=" + host
532: + " accessLog=" + accessLog + " logEvent="
533: + logEvent, C_Log.ERROR);
534: }
535: }
536:
537: private static I_JoServletContextPeer getServletContextPeer(
538: I_JoHost host, String decodedRequestURI)
539: throws HttpException, UnavailableException {
540: I_JoServletContextPeer peer = host
541: .getServletContextPeer(decodedRequestURI);
542: // errormessage, if no Peer was found
543: if (peer == null) {
544: throw new HttpException(HttpServletResponse.SC_NOT_FOUND,
545: localStrings.getString("resource_not_found"));
546: }
547: if (!peer.isAvailable())
548: throw new UnavailableException(
549: localStrings
550: .getString("webapplication_temporarliy_unavailable"),
551: 5);
552: return peer;
553: }
554:
555: public boolean executeInterceptors() {
556: try {
557: for (int i = 0, max = requestInterceptors.length; i < max; i++) {
558: if (requestInterceptors[i].intercept(request
559: .getRequestLine(), request.getHttpHeader()) == InterceptorResult.STOP)
560: break;
561: }
562: } catch (Exception e) {
563: Log.getLog(service.getName()).log(e, C_Log.ERROR);
564: return false;
565: }
566: return true;
567: }
568:
569: /**
570: * Sends an error message.
571: *
572: * @param aStatusCode
573: * @param aMessage
574: */
575: private void sendError(I_JoHost host, int aStatusCode,
576: String aMessage) throws IOException {
577: if (host != null) {
578: response.sendError(aStatusCode, host.getErrorPage(
579: aStatusCode, aMessage));
580: } else {
581: response.sendError(aStatusCode,
582: ((I_JoServletService) service).getErrorPage(
583: aStatusCode, aMessage));
584: }
585: }
586:
587: /**
588: * Returns a String representation of this handler.
589: */
590: public String toString() {
591: try {
592: return super .toString() + " - "
593: + request.getRequestLine().toString();
594: } catch (Exception e) {
595: } // can this happen? (rik)
596:
597: return super.toString();
598: }
599:
600: }
|