Melati.java

/*
 * $Source$
 * $Revision$
 *
 * Copyright (C) 2000 William Chesters
 *
 * Part of Melati (http://melati.org), a framework for the rapid
 * development of clean, maintainable web applications.
 *
 * Melati is free software; Permission is granted to copy, distribute
 * and/or modify this software under the terms either:
 *
 * a) the GNU General Public License as published by the Free Software
 *    Foundation; either version 2 of the License, or (at your option)
 *    any later version,
 *
 *    or
 *
 * b) any version of the Melati Software License, as published
 *    at http://melati.org
 *
 * You should have received a copy of the GNU General Public License and
 * the Melati Software License along with this program;
 * if not, write to the Free Software Foundation, Inc.,
 * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 * GNU General Public License and visit http://melati.org to obtain the
 * Melati Software License.
 *
 * Feel free to contact the Developers of Melati (http://melati.org),
 * if you would like to work out a different arrangement than the options
 * outlined here.  It is our intention to allow Melati to be used by as
 * wide an audience as possible.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * Contact details for copyright holder:
 *
 *     William Chesters <williamc At paneris.org>
 *     http://paneris.org/~williamc
 *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
 */

package org.melati;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Constructor;
import java.util.Vector;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;

import org.melati.poem.Database;
import org.melati.poem.Field;
import org.melati.poem.NotInSessionPoemException;
import org.melati.poem.Persistent;
import org.melati.poem.PoemLocale;
import org.melati.poem.PoemThread;
import org.melati.poem.ReferencePoemType;
import org.melati.poem.Table;
import org.melati.poem.User;
import org.melati.poem.util.StringUtils;
import org.melati.servlet.Form;
import org.melati.template.HTMLMarkupLanguage;
import org.melati.template.MarkupLanguage;
import org.melati.template.ServletTemplateContext;
import org.melati.template.ServletTemplateEngine;
import org.melati.template.TemplateContext;
import org.melati.template.TemplateEngine;
import org.melati.util.AcceptCharset;
import org.melati.util.CharsetException;
import org.melati.util.DatabaseInitException;
import org.melati.util.HttpHeader;
import org.melati.util.HttpUtil;
import org.melati.util.MelatiBufferedWriter;
import org.melati.util.MelatiBugMelatiException;
import org.melati.util.MelatiIOException;
import org.melati.util.MelatiSimpleWriter;
import org.melati.util.MelatiStringWriter;
import org.melati.util.MelatiWriter;
import org.melati.util.UTF8URLEncoder;
import org.melati.util.UnexpectedExceptionException;

/**
 * This is the main entry point for using the Melati framework.
 * A Melati exists once per request, or command from an application.
 * <p>
 * It provides a central container for all the relevant objects that 
 * a Servlet or command line application needs to create textual 
 * output, optionally using a Template Engine or a Database.
 * <p>
 * You will need to create a MelatiConfig in order to construct a Melati.
 * <p>
 * If you are using servlets, you will want to construct a Melati with
 * a request and response object.  Otherwise, simply pass in a Writer.
 * <p>
 * If you are using a template engine outside of a servlets context you will 
 * still need the servlets jar in your classpath, annoyingly, as Velocity and 
 * WebMacro introspect all possible methods and throw a ClassNotFound exception 
 * if the servlets classes are not available.  
 * <p>
 * Melati is typically used with Servlets, POEM (Persistent Object Engine for
 * Melati) and a Template Engine
 *
 * @see org.melati.MelatiConfig
 * @see org.melati.servlet.ConfigServlet
 * @see org.melati.servlet.PoemServlet
 * @see org.melati.servlet.TemplateServlet
 */

public class Melati {

  /** UTF-8. */
  public static final String DEFAULT_ENCODING = "UTF-8";
  
  private MelatiConfig config;
  private PoemContext poemContext;
  private HttpServletRequest request;
  private HttpServletResponse response;
  private Database database = null;
  private Table<?> table = null;
  private Persistent object = null;
  private MarkupLanguage markupLanguage = null;
  
  private String[] arguments;

