001 // Copyright (c) 2001 Hursh Jain (http://www.mollypages.org) 002 // The Molly framework is freely distributable under the terms of an 003 // MIT-style license. For details, see the molly pages web site at: 004 // http://www.mollypages.org/. Use, modify, have fun ! 005 006 package fc.web.page; 007 008 import java.io.*; 009 import java.util.*; 010 import javax.servlet.*; 011 import javax.servlet.http.*; 012 013 import fc.io.*; 014 import fc.web.servlet.*; 015 016 /** 017 The page servlet. Handles web requests that are mapped to a molly server 018 page (typically via a <code>*.mp</code> extension). 019 <p> 020 All uncaught exceptions in the rendered page are wrapped in a 021 <tt>ServletException</tt> and thrown up to the servlet container. These are 022 typically handled by the container by either showing the full stack trace 023 to the user or using a error page configured in the containers 024 <tt>web.xml</tt> file. This latter approach is recommended for production 025 use. An 026 <i>example</i> is shown below. 027 <blockquote> 028 <xmp> 029 <!-- in web.xml --> 030 <error-page> 031 <exception-type>java.lang.Exception</exception-type> 032 <location>/errors/error.mp</location> 033 </error-page> 034 </xmp> 035 </blockquote> 036 <p> 037 However, if some partial response has already been sent to the browser and 038 an exception occurs (later in java code on that same page), then the 039 error page (if configured in web.xml) cannot typically be displayed. 040 <p> 041 For that scenario, this servlet accepts an <i>optional</i> 042 <b><tt>error_page</tt></b> initialization parameter. This parameter, if 043 present, should contain a <i>webapp</i>-relative path to an error page that 044 will be <i>included</i> in the rendered page if there is an exception 045 <i>after</i> the response has been committed. This error page is then 046 <i>included</i> in the response sent to the browser. 047 <p> 048 The following attributes are available in the error page 049 <pre> 050 javax.servlet.error.status_code 051 javax.servlet.error.exception 052 javax.servlet.error.request_uri 053 javax.servlet.error.servlet_name 054 </pre> 055 <p> 056 This servlet also accepts an <i>optional</i> <b><tt>404_page</tt></b> 057 parameter. This parameter, if present, should contain a <i>web document 058 root</i>-relative path to a 404 or not found page. This page is different 059 than the <i>error_page</i> because it signifies a badly typed URL request 060 for a page that does not exist. (for example, 061 <tt>http://somehost/badpage.mp</tt>). This parameter should be the same as 062 the 404 error code parameter in <tt>web.xml</tt>. 063 For example: 064 <blockquote> 065 <xmp> 066 <!-- in web.xml --> 067 <error-page> 068 <error-code>404</error-code> 069 <location>/errors/not_found.html</location> 070 </error-page> 071 </xmp> 072 </blockquote> 073 <p> 074 075 @author hursh jain 076 */ 077 public class PageServlet extends FCBaseServlet 078 { 079 private static final boolean dbg = false; 080 081 PageMgr pagemgr; 082 083 //error page to include if the response has already been committed 084 //and an exception occurs. 085 String error_page; 086 String page_404; 087 088 public void init(ServletConfig conf) throws ServletException 089 { 090 super.init(conf); 091 try { 092 ServletContext context = conf.getServletContext(); 093 094 synchronized (this) 095 { 096 String docrootstr = context.getRealPath("/"); 097 /* 098 docroot will be: 099 context = "", path = <wwwroot> 100 context = foo, path = <wwwroot>/foo 101 */ 102 if (docrootstr == null) 103 throw new UnavailableException("docroot == null, so I ask you, what can I do ?"); 104 105 File docroot = new File(docrootstr); 106 if (! docroot.exists()) 107 throw new UnavailableException("docroot " + docroot + " does not exist...so I ask you, what can I do ?"); 108 109 File scratchroot = null; 110 if (pagemgr == null) 111 { 112 scratchroot = new File(docroot + File.separator + 113 "WEB-INF" + File.separator + "molly_tmp"); 114 115 //mkdirs for /a/b/c.java makes THREE dirs: 'a', 'b' and 'c.java' 116 //but that's not an issue here. 117 if (! scratchroot.exists()) 118 scratchroot.mkdirs(); 119 120 pagemgr = new PageMgr(this, docroot, scratchroot, log); 121 } 122 123 error_page = WebUtil.getParam(this, "error_page", null); 124 page_404 = WebUtil.getParam(this, "404_page", null); 125 126 log.info("PageServlet init() finished"); 127 log.info("Document root=", docroot); 128 log.info("Scratch root=", scratchroot); 129 log.info("Error page=", error_page); 130 log.info("404 page=", page_404); 131 } 132 } 133 catch (Exception e) { 134 throw new ServletException(e); 135 } 136 } 137 138 protected void service(HttpServletRequest req, HttpServletResponse res) 139 throws ServletException, java.io.IOException 140 { 141 //The servlet engine fills in the servletpath appropriately for 142 //extension mapped servlets, so for example, if we have mapped 143 //*.mp or *.jsp to PageServlet, then when PageServlet gets the 144 //request for /a/b/c.mp, the servletpath in the request object 145 //will be /a/b/c.mp 146 147 //tomcat(piece of shit) sends context=/foo, servletpath=bar.jsp 148 //for /foo/bar.jsp even for the *DEFAULT* (/) context. 149 //So we say: 150 // String path = req.getContextPath() + req.getServletPath(); 151 //looks like tomcat(piece of shit) may or may not have fixed the 152 //above bug. i just don't give a shit about tomcat anymore. 153 // 154 //The page path is relative from the context. The pagemgr was created 155 //with the appropriate docroot that includes the webapp context (if any). 156 //We just need to pass it the servlet path to get the page. 157 String path = req.getServletPath(); 158 159 if (dbg) log.bug("Enter request for path: ", path); 160 161 if (path == null) 162 throw new ServletException("Don't know how to handle this request, servletpath was null"); 163 164 try { 165 if (dbg) { 166 System.out.format("PageServlet: INCLUDE info: uri=%s, c_path=%s, s_path=%s, path_info=%s, q=%s", 167 (String) req.getAttribute("javax.servlet.include.req_uri"), 168 (String) req.getAttribute("javax.servlet.include.context_path"), 169 (String) req.getAttribute("javax.servlet.include.servlet_path"), 170 (String) req.getAttribute("javax.servlet.include.path_info"), 171 (String) req.getAttribute("javax.servlet.include.query_string")); 172 173 System.out.println(); 174 175 System.out.format("PageServlet: FORWARD info: uri=%s, c_path=%s, s_path=%s, path_info=%s, q=%s", 176 (String) req.getAttribute("javax.servlet.forward.req_uri"), 177 (String) req.getAttribute("javax.servlet.forward.context_path"), 178 (String) req.getAttribute("javax.servlet.forward.servlet_path"), 179 (String) req.getAttribute("javax.servlet.forward.path_info"), 180 (String) req.getAttribute("javax.servlet.forward.query_string")); 181 182 System.out.println(); 183 } 184 185 final String include_path = 186 (String) req.getAttribute("javax.servlet.include.servlet_path"); 187 188 if (include_path != null) { 189 //RequestDispatcher include ? 190 path = include_path; 191 } 192 else{ 193 /* 194 WE DONT DO THIS SINCE forward.* properties are for the ORIGINAL 195 REQUEST. the path is already properly set for forwards. 196 197 final String forward_path = 198 (String) req.getAttribute("javax.servlet.forward.servlet_path"); 199 //RequestDispatcher forward ? 200 if (forward_path != null) { 201 path = forward_path; 202 } 203 */ 204 } 205 206 if (dbg) log.bug("Asking PageMgr for the page: ", path); 207 Page page = pagemgr.getPage(path); 208 if (dbg) log.bug("PageMgr returned page: [", path, "]:", page); 209 210 if (page == null) 211 { 212 if (page_404 == null) { 213 throw new ServletException("Molly page:" + path + " not found."); 214 } 215 else{ 216 WebUtil.clientRedirect(req, res, page_404); 217 return; 218 } 219 } 220 221 page.render(req, res); 222 } 223 catch (Exception e) 224 { 225 //this helps us figure out bugs in the "error.mp" page itself (which 226 //otherwise hard to see, since error.mp -> parse error --> error.mp) 227 if (dbg) log.bug(IOUtil.throwableToString(e)); 228 229 //error occured after page partially shown to user 230 if (res.isCommitted() && error_page != null) 231 { 232 req.setAttribute("javax.servlet.error.status_code", 500); 233 req.setAttribute("javax.servlet.error.exception", e); 234 req.setAttribute("javax.servlet.error.request_uri", path); 235 req.setAttribute("javax.servlet.error.servlet_name", "Molly Page Servlet"); 236 237 RequestDispatcher rd = req.getRequestDispatcher(error_page); 238 rd.include(req, res); 239 return; 240 } 241 242 //page hasn't been shown to the user. The container will set all 243 //the javax.servlet.error.* properties and forward to the error 244 //page (if any) that is configured for the container. 245 ServletException se = new ServletException(e.toString()); 246 se.setStackTrace(e.getStackTrace()); 247 throw se; 248 } 249 } 250 251 public String toString() { 252 return "Scrumptious and delicious Page Servlet ! (See: www.mollypages.org)"; 253 } 254 255 public void destroy() 256 { 257 try { 258 pagemgr.destroy(); 259 pagemgr = null; 260 super.destroy(); 261 } 262 catch (Exception e) { 263 System.out.println(IOUtil.throwableToString(e)); 264 } 265 } 266 267 }