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