Coverage Report - org.melati.app.AbstractPoemApp
 
Classes in this File Line Coverage Branch Coverage Complexity
AbstractPoemApp
96%
63/65
93%
15/16
3
AbstractPoemApp$1
88%
15/17
N/A
3
 
 1  
 /*
 2  
  * $Source$
 3  
  * $Revision$
 4  
  *
 5  
  * Copyright (C) 2005 Tim Pizey
 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 Pizey <timp At paneris.org>
 42  
  *     http://paneris.org/~timp
 43  
  */
 44  
 
 45  
 package org.melati.app;
 46  
 
 47  
 
 48  
 import java.io.IOException;
 49  
 
 50  
 import org.melati.Melati;
 51  
 import org.melati.PoemContext;
 52  
 import org.melati.poem.AccessPoemException;
 53  
 import org.melati.poem.PoemDatabaseFactory;
 54  
 import org.melati.poem.PoemThread;
 55  
 import org.melati.poem.PoemTask;
 56  
 import org.melati.poem.AccessToken;
 57  
 import org.melati.poem.util.ArrayUtils;
 58  
 import org.melati.util.ConfigException;
 59  
 import org.melati.util.MelatiException;
 60  
 import org.melati.util.UnexpectedExceptionException;
 61  
 
 62  
 /**
 63  
  * Base class to use Poem as an application.
 64  
  *
 65  
  * <p>
 66  
  * Simply extend this class and override the {@link #doPoemRequest} method.
 67  
  * If you are going to use a template engine look at {@link AbstractTemplateApp}.
 68  
  * </p>
 69  
  *
 70  
  * <UL>
 71  
  * <LI>
 72  
  * The command line arguments are expected in the following order:
 73  
  * <BLOCKQUOTE><TT>
 74  
  * <BR>db
 75  
  * <BR>db method
 76  
  * <BR>db table method
 77  
  * <BR>db table troid  method
 78  
  * </TT></BLOCKQUOTE>
 79  
  *
 80  
  * these components are broken out of the command line arguments and passed to
 81  
  * your application code in the {@link Melati} parameter.
 82  
  *
 83  
  * <TABLE>
 84  
  *   <caption>Arguments</caption>
 85  
  *   <TR>
 86  
  *     <TD><TT><I>db</I></TT></TD>
 87  
  *     <TD>
 88  
  *       The first argument is taken to be the `logical name'
 89  
  *       of the POEM database to which the servlet should connect.  It
 90  
  *       is mapped onto JDBC connection details via the config file
 91  
  *       <TT>org.melati.LogicalDatabase.properties</TT>, of which there is an
 92  
  *       example in the source tree.  This is automatically made available in
 93  
  *       templates as <TT>$melati.Database</TT>.
 94  
  *     </TD>
 95  
  *   <TR>
 96  
  *     <TD><TT><I>table</I></TT></TD>
 97  
  *     <TD>
 98  
  *       The name of a table to work on:
 99  
  *       perhaps it is meant to list its contents.  This is automatically
 100  
  *       made available in templates as <TT>$melati.Table</TT>.
 101  
  *     </TD>
 102  
  *   </TR>
 103  
  *   <TR>
 104  
  *     <TD><TT><I>troid</I></TT></TD>
 105  
  *     <TD>
 106  
  *       The POEM `troid' (table row identifier, or row-unique integer) of a
 107  
  *       row within a <TT><I>table</I></TT>.
 108  
  *       This is automatically made
 109  
  *       available in templates as <TT>$melati.Object</TT>.
 110  
  *     </TD>
 111  
  *   </TR>
 112  
  *   <TR>
 113  
  *     <TD><TT><I>method</I></TT></TD>
 114  
  *     <TD>
 115  
  *       A freeform string telling your servlet what it is meant to do.  This
 116  
  *       is automatically made available in templates as
 117  
  *       <TT>$melati.Method</TT>.
 118  
  *     </TD>
 119  
  *   </TR>
 120  
  * </TABLE>
 121  
  *
 122  
  * <LI>
 123  
  * You can change the way these things are determined by overriding
 124  
  * {@link #poemContext}.
 125  
  * 
 126  
  * <LI>
 127  
  * Any POEM database operations you perform will be done with the access
 128  
  * rights of the POEM <TT>User</TT> associated with the POEM session.  If
 129  
  * there is no established session, the current user will be set to
 130  
  * the default `guest' user.  If this method terminates with an
 131  
  * <TT>AccessPoemException</TT>, indicating that you have attempted something
 132  
  * which you aren't entitled to do, the user will be prompted to log in, and
 133  
  * the original request will be retried.  The precise mechanism used for
 134  
  * login is <A HREF=#loginmechanism>configurable</A>.
 135  
  *
 136  
  * <LI>
 137  
  * No changes made to the database by other concurrently executing threads
 138  
  * will be visible to you (in the sense that once you have seen a particular
 139  
  * version of a record, you will always subsequently see the same one), and
 140  
  * your own changes will not be made permanent until this method completes
 141  
  * successfully or you perform an explicit <TT>PoemThread.commit()</TT>.  If
 142  
  * it terminates with an exception or you issue a
 143  
  * <TT>PoemThread.rollback()</TT>, your changes will be lost.
 144  
  *
 145  
  * <LI>
 146  
  * <A NAME=loginmechanism></A>It is possible to configure how your
 147  
  * <TT>PoemApp</TT>-derived applications implement user login. If the
 148  
  * properties file <TT>org.melati.MelatiApp.properties</TT>
 149  
  * exists and contains a setting
 150  
  * <TT>org.melati.MelatiApp.accessHandler=<I>foo</I></TT>, then
 151  
  * <TT><I>foo</I></TT> is taken to be the name of a class implementing the
 152  
  * <TT>AccessHandler</TT> interface.  
 153  
  * </UL>
 154  
  * 
 155  
  * If you do not need access handling then set your accessHandler to 
 156  
  * <tt>org.melati.login.OpenAccessHandler</tt>.
 157  
  * If you do need access handling then set your accessHandler to 
 158  
  * <tt>org.melati.login.CommandLineAccessHandler</tt>.
 159  
  * However this is not extremely secure, as the user could potentially 
 160  
  * change this seting to <tt>OpenAccessHandler</tt> as they are on the same machine.
 161  
  * 
 162  
  * You can specify the username and password to use by adding command line parameters:
 163  
  * <pre>
 164  
  * <tt>-username user -password password</tt>
 165  
  * </pre>
 166  
  *
 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.OpenAccessHandler
 176  
  * @see org.melati.login.CommandLineAccessHandler
 177  
  */
 178  
 
 179  50
 public abstract class AbstractPoemApp extends AbstractConfigApp implements  App {
 180  
 
 181  1
   private static Boolean taskPerformedOrLoggedInAndTaskAttempted = Boolean.FALSE;
 182  
 
 183  
   /**
 184  
    * Initialise.
 185  
    * 
 186  
    * @param args the command line arguments
 187  
    * @return a configured Melati
 188  
    * {@inheritDoc}
 189  
    * @see org.melati.app.AbstractConfigApp#init(java.lang.String[])
 190  
    */
 191  
   public Melati init(String[] args)  throws MelatiException {
 192  28
     Melati m = super.init(args);
 193  27
     if (m.getDatabase() == null) {
 194  
       try {
 195  1
         super.term(m);
 196  0
       } catch (IOException e) {
 197  0
         e = null;
 198  1
       }
 199  1
       throw new ConfigException("No database configured");
 200  
     }
 201  26
     return m;
 202  
   }
 203  
 
 204  
   /**
 205  
    * Clean up at end of run.
 206  
    * 
 207  
    * @param melati the melati 
 208  
    * @throws IOException 
 209  
    */
 210  
   public void term(Melati melati) throws IOException {
 211  28
     super.term(melati);
 212  28
     if (melati.getDatabase() != null)
 213  28
       PoemDatabaseFactory.disconnectDatabase(melati.getDatabase().getName());
 214  28
   }
 215  
   
 216  
   /**
 217  
    * A place holder for things you might want to do before 
 218  
    * setting up a <code>PoemSession</code>.
 219  
    *
 220  
    * @param melati the current Melati
 221  
    * @throws Exception if anything goes wrong
 222  
    */
 223  
   protected void prePoemSession(Melati melati) throws Exception {
 224  22
     Melati foolEclipse = melati;
 225  22
     melati = foolEclipse;
 226  22
   }
 227  
 
 228  
   protected void doConfiguredRequest(final Melati melati) {
 229  
     // Do something outside of the PoemSession
 230  
     try {
 231  23
       melati.getConfig().getAccessHandler().buildRequest(melati);
 232  23
       prePoemSession(melati);
 233  1
     } catch (Exception e) {
 234  1
       throw new UnexpectedExceptionException(e);
 235  22
     }
 236  
     
 237  
     // Login loop
 238  
     // If not logged-in when required then an exception is thrown.
 239  
     // The exception is handled and the task revisited
 240  
     // The flag is reset to allow this to be run again.
 241  22
     synchronized(taskPerformedOrLoggedInAndTaskAttempted) {
 242  22
       taskPerformedOrLoggedInAndTaskAttempted = Boolean.FALSE;
 243  
       //int goes = 0;
 244  42
       while (taskPerformedOrLoggedInAndTaskAttempted.equals(Boolean.FALSE)) { 
 245  
         //goes ++;
 246  
         //if (goes > 2)
 247  
         //  throw new MelatiBugMelatiException("Problem with login loop logic, goes = " + goes);
 248  24
         melati.getDatabase().inSession (
 249  24
           AccessToken.root, new PoemTask() {
 250  
             public void run () {
 251  24
               melati.getConfig().getAccessHandler().establishUser(melati);
 252  24
               melati.loadTableAndObject();
 253  
               try {
 254  
                 try {
 255  24
                   doPoemRequest(melati);
 256  18
                   taskPerformedOrLoggedInAndTaskAttempted = Boolean.TRUE;
 257  6
                 } catch (Exception e) {
 258  6
                   _handleException (melati, e);
 259  18
                 }
 260  4
               } catch (Exception e) {
 261  4
                 taskPerformedOrLoggedInAndTaskAttempted = Boolean.TRUE;
 262  
                 try {
 263  4
                   term(melati);
 264  0
                 } catch (IOException e1) {
 265  0
                   e1 = null;
 266  4
                 }
 267  4
                 throw new UnhandledExceptionException(e);
 268  20
               }
 269  20
             }
 270  
 
 271  
             // Not sure there is any point in this
 272  
             // Cannot find a way of accessing it
 273  
             //public String toString() {
 274  
             //  return "PoemApp";
 275  
             //}
 276  
           }
 277  
         );
 278  
       }
 279  18
     }
 280  18
   }
 281  
  
 282  
  /**
 283  
   * Default method to handle an exception.
 284  
   *
 285  
   * @param melati the Melati
 286  
   * @param exception the exception to handle
 287  
   */
 288  
   protected static void handleException(Melati melati, Exception exception)
 289  
       throws Exception {
 290  
 
 291  6
     if (exception instanceof AccessPoemException) {
 292  5
       melati.getConfig().getAccessHandler()
 293  5
         .handleAccessException(melati,(AccessPoemException)exception);
 294  
 
 295  
     }
 296  
     else
 297  1
       throw exception;
 298  2
   }
 299  
 
 300  
   protected final void _handleException(Melati melati, Exception exception) 
 301  
        throws Exception {
 302  
     try {
 303  6
       handleException(melati, exception);
 304  
     }
 305  4
     catch (Exception e) {
 306  4
       PoemThread.rollback();
 307  4
       throw e;
 308  2
     }
 309  2
   }
 310  
 
 311  
 
 312  
   protected PoemContext poemContext(Melati melati) 
 313  
       throws InvalidArgumentsException {
 314  19
     String[] args = melati.getArguments();
 315  
     
 316  19
     PoemContext pc = new PoemContext();
 317  19
     if (args.length > 0) {
 318  18
       pc.setLogicalDatabase(args[0]);
 319  18
       setTableTroidMethod(pc, (String[])ArrayUtils.section(args,  1,  args.length));
 320  
     }
 321  
 
 322  18
     return pc;
 323  
   }
 324  
 
 325  
   protected void setTableTroidMethod(PoemContext pc, String[] args){
 326  27
     if (args.length == 1) { 
 327  3
       pc.setMethod(args[0]);
 328  
     }
 329  27
     if (args.length == 2) {
 330  3
       pc.setTable(args[0]);
 331  
       try {
 332  3
         pc.setTroid(new Integer (args[1]));
 333  
       }
 334  1
       catch (NumberFormatException e) {
 335  1
         pc.setMethod(args[1]);
 336  2
       }
 337  
     }
 338  27
     if (args.length >= 3) {
 339  18
       pc.setTable(args[0]);
 340  
       try {
 341  18
         pc.setTroid(new Integer (args[1]));
 342  
       }
 343  1
       catch (NumberFormatException e) {
 344  1
         throw new UnexpectedExceptionException(new InvalidArgumentsException(args, e));
 345  17
       }
 346  17
       pc.setMethod(args[2]);
 347  
     }
 348  26
   }
 349  
   
 350  
    
 351  
   /**
 352  
    * This is provided for convenience, so you don't have to specify the 
 353  
    * logical database in the arguments.  This is useful when
 354  
    * writing applications where you are only accessing a single database.
 355  
    *
 356  
    * Simply override {@link #poemContext(Melati melati)} thus:
 357  
    *
 358  
    * <PRE>
 359  
    * protected PoemContext poemContext(Melati melati) 
 360  
    *     throws InvalidArgumentsException {
 361  
    *   return poemContextWithLDB(melati,"&lt;your logical database name&gt;");
 362  
    * }
 363  
    * </PRE>
 364  
    *
 365  
    */
 366  
   protected PoemContext poemContextWithLDB(Melati melati, 
 367  
                                            String logicalDatabase) 
 368  
       throws InvalidArgumentsException {
 369  9
     PoemContext pc = new PoemContext();
 370  9
     pc.setLogicalDatabase(logicalDatabase);
 371  9
     setTableTroidMethod(pc, melati.getArguments());
 372  9
     return pc;
 373  
   }
 374  
 
 375  
   /**
 376  
    * Override this method to do your own thing.
 377  
    *
 378  
    * @param melati a {@link Melati} containing POEM and other configuration data
 379  
    */
 380  
   protected abstract void doPoemRequest(Melati melati) throws Exception;
 381  
 
 382  
 }