Coverage Report - org.melati.servlet.PoemServlet
 
Classes in this File Line Coverage Branch Coverage Complexity
PoemServlet
81%
64/79
83%
25/30
3.667
PoemServlet$1
78%
25/32
0%
0/2
3.667
 
 1  
 /*
 2  
  * $Source: /usr/cvsroot/melati/melati/src/main/java/org/melati/servlet/PoemServlet.java,v $
 3  
  * $Revision: 1.45 $
 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  
 
 50  
 import javax.servlet.ServletException;
 51  
 import javax.servlet.http.HttpServletRequest;
 52  
 
 53  
 import org.melati.Melati;
 54  
 import org.melati.PoemContext;
 55  
 import org.melati.poem.AccessPoemException;
 56  
 import org.melati.poem.Field;
 57  
 import org.melati.poem.NoSuchColumnPoemException;
 58  
 import org.melati.poem.PoemThread;
 59  
 import org.melati.poem.PoemTask;
 60  
 import org.melati.poem.AccessToken;
 61  
 import org.melati.poem.util.StringUtils;
 62  
 
 63  
 /**
 64  
  * Base class to use Poem with Servlets.
 65  
  * <p>
 66  
  * Simply extend this class and override the doPoemRequest method. If you are
 67  
  * going to use a template engine look at TemplateServlet.
 68  
  * <UL>
 69  
  * <LI> <A NAME=pathinfoscan>By default, the path info of the URL by which the
 70  
  * servlet was called up is examined to determine the `logical name' of the
 71  
  * Melati POEM database to which the servlet should connect, and possibly a
 72  
  * table within that database, an object within that table, and a `method' to
 73  
  * apply to that object.</A> The URL is expected to take one of the following
 74  
  * forms: <BLOCKQUOTE><TT> http://<I>h</I>/<I>s</I>/<I>db</I>/ <BR>
 75  
  * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>meth</I> <BR>
 76  
  * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>tbl</I>/<I>meth</I> <BR>
 77  
  * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>tbl</I>/<I>troid</I>/<I>meth</I>
 78  
  * <BR>
 79  
  * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>tbl</I>/<I>troid</I>/<I>meth</I>/<I>other</I>
 80  
  * </TT></BLOCKQUOTE> and the following components are broken out of the path
 81  
  * info and passed to your application code in the <TT>melati</TT> parameter
 82  
  * (which is also copied automatically into <TT>context</TT> so that it is
 83  
  * easily available in templates): 
 84  
  * <TABLE>
 85  
  * <TR>
 86  
  * <TD><TT><I>h</I></TT></TD>
 87  
  * <TD>host name, such as <TT>www.melati.org</TT></TD>
 88  
  * </TR>
 89  
  * <TR>
 90  
  * <TD><TT><I>s</I></TT></TD>
 91  
  * <TD> servlet-determining part, such as <TT>melati/org.melati.admin.Admin</TT>
 92  
  * </TD>
 93  
  * </TR>
 94  
  * <TR>
 95  
  * <TD><TT><I>db</I></TT></TD>
 96  
  * <TD> The first element of the path info is taken to be the `logical name' of
 97  
  * the Melati POEM database to which the servlet should connect. It is mapped
 98  
  * onto JDBC connection details via the config file <TT>org.melati.LogicalDatabase.properties</TT>,
 99  
  * of which there is an example in the source tree. This is automatically made
 100  
  * available in templates as <TT>$melati.Database</TT>. </TD>
 101  
  * <TR>
 102  
  * <TD><TT><I>tbl</I></TT></TD>
 103  
  * <TD> The DBMS name of a table with which the servlet is concerned: perhaps it
 104  
  * is meant to list its contents. This is automatically made available in
 105  
  * templates as <TT>$melati.Table</TT>. </TD>
 106  
  * </TR>
 107  
  * <TR>
 108  
  * <TD><TT><I>troid</I></TT></TD>
 109  
  * <TD> The POEM `troid' (table row identifier, or row-unique integer) of a row
 110  
  * within <TT><I>tbl</I></TT> with which the servlet is concerned: perhaps it
 111  
  * is meant to display it. This is automatically made available in templates as
 112  
  * <TT>$melati.Object</TT>. </TD>
 113  
  * </TR>
 114  
  * <TR>
 115  
  * <TD><TT><I>meth</I></TT></TD>
 116  
  * <TD> A freeform string telling your servlet what it is meant to do. This is
 117  
  * automatically made available in templates as <TT>$melati.Method</TT>.
 118  
  * </TD>
 119  
  * </TR>
 120  
  * <TR>
 121  
  * <TD><TT><I>other</I></TT></TD>
 122  
  * <TD> Any other information you wish to put in the pathinfo. This is useful,
 123  
  * for instance, if you wish to specify the &quot;filename&quot; of your
 124  
  * servlet. For instance, if you call <TT>/db/myfiles/0/Download/afile.html</TT>
 125  
  * and return a stream with a content-type of <tt>application/octet-stream</tt>
 126  
  * most browsers will prompt you to save the &quot;file&quot; as
 127  
  * <tt>afile.html</tt> </TD>
 128  
  * </TR>
 129  
  * </TABLE>
 130  
  * <LI> You can change the way these things are determined by overriding <TT>poemContext</TT>.
 131  
  * <LI> Any POEM database operations you perform will be done with the access
 132  
  * rights of the POEM <TT>User</TT> associated with the servlet session. If
 133  
  * there is no established servlet session, the current user will be set to the
 134  
  * default `guest' user. If this method terminates with an <TT>AccessPoemException</TT>,
 135  
  * indicating that you have attempted something which you aren't entitled to do,
 136  
  * the user will be prompted to log in, and the original request will be
 137  
  * retried. The precise mechanism used for login is <A
 138  
  * HREF=#loginmechanism>configurable</A>.
 139  
  * <LI>
 140  
  *  No changes made to the database by other concurrently executing threads
 141  
  * will be visible to you (in the sense that once you have seen a particular
 142  
  * version of a record, you will always subsequently see the same one), and your
 143  
  * own changes will not be made permanent until this method completes
 144  
  * successfully or you perform an explicit <TT>PoemThread.commit()</TT>. If
 145  
  * it terminates with an exception or you issue a <TT>PoemThread.rollback()</TT>,
 146  
  * your changes will be lost.
 147  
  * <LI> <A NAME=loginmechanism>
 148  
  * It's possible to configure how your <TT>PoemServlet</TT>-derived
 149  
  * servlets implement user login.</A> If the properties file <TT><A
 150  
  * HREF=../org.melati.MelatiConfig.properties>
 151  
  * org.melati.MelatiConfig.properties</A></TT> exists and contains a setting
 152  
  * <TT>org.melati.MelatiConfig.accessHandler=<I>foo</I></TT>, then <TT><I>foo</I></TT>
 153  
  * is taken to be the name of a class implementing the <TT>AccessHandler</TT>
 154  
  * interface. The default is <TT>HttpSessionAccessHandler</TT>, which stores
 155  
  * the user id in the servlet session, and redirects to the <TT>Login</TT>
 156  
  * servlet to throw up templated login screens. If instead you specify 
 157  
  * <TT>HttpBasicAuthenticationAccessHandler</TT>, the user id is maintained 
 158  
  * using HTTP Basic Authentication (RFC2068 11.1, the
 159  
  * mechanism commonly used to password-protect static pages), and the task of
 160  
  * popping up login dialogs is delegated to the browser. The advantage of the
 161  
  * former method is that the user gets a more informative interface which is
 162  
  * more under the designer's control; the advantage of the latter method is that
 163  
  * no cookies or URL rewriting are required---for instance it is probably more
 164  
  * appropriate for WAP phones. Both methods involve sending the user's password
 165  
  * in plain text across the public network.
 166  
  * </UL>
 167  
  * 
 168  
  * @see org.melati.poem.Database#guestAccessToken
 169  
  * @see org.melati.poem.PoemThread#commit
 170  
  * @see org.melati.poem.PoemThread#rollback
 171  
  * @see #poemContext
 172  
  * @see org.melati.login.AccessHandler
 173  
  * @see org.melati.login.HttpSessionAccessHandler
 174  
  * @see org.melati.login.Login
 175  
  * @see org.melati.login.HttpBasicAuthenticationAccessHandler
 176  
  */
 177  
 
 178  28
 public abstract class PoemServlet extends ConfigServlet {
 179  
 
 180  
   /**
 181  
    * Eclipse generated.
 182  
    */
 183  
   private static final long serialVersionUID = 7694978400584943446L;
 184  
 
 185  
   /**
 186  
    * A place to do things before entering the session 
 187  
    * of the user, here is a good place to use root access token.
 188  
    * 
 189  
    * Overriden in TemplateServlet.
 190  
    * 
 191  
    * @param melati
 192  
    *          org.melati.Melati A source of information about the Melati
 193  
    *          database context (database, table, object) and utility objects
 194  
    *          such as error handlers.
 195  
    */
 196  
 
 197  
   protected void prePoemSession(Melati melati) throws Exception {
 198  1
   }
 199  
 
 200  
   /**
 201  
    * @see javax.servlet.Servlet#destroy()
 202  
    */
 203  
   public void destroy() {
 204  15
     super.destroy();
 205  15
   }
 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  532
       melati.getConfig().getAccessHandler().buildRequest(melati);
 219  532
       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("Problem in prePoemSession", e);
 224  532
     }
 225  
 
 226  532
     final PoemServlet _this = this;
 227  
 
 228  532
     melati.getDatabase().inSession(AccessToken.root, new PoemTask() {
 229  
       @SuppressWarnings("unchecked")
 230  
       public void run() {
 231  532
         String poemAdministratorsName = null;
 232  532
         String poemAdministratorsEmail = null;
 233  
 
 234  
         try {
 235  
           try {
 236  532
             poemAdministratorsName = melati.getDatabase().administratorUser().getName();
 237  532
             Field<String> emailField = null;
 238  
             try {
 239  532
               emailField = (Field<String>)melati.getDatabase().administratorUser().getField("email");
 240  465
               poemAdministratorsEmail = emailField.toString();
 241  67
             } catch (NoSuchColumnPoemException e) {
 242  67
               poemAdministratorsEmail = "noEmailDefined@nobody.com";
 243  465
             }
 244  532
             _this.setSysAdminName(poemAdministratorsName);
 245  532
             _this.setSysAdminEmail(poemAdministratorsEmail);
 246  
             
 247  0
           } catch (Exception e) {
 248  0
             _handleException(melati, e);
 249  532
           }
 250  0
         } catch (Exception e) {
 251  
           // we have to log this here, otherwise we lose the stacktrace
 252  0
           error(melati, e);
 253  0
           throw new TrappedException(e);
 254  532
         }
 255  
         
 256  
         
 257  532
         melati.getConfig().getAccessHandler().establishUser(melati);
 258  532
         melati.loadTableAndObject();
 259  
 
 260  
         try {
 261  
           try {
 262  532
             _this.doPoemRequest(melati);
 263  18
           } catch (Exception e) {
 264  18
             _handleException(melati, e);
 265  514
           }
 266  10
         } catch (Exception e) {
 267  
           // we have to log this here, otherwise we lose the stacktrace
 268  10
           error(melati, e);
 269  10
           throw new TrappedException(e);
 270  522
         }
 271  522
       }
 272  
 
 273  
       public String toString() {
 274  0
         HttpServletRequest request = melati.getRequest();
 275  0
         return "PoemServlet: "
 276  
             + ((request == null) ? "(no request present)" : request
 277  
                 .getRequestURI());
 278  
       }
 279  
     });
 280  522
   }
 281  
 
 282  
   /**
 283  
    * Override this to provide a different administrator's details to the
 284  
    * database admin user.
 285  
    * 
 286  
    * @return the System Administrators name.
 287  
    */
 288  
   public String getSysAdminName() {
 289  13
     return sysAdminName;
 290  
   }
 291  
 
 292  
   /**
 293  
    * Override this to provide a different administrator's details to the
 294  
    * database admin user.
 295  
    * 
 296  
    * @return the System Administrators email address.
 297  
    */
 298  
   public String getSysAdminEmail() {
 299  13
     return sysAdminEmail;
 300  
   }
 301  
 
 302  
   /**
 303  
    * Default method to handle an exception without a template engine.
 304  
    * 
 305  
    * @param melati
 306  
    *          the Melati
 307  
    * @param exception
 308  
    *          the exception to handle
 309  
    */
 310  
   protected void handleException(Melati melati, Exception exception)
 311  
       throws Exception {
 312  
 
 313  18
     if (exception instanceof AccessPoemException) {
 314  8
       melati.getConfig().getAccessHandler().handleAccessException(melati,
 315  
           (AccessPoemException) exception);
 316  
     } else
 317  10
       throw exception;
 318  8
   }
 319  
 
 320  
   protected final void _handleException(Melati melati, Exception exception)
 321  
       throws Exception {
 322  
     try {
 323  18
       handleException(melati, exception);
 324  10
     } catch (Exception e) {
 325  10
       PoemThread.rollback();
 326  10
       throw e;
 327  8
     }
 328  8
   }
 329  
 
 330  
   protected PoemContext poemContext(Melati melati) throws PathInfoException {
 331  
 
 332  523
     PoemContext it = new PoemContext();
 333  
 
 334  523
     String initParameterPathInfo = getInitParameter("pathInfo");
 335  
     String[] parts;
 336  523
     if (initParameterPathInfo != null)
 337  3
       parts = StringUtils.split(initParameterPathInfo, '/');
 338  
     else
 339  520
       parts = melati.getPathInfoParts();
 340  
 
 341  
     // set it to something in order to provoke meaningful error
 342  523
     it.setLogicalDatabase("");
 343  523
     if (parts.length > 0) {
 344  520
       it.setLogicalDatabase(parts[0]);
 345  520
       if (parts.length == 2)
 346  78
         it.setMethod(parts[1]);
 347  520
       if (parts.length == 3) {
 348  320
         it.setTable(parts[1]);
 349  320
         it.setMethod(parts[2]);
 350  
       }
 351  520
       if (parts.length >= 4) {
 352  82
         it.setTable(parts[1]);
 353  
         try {
 354  82
           it.setTroid(new Integer(parts[2]));
 355  0
         } catch (NumberFormatException e) {
 356  0
           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
 357  82
         }
 358  82
         if (parts.length == 4) {
 359  81
           it.setMethod(parts[3]);
 360  
         } else {
 361  1
           String pathInfo = melati.getRequest().getPathInfo();
 362  1
           pathInfo = pathInfo.substring(1);
 363  4
           for (int i = 0; i < 3; i++) {
 364  3
             pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
 365  
           }
 366  1
           it.setMethod(pathInfo);
 367  
         }
 368  
       }
 369  
     }
 370  523
     return it;
 371  
   }
 372  
 
 373  
   /*
 374  
    * This is provided for convenience, so you don't have to specify the
 375  
    * logicaldatabase on the pathinfo. This is a very good idea when writing your
 376  
    * applications where you are typically only accessing a single database.
 377  
    * Simply override poemContext(Melati melati) thus: 
 378  
    * <code> 
 379  
    * protected PoemContext poemContext(Melati melati) throws PathInfoException { 
 380  
    *   return poemContextWithLDB(melati,"<your logical database name>"); 
 381  
    * } 
 382  
    * </code>
 383  
    */
 384  
   protected PoemContext poemContextWithLDB(Melati melati, String logicalDatabase)
 385  
       throws PathInfoException {
 386  12
     PoemContext it = new PoemContext();
 387  12
     String initParameterPathInfo = getInitParameter("pathInfo");
 388  
     String[] parts;
 389  12
     if (initParameterPathInfo != null)
 390  0
       parts = StringUtils.split(initParameterPathInfo, '/');
 391  
     else
 392  12
       parts = melati.getPathInfoParts();
 393  
 
 394  
     // set it to something in order to provoke meaningful error
 395  12
     it.setLogicalDatabase(logicalDatabase);
 396  12
     if (parts.length > 0) {
 397  1
       if (parts.length == 1)
 398  1
         it.setMethod(parts[0]);
 399  1
       if (parts.length == 2) {
 400  0
         it.setTable(parts[0]);
 401  0
         it.setMethod(parts[1]);
 402  
       }
 403  1
       if (parts.length >= 3) {
 404  0
         it.setTable(parts[0]);
 405  0
         it.setMethod(parts[2]);
 406  
         try {
 407  0
           it.setTroid(new Integer(parts[1]));
 408  0
         } catch (NumberFormatException e) {
 409  0
           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
 410  0
         }
 411  
       }
 412  1
       if (parts.length == 3) {
 413  0
         it.setMethod(parts[2]);
 414  
       } else {
 415  1
         String pathInfo = melati.getRequest().getPathInfo();
 416  1
         pathInfo = pathInfo.substring(1);
 417  3
         for (int i = 0; i < 2; i++) {
 418  2
           pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
 419  
         }
 420  1
         it.setMethod(pathInfo);
 421  
       }
 422  
 
 423  
     }
 424  12
     return it;
 425  
   }
 426  
 
 427  
   /**
 428  
    * Override this method to build up your own output.
 429  
    * 
 430  
    * @param melati
 431  
    */
 432  
   protected abstract void doPoemRequest(Melati melati) throws Exception;
 433  
 
 434  
 }