  // the template engine that is in use (if any)
  private TemplateEngine templateEngine;
  // the object that is used by the template engine to expand the template
  // against
  private TemplateContext templateContext;
  // are we manually flushing the output
  private boolean flushing = false;
  // are we buffering the output
  private boolean buffered= true;
  // the output writer
  private MelatiWriter writer;

  private String encoding;

  /**
   * Construct a Melati for use with Servlets.
   *
   * @param config - the MelatiConfig
   * @param request - the Servlet Request
   * @param response - the Servlet Response
   */
  public Melati(MelatiConfig config,
                HttpServletRequest request,
                HttpServletResponse response) {
    this.request = request;
    this.response = response;
    this.config = config;
  }

  /** Convenience constructor. */
  public Melati() {
    this.config = new MelatiConfig();
    this.writer = new MelatiStringWriter();
  }
  /** Convenience constructor. */
  public Melati(MelatiWriter writer) {
    this.config = new MelatiConfig();
    this.writer = writer;
  }
  /**
   * Construct a Melati for use in 'stand alone' mode.
   * NB: you will not have access to servlet related stuff (eg sessions)
   *
   * @param config - the MelatiConfig
   * @param writer - the Writer that all output is written to
   */

  public Melati(MelatiConfig config, MelatiWriter writer) {
    this.config = config;
    this.writer = writer;
  }

  /**
   * Get the servlet request object.
   *
   * @return the Servlet Request
   */

  public HttpServletRequest getRequest() {
    return request;
  }

  /**
   * It is sometimes convenient to reconstruct the request object and
   * reset it, for example when returning from a log-in page.
   *
   * @see org.melati.login.HttpSessionAccessHandler
   * @param request - new request object
   */
  public void setRequest(HttpServletRequest request) {
    this.request = request;
  }

  /**
   * Used to set response mock in tests.
   * @see org.melati.login.HttpSessionAccessHandler
   * @param response - mock response object
   */
  public void setResponse(HttpServletResponse response) {
    this.response = response;
  }
  
  /**
   * Get the servlet response object.
   *
   * @return - the Servlet Response
   */

  public HttpServletResponse getResponse() {
    return response;
  }

  /**
   * Set the {@link PoemContext} for this request.  If the Context has a
   * LogicalDatabase set, this will be used to establish a connection
   * to the database.
   *
   * @param context - a PoemContext
   * @throws DatabaseInitException - if the database fails to initialise for
   *                                 some reason
   * @see org.melati.LogicalDatabase
   * @see org.melati.servlet.PoemServlet
   */
  public void setPoemContext(PoemContext context)
      throws DatabaseInitException {
    this.poemContext = context;
    if (poemContext.getLogicalDatabase() != null)
      database = LogicalDatabase.getDatabase(poemContext.getLogicalDatabase());
  }

  /**
   * Load a POEM Table and POEM Object for use in this request.  This is useful
   * as often Servlet requests are relevant for a single Table and/or Object.
   *
   * The Table name and Object id are set from the PoemContext.
   *
   * @see org.melati.admin.Admin
   * @see org.melati.servlet.PoemServlet
   */
  public void loadTableAndObject() {
    if (database != null)
      if (poemContext.getTable() != null ) {
        table = database.getTable(poemContext.getTable());
        if (poemContext.getTroid() != null)
          object = table.getObject(poemContext.getTroid().intValue());
        else
          object = null;
      }
  }


  /**
   * Get the PoemContext for this Request.
   *
   * @return - the PoemContext for this Request
   */
  public PoemContext getPoemContext() {
    return poemContext;
  }

  /**
   * Get the POEM Database for this Request.
   *
   * @return - the POEM Database for this Request
   * @see #setPoemContext
   */
  public Database getDatabase() {
    return database;
  }
  
  /**
   * @return the name of the Database 
   */
  public String getDatabaseName() { 
    return getPoemContext().getLogicalDatabase();  
  }
  /**
   * Return the names of other databases known at the moment. 
   *  
   * @return a Vector of database names
   */
  public Vector<String> getKnownDatabaseNames() {
    return LogicalDatabase.
               getInitialisedDatabaseNames();
  }

