Coverage Report - org.melati.servlet.PoemServlet
 
Classes in this File Line Coverage Branch Coverage Complexity
PoemServlet
50%
47/94
41%
13/32
3.462
PoemServlet$1
78%
25/32
0%
0/2
3.462
 
 1  
 /*
 2  
  * $Source: /usr/cvsroot/melati/melati/src/main/java/org/melati/servlet/PoemServlet.java,v $
 3  
  * $Revision: 1.39 $
 4  
  *
 5  
  * Copyright (C) 2000 Tim Joyce
 6  
  *
 7  
  * Part of Melati (http://melati.org), a framework for the rapid
 8  
  * development of clean, maintainable web applications.
 9  
  *
 10  
  * Melati is free software; Permission is granted to copy, distribute
 11  
  * and/or modify this software under the terms either:
 12  
  *
 13  
  * a) the GNU General Public License as published by the Free Software
 14  
  *    Foundation; either version 2 of the License, or (at your option)
 15  
  *    any later version,
 16  
  *
 17  
  *    or
 18  
  *
 19  
  * b) any version of the Melati Software License, as published
 20  
  *    at http://melati.org
 21  
  *
 22  
  * You should have received a copy of the GNU General Public License and
 23  
  * the Melati Software License along with this program;
 24  
  * if not, write to the Free Software Foundation, Inc.,
 25  
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 26  
  * GNU General Public License and visit http://melati.org to obtain the
 27  
  * Melati Software License.
 28  
  *
 29  
  * Feel free to contact the Developers of Melati (http://melati.org),
 30  
  * if you would like to work out a different arrangement than the options
 31  
  * outlined here.  It is our intention to allow Melati to be used by as
 32  
  * wide an audience as possible.
 33  
  *
 34  
  * This program is distributed in the hope that it will be useful,
 35  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 36  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 37  
  * GNU General Public License for more details.
 38  
  *
 39  
  * Contact details for copyright holder:
 40  
  *
 41  
  *     Tim Joyce <timj@paneris.org>
 42  
  *     http://paneris.org/
 43  
  *     68 Sandbanks Rd, Poole, Dorset. BH14 8BY. UK
 44  
  */
 45  
 
 46  
 package org.melati.servlet;
 47  
 
 48  
 import java.io.IOException;
 49  
 import java.io.PrintWriter;
 50  
 
 51  
 import javax.servlet.ServletException;
 52  
 import javax.servlet.http.HttpServletRequest;
 53  
 
 54  
 import org.melati.Melati;
 55  
 import org.melati.PoemContext;
 56  
 import org.melati.poem.AccessPoemException;
 57  
 import org.melati.poem.Field;
 58  
 import org.melati.poem.NoSuchColumnPoemException;
 59  
 import org.melati.poem.PoemThread;
 60  
 import org.melati.poem.PoemTask;
 61  
 import org.melati.poem.AccessToken;
 62  
 import org.melati.poem.NoMoreTransactionsException;
 63  
 import org.melati.util.MelatiWriter;
 64  
 import org.melati.util.StringUtils;
 65  
 
 66  
 /**
 67  
  * Base class to use Poem with Servlets.
 68  
  * <p>
 69  
  * Simply extend this class and override the doPoemRequest method. If you are
 70  
  * going to use a template engine look at TemplateServlet.
 71  
  * <UL>
 72  
  * <LI> <A NAME=pathinfoscan>By default, the path info of the URL by which the
 73  
  * servlet was called up is examined to determine the `logical name' of the
 74  
  * Melati POEM database to which the servlet should connect, and possibly a
 75  
  * table within that database, an object within that table, and a `method' to
 76  
  * apply to that object.</A> The URL is expected to take one of the following
 77  
  * forms: <BLOCKQUOTE><TT> http://<I>h</I>/<I>s</I>/<I>db</I>/ <BR>
 78  
  * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>meth</I> <BR>
 79  
  * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>tbl</I>/<I>meth</I> <BR>
 80  
  * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>tbl</I>/<I>troid</I>/<I>meth</I>
 81  
  * <BR>
 82  
  * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>tbl</I>/<I>troid</I>/<I>meth</I>/<I>other</I>
 83  
  * </TT></BLOCKQUOTE> and the following components are broken out of the path
 84  
  * info and passed to your application code in the <TT>melati</TT> parameter
 85  
  * (which is also copied automatically into <TT>context</TT> so that it is
 86  
  * easily available in templates): 
 87  
  * <TABLE>
 88  
  * <TR>
 89  
  * <TD><TT><I>h</I></TT></TD>
 90  
  * <TD>host name, such as <TT>www.melati.org</TT></TD>
 91  
  * </TR>
 92  
  * <TR>
 93  
  * <TD><TT><I>s</I></TT></TD>
 94  
  * <TD> servlet-determining part, such as <TT>melati/org.melati.admin.Admin</TT>
 95  
  * </TD>
 96  
  * </TR>
 97  
  * <TR>
 98  
  * <TD><TT><I>db</I></TT></TD>
 99  
  * <TD> The first element of the path info is taken to be the `logical name' of
 100  
  * the Melati POEM database to which the servlet should connect. It is mapped
 101  
  * onto JDBC connection details via the config file <TT>org.melati.LogicalDatabase.properties</TT>,
 102  
  * of which there is an example in the source tree. This is automatically made
 103  
  * available in templates as <TT>$melati.Database</TT>. </TD>
 104  
  * <TR>
 105  
  * <TD><TT><I>tbl</I></TT></TD>
 106  
  * <TD> The DBMS name of a table with which the servlet is concerned: perhaps it
 107  
  * is meant to list its contents. This is automatically made available in
 108  
  * templates as <TT>$melati.Table</TT>. </TD>
 109  
  * </TR>
 110  
  * <TR>
 111  
  * <TD><TT><I>troid</I></TT></TD>
 112  
  * <TD> The POEM `troid' (table row identifier, or row-unique integer) of a row
 113  
  * within <TT><I>tbl</I></TT> with which the servlet is concerned: perhaps it
 114  
  * is meant to display it. This is automatically made available in templates as
 115  
  * <TT>$melati.Object</TT>. </TD>
 116  
  * </TR>
 117  
  * <TR>
 118  
  * <TD><TT><I>meth</I></TT></TD>
 119  
  * <TD> A freeform string telling your servlet what it is meant to do. This is
 120  
  * automatically made available in templates as <TT>$melati.Method</TT>.
 121  
  * </TD>
 122  
  * </TR>
 123  
  * <TR>
 124  
  * <TD><TT><I>other</I></TT></TD>
 125  
  * <TD> Any other information you wish to put in the pathinfo. This is useful,
 126  
  * for instance, if you wish to specify the &quot;filename&quot; of your
 127  
  * servlet. For instance, if you call <TT>/db/myfiles/0/Download/afile.html</TT>
 128  
  * and return a stream with a content-type of <tt>application/octet-stream</tt>
 129  
  * most browsers will prompt you to save the &quot;file&quot; as
 130  
  * <tt>afile.html</tt> </TD>
 131  
  * </TR>
 132  
  * </TABLE>
 133  
  * <LI> You can change the way these things are determined by overriding <TT>poemContext</TT>.
 134  
  * <LI> Any POEM database operations you perform will be done with the access
 135  
  * rights of the POEM <TT>User</TT> associated with the servlet session. If
 136  
  * there is no established servlet session, the current user will be set to the
 137  
  * default `guest' user. If this method terminates with an <TT>AccessPoemException</TT>,
 138  
  * indicating that you have attempted something which you aren't entitled to do,
 139  
  * the user will be prompted to log in, and the original request will be
 140  
  * retried. The precise mechanism used for login is <A
 141  
  * HREF=#loginmechanism>configurable</A>.
 142  
  * <LI>
 143  
  *  No changes made to the database by other concurrently executing threads
 144  
  * will be visible to you (in the sense that once you have seen a particular
 145  
  * version of a record, you will always subsequently see the same one), and your
 146  
  * own changes will not be made permanent until this method completes
 147  
  * successfully or you perform an explicit <TT>PoemThread.commit()</TT>. If
 148  
  * it terminates with an exception or you issue a <TT>PoemThread.rollback()</TT>,
 149  
  * your changes will be lost.
 150  
  * <LI> <A NAME=loginmechanism>
 151  
  * It's possible to configure how your <TT>PoemServlet</TT>-derived
 152  
  * servlets implement user login.</A> If the properties file <TT><A
 153  
  * HREF=../org.melati.MelatiConfig.properties>
 154  
  * org.melati.MelatiConfig.properties</A></TT> exists and contains a setting
 155  
  * <TT>org.melati.MelatiConfig.accessHandler=<I>foo</I></TT>, then <TT><I>foo</I></TT>
 156  
  * is taken to be the name of a class implementing the <TT>AccessHandler</TT>
 157  
  * interface. The default is <TT>HttpSessionAccessHandler</TT>, which stores
 158  
  * the user id in the servlet session, and redirects to the <TT>Login</TT>
 159  
  * servlet to throw up templated login screens. If instead you specify 
 160  
  * <TT>HttpBasicAuthenticationAccessHandler</TT>, the user id is maintained 
 161  
  * using HTTP Basic Authentication (RFC2068 11.1, the
 162  
  * mechanism commonly used to password-protect static pages), and the task of
 163  
  * popping up login dialogs is delegated to the browser. The advantage of the
 164  
  * former method is that the user gets a more informative interface which is
 165  
  * more under the designer's control; the advantage of the latter method is that
 166  
  * no cookies or URL rewriting are required---for instance it is probably more
 167  
  * appropriate for WAP phones. Both methods involve sending the user's password
 168  
  * in plain text across the public network.
 169  
  * </UL>
 170  
  * 
 171  
  * @see org.melati.poem.Database#guestAccessToken
 172  
  * @see org.melati.poem.PoemThread#commit
 173  
  * @see org.melati.poem.PoemThread#rollback
 174  
  * @see #poemContext
 175  
  * @see org.melati.login.AccessHandler
 176  
  * @see org.melati.login.HttpSessionAccessHandler
 177  
  * @see org.melati.login.Login
 178  
  * @see org.melati.login.HttpBasicAuthenticationAccessHandler
 179  
  */
 180  
 
 181  15
 public abstract class PoemServlet extends ConfigServlet {
 182  
 
 183  
   /**
 184  
    * A place to do things before entering the session 
 185  
    * of the user, here is a good place to use root access token.
 186  
    * 
 187  
    * Overriden in TemplateServlet.
 188  
    * 
 189  
    * @param melati
 190  
    *          org.melati.Melati A source of information about the Melati
 191  
    *          database context (database, table, object) and utility objects
 192  
    *          such as error handlers.
 193  
    */
 194  
 
 195  
   protected void prePoemSession(Melati melati) throws Exception {
 196  1
     Melati shutEclipseUp = melati;
 197  1
     melati = shutEclipseUp;
 198  1
   }
 199  
 
 200  
   /**
 201  
    * @see javax.servlet.Servlet#destroy()
 202  
    */
 203  
   public void destroy() {
 204  14
     super.destroy();
 205  14
   }
 206  
 
 207  
   /**
 208  
    * Process the request.
 209  
    */
 210  
 
 211  
   protected void doConfiguredRequest(final Melati melati)
 212  
       throws ServletException, IOException {
 213  
 
 214  
     // Set up a POEM session and call the application code
 215  
 
 216  
     // Do something outside of the PoemSession
 217  
     try {
 218  12
       melati.getConfig().getAccessHandler().buildRequest(melati);
 219  12
       prePoemSession(melati);
 220  0
     } catch (Exception e) {
 221  
         // we have to log this here, otherwise we lose the stacktrace
 222  0
         error(melati, e);
 223  0
         throw new TrappedException(e.toString());
 224  12
     }
 225  
 
 226  12
     final PoemServlet _this = this;
 227  
 
 228  12
     melati.getDatabase().inSession(AccessToken.root, new PoemTask() {
 229  
       public void run() {
 230  12
         String poemAdministratorsName = null;
 231  12
         String poemAdministratorsEmail = null;
 232  
 
 233  
         try {
 234  
           try {
 235  12
             poemAdministratorsName = melati.getDatabase().administratorUser().getName();
 236  12
             Field emailField = null;
 237  
             try {
 238  12
               emailField = melati.getDatabase().administratorUser().getField("email");
 239  2
               poemAdministratorsEmail = emailField.toString();
 240  10
             } catch (NoSuchColumnPoemException e) {
 241  10
               poemAdministratorsEmail = "noEmailDefined@nobody.com";
 242  2
             }
 243  12
             _this.setSysAdminName(poemAdministratorsName);
 244  12
             _this.setSysAdminEmail(poemAdministratorsEmail);
 245  
             
 246  0
           } catch (Exception e) {
 247  0
             _handleException(melati, e);
 248  12
           }
 249  0
         } catch (Exception e) {
 250  
           // we have to log this here, otherwise we lose the stacktrace
 251  0
           error(melati, e);
 252  0
           throw new TrappedException(e.toString());
 253  12
         }
 254  
         
 255  
         
 256  12
         melati.getConfig().getAccessHandler().establishUser(melati);
 257  12
         melati.loadTableAndObject();
 258  
 
 259  
         try {
 260  
           try {
 261  12
             _this.doPoemRequest(melati);
 262  5
           } catch (Exception e) {
 263  5
             _handleException(melati, e);
 264  7
           }
 265  2
         } catch (Exception e) {
 266  
           // we have to log this here, otherwise we lose the stacktrace
 267  2
           error(melati, e);
 268  2
           throw new TrappedException(e.toString());
 269  10
         }
 270  10
       }
 271  
 
 272  12
       public String toString() {
 273  0
         HttpServletRequest request = melati.getRequest();
 274  0
         return "PoemServlet: "
 275  
             + ((request == null) ? "(no request present)" : request
 276  
                 .getRequestURI());
 277  
       }
 278  
     });
 279  10
   }
 280  
 
 281  
   /**
 282  
    * Override this to provide a different administrator's details to the
 283  
    * database admin user.
 284  
    * 
 285  
    * @return the System Administrators name.
 286  
    */
 287  
   public String getSysAdminName() {
 288  4
     return sysAdminName;
 289  
   }
 290  
 
 291  
   /**
 292  
    * Override this to provide a different administrator's details to the
 293  
    * database admin user.
 294  
    * 
 295  
    * @return the System Administrators email address.
 296  
    */
 297  
   public String getSysAdminEmail() {
 298  4
     return sysAdminEmail;
 299  
   }
 300  
 
 301  
   /**
 302  
    * Default method to handle an exception without a template engine.
 303  
    * 
 304  
    * @param melati
 305  
    *          the Melati
 306  
    * @param exception
 307  
    *          the exception to handle
 308  
    */
 309  
   protected void handleException(Melati melati, Exception exception)
 310  
       throws Exception {
 311  
 
 312  5
     if (exception instanceof AccessPoemException) {
 313  3
       melati.getConfig().getAccessHandler().handleAccessException(melati,
 314  
           (AccessPoemException) exception);
 315  2
     } else if (exception instanceof NoMoreTransactionsException) {
 316  0
       exception.printStackTrace(System.err);
 317  0
       dbBusyMessage(melati);
 318  
     } else
 319  2
       throw exception;
 320  3
   }
 321  
 
 322  
   protected final void _handleException(Melati melati, Exception exception)
 323  
       throws Exception {
 324  
     try {
 325  5
       handleException(melati, exception);
 326  2
     } catch (Exception e) {
 327  2
       PoemThread.rollback();
 328  2
       throw e;
 329  3
     }
 330  3
   }
 331  
 
 332  
   protected void dbBusyMessage(Melati melati) throws IOException {
 333  0
     melati.getResponse().setContentType("text/html");
 334  0
     MelatiWriter mw = melati.getWriter();
 335  
     // get rid of anything that has been written so far
 336  0
     mw.reset();
 337  0
     PrintWriter out = mw.getPrintWriter();
 338  0
     out.println("<html>\n<head><title>Server Busy</title></head>");
 339  0
     out.println("<body>\n<h4>Server Busy</h4>");
 340  0
     out.println("<p>Please try again in a short while</p>");
 341  0
     out.println("</body>\n</html>");
 342  0
     melati.write();
 343  0
   }
 344  
 
 345  
   protected PoemContext poemContext(Melati melati) throws PathInfoException {
 346  
 
 347  7
     PoemContext it = new PoemContext();
 348  
 
 349  7
     String initParameterPathInfo = getInitParameter("pathInfo");
 350  
     String[] parts;
 351  7
     if (initParameterPathInfo != null)
 352  3
       parts = StringUtils.split(initParameterPathInfo, '/');
 353  
     else
 354  4
       parts = melati.getPathInfoParts();
 355  
 
 356  
     // set it to something in order to provoke meaningful error
 357  7
     it.setLogicalDatabase("");
 358  7
     if (parts.length > 0) {
 359  7
       it.setLogicalDatabase(parts[0]);
 360  7
       if (parts.length == 2)
 361  2
         it.setMethod(parts[1]);
 362  7
       if (parts.length == 3) {
 363  5
         it.setTable(parts[1]);
 364  5
         it.setMethod(parts[2]);
 365  
       }
 366  7
       if (parts.length >= 4) {
 367  0
         it.setTable(parts[1]);
 368  
         try {
 369  0
           it.setTroid(new Integer(parts[2]));
 370  0
         } catch (NumberFormatException e) {
 371  0
           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
 372  0
         }
 373  0
         if (parts.length == 4) {
 374  0
           it.setMethod(parts[3]);
 375  
         } else {
 376  0
           String pathInfo = melati.getRequest().getPathInfo();
 377  0
           pathInfo = pathInfo.substring(1);
 378  0
           for (int i = 0; i < 3; i++) {
 379  0
             pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
 380  
           }
 381  0
           it.setMethod(pathInfo);
 382  
         }
 383  
       }
 384  
     }
 385  7
     return it;
 386  
   }
 387  
 
 388  
   /*
 389  
    * This is provided for convenience, so you don't have to specify the
 390  
    * logicaldatabase on the pathinfo. This is a very good idea when writing your
 391  
    * applications where you are typically only accessing a single database.
 392  
    * Simply override poemContext(Melati melati) thus: 
 393  
    * <code> 
 394  
    * protected PoemContext poemContext(Melati melati) throws PathInfoException { 
 395  
    *   return poemContextWithLDB(melati,"<your logical database name>"); 
 396  
    * } 
 397  
    * </code>
 398  
    */
 399  
   protected PoemContext poemContextWithLDB(Melati melati, String logicalDatabase)
 400  
       throws PathInfoException {
 401  5
     PoemContext it = new PoemContext();
 402  5
     String initParameterPathInfo = getInitParameter("pathInfo");
 403  
     String[] parts;
 404  5
     if (initParameterPathInfo != null)
 405  0
       parts = StringUtils.split(initParameterPathInfo, '/');
 406  
     else
 407  5
       parts = melati.getPathInfoParts();
 408  
 
 409  
     // set it to something in order to provoke meaningful error
 410  5
     it.setLogicalDatabase(logicalDatabase);
 411  5
     if (parts.length > 0) {
 412  0
       if (parts.length == 1)
 413  0
         it.setMethod(parts[0]);
 414  0
       if (parts.length == 2) {
 415  0
         it.setTable(parts[0]);
 416  0
         it.setMethod(parts[1]);
 417  
       }
 418  0
       if (parts.length >= 3) {
 419  0
         it.setTable(parts[0]);
 420  0
         it.setMethod(parts[2]);
 421  
         try {
 422  0
           it.setTroid(new Integer(parts[1]));
 423  0
         } catch (NumberFormatException e) {
 424  0
           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
 425  0
         }
 426  
       }
 427  0
       if (parts.length == 3) {
 428  0
         it.setMethod(parts[2]);
 429  
       } else {
 430  0
         String pathInfo = melati.getRequest().getPathInfo();
 431  0
         pathInfo = pathInfo.substring(1);
 432  0
         for (int i = 0; i < 2; i++) {
 433  0
           pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
 434  
         }
 435  0
         it.setMethod(pathInfo);
 436  
       }
 437  
 
 438  
     }
 439  5
     return it;
 440  
   }
 441  
 
 442  
   /**
 443  
    * Override this method to build up your own output.
 444  
    * 
 445  
    * @param melati
 446  
    */
 447  
   protected abstract void doPoemRequest(Melati melati) throws Exception;
 448  
 
 449  
 }