Coverage Report - org.melati.Melati
 
Classes in this File Line Coverage Branch Coverage Complexity
Melati
86%
178/205
91%
57/62
2.136
 
 1  
 /*
 2  
  * $Source$
 3  
  * $Revision$
 4  
  *
 5  
  * Copyright (C) 2000 William Chesters
 6  
  *
 7  
  * Part of Melati (http://melati.org), a framework for the rapid
 8  
  * development of clean, maintainable web applications.
 9  
  *
 10  
  * Melati is free software; Permission is granted to copy, distribute
 11  
  * and/or modify this software under the terms either:
 12  
  *
 13  
  * a) the GNU General Public License as published by the Free Software
 14  
  *    Foundation; either version 2 of the License, or (at your option)
 15  
  *    any later version,
 16  
  *
 17  
  *    or
 18  
  *
 19  
  * b) any version of the Melati Software License, as published
 20  
  *    at http://melati.org
 21  
  *
 22  
  * You should have received a copy of the GNU General Public License and
 23  
  * the Melati Software License along with this program;
 24  
  * if not, write to the Free Software Foundation, Inc.,
 25  
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 26  
  * GNU General Public License and visit http://melati.org to obtain the
 27  
  * Melati Software License.
 28  
  *
 29  
  * Feel free to contact the Developers of Melati (http://melati.org),
 30  
  * if you would like to work out a different arrangement than the options
 31  
  * outlined here.  It is our intention to allow Melati to be used by as
 32  
  * wide an audience as possible.
 33  
  *
 34  
  * This program is distributed in the hope that it will be useful,
 35  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 36  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 37  
  * GNU General Public License for more details.
 38  
  *
 39  
  * Contact details for copyright holder:
 40  
  *
 41  
  *     William Chesters <williamc At paneris.org>
 42  
  *     http://paneris.org/~williamc
 43  
  *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
 44  
  */
 45  
 
 46  
 package org.melati;
 47  
 
 48  
 import java.io.IOException;
 49  
 import java.io.PrintWriter;
 50  
 import java.io.UnsupportedEncodingException;
 51  
 import java.lang.reflect.Constructor;
 52  
 import java.util.Vector;
 53  
 
 54  
 import javax.servlet.http.HttpServletRequest;
 55  
 import javax.servlet.http.HttpServletResponse;
 56  
 import javax.servlet.http.HttpSession;
 57  
 
 58  
 import org.melati.poem.Database;
 59  
 import org.melati.poem.Field;
 60  
 import org.melati.poem.NotInSessionPoemException;
 61  
 import org.melati.poem.Persistent;
 62  
 import org.melati.poem.PoemLocale;
 63  
 import org.melati.poem.PoemThread;
 64  
 import org.melati.poem.ReferencePoemType;
 65  
 import org.melati.poem.Table;
 66  
 import org.melati.poem.User;
 67  
 import org.melati.poem.util.StringUtils;
 68  
 import org.melati.servlet.Form;
 69  
 import org.melati.template.HTMLMarkupLanguage;
 70  
 import org.melati.template.MarkupLanguage;
 71  
 import org.melati.template.ServletTemplateContext;
 72  
 import org.melati.template.ServletTemplateEngine;
 73  
 import org.melati.template.TemplateContext;
 74  
 import org.melati.template.TemplateEngine;
 75  
 import org.melati.util.AcceptCharset;
 76  
 import org.melati.util.CharsetException;
 77  
 import org.melati.util.DatabaseInitException;
 78  
 import org.melati.util.HttpHeader;
 79  
 import org.melati.util.HttpUtil;
 80  
 import org.melati.util.MelatiBufferedWriter;
 81  
 import org.melati.util.MelatiBugMelatiException;
 82  
 import org.melati.util.MelatiIOException;
 83  
 import org.melati.util.MelatiSimpleWriter;
 84  
 import org.melati.util.MelatiStringWriter;
 85  
 import org.melati.util.MelatiWriter;
 86  
 import org.melati.util.UTF8URLEncoder;
 87  
 import org.melati.util.UnexpectedExceptionException;
 88  
 
 89  
 /**
 90  
  * This is the main entry point for using the Melati framework.
 91  
  * A Melati exists once per request, or command from an application.
 92  
  * <p>
 93  
  * It provides a central container for all the relevant objects that 
 94  
  * a Servlet or command line application needs to create textual 
 95  
  * output, optionally using a Template Engine or a Database.
 96  
  * <p>
 97  
  * You will need to create a MelatiConfig in order to construct a Melati.
 98  
  * <p>
 99  
  * If you are using servlets, you will want to construct a Melati with
 100  
  * a request and response object.  Otherwise, simply pass in a Writer.
 101  
  * <p>
 102  
  * If you are using a template engine outside of a servlets context you will 
 103  
  * still need the servlets jar in your classpath, annoyingly, as Velocity and 
 104  
  * WebMacro introspect all possible methods and throw a ClassNotFound exception 
 105  
  * if the servlets classes are not available.  
 106  
  * <p>
 107  
  * Melati is typically used with Servlets, POEM (Persistent Object Engine for
 108  
  * Melati) and a Template Engine
 109  
  *
 110  
  * @see org.melati.MelatiConfig
 111  
  * @see org.melati.servlet.ConfigServlet
 112  
  * @see org.melati.servlet.PoemServlet
 113  
  * @see org.melati.servlet.TemplateServlet
 114  
  */
 115  
 
 116  
 public class Melati {
 117  
 
 118  
   /** UTF-8. */
 119  
   public static final String DEFAULT_ENCODING = "UTF-8";
 120  
   
 121  
   private MelatiConfig config;
 122  
   private PoemContext poemContext;
 123  
   private HttpServletRequest request;
 124  
   private HttpServletResponse response;
 125  833
   private Database database = null;
 126  833
   private Table<?> table = null;
 127  833
   private Persistent object = null;
 128  833
   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  833
   private boolean flushing = false;
 139  
   // are we buffering the output
 140  833
   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  573
                 HttpServletResponse response) {
 156  573
     this.request = request;
 157  573
     this.response = response;
 158  573
     this.config = config;
 159  573
   }
 160  
 
 161  
   /** Convenience constructor. */ 
 162  0
   public Melati(MelatiWriter writer) {
 163  0
     this.config = new MelatiConfig();
 164  0
     this.writer = writer;
 165  0
   }
 166  
   /**
 167  
    * Construct a Melati for use in 'stand alone' mode.
 168  
    * NB: you will not have access to servlet related stuff (eg sessions)
 169  
    *
 170  
    * @param config - the MelatiConfig
 171  
    * @param writer - the Writer that all output is written to
 172  
    */
 173  
 
 174  260
   public Melati(MelatiConfig config, MelatiWriter writer) {
 175  260
     this.config = config;
 176  260
     this.writer = writer;
 177  260
   }
 178  
 
 179  
   /**
 180  
    * Get the servlet request object.
 181  
    *
 182  
    * @return the Servlet Request
 183  
    */
 184  
 
 185  
   public HttpServletRequest getRequest() {
 186  11644
     return request;
 187  
   }
 188  
 
 189  
   /**
 190  
    * It is sometimes convenient to reconstruct the request object and
 191  
    * reset it, for example when returning from a log-in page.
 192  
    *
 193  
    * @see org.melati.login.HttpSessionAccessHandler
 194  
    * @param request - new request object
 195  
    */
 196  
   public void setRequest(HttpServletRequest request) {
 197  25
     this.request = request;
 198  25
   }
 199  
 
 200  
   /**
 201  
    * Used to set response mock in tests.
 202  
    * @see org.melati.login.HttpSessionAccessHandler
 203  
    * @param response - mock response object
 204  
    */
 205  
   public void setResponse(HttpServletResponse response) {
 206  9
     this.response = response;
 207  9
   }
 208  
   
 209  
   /**
 210  
    * Get the servlet response object.
 211  
    *
 212  
    * @return - the Servlet Response
 213  
    */
 214  
 
 215  
   public HttpServletResponse getResponse() {
 216  658
     return response;
 217  
   }
 218  
 
 219  
   /**
 220  
    * Set the {@link PoemContext} for this request.  If the Context has a
 221  
    * LogicalDatabase set, this will be used to establish a connection
 222  
    * to the database.
 223  
    *
 224  
    * @param context - a PoemContext
 225  
    * @throws DatabaseInitException - if the database fails to initialise for
 226  
    *                                 some reason
 227  
    * @see org.melati.LogicalDatabase
 228  
    * @see org.melati.servlet.PoemServlet
 229  
    */
 230  
   public void setPoemContext(PoemContext context)
 231  
       throws DatabaseInitException {
 232  846
     this.poemContext = context;
 233  846
     if (poemContext.getLogicalDatabase() != null)
 234  601
       database = LogicalDatabase.getDatabase(poemContext.getLogicalDatabase());
 235  846
   }
 236  
 
 237  
   /**
 238  
    * Load a POEM Table and POEM Object for use in this request.  This is useful
 239  
    * as often Servlet requests are relevant for a single Table and/or Object.
 240  
    *
 241  
    * The Table name and Object id are set from the PoemContext.
 242  
    *
 243  
    * @see org.melati.admin.Admin
 244  
    * @see org.melati.servlet.PoemServlet
 245  
    */
 246  
   public void loadTableAndObject() {
 247  590
     if (database != null)
 248  590
       if (poemContext.getTable() != null ) {
 249  439
         table = database.getTable(poemContext.getTable());
 250  439
         if (poemContext.getTroid() != null)
 251  109
           object = table.getObject(poemContext.getTroid().intValue());
 252  
         else
 253  330
           object = null;
 254  
       }
 255  590
   }
 256  
 
 257  
 
 258  
   /**
 259  
    * Get the PoemContext for this Request.
 260  
    *
 261  
    * @return - the PoemContext for this Request
 262  
    */
 263  
   public PoemContext getPoemContext() {
 264  1314
     return poemContext;
 265  
   }
 266  
 
 267  
   /**
 268  
    * Get the POEM Database for this Request.
 269  
    *
 270  
    * @return - the POEM Database for this Request
 271  
    * @see #setPoemContext
 272  
    */
 273  
   public Database getDatabase() {
 274  2524
     return database;
 275  
   }
 276  
   
 277  
   /**
 278  
    * @return the name of the Database 
 279  
    */
 280  
   public String getDatabaseName() { 
 281  23
     return getPoemContext().getLogicalDatabase();  
 282  
   }
 283  
   /**
 284  
    * Return the names of other databases known at the moment. 
 285  
    *  
 286  
    * @return a Vector of database names
 287  
    */
 288  
   public Vector<String> getKnownDatabaseNames() {
 289  
     return LogicalDatabase.
 290  62
                getInitialisedDatabaseNames();
 291  
   }
 292  
 
 293  
   /**
 294  
    * Get the POEM Table (if any) in use for this Request.
 295  
    *
 296  
    * @return the POEM Table for this Request
 297  
    * @see #loadTableAndObject
 298  
    */
 299  
   public Table<?> getTable() {
 300  2247
     return table;
 301  
   }
 302  
 
 303  
   /**
 304  
    * Get the POEM Object (if any) in use for this Request.
 305  
    *
 306  
    * @return the POEM Object for this Request
 307  
    * @see #loadTableAndObject
 308  
    */
 309  
   public Persistent getObject() {
 310  1068
     return object;
 311  
   }
 312  
 
 313  
   /**
 314  
    * Get the Method (if any) that has been set for this Request.
 315  
    *
 316  
    * @return the Method for this Request
 317  
    * @see org.melati.PoemContext
 318  
    * @see org.melati.servlet.ConfigServlet#poemContext
 319  
    * @see org.melati.servlet.PoemServlet#poemContext
 320  
    */
 321  
   public String getMethod() {
 322  4994
     return poemContext.getMethod();
 323  
   }
 324  
 
 325  
   /**
 326  
    * Set the template engine to be used for this Request.
 327  
    *
 328  
    * @param te - the template engine to be used
 329  
    * @see org.melati.servlet.TemplateServlet
 330  
    */
 331  
   public void setTemplateEngine(TemplateEngine te) {
 332  734
     templateEngine = te;
 333  734
   }
 334  
 
 335  
   /**
 336  
    * Get the template engine in use for this Request.
 337  
    *
 338  
    * @return - the template engine to be used
 339  
    */
 340  
   public TemplateEngine getTemplateEngine() {
 341  1465
     return templateEngine;
 342  
   }
 343  
 
 344  
   /**
 345  
    * Set the TemplateContext to be used for this Request.
 346  
    *
 347  
    * @param tc - the template context to be used
 348  
    * @see org.melati.servlet.TemplateServlet
 349  
    */
 350  
   public void setTemplateContext(TemplateContext tc) {
 351  614
     templateContext = tc;
 352  614
   }
 353  
 
 354  
   /**
 355  
    * Get the TemplateContext used for this Request.
 356  
    *
 357  
    * @return - the template context being used
 358  
    */
 359  
   public TemplateContext getTemplateContext() {
 360  28
     return templateContext;
 361  
   }
 362  
   
 363  
   /**
 364  
    * Get the TemplateContext used for this Request.
 365  
    *
 366  
    * @return - the template context being used
 367  
    */
 368  
   public ServletTemplateContext getServletTemplateContext() {
 369  550
     return (ServletTemplateContext)templateContext;
 370  
   }
 371  
 
 372  
   /**
 373  
    * Get the MelatiConfig associated with this Request.
 374  
    *
 375  
    * @return - the configuration being used
 376  
    */
 377  
   public MelatiConfig getConfig() {
 378  1805
     return config;
 379  
   }
 380  
 
 381  
   /**
 382  
    * Get the PathInfo for this Request split into Parts by '/'.
 383  
    *
 384  
    * @return - an array of the parts found on the PathInfo
 385  
    */
 386  
   public String[] getPathInfoParts() {
 387  600
     String pathInfo = request.getPathInfo();
 388  600
     if (pathInfo == null || pathInfo.length() < 1) return new String[0];
 389  573
     pathInfo = pathInfo.substring(1);
 390  573
     return StringUtils.split(pathInfo, '/');
 391  
   }
 392  
 
 393  
   /**
 394  
    * Set the aruments array from the commandline.
 395  
    *
 396  
    * @param args the arguments to set
 397  
    */
 398  
   public void setArguments(String[] args) {
 399  42
     arguments = args;
 400  42
   }
 401  
 
 402  
   /**
 403  
    * Get the Arguments array.
 404  
    *
 405  
    * @return the arguments array
 406  
    */
 407  
   public String[] getArguments() {
 408  60
     return arguments;
 409  
   }
 410  
 
 411  
   /**
 412  
    * Get the Session for this Request.
 413  
    *
 414  
    * @return - the Session for this Request
 415  
    */
 416  
   public HttpSession getSession() {
 417  1451
     return getRequest().getSession(true);
 418  
   }
 419  
 
 420  
   /**
 421  
    * Get a named context utility eg org.melati.admin.AdminUtils.
 422  
    *  
 423  
    * @param className Name of a class with a single argument Melati constructor 
 424  
    * @return the instantiated class
 425  
    */
 426  
   public Object getContextUtil(String className) {
 427  
     Constructor<?> c;
 428  
     try {
 429  32
         c  = Class.forName(className).getConstructor(new Class[] {this.getClass()});
 430  0
     } catch (NoSuchMethodException e) {
 431  
       try { 
 432  0
         c  = Class.forName(className).getConstructor(new Class[] {});
 433  
         try {
 434  0
           return c.newInstance(new Object[] {});
 435  0
         } catch (Exception e2) {
 436  0
             throw new MelatiBugMelatiException("Class " + className + 
 437  
                     " cannot be instantiated ", e2);
 438  
         }
 439  0
       } catch (Exception e2) {
 440  0
           throw new MelatiBugMelatiException("Class " + className + 
 441  
                   " cannot be instantiated ", e2);
 442  
       }
 443  1
     } catch (Exception e) {
 444  1
           throw new MelatiBugMelatiException("Class " + className + 
 445  
                 " cannot be instantiated ", e);
 446  31
     }  
 447  
     try {
 448  31
       return c.newInstance(new Object[] {this});
 449  0
     } catch (Exception e) {
 450  0
         throw new MelatiBugMelatiException("Class " + className + 
 451  
                 " cannot be instantiated ", e);
 452  
     }
 453  
   }
 454  
   
 455  
   /**
 456  
    * Get a named context utility eg org.melati.admin.AdminUtils.
 457  
    *  
 458  
    * @param className Name of a class with a single argument Melati constructor 
 459  
    * @return the instantiated class
 460  
    */
 461  
   public Object getInstance(String className) {
 462  
     Object util;
 463  
     try {
 464  0
       Constructor<?> c  = Class.forName(className).getConstructor(new Class[] {this.getClass()});
 465  0
       util = c.newInstance(new Object[] {this});
 466  0
     } catch (Exception e) {
 467  0
       throw new MelatiBugMelatiException("Class " + className + 
 468  
           " cannot be instantiated", e);
 469  0
     }  
 470  0
     return util;
 471  
   }
 472  
   
 473  
   /**
 474  
    * Get the URL for the Logout Page.
 475  
    *
 476  
    * @return - the URL for the Logout Page
 477  
    * @see org.melati.login.Logout
 478  
    */
 479  
   public String getLogoutURL() {
 480  18
     StringBuffer url = new StringBuffer();
 481  18
     HttpUtil.appendRelativeZoneURL(url, getRequest());
 482  18
     url.append('/');
 483  18
     url.append(MelatiConfig.getLogoutPageServletClassName());
 484  18
     url.append('/');
 485  18
     url.append(poemContext.getLogicalDatabase());
 486  18
     return url.toString();
 487  
   }
 488  
 
 489  
   /**
 490  
    * Get the URL for the Login Page.
 491  
    *
 492  
    * @return - the URL for the Login Page
 493  
    * @see org.melati.login.Login
 494  
    */
 495  
   public String getLoginURL() {
 496  14
     StringBuffer url = new StringBuffer();
 497  14
     HttpUtil.appendRelativeZoneURL(url, getRequest());
 498  14
     url.append('/');
 499  14
     url.append(MelatiConfig.getLoginPageServletClassName());
 500  14
     url.append('/');
 501  14
     url.append(poemContext.getLogicalDatabase());
 502  14
     return url.toString();
 503  
   }
 504  
 
 505  
   /**
 506  
    * Get the URL for this Servlet Zone.
 507  
    *
 508  
    * @return - the URL for this Servlet Zone
 509  
    * @see org.melati.util.HttpUtil#zoneURL
 510  
    */
 511  
   public String getZoneURL() {
 512  317
     return HttpUtil.zoneURL(getRequest());
 513  
   }
 514  
 
 515  
   /**
 516  
    * @return the relative url for the Servlet Zone of the current request
 517  
    */
 518  
   public String getRelativeZoneURL() { 
 519  1
     return HttpUtil.getRelativeRequestURL(getRequest());    
 520  
   }
 521  
   /**
 522  
    * Get the URL for this request.
 523  
    * Not used in Melati.
 524  
    *
 525  
    * @return - the URL for this request
 526  
    * @see org.melati.util.HttpUtil#servletURL
 527  
    */
 528  
   public String getServletURL() {
 529  1
     return HttpUtil.servletURL(getRequest());
 530  
   }
 531  
 
 532  
   /**
 533  
    * Get the URL for the JavascriptLibrary.
 534  
    * Convenience method.
 535  
    * 
 536  
    * @return - the URL for the JavascriptLibrary
 537  
    * @see org.melati.MelatiConfig#getJavascriptLibraryURL
 538  
    */
 539  
   public String getJavascriptLibraryURL() {
 540  76
     return config.getJavascriptLibraryURL();
 541  
   }
 542  
 
 543  
   /**
 544  
    * Returns a PoemLocale object based on the Accept-Language header
 545  
    * of this request.
 546  
    *
 547  
    * If no usable Accept-Language header is found or we are using 
 548  
    * Melati outside of a servlet context then the configured 
 549  
    * default locale is returned.
 550  
    *
 551  
    * @return a PoemLocale object
 552  
    */
 553  
   public PoemLocale getPoemLocale() {
 554  584
     if (getRequest() == null)
 555  42
        return MelatiConfig.getPoemLocale();
 556  542
     else if(getRequest().getLocale() == null) {
 557  11
       return MelatiConfig.getPoemLocale();
 558  
     } else 
 559  531
       return PoemLocale.from(getRequest().getLocale());
 560  
   }
 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  568
     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  568
     if (acs != null && acs.trim().length() == 0) {
 596  2
       acs = null;
 597  
     }
 598  
     try {
 599  568
       ac = new AcceptCharset(acs, config.getPreferredCharsets());
 600  
     }
 601  0
     catch (HttpHeader.HttpHeaderException e) {
 602  0
       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  568
     }
 608  568
     if (request.getCharacterEncoding() == null) {
 609  549
       responseCharset = ac.clientChoice();
 610  
       try {
 611  549
         request.setCharacterEncoding(responseCharset);
 612  
       }
 613  0
       catch (UnsupportedEncodingException e) {
 614  0
         throw new MelatiBugMelatiException("This should already have been checked by AcceptCharset", e);
 615  549
       }
 616  
     } else {
 617  19
       responseCharset = ac.serverChoice();
 618  
     }
 619  568
   }
 620  
 
 621  
   /**
 622  
    * Suggested character encoding for use in responses.
 623  
    */
 624  833
   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  527
     contentType = type;
 648  527
     if (responseCharset != null) 
 649  523
       if (type.startsWith("text/")) 
 650  520
         if (type.indexOf(";") == -1)
 651  520
           contentType += "; charset=" + responseCharset;
 652  527
     if (response != null) {
 653  523
       response.setContentType(contentType);
 654  
     }
 655  527
   }
 656  833
   protected String contentType = null;
 657  
   /**
 658  
    * @return the contentType
 659  
    */
 660  
   public String getContentType() {
 661  0
     return contentType;
 662  
   }
 663  
   
 664  
   
 665  
   /**
 666  
    * Use this method if you wish to use a different 
 667  
    * MarkupLanguage, WMLMarkupLanguage for example. 
 668  
    * Cannot be set in MelatiConfig as MarkupLanguage 
 669  
    * does not have a no argument constructor.
 670  
    * @param ml The ml to set.
 671  
    */
 672  
   public void setMarkupLanguage(MarkupLanguage ml) {
 673  186
     this.markupLanguage = ml;
 674  186
   }
 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  2935
     if (markupLanguage == null) 
 686  526
       markupLanguage = new HTMLMarkupLanguage(this,
 687  526
                                   config.getTempletLoader(),
 688  526
                                   getPoemLocale());
 689  2935
     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  961
     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  280
     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  13
     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  2346
     String qs = getRequest().getQueryString();
 741  2346
     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  3
     if (writer != null)
 759  1
       throw new IOException("You have already requested a Writer, " +
 760  
                             "and can't change it's properties now");
 761  2
     buffered = false;
 762  2
   }
 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  3
     if (writer != null)
 771  1
       throw new IOException("You have already requested a Writer, " +
 772  
                             "and can't change it's properties now");
 773  2
     flushing = true;
 774  2
   }
 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  9
     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  8
       return UTF8URLEncoder.encode(string, getURLQueryEncoding());
 836  
     }
 837  1
     catch (UnexpectedExceptionException e) {
 838  
       // Thrown if the encoding is not supported
 839  1
       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  12931
     if (encoding == null)
 854  377
       encoding = response == null ? DEFAULT_ENCODING :
 855  282
                                     response.getCharacterEncoding();
 856  12931
     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  
    */
 872  
   public MelatiWriter getWriter() {
 873  725
     if (writer == null) writer = createWriter();
 874  725
     return writer;
 875  
   }
 876  
 
 877  
   /**
 878  
    * @param writerP the MelatiWriter to set
 879  
    */
 880  
   public void setWriter(MelatiWriter writerP) {
 881  1
     writer = writerP;
 882  1
   }
 883  
   /**
 884  
    * Get a StringWriter.
 885  
    *
 886  
    * @return - one of:
 887  
    *
 888  
    * - a MelatiStringWriter from the template engine
 889  
    * - a new MelatiStringWriter if template engine not set
 890  
    *
 891  
    */
 892  
   public MelatiWriter getStringWriter() {
 893  8729
     if (templateEngine == null) {
 894  926
       return new MelatiStringWriter();
 895  
     }
 896  7803
     return templateEngine.getStringWriter();
 897  
   }
 898  
 
 899  
   /**
 900  
    * Used in a servlet setting, where the class was not constructed with 
 901  
    * output set.
 902  
    * @return a response writer 
 903  
    */
 904  
   private MelatiWriter createWriter() {
 905  
     // first effort is to use the writer supplied by the template engine
 906  550
     MelatiWriter writerL = null;
 907  550
     if (response != null) {
 908  550
       if (templateEngine != null &&
 909  
               templateEngine instanceof ServletTemplateEngine) {
 910  501
         writerL = ((ServletTemplateEngine)templateEngine).getServletWriter(response, buffered);
 911  
       } else {
 912  49
         PrintWriter printWriter = null;
 913  
         try { 
 914  49
           printWriter = response.getWriter(); 
 915  0
         } catch (IOException e) { 
 916  0
           throw new MelatiIOException(e);
 917  49
         }
 918  49
         if (buffered) {
 919  48
           writerL = new MelatiBufferedWriter(printWriter);
 920  
         } else {
 921  1
           writerL = new MelatiSimpleWriter(printWriter);
 922  
         }
 923  
       }
 924  550
       if (flushing) writerL.setFlushingOn();
 925  
     } else 
 926  0
       throw new MelatiBugMelatiException("Method createWriter called when response was null.");
 927  550
     return writerL;
 928  
   }
 929  
 
 930  
   /**
 931  
    * Write the buffered output to the Writer
 932  
    * we also need to stop the flusher if it has started.
 933  
    */
 934  
   public void write() {
 935  
     // only write stuff if we have previously got a writer
 936  596
     if (writer != null)
 937  
       try {
 938  580
         writer.close();
 939  1
       } catch (IOException e) {
 940  1
         System.err.println("Melati output already closed");
 941  579
       }
 942  596
   }
 943  
 
 944  
   /**
 945  
    * This allows an Exception to be handled inline during Template expansion
 946  
    * for example, if you would like to render AccessPoemExceptions to a
 947  
    * String to be displayed on the page that is returned to the client.
 948  
    * 
 949  
    * @see org.melati.template.MarkupLanguage#rendered(Object)
 950  
    * @see org.melati.poem.TailoredQuery
 951  
    */
 952  
   public void setPassbackExceptionHandling() { 
 953  418
     templateContext.setPassbackExceptionHandling();
 954  418
   }
 955  
   
 956  
   /**
 957  
    * The normal state of affairs: an exception is thrown and 
 958  
    * it is handled by the servlet.
 959  
    */
 960  
   public void setPropagateExceptionHandling() { 
 961  20
     templateContext.setPropagateExceptionHandling();
 962  20
   }
 963  
   /**
 964  
    * Get a User for this request (if they are logged in).
 965  
    * NOTE POEM studiously assumes there isn't necessarily a user, only
 966  
    * an AccessToken
 967  
    * @return - a User for this request
 968  
    */
 969  
   public User getUser() {
 970  
     try {
 971  124
       return (User)PoemThread.accessToken();
 972  
     }
 973  2
     catch (NotInSessionPoemException e) {
 974  2
       return null;
 975  
     }
 976  14
     catch (ClassCastException e) {
 977  
       // If the AccessToken is the RootAccessToken
 978  14
       return null;
 979  
     }
 980  
   }
 981  
   
 982  
   /**
 983  
    * Establish if field is a ReferencePoemType field.
 984  
    * 
 985  
    * @param field
 986  
    *          the field to check
 987  
    * @return whether it is a reference poem type
 988  
    */
 989  
   public boolean isReferencePoemType(Field<?> field) {
 990  291
     return field.getType() instanceof ReferencePoemType;
 991  
   }
 992  
 
 993  
   /**
 994  
    * Find a db specific template if it exists, otherwise a non-specific one, 
 995  
    * searching through all template paths.
 996  
    * 
 997  
    * @param key fileName of template, without extension
 998  
    * @return full resource name
 999  
    */
 1000  
   public String templateName(String key) {
 1001  8
     String templateName = null;
 1002  
     try {
 1003  8
       TemplateEngine te = getTemplateEngine(); 
 1004  8
       if (te == null)
 1005  1
         throw new MelatiBugMelatiException("Template engine null");
 1006  7
       Database db = getDatabase();
 1007  7
       templateName = te.getTemplateName(key, db == null ? null : db.getName());
 1008  1
     } catch (Exception e) {
 1009  1
       throw new MelatiBugMelatiException("Problem getting template named " + key  +
 1010  1
               " :" + e.toString(), e);
 1011  7
     }
 1012  7
     return templateName;
 1013  
   }
 1014  
 
 1015  
 }