001: /*
002: * Copyright 2000,2005 wingS development team.
003: *
004: * This file is part of wingS (http://wingsframework.org).
005: *
006: * wingS is free software; you can redistribute it and/or modify
007: * it under the terms of the GNU Lesser General Public License
008: * as published by the Free Software Foundation; either version 2.1
009: * of the License, or (at your option) any later version.
010: *
011: * Please see COPYING for the complete licence.
012: */
013: package org.wings.session;
014:
015: import org.apache.commons.logging.Log;
016: import org.apache.commons.logging.LogFactory;
017: import org.wings.RequestURL;
018: import org.wings.util.SStringBuilder;
019: import org.wings.externalizer.AbstractExternalizeManager;
020: import org.wings.externalizer.ExternalizedResource;
021: import org.wings.externalizer.SystemExternalizeManager;
022: import org.wings.io.Device;
023: import org.wings.io.ServletDevice;
024:
025: import javax.servlet.*;
026: import javax.servlet.http.HttpServlet;
027: import javax.servlet.http.HttpServletRequest;
028: import javax.servlet.http.HttpServletResponse;
029: import javax.servlet.http.HttpSession;
030: import java.io.File;
031: import java.io.IOException;
032: import java.io.UnsupportedEncodingException;
033: import java.util.Enumeration;
034: import java.util.Iterator;
035: import java.text.DateFormat;
036:
037: /**
038: * Central servlet delegating all requests to the according wingS session servlet.
039: *
040: * @author <a href="mailto:engels@mercatis.de">Holger Engels</a>
041: * @author <a href="mailto:haaf@mercatis.de">Armin Haaf</a>
042: */
043: public class WingServlet extends HttpServlet {
044: protected final transient static Log log = LogFactory
045: .getLog(WingServlet.class);
046:
047: /**
048: * used to init session servlets
049: */
050: protected ServletConfig servletConfig = null;
051:
052: private String lookupName = "SessionServlet";
053:
054: public WingServlet() {
055: }
056:
057: protected void initLookupName(ServletConfig config) {
058: // with specified lookupname it is possible to handle different sessions
059: // for servlet aliases/mappings
060: lookupName = config
061: .getInitParameter("wings.servlet.lookupname");
062:
063: if (lookupName == null || lookupName.trim().length() == 0) {
064: lookupName = "SessionServlet:"
065: + config.getInitParameter("wings.mainclass");
066: }
067:
068: log.info("use session servlet lookup name " + lookupName);
069: }
070:
071: /**
072: * The following init parameters are known by wings.
073: * <p/>
074: * <dl compact>
075: * <dt>externalizer.timeout</dt><dd> - The time, externalized objects
076: * are kept, before they are removed</dd>
077: * <p/>
078: * <dt>content.maxlength</dt><dd> - Maximum content lengt for form posts.
079: * Remember to increase this, if you make use of the SFileChooser
080: * component</dd>
081: * <p/>
082: * <dt>filechooser.uploaddir</dt><dd> - The directory, where uploaded
083: * files ar stored temporarily</dd>
084: * </dl>
085: * <p/>
086: * <dt>wings.servlet.lookupname</dt><dd> - The name the wings sessions of
087: * this servlet instance are stored in the servlet session hashtable</dd>
088: * </dl>
089: *
090: * @throws ServletException
091: */
092: public void init(ServletConfig config) throws ServletException {
093: super .init(config);
094: servletConfig = config;
095:
096: if (log.isInfoEnabled()) {
097: log
098: .info("Initializing wingS global servlet with configuration:");
099: for (Enumeration en = config.getInitParameterNames(); en
100: .hasMoreElements();) {
101: String param = (String) en.nextElement();
102: log.info(" " + param + " = "
103: + config.getInitParameter(param));
104: }
105: }
106:
107: initLookupName(config);
108: }
109:
110: /**
111: * returns the last modification of an externalized resource to allow the
112: * browser to cache it.
113: */
114: protected long getLastModified(HttpServletRequest request) {
115: AbstractExternalizeManager extMgr;
116: try {
117: extMgr = getExternalizeManager(request);
118: } catch (Exception e) {
119: return System.currentTimeMillis();
120: }
121: String pathInfo = getPathInfo(request);
122: if (extMgr != null && pathInfo != null && pathInfo.length() > 1) {
123: String identifier = pathInfo.substring(1);
124: ExternalizedResource info = extMgr
125: .getExternalizedResource(identifier);
126: if (info != null) {
127: //System.err.println(" **>" + info.getLastModified());
128: return info.getLastModified();
129: }
130: }
131: return -1;
132: }
133:
134: /**
135: * Parse POST request with <code>MultipartRequest</code> and passes to <code>doGet()</code>
136: */
137: public final void doPost(HttpServletRequest req,
138: HttpServletResponse res) throws ServletException {
139: SessionServlet sessionServlet = getSessionServlet(req, res,
140: true);
141:
142: if (log.isDebugEnabled()) {
143: log.debug((sessionServlet != null) ? lookupName
144: : "no session yet ..");
145: }
146:
147: // Wrap with MultipartRequest which can handle multipart/form-data
148: // (file - upload), otherwise behaves like normal HttpServletRequest
149: try {
150: int maxContentLength = sessionServlet.getSession()
151: .getMaxContentLength();
152: req = new MultipartRequest(req, maxContentLength * 1024);
153: } catch (Exception e) {
154: log.fatal(null, e);
155: }
156:
157: if (log.isDebugEnabled()) {
158: if (req instanceof MultipartRequest) {
159: MultipartRequest multi = (MultipartRequest) req;
160: log.debug("Files:");
161: Iterator files = multi.getFileNames();
162: while (files.hasNext()) {
163: String name = (String) files.next();
164: String filename = multi.getFileName(name);
165: String type = multi.getContentType(name);
166: File f = multi.getFile(name);
167: log.debug("name: " + name);
168: log.debug("filename: " + filename);
169: log.debug("type: " + type);
170: if (f != null) {
171: log.debug("f.toString(): " + f.toString());
172: log.debug("f.getDescription(): " + f.getName());
173: log.debug("f.exists(): " + f.exists());
174: log.debug("f.length(): " + f.length());
175: log.debug("\n");
176: }
177: }
178: }
179: }
180:
181: doGet(req, res);
182: }
183:
184: private final SessionServlet newSession(HttpServletRequest request,
185: HttpServletResponse response) throws ServletException {
186: long timestamp = System.currentTimeMillis();
187: try {
188: log.debug("--- new SessionServlet()");
189:
190: SessionServlet sessionServlet = new SessionServlet();
191: sessionServlet.setParent(this );
192: sessionServlet.init(servletConfig, request, response);
193:
194: Session session = sessionServlet.getSession();
195: /* the request URL is needed already in the setup-phase. Note,
196: * that at this point, the URL will always be encoded, since
197: * we (or better: the servlet engine) does not know yet, if setting
198: * a cookie will be successful (it has to await the response).
199: * Subsequent requests might decide, _not_ to encode the sessionid
200: * in the URL (see SessionServlet::doGet()) -hen
201: */
202: RequestURL requestURL = new RequestURL("", SessionServlet
203: .getSessionEncoding(response));
204:
205: session.setProperty("request.url", requestURL);
206:
207: log.debug("--- Time needed to create new session "
208: + (System.currentTimeMillis() - timestamp) + "ms");
209:
210: return sessionServlet;
211: } catch (Exception e) {
212: log.fatal("Error on creating new wingS session", e);
213: throw new ServletException(e);
214: }
215: }
216:
217: public final SessionServlet getSessionServlet(
218: HttpServletRequest request, HttpServletResponse response,
219: boolean createSessionServlet) throws ServletException {
220: final HttpSession httpSession = request.getSession(true);
221:
222: // it should be enough to synchronize on the http session object...
223: synchronized (httpSession) {
224: SessionServlet sessionServlet = null;
225:
226: if (httpSession != null) {
227: sessionServlet = (SessionServlet) httpSession
228: .getAttribute(lookupName);
229: }
230:
231: // Sanity check - maybe this is a stored/deserialized session servlet?
232: if (sessionServlet != null && !sessionServlet.isValid()) {
233: sessionServlet.destroy();
234: sessionServlet = null;
235: }
236:
237: /*
238: * we are only interested in a new session, if the response is
239: * not null. If it is null, then we just called getSessionServlet()
240: * for lookup purposes and are satisfied, if we don't get anything.
241: */
242: if (sessionServlet == null) {
243: if (createSessionServlet) {
244: log.info("no session servlet, create new one");
245: sessionServlet = newSession(request, response);
246: httpSession
247: .setAttribute(lookupName, sessionServlet);
248: } else {
249: return null;
250: }
251: }
252:
253: if (log.isDebugEnabled()) {
254: SStringBuilder message = new SStringBuilder()
255: .append("session id: ")
256: .append(request.getRequestedSessionId())
257: .append(", created at: ")
258: .append(
259: DateFormat.getDateTimeInstance(
260: DateFormat.SHORT,
261: DateFormat.SHORT).format(
262: new java.util.Date(httpSession
263: .getCreationTime())))
264: .append(", identified via:")
265: .append(
266: request
267: .isRequestedSessionIdFromCookie() ? " cookie"
268: : "")
269: .append(
270: request.isRequestedSessionIdFromURL() ? " URL"
271: : "").append(
272: ", expiring after: ").append(
273: httpSession.getMaxInactiveInterval())
274: .append("s ");
275: log.debug(message.toString());
276: //log.debug("session valid " + request.isRequestedSessionIdValid());
277: //log.debug("session httpsession id " + httpSession.getId());
278: //log.debug("session httpsession new " + httpSession.isNew());
279: //log.debug("session last accessed at " +
280: // new java.util.Date(httpSession.getLastAccessedTime()));
281: //log.debug("session expiration timeout (s) " +
282: // httpSession.getMaxInactiveInterval());
283: //log.debug("session contains wings session " +
284: // (httpSession.getAttribute(lookupName) != null));
285: }
286:
287: sessionServlet.getSession().getExternalizeManager()
288: .setResponse(response);
289:
290: /* Handling of the requests character encoding.
291: * --------------------------------------------
292: * The following block is needed for a correct handling of
293: * non-ISO-8859-1 data:
294: *
295: * Using LocaleCharacterSet and/or charset.properties we can
296: * advise the client to use i.e. UTF-8 as character encoding.
297: * Once told the browser consequently also encodes his requests
298: * in the choosen characterset of the sings session. This is
299: * achieved by adding the HTML code
300: * <meta http-equiv="Content-Type" content="text/html;charset="<charset>">
301: * to the generated pages.
302: *
303: * If the user hasn't overridden the encoding in their browser,
304: * then all form data (e.g. mueller) is submitted with data encoded
305: * like m%C3%BCller because byte pair C3 BC is how the german
306: * u-umlaut is represented in UTF-8. If the form is
307: * iso-8859-1 encoded then you get m%FCller, because byte FC is
308: * how it is presented in iso-8859-1.
309: *
310: * So the browser behaves correctly by sending his form input
311: * correctly encoded in the advised character encoding. The issue
312: * is that the servlet container is typically unable to determine
313: * the correct encoding of this form data. By proposal the browser
314: * should als declare the used character encoding for his data.
315: * But actual browsers omit this information and hence the servlet
316: * container is unable to guess the right encoding (Tomcat actually
317: * thenalways guesses ISO 8859-1). This results in totally
318: * scrumbled up data for all non ISO-8859-1 character encodings.
319: * With the block below we tell the servlet container about the
320: * character encoding we expect in the browsers request and hence
321: * the servlet container can do the correct decoding.
322: * This has to be done at very first, otherwise the servlet
323: * container will ignore this setting.
324: */
325: if ((request.getCharacterEncoding() == null)) { // was servlet container able to identify encoding?
326: try {
327: String sessionCharacterEncoding = sessionServlet
328: .getSession().getCharacterEncoding();
329: // We know better about the used character encoding than tomcat
330: log
331: .debug("Advising servlet container to interpret request as "
332: + sessionCharacterEncoding);
333: request
334: .setCharacterEncoding(sessionCharacterEncoding);
335: } catch (UnsupportedEncodingException e) {
336: log
337: .warn(
338: "Problem on applying current session character encoding",
339: e);
340: }
341: }
342:
343: return sessionServlet;
344: }
345: }
346:
347: public static void installSession(HttpServletRequest req,
348: HttpServletResponse res) {
349: ServletContext context = req.getSession().getServletContext();
350: String lookupName = context
351: .getInitParameter("wings.servlet.lookupname");
352:
353: if (lookupName == null || lookupName.trim().length() == 0) {
354: lookupName = "SessionServlet:"
355: + context.getInitParameter("wings.mainclass");
356: }
357: SessionServlet sessionServlet = (SessionServlet) req
358: .getSession().getAttribute(lookupName);
359: if (sessionServlet != null) {
360: Session session = sessionServlet.getSession();
361: session.setServletRequest(req);
362: session.setServletResponse(res);
363: SessionManager.setSession(session);
364: }
365: }
366:
367: public static void uninstallSession() {
368: SessionManager.removeSession();
369: }
370:
371: /** -- externalization -- **/
372:
373: /**
374: * returns, whether this request is to serve an externalize request.
375: */
376: protected boolean isSystemExternalizeRequest(
377: HttpServletRequest request) {
378: String pathInfo = getPathInfo(request);
379: return (pathInfo != null && pathInfo.length() > 1 && pathInfo
380: .startsWith("/-"));
381: }
382:
383: protected AbstractExternalizeManager getExternalizeManager(
384: HttpServletRequest req) throws ServletException {
385: if (isSystemExternalizeRequest(req)) {
386: return SystemExternalizeManager.getSharedInstance();
387: } else {
388: SessionServlet sessionServlet = getSessionServlet(req,
389: null, false);
390: if (sessionServlet == null) {
391: return null;
392: }
393: return sessionServlet.getSession().getExternalizeManager();
394: }
395: }
396:
397: public void doGet(HttpServletRequest req,
398: HttpServletResponse response) throws ServletException {
399:
400: try {
401: /*
402: * make sure, that our context ends with '/'. Otherwise redirect
403: * to the same location with appended slash.
404: *
405: * We need a '/' at the
406: * end of the servlet, so that relative requests work. Relative
407: * requests are either externalization requests, providing the
408: * required resource name in the path info (like 'abc_121.gif')
409: * or 'normal' requests which are just an empty URL with the
410: * request parameter (like '?12_22=121').
411: * The browser assembles the request URL from the current context
412: * (the 'directory' it assumes it is in) plus the relative URL.
413: * Thus emitted URLs are as short as possible and thus the
414: * generated page size.
415: */
416: String pathInfo = getPathInfo(req);
417:
418: if (pathInfo == null || pathInfo.length() == 0) {
419: StringBuffer pathUrl = req.getRequestURL();
420: pathUrl.append('/');
421: if (req.getQueryString() != null) {
422: pathUrl.append('?').append(req.getQueryString());
423: }
424:
425: log.debug("redirect to " + pathUrl.toString());
426: response.sendRedirect(pathUrl.toString());
427: return;
428: }
429:
430: /*
431: * we either have a request for the system externalizer
432: * (if there is something in the path info, that starts with '-')
433: * or just a normal request to this servlet.
434: */
435: if (isSystemExternalizeRequest(req)) {
436: String identifier = pathInfo.substring(1);
437: AbstractExternalizeManager extManager = SystemExternalizeManager
438: .getSharedInstance();
439: ExternalizedResource extInfo = extManager
440: .getExternalizedResource(identifier);
441: if (extInfo != null) {
442: final Device outputDevice = createOutputDevice(req,
443: response, extInfo);
444: try {
445: extManager.deliver(extInfo, response,
446: outputDevice);
447: } finally {
448: outputDevice.close();
449: }
450: }
451: return;
452: }
453:
454: SessionServlet sessionServlet = getSessionServlet(req,
455: response, true);
456:
457: sessionServlet.doGet(req, response);
458:
459: } catch (ServletException e) {
460: log.fatal("doGet", e);
461: throw e;
462: } catch (Throwable e) {
463: log.fatal("doGet", e);
464: throw new ServletException(e);
465: }
466: }
467:
468: /**
469: * create a Device that is used to deliver the content, that is
470: * not session specific, i.e. that is delivered by the SystemExternalizer.
471: * The default
472: * implementation just creates a ServletDevice. You can override this
473: * method to decide yourself what happens to the output. You might, for
474: * instance, write some device, that logs the output for debugging
475: * purposes, or one that creates a gziped output stream to transfer
476: * data more efficiently. You get the request and response as well as
477: * the ExternalizedResource to decide, what kind of device you want to create.
478: * You can rely on the fact, that extInfo is not null.
479: * Further, you can rely on the fact, that noting has been written yet
480: * to the output, so that you can set you own set of Headers.
481: *
482: * @param request the HttpServletRequest that is answered
483: * @param response the HttpServletResponse.
484: * @param extInfo the externalized info of the resource about to be
485: * delivered.
486: */
487: protected Device createOutputDevice(HttpServletRequest request,
488: HttpServletResponse response, ExternalizedResource extInfo)
489: throws IOException {
490: return new ServletDevice(response.getOutputStream(), response
491: .getCharacterEncoding());
492: }
493:
494: // TODO BSC: This issue is still pending. Refer to http://jira.j-wings.org/browse/WGS-84
495:
496: /**
497: * Workaround implementation for WebSphere.
498: *
499: * @return "/" if <code>request.getPathInfo()</code> returns null but URL indicates a trailing slash.
500: * Otherwise original value is returned.
501: */
502: private static String getPathInfo(HttpServletRequest request) {
503: final String pathInfo = request.getPathInfo();
504: if (pathInfo == null) {
505: final String requestURL = request.getRequestURL()
506: .toString();
507: return (requestURL.lastIndexOf("/") == requestURL.length() - 1) ? "/"
508: : null;
509: } else {
510: return pathInfo;
511: }
512: }
513: }
|