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,"<your logical database name>");
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 }