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