View Javadoc

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