AbstractPoemApp.java
/*
* $Source$
* $Revision$
*
* Copyright (C) 2005 Tim Pizey
*
* Part of Melati (http://melati.org), a framework for the rapid
* development of clean, maintainable web applications.
*
* Melati is free software; Permission is granted to copy, distribute
* and/or modify this software under the terms either:
*
* a) the GNU General Public License as published by the Free Software
* Foundation; either version 2 of the License, or (at your option)
* any later version,
*
* or
*
* b) any version of the Melati Software License, as published
* at http://melati.org
*
* You should have received a copy of the GNU General Public License and
* the Melati Software License along with this program;
* if not, write to the Free Software Foundation, Inc.,
* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
* GNU General Public License and visit http://melati.org to obtain the
* Melati Software License.
*
* Feel free to contact the Developers of Melati (http://melati.org),
* if you would like to work out a different arrangement than the options
* outlined here. It is our intention to allow Melati to be used by as
* wide an audience as possible.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* Contact details for copyright holder:
*
* Tim Pizey <timp At paneris.org>
* http://paneris.org/~timp
*/
package org.melati.app;
import java.io.IOException;
import org.melati.Melati;
import org.melati.PoemContext;
import org.melati.poem.AccessPoemException;
import org.melati.poem.PoemDatabaseFactory;
import org.melati.poem.PoemThread;
import org.melati.poem.PoemTask;
import org.melati.poem.AccessToken;
import org.melati.poem.util.ArrayUtils;
import org.melati.util.ConfigException;
import org.melati.util.MelatiException;
import org.melati.util.UnexpectedExceptionException;
/**
* Base class to use Poem as an application.
*
* <p>
* Simply extend this class and override the {@link #doPoemRequest} method.
* If you are going to use a template engine look at {@link AbstractTemplateApp}.
* </p>
*
* <UL>
* <LI>
* The command line arguments are expected in the following order:
* <BLOCKQUOTE><TT>
* <BR>db
* <BR>db method
* <BR>db table method
* <BR>db table troid method
* </TT></BLOCKQUOTE>
*
* these components are broken out of the command line arguments and passed to
* your application code in the {@link Melati} parameter.
*
* <TABLE>
* <caption>Arguments</caption>
* <TR>
* <TD><TT><I>db</I></TT></TD>
* <TD>
* The first argument is taken to be the `logical name'
* of the POEM database to which the servlet should connect. It
* is mapped onto JDBC connection details via the config file
* <TT>org.melati.LogicalDatabase.properties</TT>, of which there is an
* example in the source tree. This is automatically made available in
* templates as <TT>$melati.Database</TT>.
* </TD>
* <TR>
* <TD><TT><I>table</I></TT></TD>
* <TD>
* The name of a table to work on:
* perhaps it is meant to list its contents. This is automatically
* made available in templates as <TT>$melati.Table</TT>.
* </TD>
* </TR>
* <TR>
* <TD><TT><I>troid</I></TT></TD>
* <TD>
* The POEM `troid' (table row identifier, or row-unique integer) of a
* row within a <TT><I>table</I></TT>.
* This is automatically made
* available in templates as <TT>$melati.Object</TT>.
* </TD>
* </TR>
* <TR>
* <TD><TT><I>method</I></TT></TD>
* <TD>
* A freeform string telling your servlet what it is meant to do. This
* is automatically made available in templates as
* <TT>$melati.Method</TT>.
* </TD>
* </TR>
* </TABLE>
*
* <LI>
* You can change the way these things are determined by overriding
* {@link #poemContext}.
*
* <LI>
* Any POEM database operations you perform will be done with the access
* rights of the POEM <TT>User</TT> associated with the POEM session. If
* there is no established session, the current user will be set to
* the default `guest' user. If this method terminates with an
* <TT>AccessPoemException</TT>, indicating that you have attempted something
* which you aren't entitled to do, the user will be prompted to log in, and
* the original request will be retried. The precise mechanism used for
* login is <A HREF=#loginmechanism>configurable</A>.
*
* <LI>
* No changes made to the database by other concurrently executing threads
* will be visible to you (in the sense that once you have seen a particular
* version of a record, you will always subsequently see the same one), and
* your own changes will not be made permanent until this method completes
* successfully or you perform an explicit <TT>PoemThread.commit()</TT>. If
* it terminates with an exception or you issue a
* <TT>PoemThread.rollback()</TT>, your changes will be lost.
*
* <LI>
* <A NAME=loginmechanism></A>It is possible to configure how your
* <TT>PoemApp</TT>-derived applications implement user login. If the
* properties file <TT>org.melati.MelatiApp.properties</TT>
* exists and contains a setting
* <TT>org.melati.MelatiApp.accessHandler=<I>foo</I></TT>, then
* <TT><I>foo</I></TT> is taken to be the name of a class implementing the
* <TT>AccessHandler</TT> interface.
* </UL>
*
* If you do not need access handling then set your accessHandler to
* <tt>org.melati.login.OpenAccessHandler</tt>.
* If you do need access handling then set your accessHandler to
* <tt>org.melati.login.CommandLineAccessHandler</tt>.
* However this is not extremely secure, as the user could potentially
* change this seting to <tt>OpenAccessHandler</tt> as they are on the same machine.
*
* You can specify the username and password to use by adding command line parameters:
* <pre>
* <tt>-username user -password password</tt>
* </pre>
*
*
* @see org.melati.poem.Database#guestAccessToken
* @see org.melati.poem.PoemThread#commit
* @see org.melati.poem.PoemThread#rollback
* @see #poemContext
* @see org.melati.login.AccessHandler
* @see org.melati.login.HttpSessionAccessHandler
* @see org.melati.login.Login
* @see org.melati.login.OpenAccessHandler
* @see org.melati.login.CommandLineAccessHandler
*/
public abstract class AbstractPoemApp extends AbstractConfigApp implements App {
private static Boolean taskPerformedOrLoggedInAndTaskAttempted = Boolean.FALSE;
/**
* Initialise.
*
* @param args the command line arguments
* @return a configured Melati
* {@inheritDoc}
* @see org.melati.app.AbstractConfigApp#init(java.lang.String[])
*/
public Melati init(String[] args) throws MelatiException {
Melati m = super.init(args);
if (m.getDatabase() == null) {
try {
super.term(m);
} catch (IOException e) {
e = null;
}
throw new ConfigException("No database configured");
}
return m;
}
/**
* Clean up at end of run.
*
* @param melati the melati
* @throws IOException
*/
public void term(Melati melati) throws IOException {
super.term(melati);
if (melati.getDatabase() != null)
PoemDatabaseFactory.disconnectDatabase(melati.getDatabase().getName());
}
/**
* A place holder for things you might want to do before
* setting up a <code>PoemSession</code>.
*
* @param melati the current Melati
* @throws Exception if anything goes wrong
*/
protected void prePoemSession(Melati melati) throws Exception {
Melati foolEclipse = melati;
melati = foolEclipse;
}
protected void doConfiguredRequest(final Melati melati) {
// Do something outside of the PoemSession
try {
melati.getConfig().getAccessHandler().buildRequest(melati);
prePoemSession(melati);
} catch (Exception e) {
throw new UnexpectedExceptionException(e);
}
// Login loop
// If not logged-in when required then an exception is thrown.
// The exception is handled and the task revisited
// The flag is reset to allow this to be run again.
synchronized(taskPerformedOrLoggedInAndTaskAttempted) {
taskPerformedOrLoggedInAndTaskAttempted = Boolean.FALSE;
//int goes = 0;
while (taskPerformedOrLoggedInAndTaskAttempted.equals(Boolean.FALSE)) {
//goes ++;
//if (goes > 2)
// throw new MelatiBugMelatiException("Problem with login loop logic, goes = " + goes);
melati.getDatabase().inSession (
AccessToken.root, new PoemTask() {
public void run () {
melati.getConfig().getAccessHandler().establishUser(melati);
melati.loadTableAndObject();
try {
try {
doPoemRequest(melati);
taskPerformedOrLoggedInAndTaskAttempted = Boolean.TRUE;
} catch (Exception e) {
_handleException (melati, e);
}
} catch (Exception e) {
taskPerformedOrLoggedInAndTaskAttempted = Boolean.TRUE;
try {
term(melati);
} catch (IOException e1) {
e1 = null;
}
throw new UnhandledExceptionException(e);
}
}
// Not sure there is any point in this
// Cannot find a way of accessing it
//public String toString() {
// return "PoemApp";
//}
}
);
}
}
}
/**
* Default method to handle an exception.
*
* @param melati the Melati
* @param exception the exception to handle
*/
protected static void handleException(Melati melati, Exception exception)
throws Exception {
if (exception instanceof AccessPoemException) {
melati.getConfig().getAccessHandler()
.handleAccessException(melati,(AccessPoemException)exception);
}
else
throw exception;
}
protected final void _handleException(Melati melati, Exception exception)
throws Exception {
try {
handleException(melati, exception);
}
catch (Exception e) {
PoemThread.rollback();
throw e;
}
}
protected PoemContext poemContext(Melati melati)
throws InvalidArgumentsException {
String[] args = melati.getArguments();
PoemContext pc = new PoemContext();
if (args.length > 0) {
pc.setLogicalDatabase(args[0]);
setTableTroidMethod(pc, (String[])ArrayUtils.section(args, 1, args.length));
}
return pc;
}
protected void setTableTroidMethod(PoemContext pc, String[] args){
if (args.length == 1) {
pc.setMethod(args[0]);
}
if (args.length == 2) {
pc.setTable(args[0]);
try {
pc.setTroid(new Integer (args[1]));
}
catch (NumberFormatException e) {
pc.setMethod(args[1]);
}
}
if (args.length >= 3) {
pc.setTable(args[0]);
try {
pc.setTroid(new Integer (args[1]));
}
catch (NumberFormatException e) {
throw new UnexpectedExceptionException(new InvalidArgumentsException(args, e));
}
pc.setMethod(args[2]);
}
}
/**
* This is provided for convenience, so you don't have to specify the
* logical database in the arguments. This is useful when
* writing applications where you are only accessing a single database.
*
* Simply override {@link #poemContext(Melati melati)} thus:
*
* <PRE>
* protected PoemContext poemContext(Melati melati)
* throws InvalidArgumentsException {
* return poemContextWithLDB(melati,"<your logical database name>");
* }
* </PRE>
*
*/
protected PoemContext poemContextWithLDB(Melati melati,
String logicalDatabase)
throws InvalidArgumentsException {
PoemContext pc = new PoemContext();
pc.setLogicalDatabase(logicalDatabase);
setTableTroidMethod(pc, melati.getArguments());
return pc;
}
/**
* Override this method to do your own thing.
*
* @param melati a {@link Melati} containing POEM and other configuration data
*/
protected abstract void doPoemRequest(Melati melati) throws Exception;
}