View Javadoc

1   /*
2    * $Source: /usr/cvsroot/melati/melati/src/main/java/org/melati/servlet/PoemServlet.java,v $
3    * $Revision: 1.44 $
4    *
5    * Copyright (C) 2000 Tim Joyce
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 Joyce <timj At paneris.org>
42   *     http://paneris.org/
43   *     68 Sandbanks Rd, Poole, Dorset. BH14 8BY. UK
44   */
45  
46  package org.melati.servlet;
47  
48  import java.io.IOException;
49  
50  import javax.servlet.ServletException;
51  import javax.servlet.http.HttpServletRequest;
52  
53  import org.melati.Melati;
54  import org.melati.PoemContext;
55  import org.melati.poem.AccessPoemException;
56  import org.melati.poem.Field;
57  import org.melati.poem.NoSuchColumnPoemException;
58  import org.melati.poem.PoemThread;
59  import org.melati.poem.PoemTask;
60  import org.melati.poem.AccessToken;
61  import org.melati.poem.util.StringUtils;
62  
63  /**
64   * Base class to use Poem with Servlets.
65   * <p>
66   * Simply extend this class and override the doPoemRequest method. If you are
67   * going to use a template engine look at TemplateServlet.
68   * <UL>
69   * <LI> <A NAME=pathinfoscan>By default, the path info of the URL by which the
70   * servlet was called up is examined to determine the `logical name' of the
71   * Melati POEM database to which the servlet should connect, and possibly a
72   * table within that database, an object within that table, and a `method' to
73   * apply to that object.</A> The URL is expected to take one of the following
74   * forms: <BLOCKQUOTE><TT> http://<I>h</I>/<I>s</I>/<I>db</I>/ <BR>
75   * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>meth</I> <BR>
76   * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>tbl</I>/<I>meth</I> <BR>
77   * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>tbl</I>/<I>troid</I>/<I>meth</I>
78   * <BR>
79   * http://<I>h</I>/<I>s</I>/<I>db</I>/<I>tbl</I>/<I>troid</I>/<I>meth</I>/<I>other</I>
80   * </TT></BLOCKQUOTE> and the following components are broken out of the path
81   * info and passed to your application code in the <TT>melati</TT> parameter
82   * (which is also copied automatically into <TT>context</TT> so that it is
83   * easily available in templates): 
84   * <TABLE>
85   * <TR>
86   * <TD><TT><I>h</I></TT></TD>
87   * <TD>host name, such as <TT>www.melati.org</TT></TD>
88   * </TR>
89   * <TR>
90   * <TD><TT><I>s</I></TT></TD>
91   * <TD> servlet-determining part, such as <TT>melati/org.melati.admin.Admin</TT>
92   * </TD>
93   * </TR>
94   * <TR>
95   * <TD><TT><I>db</I></TT></TD>
96   * <TD> The first element of the path info is taken to be the `logical name' of
97   * the Melati POEM database to which the servlet should connect. It is mapped
98   * onto JDBC connection details via the config file <TT>org.melati.LogicalDatabase.properties</TT>,
99   * of which there is an example in the source tree. This is automatically made
100  * available in templates as <TT>$melati.Database</TT>. </TD>
101  * <TR>
102  * <TD><TT><I>tbl</I></TT></TD>
103  * <TD> The DBMS name of a table with which the servlet is concerned: perhaps it
104  * is meant to list its contents. This is automatically made available in
105  * templates as <TT>$melati.Table</TT>. </TD>
106  * </TR>
107  * <TR>
108  * <TD><TT><I>troid</I></TT></TD>
109  * <TD> The POEM `troid' (table row identifier, or row-unique integer) of a row
110  * within <TT><I>tbl</I></TT> with which the servlet is concerned: perhaps it
111  * is meant to display it. This is automatically made available in templates as
112  * <TT>$melati.Object</TT>. </TD>
113  * </TR>
114  * <TR>
115  * <TD><TT><I>meth</I></TT></TD>
116  * <TD> A freeform string telling your servlet what it is meant to do. This is
117  * automatically made available in templates as <TT>$melati.Method</TT>.
118  * </TD>
119  * </TR>
120  * <TR>
121  * <TD><TT><I>other</I></TT></TD>
122  * <TD> Any other information you wish to put in the pathinfo. This is useful,
123  * for instance, if you wish to specify the &quot;filename&quot; of your
124  * servlet. For instance, if you call <TT>/db/myfiles/0/Download/afile.html</TT>
125  * and return a stream with a content-type of <tt>application/octet-stream</tt>
126  * most browsers will prompt you to save the &quot;file&quot; as
127  * <tt>afile.html</tt> </TD>
128  * </TR>
129  * </TABLE>
130  * <LI> You can change the way these things are determined by overriding <TT>poemContext</TT>.
131  * <LI> Any POEM database operations you perform will be done with the access
132  * rights of the POEM <TT>User</TT> associated with the servlet session. If
133  * there is no established servlet session, the current user will be set to the
134  * default `guest' user. If this method terminates with an <TT>AccessPoemException</TT>,
135  * indicating that you have attempted something which you aren't entitled to do,
136  * the user will be prompted to log in, and the original request will be
137  * retried. The precise mechanism used for login is <A
138  * HREF=#loginmechanism>configurable</A>.
139  * <LI>
140  *  No changes made to the database by other concurrently executing threads
141  * will be visible to you (in the sense that once you have seen a particular
142  * version of a record, you will always subsequently see the same one), and your
143  * own changes will not be made permanent until this method completes
144  * successfully or you perform an explicit <TT>PoemThread.commit()</TT>. If
145  * it terminates with an exception or you issue a <TT>PoemThread.rollback()</TT>,
146  * your changes will be lost.
147  * <LI> <A NAME=loginmechanism>
148  * It's possible to configure how your <TT>PoemServlet</TT>-derived
149  * servlets implement user login.</A> If the properties file <TT><A
150  * HREF=../org.melati.MelatiConfig.properties>
151  * org.melati.MelatiConfig.properties</A></TT> exists and contains a setting
152  * <TT>org.melati.MelatiConfig.accessHandler=<I>foo</I></TT>, then <TT><I>foo</I></TT>
153  * is taken to be the name of a class implementing the <TT>AccessHandler</TT>
154  * interface. The default is <TT>HttpSessionAccessHandler</TT>, which stores
155  * the user id in the servlet session, and redirects to the <TT>Login</TT>
156  * servlet to throw up templated login screens. If instead you specify 
157  * <TT>HttpBasicAuthenticationAccessHandler</TT>, the user id is maintained 
158  * using HTTP Basic Authentication (RFC2068 11.1, the
159  * mechanism commonly used to password-protect static pages), and the task of
160  * popping up login dialogs is delegated to the browser. The advantage of the
161  * former method is that the user gets a more informative interface which is
162  * more under the designer's control; the advantage of the latter method is that
163  * no cookies or URL rewriting are required---for instance it is probably more
164  * appropriate for WAP phones. Both methods involve sending the user's password
165  * in plain text across the public network.
166  * </UL>
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.HttpBasicAuthenticationAccessHandler
176  */
177 
178 public abstract class PoemServlet extends ConfigServlet {
179 
180   /**
181    * Eclipse generated.
182    */
183   private static final long serialVersionUID = 7694978400584943446L;
184 
185   /**
186    * A place to do things before entering the session 
187    * of the user, here is a good place to use root access token.
188    * 
189    * Overriden in TemplateServlet.
190    * 
191    * @param melati
192    *          org.melati.Melati A source of information about the Melati
193    *          database context (database, table, object) and utility objects
194    *          such as error handlers.
195    */
196 
197   protected void prePoemSession(Melati melati) throws Exception {
198   }
199 
200   /**
201    * @see javax.servlet.Servlet#destroy()
202    */
203   public void destroy() {
204     super.destroy();
205   }
206 
207   /**
208    * Process the request.
209    */
210 
211   protected void doConfiguredRequest(final Melati melati)
212       throws ServletException, IOException {
213 
214     // Set up a POEM session and call the application code
215 
216     // Do something outside of the PoemSession
217     try {
218       melati.getConfig().getAccessHandler().buildRequest(melati);
219       prePoemSession(melati);
220     } catch (Exception e) {
221         // we have to log this here, otherwise we lose the stacktrace
222         error(melati, e);
223         throw new TrappedException("Problem in prePoemSession", e);
224     }
225 
226     final PoemServlet _this = this;
227 
228     melati.getDatabase().inSession(AccessToken.root, new PoemTask() {
229       public void run() {
230         String poemAdministratorsName = null;
231         String poemAdministratorsEmail = null;
232 
233         try {
234           try {
235             poemAdministratorsName = melati.getDatabase().administratorUser().getName();
236             Field emailField = null;
237             try {
238               emailField = melati.getDatabase().administratorUser().getField("email");
239               poemAdministratorsEmail = emailField.toString();
240             } catch (NoSuchColumnPoemException e) {
241               poemAdministratorsEmail = "noEmailDefined@nobody.com";
242             }
243             _this.setSysAdminName(poemAdministratorsName);
244             _this.setSysAdminEmail(poemAdministratorsEmail);
245             
246           } catch (Exception e) {
247             _handleException(melati, e);
248           }
249         } catch (Exception e) {
250           // we have to log this here, otherwise we lose the stacktrace
251           error(melati, e);
252           throw new TrappedException(e);
253         }
254         
255         
256         melati.getConfig().getAccessHandler().establishUser(melati);
257         melati.loadTableAndObject();
258 
259         try {
260           try {
261             _this.doPoemRequest(melati);
262           } catch (Exception e) {
263             _handleException(melati, e);
264           }
265         } catch (Exception e) {
266           // we have to log this here, otherwise we lose the stacktrace
267           error(melati, e);
268           throw new TrappedException(e);
269         }
270       }
271 
272       public String toString() {
273         HttpServletRequest request = melati.getRequest();
274         return "PoemServlet: "
275             + ((request == null) ? "(no request present)" : request
276                 .getRequestURI());
277       }
278     });
279   }
280 
281   /**
282    * Override this to provide a different administrator's details to the
283    * database admin user.
284    * 
285    * @return the System Administrators name.
286    */
287   public String getSysAdminName() {
288     return sysAdminName;
289   }
290 
291   /**
292    * Override this to provide a different administrator's details to the
293    * database admin user.
294    * 
295    * @return the System Administrators email address.
296    */
297   public String getSysAdminEmail() {
298     return sysAdminEmail;
299   }
300 
301   /**
302    * Default method to handle an exception without a template engine.
303    * 
304    * @param melati
305    *          the Melati
306    * @param exception
307    *          the exception to handle
308    */
309   protected void handleException(Melati melati, Exception exception)
310       throws Exception {
311 
312     if (exception instanceof AccessPoemException) {
313       melati.getConfig().getAccessHandler().handleAccessException(melati,
314           (AccessPoemException) exception);
315     } else
316       throw exception;
317   }
318 
319   protected final void _handleException(Melati melati, Exception exception)
320       throws Exception {
321     try {
322       handleException(melati, exception);
323     } catch (Exception e) {
324       PoemThread.rollback();
325       throw e;
326     }
327   }
328 
329   protected PoemContext poemContext(Melati melati) throws PathInfoException {
330 
331     PoemContext it = new PoemContext();
332 
333     String initParameterPathInfo = getInitParameter("pathInfo");
334     String[] parts;
335     if (initParameterPathInfo != null)
336       parts = StringUtils.split(initParameterPathInfo, '/');
337     else
338       parts = melati.getPathInfoParts();
339 
340     // set it to something in order to provoke meaningful error
341     it.setLogicalDatabase("");
342     if (parts.length > 0) {
343       it.setLogicalDatabase(parts[0]);
344       if (parts.length == 2)
345         it.setMethod(parts[1]);
346       if (parts.length == 3) {
347         it.setTable(parts[1]);
348         it.setMethod(parts[2]);
349       }
350       if (parts.length >= 4) {
351         it.setTable(parts[1]);
352         try {
353           it.setTroid(new Integer(parts[2]));
354         } catch (NumberFormatException e) {
355           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
356         }
357         if (parts.length == 4) {
358           it.setMethod(parts[3]);
359         } else {
360           String pathInfo = melati.getRequest().getPathInfo();
361           pathInfo = pathInfo.substring(1);
362           for (int i = 0; i < 3; i++) {
363             pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
364           }
365           it.setMethod(pathInfo);
366         }
367       }
368     }
369     return it;
370   }
371 
372   /*
373    * This is provided for convenience, so you don't have to specify the
374    * logicaldatabase on the pathinfo. This is a very good idea when writing your
375    * applications where you are typically only accessing a single database.
376    * Simply override poemContext(Melati melati) thus: 
377    * <code> 
378    * protected PoemContext poemContext(Melati melati) throws PathInfoException { 
379    *   return poemContextWithLDB(melati,"<your logical database name>"); 
380    * } 
381    * </code>
382    */
383   protected PoemContext poemContextWithLDB(Melati melati, String logicalDatabase)
384       throws PathInfoException {
385     PoemContext it = new PoemContext();
386     String initParameterPathInfo = getInitParameter("pathInfo");
387     String[] parts;
388     if (initParameterPathInfo != null)
389       parts = StringUtils.split(initParameterPathInfo, '/');
390     else
391       parts = melati.getPathInfoParts();
392 
393     // set it to something in order to provoke meaningful error
394     it.setLogicalDatabase(logicalDatabase);
395     if (parts.length > 0) {
396       if (parts.length == 1)
397         it.setMethod(parts[0]);
398       if (parts.length == 2) {
399         it.setTable(parts[0]);
400         it.setMethod(parts[1]);
401       }
402       if (parts.length >= 3) {
403         it.setTable(parts[0]);
404         it.setMethod(parts[2]);
405         try {
406           it.setTroid(new Integer(parts[1]));
407         } catch (NumberFormatException e) {
408           throw new PathInfoException(melati.getRequest().getPathInfo(), e);
409         }
410       }
411       if (parts.length == 3) {
412         it.setMethod(parts[2]);
413       } else {
414         String pathInfo = melati.getRequest().getPathInfo();
415         pathInfo = pathInfo.substring(1);
416         for (int i = 0; i < 2; i++) {
417           pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
418         }
419         it.setMethod(pathInfo);
420       }
421 
422     }
423     return it;
424   }
425 
426   /**
427    * Override this method to build up your own output.
428    * 
429    * @param melati
430    */
431   protected abstract void doPoemRequest(Melati melati) throws Exception;
432 
433 }