View Javadoc
1   /*
2    * $Source$
3    * $Revision$
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.PrintWriter;
49  import java.io.IOException;
50  
51  import javax.servlet.ServletConfig;
52  import javax.servlet.ServletException;
53  import javax.servlet.http.HttpServlet;
54  import javax.servlet.http.HttpServletRequest;
55  import javax.servlet.http.HttpServletResponse;
56  
57  import org.melati.Melati;
58  import org.melati.PoemContext;
59  import org.melati.MelatiConfig;
60  import org.melati.poem.AccessPoemException;
61  import org.melati.poem.NoSuchRowPoemException;
62  import org.melati.util.ConnectionPendingException;
63  import org.melati.util.MelatiWriter;
64  
65  /**
66   * Config Servlet is the simplest way to use Melati.
67   *
68   * All a ConfigServlet does is to configure a melati and combine the
69   * doGet and doPost methods.  Importantly it does not establish a poem session
70   * leaving you to do this for yourself.
71   *
72   * If you want a poem session established, please extend PoemServlet.
73   *
74   * ConfigServlet does set up a basic PoemContext with the Method set,
75   * but not the POEM logicaldatabase, table or troid.
76   *
77   * The URL is expected to take one of the following form:
78   *
79   * <BLOCKQUOTE><TT>
80   * http://<I>h</I>/<I>s</I>/<I>meth</I>;
81   * </TT></BLOCKQUOTE>
82   *
83   * the method is broken out of the path info and passed to
84   * your application code in the <TT>Melati</TT> and
85   * <TT>PoemContext</TT> parameter
86   *
87   * <TABLE>
88   *   <caption>Path context elements</caption>
89   *   <TR>
90   *     <TD><TT><I>h</I></TT></TD>
91   *     <TD>host name, such as <TT>www.melati.org</TT></TD>
92   *   </TR>
93   *   <TR>
94   *     <TD><TT><I>s</I></TT></TD>
95   *     <TD>
96   *       servlet-determining part, such as
97   *       <TT>melati/org.melati.admin.Admin</TT>
98   *     </TD>
99   *   </TR>
100  *   <TR>
101  *     <TD><TT><I>meth</I></TT></TD>
102  *     <TD>
103  *       A freeform string telling your servlet what it is meant to do.  This
104  *       is automatically made available in templates as
105  *       <TT>$melati.Method</TT>.
106  *     </TD>
107  *   </TR>
108  * </TABLE>
109  *
110  * You can change the way these things are determined by overriding
111  * <TT>poemContext(Melati)</TT>.
112  */
113 
114 public abstract class ConfigServlet extends HttpServlet {
115 
116   /**
117    * Eclipse generated.
118    */
119   private static final long serialVersionUID = 8995954958766276122L;
120   
121   protected MelatiConfig melatiConfig;
122   protected String sysAdminName = "nobody";
123   protected String sysAdminEmail = "nobody@nobody.com";;
124   
125   /**
126    * Inititialise Melati.
127    *
128    * @param config a <code>ServletConfig</code>
129    * @throws ServletException is anything goes wrong
130    */
131   public void init(ServletConfig config) throws ServletException {
132     super.init(config);
133     melatiConfig = melatiConfig();
134   }
135 
136   /**
137    * Handles GET.
138    *
139    * @param request the incoming <code>HttpServletRequest</code>
140    * @param response the outgoing <code>HttpServletResponse</code>
141    */
142   public void doGet(HttpServletRequest request, 
143                     HttpServletResponse response) {
144     doGetPostRequest(request, response);
145   }
146 
147   /**
148    * Handle a POST.
149    *
150    * @param request the incoming <code>HttpServletRequest</code>
151    * @param response the outgoing <code>HttpServletResponse</code>
152    */
153   public void doPost(HttpServletRequest request, 
154                      HttpServletResponse response) {
155     doGetPostRequest(request, response);
156   }
157 
158   /**
159    * Process the request.
160    *
161    * Exceptions are presented to the user if practicable, or written to the log. 
162    * 
163    * @param request the incoming <code>HttpServletRequest</code>
164    * @param response the outgoing <code>HttpServletResponse</code>
165    */
166   private void doGetPostRequest(final HttpServletRequest request, 
167                                 final HttpServletResponse response) {
168     Melati melati = new Melati(melatiConfig, request, response);
169     try {
170       melati.establishCharsets();
171       melati.setPoemContext(poemContext(melati));
172       doConfiguredRequest(melati);
173       // send the output to the client
174       melati.write();
175     }
176     catch (Exception e) {
177       error(melati,e);
178     }
179   }
180 
181   /**
182    * Send an error message.
183    *
184    * @param melati the {@link Melati}
185    * @param e      the {@link Exception} to report
186    */
187   public void error(Melati melati, Exception e) {
188     melati.getResponse().setStatus(httpStatusCode(e));
189     
190     // has it been trapped already, if so, we don't need to relog it here
191     if (! (e instanceof TrappedException)) {
192       try { 
193         // log it
194         e.printStackTrace(System.err);
195         // and put it on the page
196         melati.setResponseContentType ("text/html");
197         MelatiWriter mw =  melati.getWriter();
198         // get rid of anything that has been written so far
199         mw.reset();
200         PrintWriter out = new PrintWriter(mw.getWriter());
201         if (e instanceof ConnectionPendingException) {
202           writeConnectionPendingException(out,e);
203         } else {
204           writeError(out,e);
205         }
206         melati.write();
207       } catch (IOException f) {
208         e.printStackTrace(System.err);
209         throw new TrappedException("Problem logging error", f);
210       }
211     }
212   }
213   
214   protected int httpStatusCode(Exception e) {
215     if (e instanceof AccessPoemException)
216       return 401; // Not Authorized
217     if (e instanceof InvalidUsageException)
218       return 400; // Client error
219     if (e instanceof NoSuchRowPoemException)
220       return 404; // Not found
221     return 500; // Server error
222   }
223 
224   /**
225    * Print an error directly to the client.
226    *
227    * This is rarely called, eg when the template engine 
228    * fails to render the default error template.
229    *
230    * @param out the <code>PrintWriter</code> to print to 
231    * @param e   the {@link Exception} to report
232    */
233   public void writeError(PrintWriter out, Exception e) {
234     out.println("<html><head><title>Melati Error</title></head>");
235     out.println("<!-- HTML generated in " + 
236                 "org.melati.servlet.ConfigServlet.java -->");
237     out.println("<body><h2>Melati Error</h2>");
238     out.println("<h3>Reported from ConfigServlet</h3>");
239     out.println("<p>An error has occured in the application"); 
240     out.println("that runs this website, please contact <a href='mailto:");
241     out.println(getSysAdminEmail() + "'>" + getSysAdminName() + "</a>");
242     out.println(", with the information given below.</p>");
243     out.println("<h4><font color='red'><pre>");
244     e.printStackTrace(out);
245     out.println("</pre></font></h4></body></html>");
246   }    
247   
248   /**
249    * Print the <code>ConnectionPendingException</code>  directly to the client.
250    *
251    * This is called if a request is made whilst the system is 
252    * still being initialised.
253    *
254    * @param out the <code>PrintWriter</code> to print to 
255    * @param e   the {@link Exception} to report
256    */
257   public void writeConnectionPendingException(PrintWriter out, Exception e) {
258     out.println("<html><head><title>Database Initialising</title>\n");
259     out.println("<META HTTP-EQUIV='Refresh' CONTENT='30'>\n</head>\n");
260     out.println("<!-- Generated in org.melati.servlet.ConfigServlet.java -->");
261     out.println("<body><center><h2>Database Initialising</h2><p>&nbsp;</p>");
262     out.println("<p><b>Sorry</b>, ");
263     out.println("the database that runs this website is just starting up.");
264     out.println("This takes a few seconds, ");
265     out.println("so you should be able to use the site in a moment.");
266     out.println("<p>This page will refresh in 30 seconds, ");
267     out.println("and you will be able to continue.</p>");
268     out.println("<!--");
269     e.printStackTrace(out);
270     out.println("--></center></body></html>");
271   }    
272 
273   /** 
274    * This method <b>SHOULD</b> be overidden.
275    * @return the System Administrators name.
276    */
277   public String getSysAdminName () {
278     return sysAdminName;
279   }
280 
281   /** 
282    * This method <b>SHOULD</b> be overidden.
283    * @return the System Administrators email address.
284    */
285   public String getSysAdminEmail () {
286     return sysAdminEmail;
287   }
288 
289   /**
290    * @param sysAdminEmail The sysAdminEmail to set.
291    */
292   protected void setSysAdminEmail(String sysAdminEmail) {
293     this.sysAdminEmail = sysAdminEmail;
294   }
295 
296   
297   /**
298    * @param sysAdminName The sysAdminName to set.
299    */
300   protected void setSysAdminName(String sysAdminName) {
301     this.sysAdminName = sysAdminName;
302   }
303 
304   protected PoemContext poemContext(Melati melati) 
305       throws PathInfoException {
306     PoemContext it = new PoemContext();
307     String[] parts = melati.getPathInfoParts();
308     if (parts.length > 0)
309      it.setMethod(parts[parts.length - 1]);
310    return it;
311  }
312   
313   /** 
314    * To override any setting from org.melati.MelatiConfig.properties,
315    * simply override this method and return a valid MelatiConfig.
316    *
317    * eg to use a different AccessHandler from the default:
318    *
319    * <PRE>
320    *   protected MelatiConfig melatiConfig() throws MelatiException {
321    *     MelatiConfig config = super.melatiConfig();
322    *     config.setAccessHandler(new YourAccessHandler());
323    *     return config;
324    *   }
325    * </PRE>
326    * 
327    * @return a new {@link MelatiConfig}
328    */
329   protected MelatiConfig melatiConfig() {
330     MelatiConfig m = new MelatiConfig();
331     String realPath = getServletConfig().getServletContext().getRealPath("/");
332     if (realPath == null)
333       throw new NullPointerException();
334     m.setRealPath(realPath);
335     return m;
336   }
337   
338   /**
339    * Instantiate this method to build up your own output.
340    * @param melati
341    * @throws Exception if anything goes wrong
342    */
343   protected abstract void doConfiguredRequest(Melati melati)
344       throws Exception;
345 
346   
347 }