  /**
   * Get the POEM Table (if any) in use for this Request.
   *
   * @return the POEM Table for this Request
   * @see #loadTableAndObject
   */
  public Table<?> getTable() {
    return table;
  }

  /**
   * Get the POEM Object (if any) in use for this Request.
   *
   * @return the POEM Object for this Request
   * @see #loadTableAndObject
   */
  public Persistent getObject() {
    return object;
  }

  /**
   * Get the Method (if any) that has been set for this Request.
   *
   * @return the Method for this Request
   * @see org.melati.PoemContext
   * @see org.melati.servlet.ConfigServlet#poemContext
   * @see org.melati.servlet.PoemServlet#poemContext
   */
  public String getMethod() {
    return poemContext.getMethod();
  }

  /**
   * Set the template engine to be used for this Request.
   *
   * @param te - the template engine to be used
   * @see org.melati.servlet.TemplateServlet
   */
  public void setTemplateEngine(TemplateEngine te) {
    templateEngine = te;
  }

  /**
   * Get the template engine in use for this Request.
   *
   * @return - the template engine to be used
   */
  public TemplateEngine getTemplateEngine() {
    return templateEngine;
  }

  /**
   * Set the TemplateContext to be used for this Request.
   *
   * @param tc - the template context to be used
   * @see org.melati.servlet.TemplateServlet
   */
  public void setTemplateContext(TemplateContext tc) {
    templateContext = tc;
  }

  /**
   * Get the TemplateContext used for this Request.
   *
   * @return - the template context being used
   */
  public TemplateContext getTemplateContext() {
    return templateContext;
  }
  
  /**
   * Get the TemplateContext used for this Request.
   *
   * @return - the template context being used
   */
  public ServletTemplateContext getServletTemplateContext() {
    return (ServletTemplateContext)templateContext;
  }

  /**
   * Get the MelatiConfig associated with this Request.
   *
   * @return - the configuration being used
   */
  public MelatiConfig getConfig() {
    return config;
  }

  /**
   * Get the PathInfo for this Request split into Parts by '/'.
   *
   * @return - an array of the parts found on the PathInfo
   */
  public String[] getPathInfoParts() {
    String pathInfo = request.getPathInfo();
    if (pathInfo == null || pathInfo.length() < 1) return new String[0];
    pathInfo = pathInfo.substring(1);
    return StringUtils.split(pathInfo, '/');
  }

  /**
   * Set the aruments array from the commandline.
   *
   * @param args the arguments to set
   */
  public void setArguments(String[] args) {
    arguments = args;
  }

  /**
   * Get the Arguments array.
   *
   * @return the arguments array
   */
  public String[] getArguments() {
    return arguments;
  }

  /**
   * Get the Session for this Request.
   *
   * @return - the Session for this Request
   */
  public HttpSession getSession() {
    return getRequest().getSession(true);
  }

  /**
   * Get a named context utility eg org.melati.admin.AdminUtils.
   *  
   * @param className Name of a class with a single argument Melati constructor 
   * @return the instantiated class
   */
  public Object getContextUtil(String className) {
    Constructor<?> c;
    try {
        c  = Class.forName(className).getConstructor(new Class[] {this.getClass()});
    } catch (NoSuchMethodException e) {
      try { 
        c  = Class.forName(className).getConstructor(new Class[] {});
        try {
          return c.newInstance(new Object[] {});
        } catch (Exception e2) {
            throw new MelatiBugMelatiException("Class " + className + 
                    " cannot be instantiated ", e2);
        }
      } catch (Exception e2) {
          throw new MelatiBugMelatiException("Class " + className + 
                  " cannot be instantiated ", e2);
      }
    } catch (Exception e) {
          throw new MelatiBugMelatiException("Class " + className + 
                " cannot be instantiated ", e);
    }  
    try {
      return c.newInstance(new Object[] {this});
    } catch (Exception e) {
        throw new MelatiBugMelatiException("Class " + className + 
                " cannot be instantiated ", e);
    }
  }
  
