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/site/resources/withWebmacro/org.melati.servlet.PoemServlet.html,v $
 3  
  * $Revision: 1.1 $
 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  56
 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  2
   }
 199  
 
 200  
   /**
 201  
    * @see javax.servlet.Servlet#destroy()
 202  
    */
 203  
   public void destroy() {
 204  30
     super.destroy();
 205  30
   }
 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  1121
       melati.getConfig().getAccessHandler().buildRequest(melati);
 219  1121
       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  1121
     }
 225  
 
 226  1121
     final PoemServlet _this = this;
 227  
 
 228  1121
     melati.getDatabase().inSession(AccessToken.root, new PoemTask() {
 229  
       public void run() {
 230  1121
         String poemAdministratorsName = null;
 231  1121
         String poemAdministratorsEmail = null;
 232  
 
 233  
         try {
 234  
           try {
 235  1121
             poemAdministratorsName = melati.getDatabase().administratorUser().getName();
 236  1121
             Field emailField = null;
 237  
             try {
 238  1121
               emailField = melati.getDatabase().administratorUser().getField("email");
 239  982
               poemAdministratorsEmail = emailField.toString();
 240  139
             } catch (NoSuchColumnPoemException e) {
 241  139
               poemAdministratorsEmail = "noEmailDefined@nobody.com";
 242  982
             }
 243  1121
             _this.setSysAdminName(poemAdministratorsName);
 244  1121
             _this.setSysAdminEmail(poemAdministratorsEmail);
 245  
             
 246  0
           } catch (Exception e) {
 247  0
             _handleException(melati, e);
 248  1121
           }
 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);
 253  1121
         }
 254  
         
 255  
         
 256  1121
         melati.getConfig().getAccessHandler().establishUser(melati);
 257  1121
         melati.loadTableAndObject();
 258  
 
 259  
         try {
 260  
           try {
 261  1121
             _this.doPoemRequest(melati);
 262  34
           } catch (Exception e) {
 263  34
             _handleException(melati, e);
 264  1087
           }
 265  18
         } catch (Exception e) {
 266  
           // we have to log this here, otherwise we lose the stacktrace
 267  18
           error(melati, e);
 268  18
           throw new TrappedException(e);
 269  1103
         }
 270  1103
       }
 271  
 
 272  
       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  1103
   }
 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  28
     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  28
     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  34
     if (exception instanceof AccessPoemException) {
 313  16
       melati.getConfig().getAccessHandler().handleAccessException(melati,
 314  
           (AccessPoemException) exception);
 315  
     } else
 316  18
       throw exception;
 317  16
   }
 318  
 
 319  
   protected final void _handleException(Melati melati, Exception exception)
 320  
       throws Exception {
 321  
     try {
 322  34
       handleException(melati, exception);
 323  18
     } catch (Exception e) {
 324  18
       PoemThread.rollback();
 325  18
       throw e;
 326  16
     }
 327  16
   }
 328  
 
 329  
   protected PoemContext poemContext(Melati melati) throws PathInfoException {
 330  
 
 331  1103
     PoemContext it = new PoemContext();
 332  
 
 333  1103
     String initParameterPathInfo = getInitParameter("pathInfo");
 334  
     String[] parts;
 335  1103
     if (initParameterPathInfo != null)
 336  6
       parts = StringUtils.split(initParameterPathInfo, '/');
 337  
     else
 338  1097
       parts = melati.getPathInfoParts();
 339  
 
 340  
     // set it to something in order to provoke meaningful error
 341  1103
     it.setLogicalDatabase("");
 342  1103
     if (parts.length > 0) {
 343  1097
       it.setLogicalDatabase(parts[0]);
 344  1097
       if (parts.length == 2)
 345  165
         it.setMethod(parts[1]);
 346  1097
       if (parts.length == 3) {
 347  696
         it.setTable(parts[1]);
 348  696
         it.setMethod(parts[2]);
 349  
       }
 350  1097
       if (parts.length >= 4) {
 351  156
         it.setTable(parts[1]);
 352  
         try {
 353  156
           it.setTroid(new Integer(parts[2]));
 354  0
         } catch (NumberFormatException e) {
 355  0
           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
 356  156
         }
 357  156
         if (parts.length == 4) {
 358  154
           it.setMethod(parts[3]);
 359  
         } else {
 360  2
           String pathInfo = melati.getRequest().getPathInfo();
 361  2
           pathInfo = pathInfo.substring(1);
 362  8
           for (int i = 0; i < 3; i++) {
 363  6
             pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
 364  
           }
 365  2
           it.setMethod(pathInfo);
 366  
         }
 367  
       }
 368  
     }
 369  1103
     return it;
 370  
   }
 371  
 
 372  
   /*
 373  
    * This is provided for convenience, so you don't have to specify the
 374  
    * logicaldatabase on the pathinfo. This is a very good idea when writing your
 375  
    * applications where you are typically only accessing a single database.
 376  
    * Simply override poemContext(Melati melati) thus: 
 377  
    * <code> 
 378  
    * protected PoemContext poemContext(Melati melati) throws PathInfoException { 
 379  
    *   return poemContextWithLDB(melati,"<your logical database name>"); 
 380  
    * } 
 381  
    * </code>
 382  
    */
 383  
   protected PoemContext poemContextWithLDB(Melati melati, String logicalDatabase)
 384  
       throws PathInfoException {
 385  24
     PoemContext it = new PoemContext();
 386  24
     String initParameterPathInfo = getInitParameter("pathInfo");
 387  
     String[] parts;
 388  24
     if (initParameterPathInfo != null)
 389  0
       parts = StringUtils.split(initParameterPathInfo, '/');
 390  
     else
 391  24
       parts = melati.getPathInfoParts();
 392  
 
 393  
     // set it to something in order to provoke meaningful error
 394  24
     it.setLogicalDatabase(logicalDatabase);
 395  24
     if (parts.length > 0) {
 396  2
       if (parts.length == 1)
 397  2
         it.setMethod(parts[0]);
 398  2
       if (parts.length == 2) {
 399  0
         it.setTable(parts[0]);
 400  0
         it.setMethod(parts[1]);
 401  
       }
 402  2
       if (parts.length >= 3) {
 403  0
         it.setTable(parts[0]);
 404  0
         it.setMethod(parts[2]);
 405  
         try {
 406  0
           it.setTroid(new Integer(parts[1]));
 407  0
         } catch (NumberFormatException e) {
 408  0
           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
 409  0
         }
 410  
       }
 411  2
       if (parts.length == 3) {
 412  0
         it.setMethod(parts[2]);
 413  
       } else {
 414  2
         String pathInfo = melati.getRequest().getPathInfo();
 415  2
         pathInfo = pathInfo.substring(1);
 416  6
         for (int i = 0; i < 2; i++) {
 417  4
           pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
 418  
         }
 419  2
         it.setMethod(pathInfo);
 420  
       }
 421  
 
 422  
     }
 423  24
     return it;
 424  
   }
 425  
 
 426  
   /**
 427  
    * Override this method to build up your own output.
 428  
    * 
 429  
    * @param melati
 430  
    */
 431  
   protected abstract void doPoemRequest(Melati melati) throws Exception;
 432  
 
 433  
 }