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
006package fc.web.page;
007
008import java.io.*;
009import java.util.*;
010import javax.servlet.*;
011import javax.servlet.http.*;
012
013import fc.io.*;
014import fc.web.servlet.*;
015
016/** 
017The page servlet. Handles web requests that are mapped to a molly server
018page (typically via a <code>*.mp</code> extension).
019<p>
020All uncaught exceptions in the rendered page are wrapped in a
021<tt>ServletException</tt> and thrown up to the servlet container. These are
022typically handled by the container by either showing the full stack trace
023to the user or using a error page configured in the containers
024<tt>web.xml</tt> file. This latter approach is recommended for production
025use. 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>
037However, if some partial response has already been sent to the browser and
038an exception occurs (later in java code on that same page), then the
039error page (if configured in web.xml) cannot typically be displayed.
040<p>
041For that scenario, this servlet accepts an <i>optional</i>
042<b><tt>error_page</tt></b> initialization parameter. This parameter, if
043present, should contain a <i>webapp</i>-relative path to an error page that
044will 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>
048The following attributes are available in the error page
049<pre>
050javax.servlet.error.status_code 
051javax.servlet.error.exception
052javax.servlet.error.request_uri
053javax.servlet.error.servlet_name
054</pre>
055<p>
056This servlet also accepts an <i>optional</i> <b><tt>404_page</tt></b>
057parameter. This parameter, if present, should contain a <i>web document
058root</i>-relative path to a 404 or not found page. This page is different
059than the <i>error_page</i> because it signifies a badly typed URL request
060for a page that does not exist.  (for example,
061<tt>http://somehost/badpage.mp</tt>). This parameter should be the same as
062the 404 error code parameter in <tt>web.xml</tt>.
063For 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*/
077public class PageServlet extends FCBaseServlet
078{
079private static final boolean dbg = false;
080
081PageMgr   pagemgr;
082
083//error page to include if the response has already been committed
084//and an exception occurs.
085String    error_page;
086String    page_404;
087
088public 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
138protected void service(HttpServletRequest req, HttpServletResponse res)
139throws 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            
251public String toString() {
252  return  "Scrumptious and delicious Page Servlet ! (See: www.mollypages.org)";
253  } 
254  
255public 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}