  /**
   * Get a named context utility eg org.melati.admin.AdminUtils.
   *  
   * @param className Name of a class with a single argument Melati constructor 
   * @return the instantiated class
   */
  public Object getInstance(String className) {
    Object util;
    try {
      Constructor<?> c  = Class.forName(className).getConstructor(new Class[] {this.getClass()});
      util = c.newInstance(new Object[] {this});
    } catch (Exception e) {
      throw new MelatiBugMelatiException("Class " + className + 
          " cannot be instantiated", e);
    }  
    return util;
  }
  
  /**
   * Get the URL for the Logout Page.
   *
   * @return - the URL for the Logout Page
   * @see org.melati.login.Logout
   */
  public String getLogoutURL() {
    StringBuffer url = new StringBuffer();
    HttpUtil.appendRelativeZoneURL(url, getRequest());
    url.append('/');
    url.append(MelatiConfig.getLogoutPageServletClassName());
    url.append('/');
    url.append(poemContext.getLogicalDatabase());
    return url.toString();
  }

  /**
   * Get the URL for the Login Page.
   *
   * @return - the URL for the Login Page
   * @see org.melati.login.Login
   */
  public String getLoginURL() {
    StringBuffer url = new StringBuffer();
    HttpUtil.appendRelativeZoneURL(url, getRequest());
    url.append('/');
    url.append(MelatiConfig.getLoginPageServletClassName());
    url.append('/');
    url.append(poemContext.getLogicalDatabase());
    return url.toString();
  }

  /**
   * Get the URL for this Servlet Zone.
   *
   * @return - the URL for this Servlet Zone
   * @see org.melati.util.HttpUtil#zoneURL
   */
  public String getZoneURL() {
    return HttpUtil.zoneURL(getRequest());
  }

  /**
   * @return the relative url for the Servlet Zone of the current request
   */
  public String getRelativeZoneURL() { 
    return HttpUtil.getRelativeRequestURL(getRequest());    
  }
  /**
   * Get the URL for this request.
   * Not used in Melati.
   *
   * @return - the URL for this request
   * @see org.melati.util.HttpUtil#servletURL
   */
  public String getServletURL() {
    return HttpUtil.servletURL(getRequest());
  }

  /**
   * Get the URL for the JavascriptLibrary.
   * Convenience method.
   * 
   * @return - the URL for the JavascriptLibrary
   * @see org.melati.MelatiConfig#getJavascriptLibraryURL
   */
  public String getJavascriptLibraryURL() {
    return config.getJavascriptLibraryURL();
  }

  /**
   * Returns a PoemLocale object based on the Accept-Language header
   * of this request.
   *
   * If no usable Accept-Language header is found or we are using 
   * Melati outside of a servlet context then the configured 
   * default locale is returned.
   *
   * @return a PoemLocale object
   */
  public PoemLocale getPoemLocale() {
    if (getRequest() == null)
       return MelatiConfig.getPoemLocale();
    else if(getRequest().getLocale() == null) {
      return MelatiConfig.getPoemLocale();
    } else 
      return PoemLocale.from(getRequest().getLocale());
  }

  
  /**
   * Suggest a response character encoding and if necessary choose a
   * request encoding.
   * <p>
   * If the request encoding is provided then we choose a response
   * encoding to meet our preferences on the assumption that the
   * client will also indicate next time what its request
   * encoding is.
   * The result can optionally be set in code or possibly in
   * templates using {@link #setResponseContentType(String)}.
   * <p>
   * Otherwise we tread carefully. We assume that the encoding is
   * the first supported encoding of the client's preferences for
   * responses, as indicated by Accept-Charsets, and avoid giving
   * it any reason to change.
   * <p>
   * Actually, the server preference is a bit dodgy for
   * the response because if it does persuade the client to
   * change encodings and future requests include query strings
   * that we are providing now then we may end up with the
   * query strings being automatically decoded using the wrong
   * encoding by request.getParameter(). But by the time we
   * end up with values in such parameters the client and
   * server will probably have settled on particular encodings.
   */
  public void establishCharsets() throws CharsetException {

    AcceptCharset ac;
    String acs = request.getHeader("Accept-Charset");
    //assert acs == null || acs.trim().length() > 0 :
    //  "Accept-Charset should not be empty but can be absent";
    // Having said that we don't want to split hairs once debugged
    if (acs != null && acs.trim().length() == 0) {
      acs = null;
    }
    try {
      ac = new AcceptCharset(acs, config.getPreferredCharsets());
    }
    catch (HttpHeader.HttpHeaderException e) {
      throw new CharsetException(
          "An error was detected in your HTTP request header, " +
          "response code: " +
          HttpServletResponse.SC_BAD_REQUEST +
          ": \"" + acs + '"', e);
    }
    if (request.getCharacterEncoding() == null) {
      responseCharset = ac.clientChoice();
      try {
        request.setCharacterEncoding(responseCharset);
      }
      catch (UnsupportedEncodingException e) {
        throw new MelatiBugMelatiException("This should already have been checked by AcceptCharset", e);
      }
    } else {
      responseCharset = ac.serverChoice();
    }
  }

