View Javadoc
1   /*
2    * $Source$
3    * $Revision$
4    *
5    * Copyright (C) 2000 William Chesters
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   *     William Chesters <williamc At paneris.org>
42   *     http://paneris.org/~williamc
43   *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
44   */
45  
46  package org.melati;
47  
48  import java.io.IOException;
49  import java.io.PrintWriter;
50  import java.io.UnsupportedEncodingException;
51  import java.lang.reflect.Constructor;
52  import java.util.Vector;
53  
54  import javax.servlet.http.HttpServletRequest;
55  import javax.servlet.http.HttpServletResponse;
56  import javax.servlet.http.HttpSession;
57  
58  import org.melati.poem.Database;
59  import org.melati.poem.Field;
60  import org.melati.poem.NotInSessionPoemException;
61  import org.melati.poem.Persistent;
62  import org.melati.poem.PoemLocale;
63  import org.melati.poem.PoemThread;
64  import org.melati.poem.ReferencePoemType;
65  import org.melati.poem.Table;
66  import org.melati.poem.User;
67  import org.melati.poem.util.StringUtils;
68  import org.melati.servlet.Form;
69  import org.melati.template.HTMLMarkupLanguage;
70  import org.melati.template.MarkupLanguage;
71  import org.melati.template.ServletTemplateContext;
72  import org.melati.template.ServletTemplateEngine;
73  import org.melati.template.TemplateContext;
74  import org.melati.template.TemplateEngine;
75  import org.melati.util.AcceptCharset;
76  import org.melati.util.CharsetException;
77  import org.melati.util.DatabaseInitException;
78  import org.melati.util.HttpHeader;
79  import org.melati.util.HttpUtil;
80  import org.melati.util.MelatiBufferedWriter;
81  import org.melati.util.MelatiBugMelatiException;
82  import org.melati.util.MelatiIOException;
83  import org.melati.util.MelatiSimpleWriter;
84  import org.melati.util.MelatiStringWriter;
85  import org.melati.util.MelatiWriter;
86  import org.melati.util.UTF8URLEncoder;
87  import org.melati.util.UnexpectedExceptionException;
88  
89  /**
90   * This is the main entry point for using the Melati framework.
91   * A Melati exists once per request, or command from an application.
92   * <p>
93   * It provides a central container for all the relevant objects that 
94   * a Servlet or command line application needs to create textual 
95   * output, optionally using a Template Engine or a Database.
96   * <p>
97   * You will need to create a MelatiConfig in order to construct a Melati.
98   * <p>
99   * If you are using servlets, you will want to construct a Melati with
100  * a request and response object.  Otherwise, simply pass in a Writer.
101  * <p>
102  * If you are using a template engine outside of a servlets context you will 
103  * still need the servlets jar in your classpath, annoyingly, as Velocity and 
104  * WebMacro introspect all possible methods and throw a ClassNotFound exception 
105  * if the servlets classes are not available.  
106  * <p>
107  * Melati is typically used with Servlets, POEM (Persistent Object Engine for
108  * Melati) and a Template Engine
109  *
110  * @see org.melati.MelatiConfig
111  * @see org.melati.servlet.ConfigServlet
112  * @see org.melati.servlet.PoemServlet
113  * @see org.melati.servlet.TemplateServlet
114  */
115 
116 public class Melati {
117 
118   /** UTF-8. */
119   public static final String DEFAULT_ENCODING = "UTF-8";
120   
121   private MelatiConfig config;
122   private PoemContext poemContext;
123   private HttpServletRequest request;
124   private HttpServletResponse response;
125   private Database database = null;
126   private Table<?> table = null;
127   private Persistent object = null;
128   private MarkupLanguage markupLanguage = null;
129   
130   private String[] arguments;
131 
132   // the template engine that is in use (if any)
133   private TemplateEngine templateEngine;
134   // the object that is used by the template engine to expand the template
135   // against
136   private TemplateContext templateContext;
137   // are we manually flushing the output
138   private boolean flushing = false;
139   // are we buffering the output
140   private boolean buffered= true;
141   // the output writer
142   private MelatiWriter writer;
143 
144   private String encoding;
145 
146   /**
147    * Construct a Melati for use with Servlets.
148    *
149    * @param config - the MelatiConfig
150    * @param request - the Servlet Request
151    * @param response - the Servlet Response
152    */
153   public Melati(MelatiConfig config,
154                 HttpServletRequest request,
155                 HttpServletResponse response) {
156     this.request = request;
157     this.response = response;
158     this.config = config;
159   }
160 
161   /** Convenience constructor. */
162   public Melati() {
163     this.config = new MelatiConfig();
164     this.writer = new MelatiStringWriter();
165   }
166   /** Convenience constructor. */
167   public Melati(MelatiWriter writer) {
168     this.config = new MelatiConfig();
169     this.writer = writer;
170   }
171   /**
172    * Construct a Melati for use in 'stand alone' mode.
173    * NB: you will not have access to servlet related stuff (eg sessions)
174    *
175    * @param config - the MelatiConfig
176    * @param writer - the Writer that all output is written to
177    */
178 
179   public Melati(MelatiConfig config, MelatiWriter writer) {
180     this.config = config;
181     this.writer = writer;
182   }
183 
184   /**
185    * Get the servlet request object.
186    *
187    * @return the Servlet Request
188    */
189 
190   public HttpServletRequest getRequest() {
191     return request;
192   }
193 
194   /**
195    * It is sometimes convenient to reconstruct the request object and
196    * reset it, for example when returning from a log-in page.
197    *
198    * @see org.melati.login.HttpSessionAccessHandler
199    * @param request - new request object
200    */
201   public void setRequest(HttpServletRequest request) {
202     this.request = request;
203   }
204 
205   /**
206    * Used to set response mock in tests.
207    * @see org.melati.login.HttpSessionAccessHandler
208    * @param response - mock response object
209    */
210   public void setResponse(HttpServletResponse response) {
211     this.response = response;
212   }
213   
214   /**
215    * Get the servlet response object.
216    *
217    * @return - the Servlet Response
218    */
219 
220   public HttpServletResponse getResponse() {
221     return response;
222   }
223 
224   /**
225    * Set the {@link PoemContext} for this request.  If the Context has a
226    * LogicalDatabase set, this will be used to establish a connection
227    * to the database.
228    *
229    * @param context - a PoemContext
230    * @throws DatabaseInitException - if the database fails to initialise for
231    *                                 some reason
232    * @see org.melati.LogicalDatabase
233    * @see org.melati.servlet.PoemServlet
234    */
235   public void setPoemContext(PoemContext context)
236       throws DatabaseInitException {
237     this.poemContext = context;
238     if (poemContext.getLogicalDatabase() != null)
239       database = LogicalDatabase.getDatabase(poemContext.getLogicalDatabase());
240   }
241 
242   /**
243    * Load a POEM Table and POEM Object for use in this request.  This is useful
244    * as often Servlet requests are relevant for a single Table and/or Object.
245    *
246    * The Table name and Object id are set from the PoemContext.
247    *
248    * @see org.melati.admin.Admin
249    * @see org.melati.servlet.PoemServlet
250    */
251   public void loadTableAndObject() {
252     if (database != null)
253       if (poemContext.getTable() != null ) {
254         table = database.getTable(poemContext.getTable());
255         if (poemContext.getTroid() != null)
256           object = table.getObject(poemContext.getTroid().intValue());
257         else
258           object = null;
259       }
260   }
261 
262 
263   /**
264    * Get the PoemContext for this Request.
265    *
266    * @return - the PoemContext for this Request
267    */
268   public PoemContext getPoemContext() {
269     return poemContext;
270   }
271 
272   /**
273    * Get the POEM Database for this Request.
274    *
275    * @return - the POEM Database for this Request
276    * @see #setPoemContext
277    */
278   public Database getDatabase() {
279     return database;
280   }
281   
282   /**
283    * @return the name of the Database 
284    */
285   public String getDatabaseName() { 
286     return getPoemContext().getLogicalDatabase();  
287   }
288   /**
289    * Return the names of other databases known at the moment. 
290    *  
291    * @return a Vector of database names
292    */
293   public Vector<String> getKnownDatabaseNames() {
294     return LogicalDatabase.
295                getInitialisedDatabaseNames();
296   }
297 
298   /**
299    * Get the POEM Table (if any) in use for this Request.
300    *
301    * @return the POEM Table for this Request
302    * @see #loadTableAndObject
303    */
304   public Table<?> getTable() {
305     return table;
306   }
307 
308   /**
309    * Get the POEM Object (if any) in use for this Request.
310    *
311    * @return the POEM Object for this Request
312    * @see #loadTableAndObject
313    */
314   public Persistent getObject() {
315     return object;
316   }
317 
318   /**
319    * Get the Method (if any) that has been set for this Request.
320    *
321    * @return the Method for this Request
322    * @see org.melati.PoemContext
323    * @see org.melati.servlet.ConfigServlet#poemContext
324    * @see org.melati.servlet.PoemServlet#poemContext
325    */
326   public String getMethod() {
327     return poemContext.getMethod();
328   }
329 
330   /**
331    * Set the template engine to be used for this Request.
332    *
333    * @param te - the template engine to be used
334    * @see org.melati.servlet.TemplateServlet
335    */
336   public void setTemplateEngine(TemplateEngine te) {
337     templateEngine = te;
338   }
339 
340   /**
341    * Get the template engine in use for this Request.
342    *
343    * @return - the template engine to be used
344    */
345   public TemplateEngine getTemplateEngine() {
346     return templateEngine;
347   }
348 
349   /**
350    * Set the TemplateContext to be used for this Request.
351    *
352    * @param tc - the template context to be used
353    * @see org.melati.servlet.TemplateServlet
354    */
355   public void setTemplateContext(TemplateContext tc) {
356     templateContext = tc;
357   }
358 
359   /**
360    * Get the TemplateContext used for this Request.
361    *
362    * @return - the template context being used
363    */
364   public TemplateContext getTemplateContext() {
365     return templateContext;
366   }
367   
368   /**
369    * Get the TemplateContext used for this Request.
370    *
371    * @return - the template context being used
372    */
373   public ServletTemplateContext getServletTemplateContext() {
374     return (ServletTemplateContext)templateContext;
375   }
376 
377   /**
378    * Get the MelatiConfig associated with this Request.
379    *
380    * @return - the configuration being used
381    */
382   public MelatiConfig getConfig() {
383     return config;
384   }
385 
386   /**
387    * Get the PathInfo for this Request split into Parts by '/'.
388    *
389    * @return - an array of the parts found on the PathInfo
390    */
391   public String[] getPathInfoParts() {
392     String pathInfo = request.getPathInfo();
393     if (pathInfo == null || pathInfo.length() < 1) return new String[0];
394     pathInfo = pathInfo.substring(1);
395     return StringUtils.split(pathInfo, '/');
396   }
397 
398   /**
399    * Set the aruments array from the commandline.
400    *
401    * @param args the arguments to set
402    */
403   public void setArguments(String[] args) {
404     arguments = args;
405   }
406 
407   /**
408    * Get the Arguments array.
409    *
410    * @return the arguments array
411    */
412   public String[] getArguments() {
413     return arguments;
414   }
415 
416   /**
417    * Get the Session for this Request.
418    *
419    * @return - the Session for this Request
420    */
421   public HttpSession getSession() {
422     return getRequest().getSession(true);
423   }
424 
425   /**
426    * Get a named context utility eg org.melati.admin.AdminUtils.
427    *  
428    * @param className Name of a class with a single argument Melati constructor 
429    * @return the instantiated class
430    */
431   public Object getContextUtil(String className) {
432     Constructor<?> c;
433     try {
434         c  = Class.forName(className).getConstructor(new Class[] {this.getClass()});
435     } catch (NoSuchMethodException e) {
436       try { 
437         c  = Class.forName(className).getConstructor(new Class[] {});
438         try {
439           return c.newInstance(new Object[] {});
440         } catch (Exception e2) {
441             throw new MelatiBugMelatiException("Class " + className + 
442                     " cannot be instantiated ", e2);
443         }
444       } catch (Exception e2) {
445           throw new MelatiBugMelatiException("Class " + className + 
446                   " cannot be instantiated ", e2);
447       }
448     } catch (Exception e) {
449           throw new MelatiBugMelatiException("Class " + className + 
450                 " cannot be instantiated ", e);
451     }  
452     try {
453       return c.newInstance(new Object[] {this});
454     } catch (Exception e) {
455         throw new MelatiBugMelatiException("Class " + className + 
456                 " cannot be instantiated ", e);
457     }
458   }
459   
460   /**
461    * Get a named context utility eg org.melati.admin.AdminUtils.
462    *  
463    * @param className Name of a class with a single argument Melati constructor 
464    * @return the instantiated class
465    */
466   public Object getInstance(String className) {
467     Object util;
468     try {
469       Constructor<?> c  = Class.forName(className).getConstructor(new Class[] {this.getClass()});
470       util = c.newInstance(new Object[] {this});
471     } catch (Exception e) {
472       throw new MelatiBugMelatiException("Class " + className + 
473           " cannot be instantiated", e);
474     }  
475     return util;
476   }
477   
478   /**
479    * Get the URL for the Logout Page.
480    *
481    * @return - the URL for the Logout Page
482    * @see org.melati.login.Logout
483    */
484   public String getLogoutURL() {
485     StringBuffer url = new StringBuffer();
486     HttpUtil.appendRelativeZoneURL(url, getRequest());
487     url.append('/');
488     url.append(MelatiConfig.getLogoutPageServletClassName());
489     url.append('/');
490     url.append(poemContext.getLogicalDatabase());
491     return url.toString();
492   }
493 
494   /**
495    * Get the URL for the Login Page.
496    *
497    * @return - the URL for the Login Page
498    * @see org.melati.login.Login
499    */
500   public String getLoginURL() {
501     StringBuffer url = new StringBuffer();
502     HttpUtil.appendRelativeZoneURL(url, getRequest());
503     url.append('/');
504     url.append(MelatiConfig.getLoginPageServletClassName());
505     url.append('/');
506     url.append(poemContext.getLogicalDatabase());
507     return url.toString();
508   }
509 
510   /**
511    * Get the URL for this Servlet Zone.
512    *
513    * @return - the URL for this Servlet Zone
514    * @see org.melati.util.HttpUtil#zoneURL
515    */
516   public String getZoneURL() {
517     return HttpUtil.zoneURL(getRequest());
518   }
519 
520   /**
521    * @return the relative url for the Servlet Zone of the current request
522    */
523   public String getRelativeZoneURL() { 
524     return HttpUtil.getRelativeRequestURL(getRequest());    
525   }
526   /**
527    * Get the URL for this request.
528    * Not used in Melati.
529    *
530    * @return - the URL for this request
531    * @see org.melati.util.HttpUtil#servletURL
532    */
533   public String getServletURL() {
534     return HttpUtil.servletURL(getRequest());
535   }
536 
537   /**
538    * Get the URL for the JavascriptLibrary.
539    * Convenience method.
540    * 
541    * @return - the URL for the JavascriptLibrary
542    * @see org.melati.MelatiConfig#getJavascriptLibraryURL
543    */
544   public String getJavascriptLibraryURL() {
545     return config.getJavascriptLibraryURL();
546   }
547 
548   /**
549    * Returns a PoemLocale object based on the Accept-Language header
550    * of this request.
551    *
552    * If no usable Accept-Language header is found or we are using 
553    * Melati outside of a servlet context then the configured 
554    * default locale is returned.
555    *
556    * @return a PoemLocale object
557    */
558   public PoemLocale getPoemLocale() {
559     if (getRequest() == null)
560        return MelatiConfig.getPoemLocale();
561     else if(getRequest().getLocale() == null) {
562       return MelatiConfig.getPoemLocale();
563     } else 
564       return PoemLocale.from(getRequest().getLocale());
565   }
566 
567   
568   /**
569    * Suggest a response character encoding and if necessary choose a
570    * request encoding.
571    * <p>
572    * If the request encoding is provided then we choose a response
573    * encoding to meet our preferences on the assumption that the
574    * client will also indicate next time what its request
575    * encoding is.
576    * The result can optionally be set in code or possibly in
577    * templates using {@link #setResponseContentType(String)}.
578    * <p>
579    * Otherwise we tread carefully. We assume that the encoding is
580    * the first supported encoding of the client's preferences for
581    * responses, as indicated by Accept-Charsets, and avoid giving
582    * it any reason to change.
583    * <p>
584    * Actually, the server preference is a bit dodgy for
585    * the response because if it does persuade the client to
586    * change encodings and future requests include query strings
587    * that we are providing now then we may end up with the
588    * query strings being automatically decoded using the wrong
589    * encoding by request.getParameter(). But by the time we
590    * end up with values in such parameters the client and
591    * server will probably have settled on particular encodings.
592    */
593   public void establishCharsets() throws CharsetException {
594 
595     AcceptCharset ac;
596     String acs = request.getHeader("Accept-Charset");
597     //assert acs == null || acs.trim().length() > 0 :
598     //  "Accept-Charset should not be empty but can be absent";
599     // Having said that we don't want to split hairs once debugged
600     if (acs != null && acs.trim().length() == 0) {
601       acs = null;
602     }
603     try {
604       ac = new AcceptCharset(acs, config.getPreferredCharsets());
605     }
606     catch (HttpHeader.HttpHeaderException e) {
607       throw new CharsetException(
608           "An error was detected in your HTTP request header, " +
609           "response code: " +
610           HttpServletResponse.SC_BAD_REQUEST +
611           ": \"" + acs + '"', e);
612     }
613     if (request.getCharacterEncoding() == null) {
614       responseCharset = ac.clientChoice();
615       try {
616         request.setCharacterEncoding(responseCharset);
617       }
618       catch (UnsupportedEncodingException e) {
619         throw new MelatiBugMelatiException("This should already have been checked by AcceptCharset", e);
620       }
621     } else {
622       responseCharset = ac.serverChoice();
623     }
624   }
625 
626   /**
627    * Suggested character encoding for use in responses.
628    */
629   protected String responseCharset = null;
630   
631 
632   /**
633    * Sets the content type for use in the response.
634    * <p>
635    * Use of this method is optional and only makes sense in a 
636    * Servlet context. If the response is null then this is a no-op.
637    * <p>
638    * If the type starts with "text/" and does not contain a semicolon
639    * and a good response character set has been established based on
640    * the request Accept-Charset header and server preferences, then this
641    * and semicolon separator are automatically appended to the type.
642    * <p>
643    * Whether this function should be called at all may depend on
644    * the application and templates.
645    * <p>
646    * It should be called before any calls to {@link #getEncoding()}
647    * and before writing the response.
648    *
649    * @see #establishCharsets()
650    */
651   public void setResponseContentType(String type) {
652     contentType = type;
653     if (responseCharset != null) 
654       if (type.startsWith("text/")) 
655         if (type.indexOf(";") == -1)
656           contentType += "; charset=" + responseCharset;
657     if (response != null) {
658       response.setContentType(contentType);
659     }
660   }
661   protected String contentType = null;
662   /**
663    * @return the contentType
664    */
665   public String getContentType() {
666     return contentType;
667   }
668   
669   
670   /**
671    * Use this method if you wish to use a different 
672    * MarkupLanguage, WMLMarkupLanguage for example. 
673    * Cannot be set in MelatiConfig as MarkupLanguage 
674    * does not have a no argument constructor.
675    * @param ml The ml to set.
676    */
677   public void setMarkupLanguage(MarkupLanguage ml) {
678     this.markupLanguage = ml;
679   }
680   
681   /**
682    * Get a {@link MarkupLanguage} for use generating output from templates.
683    * Defaults to HTMLMarkupLanguage.
684    *
685    * @return - a MarkupLanguage, defaulting to HTMLMarkupLanguage
686    * @see org.melati.template.TempletLoader
687    * @see org.melati.poem.PoemLocale
688    */
689   public MarkupLanguage getMarkupLanguage() {
690     if (markupLanguage == null) 
691       markupLanguage = new HTMLMarkupLanguage(this,
692                                   config.getTempletLoader(),
693                                   getPoemLocale());
694     return markupLanguage;
695   }
696 
697   /**
698    * Get a HTMLMarkupLanguage.
699    * Retained for backward compatibility as there are a lot 
700    * of uses in templates.
701    *
702    * @return - a HTMLMarkupLanguage
703    */
704   public HTMLMarkupLanguage getHTMLMarkupLanguage() {
705     return (HTMLMarkupLanguage)getMarkupLanguage();
706   }
707 
708   /**
709    * The URL of the servlet request associated with this <TT>Melati</TT>, with
710    * a modified or added form parameter setting (query string component).
711    *
712    * @param field   The name of the form parameter
713    * @param value   The new value for the parameter (unencoded)
714    * @return        The request URL with <TT>field=value</TT>.  If there is
715    *                already a binding for <TT>field</TT> in the query string
716    *                it is replaced, not duplicated.  If there is no query
717    *                string, one is added.
718    * @see org.melati.servlet.Form
719    */
720   public String sameURLWith(String field, String value) {
721     return Form.sameURLWith(getRequest(), field, value);
722   }
723 
724   /**
725    * The URL of the servlet request associated with this <TT>Melati</TT>, with
726    * a modified or added form flag setting (query string component).
727    *
728    * @param field   The name of the form parameter
729    * @return        The request URL with <TT>field=1</TT>.  If there is
730    *                already a binding for <TT>field</TT> in the query string
731    *                it is replaced, not duplicated.  If there is no query
732    *                string, one is added.
733    * @see org.melati.servlet.Form
734    */
735   public String sameURLWith(String field) {
736     return sameURLWith(field, "1");
737   }
738 
739   /**
740    * The URL of the servlet request associated with this <TT>Melati</TT>.
741    *
742    * @return a string
743    */
744   public String getSameURL() {
745     String qs = getRequest().getQueryString();
746     return getRequest().getRequestURI() + (qs == null ? "" : '?' + qs);
747   }
748 
749   /**
750    * Turn off buffering of the output stream.
751    *
752    * By default, melati will buffer the output, which will not be written
753    * to the output stream until you call melati.write();
754    *
755    * Buffering allows us to catch AccessPoemExceptions and redirect the user
756    * to the login page.  This could not be done if any bytes had already  been written
757    * to the client.
758    *
759    * @see org.melati.test.FlushingServletTest
760    * @throws IOException if a writer has already been selected
761    */
762   public void setBufferingOff() throws IOException {
763     if (writer != null)
764       throw new IOException("You have already requested a Writer, " +
765                             "and can't change it's properties now");
766     buffered = false;
767   }
768 
769   /**
770    * Turn on flushing of the output stream.
771    *
772    * @throws IOException if there is a problem with the writer
773    */
774   public void setFlushingOn() throws IOException {
775     if (writer != null)
776       throw new IOException("You have already requested a Writer, " +
777                             "and can't change it's properties now");
778     flushing = true;
779   }
780 
781   /**
782    * Return the encoding that is used for URL encoded query
783    * strings.
784    * <p>
785    * The requirement here is that parameters can be encoded in
786    * query strings included in URLs in the body of responses.
787    * User interaction may result in subsequent requests with such
788    * a URL. The HTML spec. describes encoding of non-alphanumeric
789    * ASCII using % and ASCII hex codes and, in the case of forms.
790    * says the client may use the response encoding by default.
791    * Sun's javadoc for <code>java.net.URLEncoder</code>
792    * recommends UTF-8 but the default is the Java platform
793    * encoding. Most significantly perhaps,
794    * org.mortbay.http.HttpRequest uses the request encoding.
795    * We should check that this is correct in the servlet specs.
796    * <p>
797    * So we assume that the servlet runner may dictate the
798    * encoding that will work for multi-national characters in
799    * field values encoded in URL's (but not necessarily forms).
800    * <p>
801    * If the request encoding is used then we have to try and
802    * predict it. It will be the same for a session unless a client
803    * has some reason to change it. E.g. if we respond to a request
804    * in a different encoding and the client is influenced.
805    * (See {@link #establishCharsets()}.
806    * But that is only a problem if the first or second request
807    * in a session includes field values encoded in the URL and
808    * user options include manually entering the same in a form
809    * or changing their browser configuration.
810    * Or we can change the server configuration.
811    * <p>
812    * It would be better if we had control over what encoding
813    * the servlet runner used to decode parameters.
814    * Perhaps one day we will.
815    * <p>
816    * So this method implements the current policy and currently
817    * returns the current request encoding.
818    * It assumes {@link #establishCharsets()} has been called to
819    * set the request encoding if necessary.
820    *
821    * @return the character encoding
822    * @see #establishCharsets()
823    * see also org.melati.admin.Admin#selection(ServletTemplateContext, Melati)
824    */
825   public String getURLQueryEncoding() {
826     return request.getCharacterEncoding();
827   }
828 
829   /**
830    * Convenience method to URL encode a URL query string.
831    *
832    * See org.melati.admin.Admin#selection(ServletTemplateContext, Melati)
833    */
834   /**
835    * @param string the String to encode
836    * @return the encoded string
837    */
838   public String urlEncode(String string) {
839     try {
840       return UTF8URLEncoder.encode(string, getURLQueryEncoding());
841     }
842     catch (UnexpectedExceptionException e) {
843       // Thrown if the encoding is not supported
844       return string;
845     }
846   }
847 
848   /**
849    * Return the encoding that is used for writing.
850    * <p>
851    * This should always return an encoding and it should be the same
852    * for duration of use of an instance.
853    *
854    * @return Response encoding or a default in stand alone mode
855    * @see #setResponseContentType(String)
856    */
857   public String getEncoding() {
858     if (encoding == null)
859       encoding = response == null ? DEFAULT_ENCODING :
860                                     response.getCharacterEncoding();
861     return encoding;
862   }
863 
864   /**
865    * Get a Writer for this request.
866    *
867    * If you have not accessed the Writer, it is reasonable to assume that
868    * nothing has been written to the output stream.
869    *
870    * @return - one of:
871    *
872    * - the Writer that was used to construct the Melati
873    * - the Writer associated with the Servlet Response
874    * - a buffered Writer
875    * - a ThrowingPrintWriter
876    */
877   public MelatiWriter getWriter() {
878     if (writer == null) writer = createWriter();
879     return writer;
880   }
881 
882   /**
883    * @param writerP the MelatiWriter to set
884    */
885   public void setWriter(MelatiWriter writerP) {
886     writer = writerP;
887   }
888   /**
889    * Get a StringWriter.
890    *
891    * @return - one of:
892    *
893    * - a MelatiStringWriter from the template engine
894    * - a new MelatiStringWriter if template engine not set
895    *
896    */
897   public MelatiWriter getStringWriter() {
898     if (templateEngine == null) {
899       return new MelatiStringWriter();
900     }
901     return templateEngine.getStringWriter();
902   }
903 
904   /**
905    * Used in a servlet setting, where the class was not constructed with 
906    * output set.
907    * @return a response writer 
908    */
909   private MelatiWriter createWriter() {
910     // first effort is to use the writer supplied by the template engine
911     MelatiWriter writerL = null;
912     if (response != null) {
913       if (templateEngine != null &&
914               templateEngine instanceof ServletTemplateEngine) {
915         writerL = ((ServletTemplateEngine)templateEngine).getServletWriter(response, buffered);
916       } else {
917         PrintWriter printWriter = null;
918         try { 
919           printWriter = response.getWriter(); 
920         } catch (IOException e) { 
921           throw new MelatiIOException(e);
922         }
923         if (buffered) {
924           writerL = new MelatiBufferedWriter(printWriter);
925         } else {
926           writerL = new MelatiSimpleWriter(printWriter);
927         }
928       }
929       if (flushing) writerL.setFlushingOn();
930     } else 
931       throw new MelatiBugMelatiException("Method createWriter called when response was null.");
932     return writerL;
933   }
934 
935   /**
936    * Write the buffered output to the Writer
937    * we also need to stop the flusher if it has started.
938    */
939   public void write() {
940     // only write stuff if we have previously got a writer
941     if (writer != null)
942       try {
943         writer.close();
944       } catch (IOException e) {
945         System.err.println("Melati output already closed");
946       }
947   }
948 
949   /**
950    * This allows an Exception to be handled inline during Template expansion
951    * for example, if you would like to render AccessPoemExceptions to a
952    * String to be displayed on the page that is returned to the client.
953    * 
954    * @see org.melati.template.MarkupLanguage#rendered(Object)
955    * @see org.melati.poem.TailoredQuery
956    */
957   public void setPassbackExceptionHandling() { 
958     templateContext.setPassbackExceptionHandling();
959   }
960   
961   /**
962    * The normal state of affairs: an exception is thrown and 
963    * it is handled by the servlet.
964    */
965   public void setPropagateExceptionHandling() { 
966     templateContext.setPropagateExceptionHandling();
967   }
968   /**
969    * Get a User for this request (if they are logged in).
970    * NOTE POEM studiously assumes there isn't necessarily a user, only
971    * an AccessToken
972    * @return - a User for this request
973    */
974   public User getUser() {
975     try {
976       return (User)PoemThread.accessToken();
977     }
978     catch (NotInSessionPoemException e) {
979       return null;
980     }
981     catch (ClassCastException e) {
982       // If the AccessToken is the RootAccessToken
983       return null;
984     }
985   }
986   
987   /**
988    * Establish if field is a ReferencePoemType field.
989    * 
990    * @param field
991    *          the field to check
992    * @return whether it is a reference poem type
993    */
994   public boolean isReferencePoemType(Field<?> field) {
995     return field.getType() instanceof ReferencePoemType;
996   }
997 
998   /**
999    * Find a db specific template if it exists, otherwise a non-specific one, 
1000    * searching through all template paths.
1001    * 
1002    * @param key fileName of template, without extension
1003    * @return full resource name
1004    */
1005   public String templateName(String key) {
1006     String templateName = null;
1007     try {
1008       TemplateEngine te = getTemplateEngine(); 
1009       if (te == null)
1010         throw new MelatiBugMelatiException("Template engine null");
1011       Database db = getDatabase();
1012       templateName = te.getTemplateName(key, db == null ? null : db.getName());
1013     } catch (Exception e) {
1014       throw new MelatiBugMelatiException("Problem getting template named " + key  +
1015               " :" + e.toString(), e);
1016     }
1017     return templateName;
1018   }
1019 
1020 }