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