  /**
   * Suggested character encoding for use in responses.
   */
  protected String responseCharset = null;
  

  /**
   * Sets the content type for use in the response.
   * <p>
   * Use of this method is optional and only makes sense in a 
   * Servlet context. If the response is null then this is a no-op.
   * <p>
   * If the type starts with "text/" and does not contain a semicolon
   * and a good response character set has been established based on
   * the request Accept-Charset header and server preferences, then this
   * and semicolon separator are automatically appended to the type.
   * <p>
   * Whether this function should be called at all may depend on
   * the application and templates.
   * <p>
   * It should be called before any calls to {@link #getEncoding()}
   * and before writing the response.
   *
   * @see #establishCharsets()
   */
  public void setResponseContentType(String type) {
    contentType = type;
    if (responseCharset != null) 
      if (type.startsWith("text/")) 
        if (type.indexOf(";") == -1)
          contentType += "; charset=" + responseCharset;
    if (response != null) {
      response.setContentType(contentType);
    }
  }
  protected String contentType = null;
  /**
   * @return the contentType
   */
  public String getContentType() {
    return contentType;
  }
  
  
  /**
   * Use this method if you wish to use a different 
   * MarkupLanguage, WMLMarkupLanguage for example. 
   * Cannot be set in MelatiConfig as MarkupLanguage 
   * does not have a no argument constructor.
   * @param ml The ml to set.
   */
  public void setMarkupLanguage(MarkupLanguage ml) {
    this.markupLanguage = ml;
  }
  
  /**
   * Get a {@link MarkupLanguage} for use generating output from templates.
   * Defaults to HTMLMarkupLanguage.
   *
   * @return - a MarkupLanguage, defaulting to HTMLMarkupLanguage
   * @see org.melati.template.TempletLoader
   * @see org.melati.poem.PoemLocale
   */
  public MarkupLanguage getMarkupLanguage() {
    if (markupLanguage == null) 
      markupLanguage = new HTMLMarkupLanguage(this,
                                  config.getTempletLoader(),
                                  getPoemLocale());
    return markupLanguage;
  }

  /**
   * Get a HTMLMarkupLanguage.
   * Retained for backward compatibility as there are a lot 
   * of uses in templates.
   *
   * @return - a HTMLMarkupLanguage
   */
  public HTMLMarkupLanguage getHTMLMarkupLanguage() {
    return (HTMLMarkupLanguage)getMarkupLanguage();
  }

  /**
   * The URL of the servlet request associated with this <TT>Melati</TT>, with
   * a modified or added form parameter setting (query string component).
   *
   * @param field   The name of the form parameter
   * @param value   The new value for the parameter (unencoded)
   * @return        The request URL with <TT>field=value</TT>.  If there is
   *                already a binding for <TT>field</TT> in the query string
   *                it is replaced, not duplicated.  If there is no query
   *                string, one is added.
   * @see org.melati.servlet.Form
   */
  public String sameURLWith(String field, String value) {
    return Form.sameURLWith(getRequest(), field, value);
  }

  /**
   * The URL of the servlet request associated with this <TT>Melati</TT>, with
   * a modified or added form flag setting (query string component).
   *
   * @param field   The name of the form parameter
   * @return        The request URL with <TT>field=1</TT>.  If there is
   *                already a binding for <TT>field</TT> in the query string
   *                it is replaced, not duplicated.  If there is no query
   *                string, one is added.
   * @see org.melati.servlet.Form
   */
  public String sameURLWith(String field) {
    return sameURLWith(field, "1");
  }

