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 "filename" 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 "file" 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 }