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    }