  /**
   * The URL of the servlet request associated with this <TT>Melati</TT>.
   *
   * @return a string
   */
  public String getSameURL() {
    String qs = getRequest().getQueryString();
    return getRequest().getRequestURI() + (qs == null ? "" : '?' + qs);
  }

  /**
   * Turn off buffering of the output stream.
   *
   * By default, melati will buffer the output, which will not be written
   * to the output stream until you call melati.write();
   *
   * Buffering allows us to catch AccessPoemExceptions and redirect the user
   * to the login page.  This could not be done if any bytes had already  been written
   * to the client.
   *
   * @see org.melati.test.FlushingServletTest
   * @throws IOException if a writer has already been selected
   */
  public void setBufferingOff() throws IOException {
    if (writer != null)
      throw new IOException("You have already requested a Writer, " +
                            "and can't change it's properties now");
    buffered = false;
  }

  /**
   * Turn on flushing of the output stream.
   *
   * @throws IOException if there is a problem with the writer
   */
  public void setFlushingOn() throws IOException {
    if (writer != null)
      throw new IOException("You have already requested a Writer, " +
                            "and can't change it's properties now");
    flushing = true;
  }

  /**
   * Return the encoding that is used for URL encoded query
   * strings.
   * <p>
   * The requirement here is that parameters can be encoded in
   * query strings included in URLs in the body of responses.
   * User interaction may result in subsequent requests with such
   * a URL. The HTML spec. describes encoding of non-alphanumeric
   * ASCII using % and ASCII hex codes and, in the case of forms.
   * says the client may use the response encoding by default.
   * Sun's javadoc for <code>java.net.URLEncoder</code>
   * recommends UTF-8 but the default is the Java platform
   * encoding. Most significantly perhaps,
   * org.mortbay.http.HttpRequest uses the request encoding.
   * We should check that this is correct in the servlet specs.
   * <p>
   * So we assume that the servlet runner may dictate the
   * encoding that will work for multi-national characters in
   * field values encoded in URL's (but not necessarily forms).
   * <p>
   * If the request encoding is used then we have to try and
   * predict it. It will be the same for a session unless a client
   * has some reason to change it. E.g. if we respond to a request
   * in a different encoding and the client is influenced.
   * (See {@link #establishCharsets()}.
   * But that is only a problem if the first or second request
   * in a session includes field values encoded in the URL and
   * user options include manually entering the same in a form
   * or changing their browser configuration.
   * Or we can change the server configuration.
   * <p>
   * It would be better if we had control over what encoding
   * the servlet runner used to decode parameters.
   * Perhaps one day we will.
   * <p>
   * So this method implements the current policy and currently
   * returns the current request encoding.
   * It assumes {@link #establishCharsets()} has been called to
   * set the request encoding if necessary.
   *
   * @return the character encoding
   * @see #establishCharsets()
   * see also org.melati.admin.Admin#selection(ServletTemplateContext, Melati)
   */
  public String getURLQueryEncoding() {
    return request.getCharacterEncoding();
  }

  /**
   * Convenience method to URL encode a URL query string.
   *
   * See org.melati.admin.Admin#selection(ServletTemplateContext, Melati)
   */
  /**
   * @param string the String to encode
   * @return the encoded string
   */
  public String urlEncode(String string) {
    try {
      return UTF8URLEncoder.encode(string, getURLQueryEncoding());
    }
    catch (UnexpectedExceptionException e) {
      // Thrown if the encoding is not supported
      return string;
    }
  }

  /**
   * Return the encoding that is used for writing.
   * <p>
   * This should always return an encoding and it should be the same
   * for duration of use of an instance.
   *
   * @return Response encoding or a default in stand alone mode
   * @see #setResponseContentType(String)
   */
  public String getEncoding() {
    if (encoding == null)
      encoding = response == null ? DEFAULT_ENCODING :
                                    response.getCharacterEncoding();
    return encoding;
  }

