Coverage Report - org.melati.servlet.PoemServlet
 
Classes in this File Line Coverage Branch Coverage Complexity
PoemServlet
71%
67/94
81%
26/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.40 $
 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 At 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  25
 public abstract class PoemServlet extends ConfigServlet {
 182  
 
 183  
   /**
 184  
    * Eclipse generated.
 185  
    */
 186  
   private static final long serialVersionUID = 7694978400584943446L;
 187  
 
 188  
   /**
 189  
    * A place to do things before entering the session 
 190  
    * of the user, here is a good place to use root access token.
 191  
    * 
 192  
    * Overriden in TemplateServlet.
 193  
    * 
 194  
    * @param melati
 195  
    *          org.melati.Melati A source of information about the Melati
 196  
    *          database context (database, table, object) and utility objects
 197  
    *          such as error handlers.
 198  
    */
 199  
 
 200  
   protected void prePoemSession(Melati melati) throws Exception {
 201  1
     Melati shutEclipseUp = melati;
 202  1
     melati = shutEclipseUp;
 203  1
   }
 204  
 
 205  
   /**
 206  
    * @see javax.servlet.Servlet#destroy()
 207  
    */
 208  
   public void destroy() {
 209  14
     super.destroy();
 210  14
   }
 211  
 
 212  
   /**
 213  
    * Process the request.
 214  
    */
 215  
 
 216  
   protected void doConfiguredRequest(final Melati melati)
 217  
       throws ServletException, IOException {
 218  
 
 219  
     // Set up a POEM session and call the application code
 220  
 
 221  
     // Do something outside of the PoemSession
 222  
     try {
 223  481
       melati.getConfig().getAccessHandler().buildRequest(melati);
 224  481
       prePoemSession(melati);
 225  0
     } catch (Exception e) {
 226  
         // we have to log this here, otherwise we lose the stacktrace
 227  0
         error(melati, e);
 228  0
         throw new TrappedException(e.toString());
 229  481
     }
 230  
 
 231  481
     final PoemServlet _this = this;
 232  
 
 233  481
     melati.getDatabase().inSession(AccessToken.root, new PoemTask() {
 234  
       public void run() {
 235  481
         String poemAdministratorsName = null;
 236  481
         String poemAdministratorsEmail = null;
 237  
 
 238  
         try {
 239  
           try {
 240  481
             poemAdministratorsName = melati.getDatabase().administratorUser().getName();
 241  481
             Field emailField = null;
 242  
             try {
 243  481
               emailField = melati.getDatabase().administratorUser().getField("email");
 244  143
               poemAdministratorsEmail = emailField.toString();
 245  338
             } catch (NoSuchColumnPoemException e) {
 246  338
               poemAdministratorsEmail = "noEmailDefined@nobody.com";
 247  143
             }
 248  481
             _this.setSysAdminName(poemAdministratorsName);
 249  481
             _this.setSysAdminEmail(poemAdministratorsEmail);
 250  
             
 251  0
           } catch (Exception e) {
 252  0
             _handleException(melati, e);
 253  481
           }
 254  0
         } catch (Exception e) {
 255  
           // we have to log this here, otherwise we lose the stacktrace
 256  0
           error(melati, e);
 257  0
           throw new TrappedException(e.toString());
 258  481
         }
 259  
         
 260  
         
 261  481
         melati.getConfig().getAccessHandler().establishUser(melati);
 262  481
         melati.loadTableAndObject();
 263  
 
 264  
         try {
 265  
           try {
 266  481
             _this.doPoemRequest(melati);
 267  16
           } catch (Exception e) {
 268  16
             _handleException(melati, e);
 269  465
           }
 270  8
         } catch (Exception e) {
 271  
           // we have to log this here, otherwise we lose the stacktrace
 272  8
           error(melati, e);
 273  8
           throw new TrappedException(e.toString());
 274  473
         }
 275  473
       }
 276  
 
 277  481
       public String toString() {
 278  0
         HttpServletRequest request = melati.getRequest();
 279  0
         return "PoemServlet: "
 280  
             + ((request == null) ? "(no request present)" : request
 281  
                 .getRequestURI());
 282  
       }
 283  
     });
 284  473
   }
 285  
 
 286  
   /**
 287  
    * Override this to provide a different administrator's details to the
 288  
    * database admin user.
 289  
    * 
 290  
    * @return the System Administrators name.
 291  
    */
 292  
   public String getSysAdminName() {
 293  12
     return sysAdminName;
 294  
   }
 295  
 
 296  
   /**
 297  
    * Override this to provide a different administrator's details to the
 298  
    * database admin user.
 299  
    * 
 300  
    * @return the System Administrators email address.
 301  
    */
 302  
   public String getSysAdminEmail() {
 303  12
     return sysAdminEmail;
 304  
   }
 305  
 
 306  
   /**
 307  
    * Default method to handle an exception without a template engine.
 308  
    * 
 309  
    * @param melati
 310  
    *          the Melati
 311  
    * @param exception
 312  
    *          the exception to handle
 313  
    */
 314  
   protected void handleException(Melati melati, Exception exception)
 315  
       throws Exception {
 316  
 
 317  16
     if (exception instanceof AccessPoemException) {
 318  8
       melati.getConfig().getAccessHandler().handleAccessException(melati,
 319  
           (AccessPoemException) exception);
 320  8
     } else if (exception instanceof NoMoreTransactionsException) {
 321  0
       exception.printStackTrace(System.err);
 322  0
       dbBusyMessage(melati);
 323  
     } else
 324  8
       throw exception;
 325  8
   }
 326  
 
 327  
   protected final void _handleException(Melati melati, Exception exception)
 328  
       throws Exception {
 329  
     try {
 330  16
       handleException(melati, exception);
 331  8
     } catch (Exception e) {
 332  8
       PoemThread.rollback();
 333  8
       throw e;
 334  8
     }
 335  8
   }
 336  
 
 337  
   protected void dbBusyMessage(Melati melati) throws IOException {
 338  0
     melati.getResponse().setContentType("text/html");
 339  0
     MelatiWriter mw = melati.getWriter();
 340  
     // get rid of anything that has been written so far
 341  0
     mw.reset();
 342  0
     PrintWriter out = mw.getPrintWriter();
 343  0
     out.println("<html>\n<head><title>Server Busy</title></head>");
 344  0
     out.println("<body>\n<h4>Server Busy</h4>");
 345  0
     out.println("<p>Please try again in a short while</p>");
 346  0
     out.println("</body>\n</html>");
 347  0
     melati.write();
 348  0
   }
 349  
 
 350  
   protected PoemContext poemContext(Melati melati) throws PathInfoException {
 351  
 
 352  474
     PoemContext it = new PoemContext();
 353  
 
 354  474
     String initParameterPathInfo = getInitParameter("pathInfo");
 355  
     String[] parts;
 356  474
     if (initParameterPathInfo != null)
 357  3
       parts = StringUtils.split(initParameterPathInfo, '/');
 358  
     else
 359  471
       parts = melati.getPathInfoParts();
 360  
 
 361  
     // set it to something in order to provoke meaningful error
 362  474
     it.setLogicalDatabase("");
 363  474
     if (parts.length > 0) {
 364  473
       it.setLogicalDatabase(parts[0]);
 365  473
       if (parts.length == 2)
 366  81
         it.setMethod(parts[1]);
 367  473
       if (parts.length == 3) {
 368  284
         it.setTable(parts[1]);
 369  284
         it.setMethod(parts[2]);
 370  
       }
 371  473
       if (parts.length >= 4) {
 372  78
         it.setTable(parts[1]);
 373  
         try {
 374  78
           it.setTroid(new Integer(parts[2]));
 375  0
         } catch (NumberFormatException e) {
 376  0
           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
 377  78
         }
 378  78
         if (parts.length == 4) {
 379  77
           it.setMethod(parts[3]);
 380  
         } else {
 381  1
           String pathInfo = melati.getRequest().getPathInfo();
 382  1
           pathInfo = pathInfo.substring(1);
 383  4
           for (int i = 0; i < 3; i++) {
 384  3
             pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
 385  
           }
 386  1
           it.setMethod(pathInfo);
 387  
         }
 388  
       }
 389  
     }
 390  474
     return it;
 391  
   }
 392  
 
 393  
   /*
 394  
    * This is provided for convenience, so you don't have to specify the
 395  
    * logicaldatabase on the pathinfo. This is a very good idea when writing your
 396  
    * applications where you are typically only accessing a single database.
 397  
    * Simply override poemContext(Melati melati) thus: 
 398  
    * <code> 
 399  
    * protected PoemContext poemContext(Melati melati) throws PathInfoException { 
 400  
    *   return poemContextWithLDB(melati,"<your logical database name>"); 
 401  
    * } 
 402  
    * </code>
 403  
    */
 404  
   protected PoemContext poemContextWithLDB(Melati melati, String logicalDatabase)
 405  
       throws PathInfoException {
 406  8
     PoemContext it = new PoemContext();
 407  8
     String initParameterPathInfo = getInitParameter("pathInfo");
 408  
     String[] parts;
 409  8
     if (initParameterPathInfo != null)
 410  0
       parts = StringUtils.split(initParameterPathInfo, '/');
 411  
     else
 412  8
       parts = melati.getPathInfoParts();
 413  
 
 414  
     // set it to something in order to provoke meaningful error
 415  8
     it.setLogicalDatabase(logicalDatabase);
 416  8
     if (parts.length > 0) {
 417  1
       if (parts.length == 1)
 418  1
         it.setMethod(parts[0]);
 419  1
       if (parts.length == 2) {
 420  0
         it.setTable(parts[0]);
 421  0
         it.setMethod(parts[1]);
 422  
       }
 423  1
       if (parts.length >= 3) {
 424  0
         it.setTable(parts[0]);
 425  0
         it.setMethod(parts[2]);
 426  
         try {
 427  0
           it.setTroid(new Integer(parts[1]));
 428  0
         } catch (NumberFormatException e) {
 429  0
           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
 430  0
         }
 431  
       }
 432  1
       if (parts.length == 3) {
 433  0
         it.setMethod(parts[2]);
 434  
       } else {
 435  1
         String pathInfo = melati.getRequest().getPathInfo();
 436  1
         pathInfo = pathInfo.substring(1);
 437  3
         for (int i = 0; i < 2; i++) {
 438  2
           pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
 439  
         }
 440  1
         it.setMethod(pathInfo);
 441  
       }
 442  
 
 443  
     }
 444  8
     return it;
 445  
   }
 446  
 
 447  
   /**
 448  
    * Override this method to build up your own output.
 449  
    * 
 450  
    * @param melati
 451  
    */
 452  
   protected abstract void doPoemRequest(Melati melati) throws Exception;
 453  
 
 454  
 }