View Javadoc

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