001: /* DHtmlLayoutServlet.java
002:
003: {{IS_NOTE
004: Purpose:
005:
006: Description:
007:
008: History:
009: Mon May 30 21:11:28 2005, Created by tomyeh
010: }}IS_NOTE
011:
012: Copyright (C) 2005 Potix Corporation. All Rights Reserved.
013:
014: {{IS_RIGHT
015: This program is distributed under GPL Version 2.0 in the hope that
016: it will be useful, but WITHOUT ANY WARRANTY.
017: }}IS_RIGHT
018: */
019: package org.zkoss.zk.ui.http;
020:
021: import java.util.Map;
022: import java.util.HashMap;
023: import java.util.logging.Level;
024: import java.io.Writer;
025: import java.io.OutputStream;
026: import java.io.StringWriter;
027: import java.io.IOException;
028:
029: import javax.servlet.ServletConfig;
030: import javax.servlet.ServletContext;
031: import javax.servlet.ServletException;
032: import javax.servlet.http.HttpServletRequest;
033: import javax.servlet.http.HttpServletResponse;
034: import javax.servlet.http.HttpServlet;
035: import javax.servlet.http.HttpSession;
036:
037: import org.zkoss.mesg.Messages;
038: import org.zkoss.lang.D;
039: import org.zkoss.lang.Exceptions;
040: import org.zkoss.util.logging.Log;
041:
042: import org.zkoss.web.servlet.Servlets;
043: import org.zkoss.web.servlet.http.Https;
044:
045: import org.zkoss.zk.mesg.MZk;
046: import org.zkoss.zk.ui.WebApp;
047: import org.zkoss.zk.ui.Desktop;
048: import org.zkoss.zk.ui.Page;
049: import org.zkoss.zk.ui.Session;
050: import org.zkoss.zk.ui.Execution;
051: import org.zkoss.zk.ui.Richlet;
052: import org.zkoss.zk.ui.UiException;
053: import org.zkoss.zk.ui.metainfo.PageDefinition;
054: import org.zkoss.zk.ui.metainfo.PageDefinitions;
055: import org.zkoss.zk.ui.sys.UiFactory;
056: import org.zkoss.zk.ui.sys.RequestInfo;
057: import org.zkoss.zk.ui.sys.WebAppCtrl;
058: import org.zkoss.zk.ui.sys.SessionCtrl;
059: import org.zkoss.zk.ui.sys.SessionsCtrl;
060: import org.zkoss.zk.ui.impl.RequestInfoImpl;
061:
062: /**
063: * Used to process the request for a ZUML page. Though it is called
064: * DHtmlLayoutServlet, it is used to serve all kind of HTTP-based clients,
065: * including ajax (HTML+Ajax), mil (Mobile Interactive Language)
066: * and others (see {@link Desktop#getDeviceType}.
067: *
068: * <p>Init parameters:
069: *
070: * <dl>
071: * <dt>update-uri</dt>
072: * <dd>It specifies the URI which the ZK AU engine is mapped to.</dd>
073: * <dt>compress</dt>
074: * <dd>It specifies whether to compress the output if the browser supports the compression (Accept-Encoding) and this Servlet is not included by other Servlets.</dd>
075: * <dt>log-level</dt>
076: * <dd>It specifies the default log level for org.zkoss. If not specified, the system default (usually INFO) is used.</dd>
077: * </dl>
078: * @author tomyeh
079: */
080: public class DHtmlLayoutServlet extends HttpServlet {
081: private static final Log log = Log.lookup(DHtmlLayoutServlet.class);
082: private static final String ATTR_LAYOUT_SERVLET = "org.zkoss.zk.ui.http.LayoutServlet";
083:
084: private ServletContext _ctx;
085: private WebManager _webman;
086: private boolean _compress = true;
087:
088: /** Returns the layout servlet of the specified application, or
089: * null if not loaded yet.
090: * Note: if the layout servlet is not loaded, it returns null.
091: * @since 3.0.2
092: */
093: public static DHtmlLayoutServlet getLayoutServlet(WebApp wapp) {
094: return (DHtmlLayoutServlet) ((ServletContext) wapp
095: .getNativeContext()).getAttribute(ATTR_LAYOUT_SERVLET);
096: }
097:
098: //Servlet//
099: public void init(ServletConfig config) throws ServletException {
100: //super.init(config);
101: //Note callback super to avoid saving config
102:
103: String param = config.getInitParameter("log-level");
104: if (param != null && param.length() > 0) {
105: final Level level = Log.getLevel(param);
106: if (level != null)
107: Log.lookup("org.zkoss").setLevel(level);
108: else
109: log.error("Unknown log-level: " + param);
110: }
111:
112: param = config.getInitParameter("compress");
113: if (param != null)
114: _compress = "true".equals(param);
115:
116: _ctx = config.getServletContext();
117: if (WebManager.getWebManagerIfAny(_ctx) != null)
118: throw new ServletException(
119: "Only one layout servlet is allowed in one context: "
120: + _ctx);
121:
122: String updateURI = config.getInitParameter("update-uri");
123: if (updateURI == null
124: || (updateURI = updateURI.trim()).length() == 0
125: || updateURI.charAt(0) != '/')
126: throw new ServletException(
127: "The update-uri parameter must be specified and starts with /");
128: if (updateURI.indexOf(';') >= 0 || updateURI.indexOf('?') >= 0)
129: throw new ServletException(
130: "The update-uri parameter cannot contain ';' or '?'");
131: //Jetty will encode URL by appending ';jsess..' and we have to
132: //remove it under certain situations, so not alow it
133: if (updateURI.charAt(updateURI.length() - 1) == '\\') {
134: if (updateURI.length() == 1)
135: throw new ServletException(
136: "The update-uri parameter cannot contain only '/'");
137: updateURI = updateURI.substring(0, updateURI.length() - 1);
138: //remove the trailing '\\' if any
139: }
140:
141: _webman = new WebManager(_ctx, updateURI);
142:
143: _ctx.setAttribute(ATTR_LAYOUT_SERVLET, this );
144: }
145:
146: public void destroy() {
147: if (_webman != null)
148: _webman.destroy();
149: }
150:
151: public ServletContext getServletContext() {
152: return _ctx;
153: }
154:
155: //-- super --//
156: protected void doGet(HttpServletRequest request,
157: HttpServletResponse response) throws ServletException,
158: IOException {
159: String path = Https.getThisPathInfo(request);
160: final boolean bRichlet = path != null && path.length() > 0;
161: if (!bRichlet)
162: path = Https.getThisServletPath(request);
163: // if (D.ON && log.finerable()) log.finer("Creates from "+path);
164:
165: final Session sess = WebManager.getSession(getServletContext(),
166: request);
167: if (!SessionsCtrl.requestEnter(sess)) {
168: response.sendError(response.SC_SERVICE_UNAVAILABLE,
169: Messages.get(MZk.TOO_MANY_REQUESTS));
170: return;
171: }
172: try {
173: final Object old = I18Ns.setup(sess, request, response,
174: sess.getWebApp().getConfiguration()
175: .getResponseCharset());
176: try {
177: if (!process(sess, request, response, path, bRichlet))
178: handleError(sess, request, response, path, null);
179: } catch (Throwable ex) {
180: handleError(sess, request, response, path, ex);
181: } finally {
182: I18Ns.cleanup(request, old);
183: }
184: } finally {
185: SessionsCtrl.requestExit(sess);
186: }
187: }
188:
189: protected void doPost(HttpServletRequest request,
190: HttpServletResponse response) throws ServletException,
191: IOException {
192: doGet(request, response);
193: }
194:
195: //-- private --//
196: /**
197: * Process the request.
198: * @return false if the page is not found.
199: * @since 3.0.0
200: */
201: protected boolean process(Session sess, HttpServletRequest request,
202: HttpServletResponse response, String path, boolean bRichlet)
203: throws ServletException, IOException {
204: final WebApp wapp = sess.getWebApp();
205: final WebAppCtrl wappc = (WebAppCtrl) wapp;
206:
207: final Desktop desktop = _webman.getDesktop(sess, request,
208: response, path, true);
209: final RequestInfo ri = new RequestInfoImpl(wapp, sess, desktop,
210: request, PageDefinitions.getLocator(wapp, path));
211: ((SessionCtrl) sess).notifyClientRequest(true);
212:
213: final boolean compress = _compress
214: && !Servlets.isIncluded(request);
215: final Writer out;
216: final UiFactory uf = wappc.getUiFactory();
217: if (uf.isRichlet(ri, bRichlet)) {
218: final Richlet richlet = uf.getRichlet(ri, path);
219: if (richlet == null)
220: return false; //not found
221:
222: final Page page = WebManager.newPage(uf, ri, richlet,
223: response, path);
224: final Execution exec = new ExecutionImpl(_ctx, request,
225: response, desktop, page);
226: out = compress ? (Writer) new StringWriter() : response
227: .getWriter();
228: wappc.getUiEngine().execNewPage(exec, richlet, page, out);
229: //no need to set device type here, since UiEngine will do it later
230: } else {
231: final PageDefinition pagedef = uf.getPageDefinition(ri,
232: path);
233: if (pagedef == null)
234: return false; //not found
235:
236: final Page page = WebManager.newPage(uf, ri, pagedef,
237: response, path);
238: final Execution exec = new ExecutionImpl(_ctx, request,
239: response, desktop, page);
240: out = compress ? (Writer) new StringWriter() : response
241: .getWriter();
242: wappc.getUiEngine().execNewPage(exec, pagedef, page, out);
243: }
244:
245: if (compress) {
246: final String result = ((StringWriter) out).toString();
247:
248: try {
249: final OutputStream os = response.getOutputStream();
250: //Call it first to ensure getWrite() is not called yet
251:
252: byte[] data = result.getBytes("UTF-8");
253: if (data.length > 200) {
254: byte[] bs = Https.gzip(request, response, null,
255: data);
256: if (bs != null)
257: data = bs; //yes, browser support compress
258: }
259:
260: response.setContentLength(data.length);
261: os.write(data);
262: response.flushBuffer();
263: } catch (IllegalStateException ex) { //getWriter is called
264: response.getWriter().write(result);
265: }
266: }
267: return true; //success
268: }
269:
270: /** Handles exception being thrown when rendering a page.
271: * @param err the exception being throw. If null, it means the page
272: * is not found.
273: */
274: private void handleError(Session sess, HttpServletRequest request,
275: HttpServletResponse response, String path, Throwable err)
276: throws ServletException, IOException {
277: //Note: if not included, it is handled by Web container
278: if (err != null && Servlets.isIncluded(request)) {
279: //Bug 1714094: we have to handle err, because Web container
280: //didn't allow developer to intercept errors caused by inclusion
281: final String errpg = sess.getWebApp().getConfiguration()
282: .getErrorPage(sess.getDeviceType(), err);
283: if (errpg != null) {
284: try {
285: request.setAttribute("javax.servlet.error.message",
286: Exceptions.getMessage(err));
287: request.setAttribute(
288: "javax.servlet.error.exception", err);
289: request.setAttribute(
290: "javax.servlet.error.exception_type", err
291: .getClass());
292: request.setAttribute(
293: "javax.servlet.error.status_code",
294: new Integer(500));
295: if (process(sess, request, response, errpg, false))
296: return; //done
297:
298: log.warning("The error page not found: " + errpg);
299: } catch (IOException ex) { //eat it (connection off)
300: } catch (Throwable ex) {
301: log.warning("Failed to load the error page: "
302: + errpg, ex);
303: }
304: }
305: }
306:
307: Utils.handleError(_ctx, request, response, path, err);
308: }
309: }
|