  /**
   * Get a Writer for this request.
   *
   * If you have not accessed the Writer, it is reasonable to assume that
   * nothing has been written to the output stream.
   *
   * @return - one of:
   *
   * - the Writer that was used to construct the Melati
   * - the Writer associated with the Servlet Response
   * - a buffered Writer
   * - a ThrowingPrintWriter
   */
  public MelatiWriter getWriter() {
    if (writer == null) writer = createWriter();
    return writer;
  }

  /**
   * @param writerP the MelatiWriter to set
   */
  public void setWriter(MelatiWriter writerP) {
    writer = writerP;
  }
  /**
   * Get a StringWriter.
   *
   * @return - one of:
   *
   * - a MelatiStringWriter from the template engine
   * - a new MelatiStringWriter if template engine not set
   *
   */
  public MelatiWriter getStringWriter() {
    if (templateEngine == null) {
      return new MelatiStringWriter();
    }
    return templateEngine.getStringWriter();
  }

  /**
   * Used in a servlet setting, where the class was not constructed with 
   * output set.
   * @return a response writer 
   */
  private MelatiWriter createWriter() {
    // first effort is to use the writer supplied by the template engine
    MelatiWriter writerL = null;
    if (response != null) {
      if (templateEngine != null &&
              templateEngine instanceof ServletTemplateEngine) {
        writerL = ((ServletTemplateEngine)templateEngine).getServletWriter(response, buffered);
      } else {
        PrintWriter printWriter = null;
        try { 
          printWriter = response.getWriter(); 
        } catch (IOException e) { 
          throw new MelatiIOException(e);
        }
        if (buffered) {
          writerL = new MelatiBufferedWriter(printWriter);
        } else {
          writerL = new MelatiSimpleWriter(printWriter);
        }
      }
      if (flushing) writerL.setFlushingOn();
    } else 
      throw new MelatiBugMelatiException("Method createWriter called when response was null.");
    return writerL;
  }

  /**
   * Write the buffered output to the Writer
   * we also need to stop the flusher if it has started.
   */
  public void write() {
    // only write stuff if we have previously got a writer
    if (writer != null)
      try {
        writer.close();
      } catch (IOException e) {
        System.err.println("Melati output already closed");
      }
  }

  /**
   * This allows an Exception to be handled inline during Template expansion
   * for example, if you would like to render AccessPoemExceptions to a
   * String to be displayed on the page that is returned to the client.
   * 
   * @see org.melati.template.MarkupLanguage#rendered(Object)
   * @see org.melati.poem.TailoredQuery
   */
  public void setPassbackExceptionHandling() { 
    templateContext.setPassbackExceptionHandling();
  }
  
  /**
   * The normal state of affairs: an exception is thrown and 
   * it is handled by the servlet.
   */
  public void setPropagateExceptionHandling() { 
    templateContext.setPropagateExceptionHandling();
  }
  /**
   * Get a User for this request (if they are logged in).
   * NOTE POEM studiously assumes there isn't necessarily a user, only
   * an AccessToken
   * @return - a User for this request
   */
  public User getUser() {
    try {
      return (User)PoemThread.accessToken();
    }
    catch (NotInSessionPoemException e) {
      return null;
    }
    catch (ClassCastException e) {
      // If the AccessToken is the RootAccessToken
      return null;
    }
  }
  
  /**
   * Establish if field is a ReferencePoemType field.
   * 
   * @param field
   *          the field to check
   * @return whether it is a reference poem type
   */
  public boolean isReferencePoemType(Field<?> field) {
    return field.getType() instanceof ReferencePoemType;
  }

  /**
   * Find a db specific template if it exists, otherwise a non-specific one, 
   * searching through all template paths.
   * 
   * @param key fileName of template, without extension
   * @return full resource name
   */
  public String templateName(String key) {
    String templateName = null;
    try {
      TemplateEngine te = getTemplateEngine(); 
      if (te == null)
        throw new MelatiBugMelatiException("Template engine null");
      Database db = getDatabase();
      templateName = te.getTemplateName(key, db == null ? null : db.getName());
    } catch (Exception e) {
      throw new MelatiBugMelatiException("Problem getting template named " + key  +
              " :" + e.toString(), e);
    }
    return templateName;
  }

}