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 }