##### Coverage Report - org.melati.servlet.PoemServlet

Classes in this File | Line Coverage | Branch Coverage | Complexity
PoemServlet |
81% | 64/79
83% | 25/30
| 3.6666666666666665;3.667
PoemServlet$1 |
78% | 25/32
0% | 0/2
| 3.6666666666666665;3.667

1 |  |
     /*
2 |  |
      * $Source: /usr/cvsroot/melati/melati/src/site/resources/withWebmacro/org.melati.servlet.PoemServlet.html,v $
3 |  |
      * $Revision: 1.1 $
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 | 56 |
     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 | 2 |
       }
199 |  |
200 |  |
       /**
201 |  |
        * @see javax.servlet.Servlet#destroy()
202 |  |
        */
203 |  |
       public void destroy() {
204 | 30 |
         super.destroy();
205 | 30 |
       }
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 | 1121 |
           melati.getConfig().getAccessHandler().buildRequest(melati);
219 | 1121 |
           prePoemSession(melati);
220 | 0 |
         } catch (Exception e) {
221 |  |
             // we have to log this here, otherwise we lose the stacktrace
222 | 0 |
             error(melati, e);
223 | 0 |
             throw new TrappedException("Problem in prePoemSession", e);
224 | 1121 |
         }
225 |  |
226 | 1121 |
         final PoemServlet _this = this;
227 |  |
228 | 1121 |
         melati.getDatabase().inSession(AccessToken.root, new PoemTask() {
229 |  |
           public void run() {
230 | 1121 |
             String poemAdministratorsName = null;
231 | 1121 |
             String poemAdministratorsEmail = null;
232 |  |
233 |  |
             try {
234 |  |
               try {
235 | 1121 |
                 poemAdministratorsName = melati.getDatabase().administratorUser().getName();
236 | 1121 |
                 Field emailField = null;
237 |  |
                 try {
238 | 1121 |
                   emailField = melati.getDatabase().administratorUser().getField("email");
239 | 982 |
                   poemAdministratorsEmail = emailField.toString();
240 | 139 |
                 } catch (NoSuchColumnPoemException e) {
241 | 139 |
                   poemAdministratorsEmail = "noEmailDefined@nobody.com";
242 | 982 |
                 }
243 | 1121 |
                 _this.setSysAdminName(poemAdministratorsName);
244 | 1121 |
                 _this.setSysAdminEmail(poemAdministratorsEmail);
245 |  |
246 | 0 |
               } catch (Exception e) {
247 | 0 |
                 _handleException(melati, e);
248 | 1121 |
               }
249 | 0 |
             } catch (Exception e) {
250 |  |
               // we have to log this here, otherwise we lose the stacktrace
251 | 0 |
               error(melati, e);
252 | 0 |
               throw new TrappedException(e);
253 | 1121 |
             }
254 |  |
255 |  |
256 | 1121 |
             melati.getConfig().getAccessHandler().establishUser(melati);
257 | 1121 |
             melati.loadTableAndObject();
258 |  |
259 |  |
             try {
260 |  |
               try {
261 | 1121 |
                 _this.doPoemRequest(melati);
262 | 34 |
               } catch (Exception e) {
263 | 34 |
                 _handleException(melati, e);
264 | 1087 |
               }
265 | 18 |
             } catch (Exception e) {
266 |  |
               // we have to log this here, otherwise we lose the stacktrace
267 | 18 |
               error(melati, e);
268 | 18 |
               throw new TrappedException(e);
269 | 1103 |
             }
270 | 1103 |
           }
271 |  |
272 |  |
           public String toString() {
273 | 0 |
             HttpServletRequest request = melati.getRequest();
274 | 0 |
             return "PoemServlet: "
275 |  |
                 + ((request == null) ? "(no request present)" : request
276 |  |
                     .getRequestURI());
277 |  |
           }
278 |  |
         });
279 | 1103 |
       }
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 | 28 |
         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 | 28 |
         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 | 34 |
         if (exception instanceof AccessPoemException) {
313 | 16 |
           melati.getConfig().getAccessHandler().handleAccessException(melati,
314 |  |
               (AccessPoemException) exception);
315 |  |
         } else
316 | 18 |
           throw exception;
317 | 16 |
       }
318 |  |
319 |  |
       protected final void _handleException(Melati melati, Exception exception)
