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