View Javadoc
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 public abstract class AbstractPoemApp extends AbstractConfigApp implements  App {
180 
181   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     Melati m = super.init(args);
193     if (m.getDatabase() == null) {
194       try {
195         super.term(m);
196       } catch (IOException e) {
197         e = null;
198       }
199       throw new ConfigException("No database configured");
200     }
201     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     super.term(melati);
212     if (melati.getDatabase() != null)
213       PoemDatabaseFactory.disconnectDatabase(melati.getDatabase().getName());
214   }
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     Melati foolEclipse = melati;
225     melati = foolEclipse;
226   }
227 
228   protected void doConfiguredRequest(final Melati melati) {
229     // Do something outside of the PoemSession
230     try {
231       melati.getConfig().getAccessHandler().buildRequest(melati);
232       prePoemSession(melati);
233     } catch (Exception e) {
234       throw new UnexpectedExceptionException(e);
235     }
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     synchronized(taskPerformedOrLoggedInAndTaskAttempted) {
242       taskPerformedOrLoggedInAndTaskAttempted = Boolean.FALSE;
243       //int goes = 0;
244       while (taskPerformedOrLoggedInAndTaskAttempted.equals(Boolean.FALSE)) { 
245         //goes ++;
246         //if (goes > 2)
247         //  throw new MelatiBugMelatiException("Problem with login loop logic, goes = " + goes);
248         melati.getDatabase().inSession (
249           AccessToken.root, new PoemTask() {
250             public void run () {
251               melati.getConfig().getAccessHandler().establishUser(melati);
252               melati.loadTableAndObject();
253               try {
254                 try {
255                   doPoemRequest(melati);
256                   taskPerformedOrLoggedInAndTaskAttempted = Boolean.TRUE;
257                 } catch (Exception e) {
258                   _handleException (melati, e);
259                 }
260               } catch (Exception e) {
261                 taskPerformedOrLoggedInAndTaskAttempted = Boolean.TRUE;
262                 try {
263                   term(melati);
264                 } catch (IOException e1) {
265                   e1 = null;
266                 }
267                 throw new UnhandledExceptionException(e);
268               }
269             }
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     }
280   }
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     if (exception instanceof AccessPoemException) {
292       melati.getConfig().getAccessHandler()
293         .handleAccessException(melati,(AccessPoemException)exception);
294 
295     }
296     else
297       throw exception;
298   }
299 
300   protected final void _handleException(Melati melati, Exception exception) 
301        throws Exception {
302     try {
303       handleException(melati, exception);
304     }
305     catch (Exception e) {
306       PoemThread.rollback();
307       throw e;
308     }
309   }
310 
311 
312   protected PoemContext poemContext(Melati melati) 
313       throws InvalidArgumentsException {
314     String[] args = melati.getArguments();
315     
316     PoemContext pc = new PoemContext();
317     if (args.length > 0) {
318       pc.setLogicalDatabase(args[0]);
319       setTableTroidMethod(pc, (String[])ArrayUtils.section(args,  1,  args.length));
320     }
321 
322     return pc;
323   }
324 
325   protected void setTableTroidMethod(PoemContext pc, String[] args){
326     if (args.length == 1) { 
327       pc.setMethod(args[0]);
328     }
329     if (args.length == 2) {
330       pc.setTable(args[0]);
331       try {
332         pc.setTroid(new Integer (args[1]));
333       }
334       catch (NumberFormatException e) {
335         pc.setMethod(args[1]);
336       }
337     }
338     if (args.length >= 3) {
339       pc.setTable(args[0]);
340       try {
341         pc.setTroid(new Integer (args[1]));
342       }
343       catch (NumberFormatException e) {
344         throw new UnexpectedExceptionException(new InvalidArgumentsException(args, e));
345       }
346       pc.setMethod(args[2]);
347     }
348   }
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     PoemContext pc = new PoemContext();
370     pc.setLogicalDatabase(logicalDatabase);
371     setTableTroidMethod(pc, melati.getArguments());
372     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 }