320 |  |
           throws Exception {
321 |  |
         try {
322 | 34 |
           handleException(melati, exception);
323 | 18 |
         } catch (Exception e) {
324 | 18 |
           PoemThread.rollback();
325 | 18 |
           throw e;
326 | 16 |
         }
327 | 16 |
       }
328 |  |
329 |  |
       protected PoemContext poemContext(Melati melati) throws PathInfoException {
330 |  |
331 | 1103 |
         PoemContext it = new PoemContext();
332 |  |
333 | 1103 |
         String initParameterPathInfo = getInitParameter("pathInfo");
334 |  |
         String[] parts;
335 | 1103 |
         if (initParameterPathInfo != null)
336 | 6 |
           parts = StringUtils.split(initParameterPathInfo, '/');
337 |  |
         else
338 | 1097 |
           parts = melati.getPathInfoParts();
339 |  |
340 |  |
         // set it to something in order to provoke meaningful error
341 | 1103 |
         it.setLogicalDatabase("");
342 | 1103 |
         if (parts.length > 0) {
343 | 1097 |
           it.setLogicalDatabase(parts[0]);
344 | 1097 |
           if (parts.length == 2)
345 | 165 |
             it.setMethod(parts[1]);
346 | 1097 |
           if (parts.length == 3) {
347 | 696 |
             it.setTable(parts[1]);
348 | 696 |
             it.setMethod(parts[2]);
349 |  |
           }
350 | 1097 |
           if (parts.length >= 4) {
351 | 156 |
             it.setTable(parts[1]);
352 |  |
             try {
353 | 156 |
               it.setTroid(new Integer(parts[2]));
354 | 0 |
             } catch (NumberFormatException e) {
355 | 0 |
               throw new PathInfoException(melati.getRequest().getPathInfo(), e);
356 | 156 |
             }
357 | 156 |
             if (parts.length == 4) {
358 | 154 |
               it.setMethod(parts[3]);
359 |  |
             } else {
360 | 2 |
               String pathInfo = melati.getRequest().getPathInfo();
361 | 2 |
               pathInfo = pathInfo.substring(1);
362 | 8 |
               for (int i = 0; i < 3; i++) {
363 | 6 |
                 pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
364 |  |
               }
365 | 2 |
               it.setMethod(pathInfo);
366 |  |
             }
367 |  |
           }
368 |  |
         }
369 | 1103 |
         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 | 24 |
         PoemContext it = new PoemContext();
386 | 24 |
         String initParameterPathInfo = getInitParameter("pathInfo");
387 |  |
         String[] parts;
388 | 24 |
         if (initParameterPathInfo != null)
389 | 0 |
           parts = StringUtils.split(initParameterPathInfo, '/');
390 |  |
         else
391 | 24 |
           parts = melati.getPathInfoParts();
392 |  |
393 |  |
         // set it to something in order to provoke meaningful error
394 | 24 |
         it.setLogicalDatabase(logicalDatabase);
395 | 24 |
         if (parts.length > 0) {
396 | 2 |
           if (parts.length == 1)
397 | 2 |
             it.setMethod(parts[0]);
398 | 2 |
           if (parts.length == 2) {
399 | 0 |
             it.setTable(parts[0]);
400 | 0 |
             it.setMethod(parts[1]);
401 |  |
           }
402 | 2 |
           if (parts.length >= 3) {
403 | 0 |
             it.setTable(parts[0]);
404 | 0 |
             it.setMethod(parts[2]);
405 |  |
             try {
406 | 0 |
               it.setTroid(new Integer(parts[1]));
407 | 0 |
             } catch (NumberFormatException e) {
408 | 0 |
               throw new PathInfoException(melati.getRequest().getPathInfo(), e);
409 | 0 |
             }
410 |  |
           }
411 | 2 |
           if (parts.length == 3) {
412 | 0 |
             it.setMethod(parts[2]);
413 |  |
           } else {
414 | 2 |
             String pathInfo = melati.getRequest().getPathInfo();
415 | 2 |
             pathInfo = pathInfo.substring(1);
416 | 6 |
             for (int i = 0; i < 2; i++) {
417 | 4 |
               pathInfo = pathInfo.substring(pathInfo.indexOf("/") + 1);
418 |  |
             }
419 | 2 |
             it.setMethod(pathInfo);
420 |  |
           }
421 |  |
422 |  |
         }
423 | 24 |
         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 |  |
     }