001: /*
002: * de.jwic.web.DispatcherServlet
003: * $Id: WebEngine.java,v 1.6 2007/04/18 12:57:11 cosote Exp $
004: */
005: package de.jwic.web;
006:
007: import java.io.ByteArrayOutputStream;
008: import java.io.FileInputStream;
009: import java.io.FileNotFoundException;
010: import java.io.IOException;
011: import java.io.PrintWriter;
012: import java.io.UnsupportedEncodingException;
013: import java.util.Enumeration;
014: import java.util.HashMap;
015: import java.util.Iterator;
016: import java.util.Locale;
017: import java.util.Map;
018: import java.util.Properties;
019: import java.util.StringTokenizer;
020:
021: import javax.servlet.http.HttpServletRequest;
022: import javax.servlet.http.HttpServletResponse;
023: import javax.servlet.http.HttpSession;
024:
025: import org.apache.commons.lang.StringUtils;
026: import org.apache.commons.logging.Log;
027: import org.apache.commons.logging.LogFactory;
028: import org.apache.velocity.Template;
029: import org.apache.velocity.VelocityContext;
030: import org.apache.velocity.app.VelocityEngine;
031:
032: import de.jwic.base.ConfigurationTool;
033: import de.jwic.base.Control;
034: import de.jwic.base.ControlNotFoundException;
035: import de.jwic.base.IActionController;
036: import de.jwic.base.IApplicationSetup;
037: import de.jwic.base.IControlContainer;
038: import de.jwic.base.IResourceControl;
039: import de.jwic.base.JWicException;
040: import de.jwic.base.JWicRuntime;
041: import de.jwic.base.Page;
042: import de.jwic.base.RenderContext;
043: import de.jwic.base.SessionContext;
044: import de.jwic.base.ValueChangedQueue;
045: import de.jwic.renderer.util.JWicTools;
046: import de.jwic.upload.Upload;
047: import de.jwic.upload.UploadFile;
048:
049: /**
050: * Dispatches incoming request to an existing or new JWic session.
051: *
052: * <p>The DispatcherServlet can initialize a log4j system if required. To do so,
053: * place a log4j.properties file somewhere (WEB-INF is a good place) and point
054: * to it using the init-param "log4j-init-file" in the servlet definition.</p>
055: * <p>Sample:
056: * <pre>
057: * <init-param>
058: * <param-name>log4j-init-file</param-name>
059: * <param-value>WEB-INF/log4j.properties</param-value>
060: * </init-param>
061: * </pre>
062: * </p>
063: *
064: * @author Florian Lippisch
065: * @version $Revision: 1.6 $
066: */
067: public class WebEngine {
068:
069: private static final long serialVersionUID = 1L;
070:
071: private final static String PAGE_FILE = "jwic.page";
072: private final static String PAGE_LAYER_FILE = "jwic_layer.page";
073: protected final Log log = LogFactory.getLog(getClass());
074:
075: private VelocityEngine ve = null;
076: private JWicRuntime jRuntime = null;
077: private IAuthenticator authenticator = null;
078: private IApplicationSetupProvider appSetupProvider = null;
079: private String loginPage = null;
080:
081: /**
082: * Constructs a new WebEngine.
083: * @param provider
084: * @throws Exception
085: */
086: public WebEngine(IApplicationSetupProvider provider, String rootDir)
087: throws Exception {
088: this .appSetupProvider = provider;
089:
090: /*
091: * Initialize the VelocityEngine used by this servlet.
092: */
093: ve = new VelocityEngine();
094: Properties veprop = new Properties();
095: try {
096: veprop.load(new FileInputStream(rootDir
097: + "WEB-INF/jwic/velocity.WebEngine.properties"));
098: ConfigurationTool.insertRootPath(veprop);
099: } catch (Exception ex) {
100: log
101: .warn("WEB-INF/jwic/velocity.WebEngine.properties not found, using defaults");
102: veprop.setProperty("resource.loader", "file");
103: veprop.setProperty("file.resource.loader.description",
104: "Velocity File Resource Loader");
105: veprop
106: .setProperty("file.resource.loader.class",
107: "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
108: veprop.setProperty("file.resource.loader.path", rootDir);
109: veprop.setProperty("file.resource.loader.cache", "true");
110: veprop.setProperty(
111: "file.resource.loader.modificationCheckInterval",
112: "2");
113: }
114: ve.init(veprop);
115:
116: jRuntime = JWicRuntime.getJWicRuntime();
117:
118: }
119:
120: /**
121: * Checks ModuleSession authentication.
122: * @param ms
123: * @param req
124: * @param res
125: * @return true if request is authenticated
126: * @throws NotAuthenticatedException
127: */
128: private void handleAuthentication(SessionContext sc,
129: HttpServletRequest req, HttpServletResponse res)
130: throws NotAuthenticatedException {
131:
132: if (sc.getProperty(SessionContext.PROP_AUTHENTICATION, "false")
133: .equals("true")) {
134: if (authenticator == null
135: || !authenticator.isAuthenticated(req)) {
136: // redirect to login page
137: throw new NotAuthenticatedException();
138: }
139: }
140:
141: }
142:
143: /**
144: * @param req
145: * @param res
146: */
147: public void handleRequest(HttpServletRequest req,
148: HttpServletResponse res, Upload upload) {
149:
150: servletContainerFixes(req, res);
151: long start = System.currentTimeMillis();
152: boolean resourceMode = "1".equals(req
153: .getParameter(IResourceControl.URL_RESOURCE_PARAM));
154: boolean ajaxMode = "1".equals(req.getParameter("_ajaxreq"));
155: SessionContext sc = null;
156: try {
157:
158: boolean isNew = false;
159: boolean isReloaded = false;
160:
161: String sessionID = req.getParameter("_msid");
162: String clientID = req.getSession().getId();
163: if (sessionID != null) {
164: sc = jRuntime.getSessionContext(clientID, sessionID,
165: req);
166: if (sc == null) {
167: // mark as reloaded so the user can be notified.
168: isReloaded = true;
169: }
170: }
171: if (sc == null) {
172: sc = initSession(req);
173: isNew = true;
174: }
175:
176: handleAuthentication(sc, req, res);
177:
178: if (!isNew && !resourceMode && !isReloaded) {
179: handleAction(sc, req, res, upload);
180: }
181: boolean render = handleRedirect(sc, req, res);
182:
183: if (render) {
184: // render control
185: if (resourceMode) {
186: renderResourceControl(sc, req, res, isReloaded);
187: } else if (ajaxMode) {
188: renderAjax(sc, req, res, isReloaded);
189: } else {
190: render(sc, req, res, isReloaded);
191: }
192: }
193:
194: } catch (FileNotFoundException fnf) {
195: try {
196: res.sendError(HttpServletResponse.SC_NOT_FOUND, fnf
197: .getMessage());
198: } catch (IOException ioe) {
199: log.error("Error sending error to client", ioe);
200: }
201: log.warn("File not found: " + fnf.getMessage());
202: } catch (NotAuthenticatedException nae) {
203: authFailed(req, res);
204:
205: } catch (Exception e) {
206: log.error("Error in doPost()", e);
207: displayError(req, res, e, sc);
208: }
209: long time = System.currentTimeMillis() - start;
210: log.debug("total processing time: " + time + "ms");
211:
212: }
213:
214: /**
215: * Checks if a redirect/exit has to happen.
216: * @param ms
217: * @param req
218: * @param res
219: * @return
220: */
221: private boolean handleRedirect(SessionContext sc,
222: HttpServletRequest req, HttpServletResponse res) {
223: boolean render = true;
224: boolean ajaxMode = "1".equals(req.getParameter("_ajaxreq"));
225:
226: // check if redirect had been set
227: if (sc.getRedirectToURL() != null && !ajaxMode) {
228:
229: try {
230: res.sendRedirect(sc.getRedirectToURL());
231: sc.clearRedirect();
232: } catch (IOException ioe) {
233: // sth. went wrong
234: throw new RuntimeException("Redirect to "
235: + sc.getRedirectToURL() + " not successful: "
236: + ioe);
237: }
238: render = false;
239: } else if (sc.isDoExit() && !ajaxMode) {
240: // display a message say "application has exited."
241: displayError(
242: req,
243: res,
244: new JWicException(
245: "The application has been terminated and no exit/redirect URL is given."),
246: null);
247: }
248:
249: // check if application should be closed
250: if (sc.isDoExit() && !ajaxMode) {
251: // close session
252: sc.destroy();
253: render = false;
254:
255: }
256: return render;
257: }
258:
259: /**
260: * Displays a page that describes the error.
261: * @param req
262: * @param res
263: * @param e
264: */
265: public void displayError(HttpServletRequest req,
266: HttpServletResponse res, Exception e, SessionContext sc) {
267:
268: boolean ajaxMode = "1".equals(req.getParameter("_ajaxreq"));
269: res.setContentType(ajaxMode ? "text/xml" : "text/html");
270: PrintWriter pw;
271: try {
272: pw = res.getWriter();
273: } catch (Exception ex) {
274: log.error("Error getting writer!");
275: return;
276: }
277:
278: VelocityContext ctx = new VelocityContext();
279: ctx.put("jwic", new JWicTools(Locale.getDefault()));
280: if (sc != null) {
281: ctx.put("context", sc);
282: }
283: ctx.put("exception", e);
284: try {
285: String errorFile = ajaxMode ? "WEB-INF/jwic/pages/jwic.ajax.page"
286: : "WEB-INF/jwic/pages/jwic_error.page";
287: Template pageBase = ve.getTemplate(errorFile);
288: pageBase.merge(ctx, pw);
289: } catch (Exception ex) {
290: pw.println("Error displaying error information. " + ex);
291: }
292:
293: }
294:
295: /**
296: * Handle the incoming actions.
297: * @param ms
298: * @param req
299: * @param res
300: * @return true if control should be rendered
301: */
302: private void handleAction(SessionContext sc,
303: HttpServletRequest req, HttpServletResponse res,
304: Upload upload) {
305:
306: IActionController controller = sc.getActionController();
307:
308: String srcCtrl = req.getParameter("__ctrlid");
309: String action = req.getParameter("__action");
310: String acpara = req.getParameter("__acpara");
311: String sysinfo = req.getParameter("__sysinfo");
312: String ticketNo = req.getParameter("__ticket");
313:
314: ValueChangedQueue queue = new ValueChangedQueue();
315:
316: // double post protection
317: if (ticketNo != null && ticketNo.length() != 0) {
318: long ticket = Long.parseLong(ticketNo);
319: if (!sc.validateTicket(ticket)) {
320: log
321: .info("DPP: ticket numbers don't match. actions ignored.");
322: return;
323: }
324: } else {
325: log.warn("DPP: No ticket number in the request.");
326: }
327:
328: if ("".equals(srcCtrl) && "redraw".equals(action)) {
329: log.debug("Redraw required - ignoring post content.");
330: return;
331: }
332:
333: // store system informations
334: if (sysinfo != null && sc.getTopControl() instanceof Page) {
335:
336: Page page = (Page) sc.getTopControl();
337: StringTokenizer stk = new StringTokenizer(sysinfo, ";");
338: int tokenNo = 0;
339: while (stk.hasMoreTokens()) {
340: String value = stk.nextToken();
341: switch (tokenNo) {
342: case 0: // visible width
343: page.setClientWidth(Integer.parseInt(value));
344: break;
345: case 1: // visible height
346: page.setClientHeight(Integer.parseInt(value));
347: break;
348: case 2:
349: page.setClientLeft(Integer.parseInt(value));
350: break;
351: case 3:
352: page.setClientTop(Integer.parseInt(value));
353: break;
354: }
355: tokenNo++;
356: }
357:
358: }
359:
360: // store recieved "fields" into the coresponding controls
361: for (Enumeration e = req.getParameterNames(); e
362: .hasMoreElements();) {
363: String paramName = (String) e.nextElement();
364: try {
365: controller.handleField(sc, queue, paramName, req
366: .getParameterValues(paramName));
367: } catch (ControlNotFoundException cnfe) {
368: log.warn("Can not assign field '" + paramName
369: + "'. Control not found.");
370: }
371: }
372:
373: // store recieved files into the coresponding controls
374: if (upload != null) {
375: Map files = upload.getFiles();
376: for (Iterator it = files.keySet().iterator(); it.hasNext();) {
377: String fieldname = (String) it.next();
378: UploadFile file = (UploadFile) files.get(fieldname);
379: controller.handleFile(sc, fieldname, file);
380: }
381: }
382:
383: // fire the valueChangedEvents before the action processing
384: queue.processQueue();
385:
386: // notify the control that has created the "action" link that the
387: // link has been clicked by the user.
388: if (action != null && srcCtrl != null && srcCtrl.length() != 0) {
389: controller.handleAction(sc, srcCtrl, action, acpara);
390: }
391:
392: return;
393: }
394:
395: /**
396: * Render the jWic Application.
397: * @param sc
398: * @param req
399: * @param res
400: */
401: private void render(SessionContext sc, HttpServletRequest req,
402: HttpServletResponse res, boolean markReloaded) {
403:
404: res.setContentType("text/html");
405: RenderContext context;
406: try {
407: context = new RenderContext(req, res);
408: } catch (Exception e) {
409: log.error("Error creating renderContext", e);
410: return;
411: }
412: String layerid = req.getParameter("layerid");
413: Control ctrl = layerid == null ? sc.getTopControl() : sc
414: .getControlByLayerID(layerid);
415:
416: VelocityContext ctx = new VelocityContext();
417: ctx.put("jwic", new JWicTools(sc.getLocale()));
418: ctx.put("page", ctrl);
419: ctx.put("content", new ContentRenderer(ctrl, context));
420: ctx.put("context", sc);
421: ctx.put("layerid", layerid == null ? "" : layerid);
422: ctx.put("reloaded", markReloaded ? "1" : "0");
423: ctx.put("contextPath", req.getContextPath());
424:
425: String templateName;
426: if (layerid == null) {
427: templateName = sc.getProperty("pagefile", PAGE_FILE);
428: } else {
429: templateName = sc.getProperty("layerpagefiler",
430: PAGE_LAYER_FILE);
431: }
432:
433: // build path to page template
434: String path = req.getServletPath();
435: int idx = path.lastIndexOf('/');
436: if (idx != -1) {
437: templateName = path.substring(0, idx + 1) + templateName;
438: }
439:
440: try {
441: Template pageBase = ve.getTemplate(templateName);
442: pageBase.merge(ctx, context.getWriter());
443: } catch (Exception ex) {
444: displayError(req, res, ex, sc);
445: }
446: sc.setRequireRedraw(false);
447: }
448:
449: /**
450: * Render the jWic Application and return it in XML format so that the
451: * jWic ajax script can update the DHTML controls.
452: * @param sc
453: * @param req
454: * @param res
455: */
456: private void renderAjax(SessionContext sc, HttpServletRequest req,
457: HttpServletResponse res, boolean markReloaded) {
458:
459: res.setContentType("text/xml; charset=ISO-8859-1");
460: PrintWriter pw;
461: try {
462: pw = res.getWriter();
463: } catch (Exception e) {
464: log.error("Error getting writer!");
465: return;
466: }
467: String layerid = req.getParameter("layerid");
468: Control ctrl = layerid == null ? sc.getTopControl() : sc
469: .getControlByLayerID(layerid);
470:
471: VelocityContext ctx = new VelocityContext();
472: ctx.put("jwic", new JWicTools(sc.getLocale()));
473: ctx.put("page", ctrl);
474: ctx.put("context", sc);
475: ctx.put("layerid", layerid == null ? "" : layerid);
476: ctx.put("reloaded", markReloaded ? "1" : "0");
477: ctx.put("contextPath", req.getContextPath());
478:
479: // create a list of all controls to be updated
480: if (!sc.isRequireRedraw()) {
481: Map toUpdate = new HashMap();
482: scanForUpdates(toUpdate, ctrl, req, res);
483: ctx.put("updateables", toUpdate);
484: }
485:
486: String templateName = "WEB-INF/jwic/pages/jwic.ajax.page";
487: try {
488: Template pageBase = ve.getTemplate(templateName);
489: pageBase.merge(ctx, pw);
490: } catch (Exception ex) {
491: displayError(req, res, ex, sc);
492: }
493: }
494:
495: /**
496: * Render a jWic resource control.
497: * @param sc
498: * @param req
499: * @param res
500: * @param markReloaded
501: */
502: private void renderResourceControl(SessionContext sc,
503: HttpServletRequest req, HttpServletResponse res,
504: boolean markReloaded) {
505:
506: String controlId = req
507: .getParameter(IResourceControl.URL_CONTROLID_PARAM);
508: Control ctrl = controlId != null ? sc.getControlById(controlId)
509: : null;
510:
511: if (ctrl instanceof IResourceControl) {
512: IResourceControl control = (IResourceControl) ctrl;
513: try {
514: control.attachResource(req, res);
515: } catch (Throwable t) {
516: log.error(
517: "Error during IResourceControl.attachResource",
518: t);
519: }
520: }
521: }
522:
523: /**
524: * Scan the control tree for controls that require rendering.
525: * If the control requires redraw it's rendered.
526: * If it's visible and instance of IControlContainer its controls
527: * are scaned for updates (more time intensive!).
528: * @param toUpdate
529: * @param ctrl
530: */
531: private void scanForUpdates(Map toUpdate, Control ctrl,
532: HttpServletRequest req, HttpServletResponse res) {
533:
534: long start = System.currentTimeMillis();
535: boolean showTime = true;
536:
537: if (showTime = ctrl.isRequireRedraw()) {
538: ByteArrayOutputStream out = new ByteArrayOutputStream();
539: PrintWriter pw = new PrintWriter(out);
540: ContentRenderer cr = new ContentRenderer(ctrl,
541: new RenderContext(req, res, pw));
542: cr.render();
543: pw.flush();
544: toUpdate.put(ctrl.getControlID(), StringUtils.replace(out
545: .toString(), "]]>", "]]>"));
546: } else {
547: if (!ctrl.isVisible()) {
548: return;
549: }
550: if (ctrl instanceof IControlContainer) {
551: IControlContainer container = (IControlContainer) ctrl;
552: for (Iterator it = container.getControls(); it
553: .hasNext();) {
554: Control control = (Control) it.next();
555: if (container.isRenderingRelevant(control)) {
556: scanForUpdates(toUpdate, control, req, res);
557: }
558: }
559: }
560: }
561:
562: if (false && showTime /* TODO nicer implementation */) {
563: long time = System.currentTimeMillis() - start;
564: log.error("scan for updates time: " + time + "ms for "
565: + ctrl.getControlID());
566: }
567: }
568:
569: /**
570: * Initialize the module session by launching the specified jWic application.
571: * @param req
572: * @return
573: * @throws NotAuthenticatedException
574: * @throws FileNotFoundException
575: */
576: private SessionContext initSession(HttpServletRequest req)
577: throws NotAuthenticatedException, IOException {
578:
579: // get configuration file
580: IApplicationSetup appSetup = appSetupProvider
581: .createApplicationSetup(req);
582:
583: if (appSetup.isRequireAuthentication()) {
584: if (authenticator == null
585: || !authenticator.isAuthenticated(req)) {
586: throw new NotAuthenticatedException();
587: }
588: }
589:
590: HttpSession session = req.getSession();
591: Locale locale = (Locale) session.getAttribute(Locale.class
592: .getName());
593: if (locale == null) {
594: // use the default locale if there is no locale specified for the
595: // current session.
596: locale = Locale.getDefault();
597: }
598: SessionContext sc = jRuntime.createSessionContext(appSetup,
599: locale, req);
600: sc.setCallBackURL(getFileName(req) + "?_msid="
601: + sc.getSessionId());
602:
603: return sc;
604: }
605:
606: /**
607: * @param req
608: * @return
609: */
610: private String getFileName(HttpServletRequest req) {
611: String path = req.getServletPath();
612: int i = path.lastIndexOf('/');
613: if (i != -1) {
614: return path.substring(i + 1);
615: }
616: return path;
617: }
618:
619: /**
620: * Set the Authenticator.
621: * @param authenticator
622: */
623: public void setAuthenticator(IAuthenticator authenticator) {
624: this .authenticator = authenticator;
625: }
626:
627: /**
628: * Set the loginPage.
629: * @param loginPage
630: */
631: public void setLoginPage(String loginPage) {
632: this .loginPage = loginPage;
633: }
634:
635: /* (non-Javadoc)
636: * @see de.jwic.security.Authentication#authenticate(javax.servlet.http.HttpServletRequest, javax.servlet.http.HttpServletResponse)
637: */
638: public void authFailed(HttpServletRequest req,
639: HttpServletResponse res) {
640: try {
641: if (loginPage != null) {
642: // set current url in ClientSession for later redirect
643: req.getSession().setAttribute(
644: IAuthenticator.SESSION_REDIRECT_URL,
645: req.getRequestURI());
646: // show login page
647: res.sendRedirect(loginPage);
648: } else {
649: res
650: .getWriter()
651: .println(
652: "Configuration failure: No login page configured.");
653: return;
654: }
655: } catch (IOException ioe) {
656: log.error("Error redirecting to login page: " + loginPage,
657: ioe);
658: }
659: }
660:
661: /**
662: * Fix servlet container problems, e.g. jetty 6.1.2rc3 (and earlier) ajp13 usage.
663: * @param req
664: * @param res
665: */
666: private void servletContainerFixes(HttpServletRequest req,
667: HttpServletResponse res) {
668: // correct character encoding and try to read it from header
669: String enc = req.getCharacterEncoding();
670: if (enc == null) {
671: String contentType = req.getHeader("Content-Type");
672: if (contentType != null) {
673: contentType = contentType.toLowerCase();
674: int i = contentType.indexOf("charset");
675: if (i != -1) {
676: String s = contentType.substring(i + 7).trim();
677: s = s.substring(s.indexOf('=') + 1).trim();
678: i = s.indexOf(';');
679: if (i != -1) {
680: s = s.substring(0, i).trim();
681: }
682: enc = s;
683: try {
684: req.setCharacterEncoding(enc);
685: } catch (UnsupportedEncodingException e) {
686: log.warn("Cannot set encoding", e);
687: }
688: }
689: }
690: }
691: }
692: }
|