Coverage Report - org.melati.Melati
 
Classes in this File Line Coverage Branch Coverage Complexity
Melati
97%
180/185
88%
65/74
1.857
 
 1  
 /*
 2  
  * $Source: /usr/cvsroot/melati/melati/src/main/java/org/melati/Melati.java,v $
 3  
  * $Revision: 1.123 $
 4  
  *
 5  
  * Copyright (C) 2000 William Chesters
 6  
  *
 7  
  * Part of Melati (http://melati.org), a framework for the rapid
 8  
  * development of clean, maintainable web applications.
 9  
  *
 10  
  * Melati is free software; Permission is granted to copy, distribute
 11  
  * and/or modify this software under the terms either:
 12  
  *
 13  
  * a) the GNU General Public License as published by the Free Software
 14  
  *    Foundation; either version 2 of the License, or (at your option)
 15  
  *    any later version,
 16  
  *
 17  
  *    or
 18  
  *
 19  
  * b) any version of the Melati Software License, as published
 20  
  *    at http://melati.org
 21  
  *
 22  
  * You should have received a copy of the GNU General Public License and
 23  
  * the Melati Software License along with this program;
 24  
  * if not, write to the Free Software Foundation, Inc.,
 25  
  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA to obtain the
 26  
  * GNU General Public License and visit http://melati.org to obtain the
 27  
  * Melati Software License.
 28  
  *
 29  
  * Feel free to contact the Developers of Melati (http://melati.org),
 30  
  * if you would like to work out a different arrangement than the options
 31  
  * outlined here.  It is our intention to allow Melati to be used by as
 32  
  * wide an audience as possible.
 33  
  *
 34  
  * This program is distributed in the hope that it will be useful,
 35  
  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 36  
  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 37  
  * GNU General Public License for more details.
 38  
  *
 39  
  * Contact details for copyright holder:
 40  
  *
 41  
  *     William Chesters <williamc At paneris.org>
 42  
  *     http://paneris.org/~williamc
 43  
  *     Obrechtstraat 114, 2517VX Den Haag, The Netherlands
 44  
  */
 45  
 
 46  
 package org.melati;
 47  
 
 48  
 import java.io.IOException;
 49  
 import java.io.UnsupportedEncodingException;
 50  
 import java.lang.reflect.Constructor;
 51  
 import java.util.Hashtable;
 52  
 import java.util.Vector;
 53  
 
 54  
 import javax.servlet.http.HttpServletRequest;
 55  
 import javax.servlet.http.HttpServletResponse;
 56  
 import javax.servlet.http.HttpSession;
 57  
 
 58  
 import org.melati.poem.Database;
 59  
 import org.melati.poem.Field;
 60  
 import org.melati.poem.NotInSessionPoemException;
 61  
 import org.melati.poem.Persistent;
 62  
 import org.melati.poem.PoemLocale;
 63  
 import org.melati.poem.PoemThread;
 64  
 import org.melati.poem.ReferencePoemType;
 65  
 import org.melati.poem.Table;
 66  
 import org.melati.poem.User;
 67  
 import org.melati.poem.util.StringUtils;
 68  
 import org.melati.servlet.Form;
 69  
 import org.melati.template.HTMLMarkupLanguage;
 70  
 import org.melati.template.MarkupLanguage;
 71  
 import org.melati.template.ServletTemplateContext;
 72  
 import org.melati.template.ServletTemplateEngine;
 73  
 import org.melati.template.TemplateContext;
 74  
 import org.melati.template.TemplateEngine;
 75  
 import org.melati.util.AcceptCharset;
 76  
 import org.melati.util.CharsetException;
 77  
 import org.melati.util.DatabaseInitException;
 78  
 import org.melati.util.HttpHeader;
 79  
 import org.melati.util.HttpUtil;
 80  
 import org.melati.util.MelatiBufferedWriter;
 81  
 import org.melati.util.MelatiBugMelatiException;
 82  
 import org.melati.util.MelatiSimpleWriter;
 83  
 import org.melati.util.MelatiStringWriter;
 84  
 import org.melati.util.MelatiWriter;
 85  
 import org.melati.util.UTF8URLEncoder;
 86  
 import org.melati.util.UnexpectedExceptionException;
 87  
 
 88  
 /**
 89  
  * This is the main entry point for using the Melati framework.
 90  
  * A Melati exists once per request, or command from an application.
 91  
  * It provides a central container for all the relevant objects that 
 92  
  * a Servlet or command line application needs to create textual 
 93  
  * output, optionally using a Template Engine or a Database.
 94  
  * <p>
 95  
  * You will need to create a MelatiConfig in order to construct a Melati.
 96  
  * <p>
 97  
  * If you are using servlets, you will want to construct a Melati with
 98  
  * a request and response object.  Otherwise, simply pass in a Writer.
 99  
  * <p>
 100  
  * If you are using a template engine outside of a servlets context you will 
 101  
  * still need the servlets jar in your classpath, annoyingly, as Velocity and 
 102  
  * WebMacro introspect all possible methods and throw a ClassNotFound exception 
 103  
  * if the servlets classes are not available.  
 104  
  * <p>
 105  
  * Melati is typically used with Servlets, POEM (Persistent Object Engine for
 106  
  * Melati) and a Template Engine
 107  
  *
 108  
  * @see org.melati.MelatiConfig
 109  
  * @see org.melati.servlet.ConfigServlet
 110  
  * @see org.melati.servlet.PoemServlet
 111  
  * @see org.melati.servlet.TemplateServlet
 112  
  */
 113  
 
 114  
 public class Melati {
 115  
 
 116  
   /** UTF-8. */
 117  
   public static final String DEFAULT_ENCODING = "UTF-8";
 118  
   
 119  
   private MelatiConfig config;
 120  
   private PoemContext poemContext;
 121  
   private HttpServletRequest request;
 122  
   private HttpServletResponse response;
 123  251
   private Database database = null;
 124  251
   private Table table = null;
 125  251
   private Persistent object = null;
 126  251
   private MarkupLanguage markupLanguage = null;
 127  
   
 128  
   private String[] arguments;
 129  
 
 130  
   // the template engine that is in use (if any)
 131  
   private TemplateEngine templateEngine;
 132  
   // the object that is used by the template engine to expand the template
 133  
   // against
 134  
   private TemplateContext templateContext;
 135  
   // are we manually flushing the output
 136  251
   private boolean flushing = false;
 137  
   // are we buffering the output
 138  251
   private boolean buffered= true;
 139  
   // the output writer
 140  
   private MelatiWriter writer;
 141  
 
 142  
   private static final int maxLocales = 10;
 143  1
   private static Hashtable localeHash = new Hashtable(maxLocales);
 144  
 
 145  
   private String encoding;
 146  
 
 147  
   /**
 148  
    * Construct a Melati for use with Servlets.
 149  
    *
 150  
    * @param config - the MelatiConfig
 151  
    * @param request - the Servlet Request
 152  
    * @param response - the Servlet Response
 153  
    */
 154  
   public Melati(MelatiConfig config,
 155  
                 HttpServletRequest request,
 156  26
                 HttpServletResponse response) {
 157  26
     this.request = request;
 158  26
     this.response = response;
 159  26
     this.config = config;
 160  26
   }
 161  
 
 162  
   /**
 163  
    * Construct a melati for use in 'stand alone' mode.
 164  
    * NB: you will not have access to servlet related stuff (eg sessions)
 165  
    *
 166  
    * @param config - the MelatiConfig
 167  
    * @param writer - the Writer that all output is written to
 168  
    */
 169  
 
 170  225
   public Melati(MelatiConfig config, MelatiWriter writer) {
 171  225
     this.config = config;
 172  225
     this.writer = writer;
 173  225
   }
 174  
 
 175  
   /**
 176  
    * Get the servlet request object.
 177  
    *
 178  
    * @return the Servlet Request
 179  
    */
 180  
 
 181  
   public HttpServletRequest getRequest() {
 182  280
     return request;
 183  
   }
 184  
 
 185  
   /**
 186  
    * It is sometimes convenient to reconstruct the request object and
 187  
    * reset it, for example when returning from a log-in page.
 188  
    *
 189  
    * @see org.melati.login.HttpSessionAccessHandler
 190  
    * @param request - new request object
 191  
    */
 192  
   public void setRequest(HttpServletRequest request) {
 193  24
     this.request = request;
 194  24
   }
 195  
 
 196  
   /**
 197  
    * Used to set response mock in tests.
 198  
    * @see org.melati.login.HttpSessionAccessHandler
 199  
    * @param response - mock response object
 200  
    */
 201  
   public void setResponse(HttpServletResponse response) {
 202  8
     this.response = response;
 203  8
   }
 204  
   
 205  
   /**
 206  
    * Get the servlet response object.
 207  
    *
 208  
    * @return - the Servlet Response
 209  
    */
 210  
 
 211  
   public HttpServletResponse getResponse() {
 212  16
     return response;
 213  
   }
 214  
 
 215  
   /**
 216  
    * Set the {@link PoemContext} for this request.  If the Context has a
 217  
    * LogicalDatabase set, this will be used to establish a connection
 218  
    * to the database.
 219  
    *
 220  
    * @param context - a PoemContext
 221  
    * @throws DatabaseInitException - if the database fails to initialise for
 222  
    *                                 some reason
 223  
    * @see org.melati.LogicalDatabase
 224  
    * @see org.melati.servlet.PoemServlet
 225  
    */
 226  
   public void setPoemContext(PoemContext context)
 227  
       throws DatabaseInitException {
 228  250
     this.poemContext = context;
 229  250
     if (poemContext.getLogicalDatabase() != null)
 230  63
       database = LogicalDatabase.getDatabase(poemContext.getLogicalDatabase());
 231  250
   }
 232  
 
 233  
   /**
 234  
    * Load a POEM Table and POEM Object for use in this request.  This is useful
 235  
    * as often Servlet requests are relevant for a single Table and/or Object.
 236  
    *
 237  
    * The Table name and Object id are set from the PoemContext.
 238  
    *
 239  
    * @see org.melati.admin.Admin
 240  
    * @see org.melati.servlet.PoemServlet
 241  
    */
 242  
   public void loadTableAndObject() {
 243  39
     if (poemContext.getTable() != null && database != null)
 244  25
       table = database.getTable(poemContext.getTable());
 245  39
     if (poemContext.getTroid() != null && table != null)
 246  19
       object = table.getObject(poemContext.getTroid().intValue());
 247  39
   }
 248  
 
 249  
 
 250  
   /**
 251  
    * Get the PoemContext for this Request.
 252  
    *
 253  
    * @return - the PoemContext for this Request
 254  
    */
 255  
   public PoemContext getPoemContext() {
 256  80
     return poemContext;
 257  
   }
 258  
 
 259  
   /**
 260  
    * Get the POEM Database for this Request.
 261  
    *
 262  
    * @return - the POEM Database for this Request
 263  
    * @see #setPoemContext
 264  
    */
 265  
   public Database getDatabase() {
 266  288
     return database;
 267  
   }
 268  
   
 269  
   /**
 270  
    * @return the name of the Database 
 271  
    */
 272  
   public String getDatabaseName() { 
 273  4
     return getPoemContext().getLogicalDatabase();  
 274  
   }
 275  
   /**
 276  
    * Return the names of other databases known at the moment. 
 277  
    *  
 278  
    * @return a Vector of database names
 279  
    */
 280  
   public Vector getKnownDatabaseNames() {
 281  2
     return LogicalDatabase.
 282  
                getInitialisedDatabaseNames();
 283  
   }
 284  
 
 285  
   /**
 286  
    * Get the POEM Table (if any) in use for this Request.
 287  
    *
 288  
    * @return the POEM Table for this Request
 289  
    * @see #loadTableAndObject
 290  
    */
 291  
   public Table getTable() {
 292  31
     return table;
 293  
   }
 294  
 
 295  
   /**
 296  
    * Get the POEM Object (if any) in use for this Request.
 297  
    *
 298  
    * @return the POEM Object for this Request
 299  
    * @see #loadTableAndObject
 300  
    */
 301  
   public Persistent getObject() {
 302  40
     return object;
 303  
   }
 304  
 
 305  
   /**
 306  
    * Get the Method (if any) that has been set for this Request.
 307  
    *
 308  
    * @return the Method for this Request
 309  
    * @see org.melati.PoemContext
 310  
    * @see org.melati.servlet.ConfigServlet#poemContext
 311  
    * @see org.melati.servlet.PoemServlet#poemContext
 312  
    */
 313  
   public String getMethod() {
 314  47
     return poemContext.getMethod();
 315  
   }
 316  
 
 317  
   /**
 318  
    * Set the template engine to be used for this Request.
 319  
    *
 320  
    * @param te - the template engine to be used
 321  
    * @see org.melati.servlet.TemplateServlet
 322  
    */
 323  
   public void setTemplateEngine(TemplateEngine te) {
 324  182
     templateEngine = te;
 325  182
   }
 326  
 
 327  
   /**
 328  
    * Get the template engine in use for this Request.
 329  
    *
 330  
    * @return - the template engine to be used
 331  
    */
 332  
   public TemplateEngine getTemplateEngine() {
 333  500
     return templateEngine;
 334  
   }
 335  
 
 336  
   /**
 337  
    * Set the TemplateContext to be used for this Request.
 338  
    *
 339  
    * @param tc - the template context to be used
 340  
    * @see org.melati.servlet.TemplateServlet
 341  
    */
 342  
   public void setTemplateContext(TemplateContext tc) {
 343  195
     templateContext = tc;
 344  195
   }
 345  
 
 346  
   /**
 347  
    * Get the TemplateContext used for this Request.
 348  
    *
 349  
    * @return - the template context being used
 350  
    */
 351  
   public TemplateContext getTemplateContext() {
 352  70
     return templateContext;
 353  
   }
 354  
   
 355  
   /**
 356  
    * Get the TemplateContext used for this Request.
 357  
    *
 358  
    * @return - the template context being used
 359  
    */
 360  
   public ServletTemplateContext getServletTemplateContext() {
 361  16
     return (ServletTemplateContext)templateContext;
 362  
   }
 363  
 
 364  
   /**
 365  
    * Get the MelatiConfig associated with this Request.
 366  
    *
 367  
    * @return - the configuration being used
 368  
    */
 369  
   public MelatiConfig getConfig() {
 370  126
     return config;
 371  
   }
 372  
 
 373  
   /**
 374  
    * Get the PathInfo for this Request split into Parts by '/'.
 375  
    *
 376  
    * @return - an array of the parts found on the PathInfo
 377  
    */
 378  
   public String[] getPathInfoParts() {
 379  20
     String pathInfo = request.getPathInfo();
 380  20
     if (pathInfo == null || pathInfo.length() < 1) return new String[0];
 381  12
     pathInfo = pathInfo.substring(1);
 382  12
     return StringUtils.split(pathInfo, '/');
 383  
   }
 384  
 
 385  
   /**
 386  
    * Set the aruments array from the commandline.
 387  
    *
 388  
    * @param args
 389  
    */
 390  
   public void setArguments(String[] args) {
 391  38
     arguments = args;
 392  38
   }
 393  
 
 394  
   /**
 395  
    * Get the Arguments array.
 396  
    *
 397  
    * @return the arguments array
 398  
    */
 399  
   public String[] getArguments() {
 400  62
     return arguments;
 401  
   }
 402  
 
 403  
   /**
 404  
    * Get the Session for this Request.
 405  
    *
 406  
    * @return - the Session for this Request
 407  
    */
 408  
   public HttpSession getSession() {
 409  14
     return getRequest().getSession(true);
 410  
   }
 411  
 
 412  
   /**
 413  
    * Get a named context utility eg org.melati.admin.AdminUtils.
 414  
    *  
 415  
    * @param className Name of a class with a single argument Melati constructor 
 416  
    * @return the instantiated class
 417  
    */
 418  
   public Object getContextUtil(String className) {
 419  
     Object util;
 420  
     try {
 421  20
       Constructor c  = Class.forName(className).getConstructor(new Class[] {this.getClass()});
 422  19
       util = c.newInstance(new Object[] {this});
 423  1
     } catch (Exception e) {
 424  1
       throw new MelatiBugMelatiException("Class " + className + 
 425  
           " cannot be instantiated; is PoemContext set?", e);
 426  19
     }  
 427  19
     return util;
 428  
   }
 429  
   
 430  
   /**
 431  
    * Get the URL for the Logout Page.
 432  
    *
 433  
    * @return - the URL for the Logout Page
 434  
    * @see org.melati.login.Logout
 435  
    */
 436  
   public String getLogoutURL() {
 437  1
     StringBuffer url = new StringBuffer();
 438  1
     HttpUtil.appendRelativeZoneURL(url, getRequest());
 439  1
     url.append('/');
 440  1
     url.append(MelatiConfig.getLogoutPageServletClassName());
 441  1
     url.append('/');
 442  1
     url.append(poemContext.getLogicalDatabase());
 443  1
     return url.toString();
 444  
   }
 445  
 
 446  
   /**
 447  
    * Get the URL for the Login Page.
 448  
    *
 449  
    * @return - the URL for the Login Page
 450  
    * @see org.melati.login.Login
 451  
    */
 452  
   public String getLoginURL() {
 453  1
     StringBuffer url = new StringBuffer();
 454  1
     HttpUtil.appendRelativeZoneURL(url, getRequest());
 455  1
     url.append('/');
 456  1
     url.append(MelatiConfig.getLoginPageServletClassName());
 457  1
     url.append('/');
 458  1
     url.append(poemContext.getLogicalDatabase());
 459  1
     return url.toString();
 460  
   }
 461  
 
 462  
   /**
 463  
    * Get the URL for this Servlet Zone.
 464  
    *
 465  
    * @return - the URL for this Servlet Zone
 466  
    * @see org.melati.util.HttpUtil#zoneURL
 467  
    */
 468  
   public String getZoneURL() {
 469  60
     return HttpUtil.zoneURL(getRequest());
 470  
   }
 471  
 
 472  
   /**
 473  
    * Get the URL for this request.
 474  
    * Not used in Melati.
 475  
    *
 476  
    * @return - the URL for this request
 477  
    * @see org.melati.util.HttpUtil#servletURL
 478  
    */
 479  
   public String getServletURL() {
 480  1
     return HttpUtil.servletURL(getRequest());
 481  
   }
 482  
 
 483  
   /**
 484  
    * Get the URL for the JavascriptLibrary.
 485  
    * Convenience method.
 486  
    * 
 487  
    * @return - the URL for the JavascriptLibrary
 488  
    * @see org.melati.MelatiConfig#getJavascriptLibraryURL
 489  
    */
 490  
   public String getJavascriptLibraryURL() {
 491  2
     return config.getJavascriptLibraryURL();
 492  
   }
 493  
 
 494  
   /**
 495  
    * Returns a PoemLocale object based on the Accept-Language header
 496  
    * of this request.
 497  
    *
 498  
    * If we are using Melati outside of a servlet context then the
 499  
    * configured locale is returned.
 500  
    *
 501  
    * @return a PoemLocale object
 502  
    */
 503  
   public PoemLocale getPoemLocale() {
 504  48
     HttpServletRequest r = getRequest();
 505  48
     PoemLocale ml = null;
 506  48
     if (r != null) {
 507  13
       String acceptLanguage = r.getHeader("Accept-Language");
 508  13
       if (acceptLanguage != null)
 509  3
         ml = getPoemLocale(acceptLanguage);
 510  
     }
 511  48
    return ml != null ? ml : MelatiConfig.getPoemLocale();
 512  
   }
 513  
 
 514  
   /**
 515  
    * Returns a PoemLocale based on a language tag. Locales are cached for
 516  
    * future use.
 517  
    * 
 518  
    * @param languageHeader
 519  
    *        A language header from RFC 3282
 520  
    * @return a PoemLocale based on a language tag or null if not found
 521  
    * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
 522  
    */
 523  
   public static PoemLocale getPoemLocale(String languageHeader) {
 524  
 
 525  
     // language headers may have multiple language tags sperated by ,
 526  16
     String tags[] = StringUtils.split(languageHeader, ',');
 527  16
     PoemLocale ml = null;
 528  
 
 529  
     // loop through until we find a tag we like
 530  24
     for (int i = 0; i < tags.length; i++) {
 531  16
       String tag = tags[i];
 532  
 
 533  
       // remove quality value if it exists.
 534  
       // we'll just try them in order
 535  16
       int indexSemicolon = tag.indexOf(';');
 536  16
       if (indexSemicolon != -1)
 537  8
         tag = tag.substring(0, indexSemicolon);
 538  
 
 539  16
       String lowerTag = tag.trim().toLowerCase();
 540  
 
 541  
       // try our cache
 542  16
       ml = (PoemLocale)localeHash.get(lowerTag);
 543  16
       if (ml != null)
 544  6
         return ml;
 545  
 
 546  
       // try creating a locale from this tag
 547  10
       ml = PoemLocale.fromLanguageTag(lowerTag);
 548  10
       if (ml != null) {
 549  2
         localeHash.put(lowerTag, ml);
 550  2
         return ml;
 551  
       }
 552  
     }
 553  
 
 554  8
     return null;
 555  
   }
 556  
   
 557  
   /**
 558  
    * Suggest a response character encoding and if necessary choose a
 559  
    * request encoding.
 560  
    * <p>
 561  
    * If the request encoding is provided then we choose a response
 562  
    * encoding to meet our preferences on the assumption that the
 563  
    * client will also indicate next time what its request
 564  
    * encoding is.
 565  
    * The result can optionally be set in code or possibly in
 566  
    * templates using {@link #setResponseContentType(String)}.
 567  
    * <p>
 568  
    * Otherwise we tread carefully. We assume that the encoding is
 569  
    * the first supported encoding of the client's preferences for
 570  
    * responses, as indicated by Accept-Charsets, and avoid giving
 571  
    * it any reason to change.
 572  
    * <p>
 573  
    * Actually, the server preference is a bit dodgy for
 574  
    * the response because if it does persuade the client to
 575  
    * change encodings and future requests include query strings
 576  
    * that we are providing now then we may end up with the
 577  
    * query strings being automatically decoded using the wrong
 578  
    * encoding by request.getParameter(). But by the time we
 579  
    * end up with values in such parameters the client and
 580  
    * server will probably have settled on particular encodings.
 581  
    */
 582  
   public void establishCharsets() throws CharsetException {
 583  
 
 584  
     AcceptCharset ac;
 585  21
     String acs = request.getHeader("Accept-Charset");
 586  
     //assert acs == null || acs.trim().length() > 0 :
 587  
     //  "Accept-Charset should not be empty but can be absent";
 588  
     // Having said that we don't want to split hairs once debugged
 589  21
     if (acs != null && acs.trim().length() == 0) {
 590  2
       acs = null;
 591  
     }
 592  
     try {
 593  21
       ac = new AcceptCharset(acs, config.getPreferredCharsets());
 594  
     }
 595  1
     catch (HttpHeader.HttpHeaderException e) {
 596  1
       throw new CharsetException(
 597  
           "An error was detected in your HTTP request header, " +
 598  
           "response code: " +
 599  
           HttpServletResponse.SC_BAD_REQUEST +
 600  
           ": \"" + acs + '"', e);
 601  20
     }
 602  20
     if (request.getCharacterEncoding() == null) {
 603  3
       responseCharset = ac.clientChoice();
 604  
       try {
 605  3
         request.setCharacterEncoding(responseCharset);
 606  
       }
 607  0
       catch (UnsupportedEncodingException e) {
 608  0
         throw new MelatiBugMelatiException("This should already have been checked by AcceptCharset", e);
 609  3
       }
 610  
     } else {
 611  17
       responseCharset = ac.serverChoice();
 612  
     }
 613  20
   }
 614  
 
 615  
   /**
 616  
    * Suggested character encoding for use in responses.
 617  
    */
 618  251
   protected String responseCharset = null;
 619  
 
 620  
   /**
 621  
    * Sets the content type for use in the response.
 622  
    * <p>
 623  
    * Use of this method is optional and only makes sense in a 
 624  
    * Servlet context. If the response is null then this is a no-op.
 625  
    * <p>
 626  
    * If the type starts with "text/" and does not contain a semicolon
 627  
    * and a good response character set has been established based on
 628  
    * the request Accept-Charset header and server preferences, then this
 629  
    * and semicolon separator are automatically appended to the type.
 630  
    * I am guessing that this makes sense.
 631  
    * <p>
 632  
    * Whether this function should be called at all may depend on
 633  
    * the application and templates.
 634  
    * <p>
 635  
    * It should be called before any calls to {@link #getEncoding()}
 636  
    * and before writing the response.
 637  
    *
 638  
    * @see #establishCharsets()
 639  
    */
 640  
   public void setResponseContentType(String type) {
 641  15
     if (responseCharset != null && type.startsWith("text/")
 642  
         && type.indexOf(";") == -1) {
 643  12
       type += "; charset=" + responseCharset;
 644  
     }
 645  15
     if (response != null)
 646  12
       response.setContentType(type);
 647  15
   }
 648  
 
 649  
   /**
 650  
    * Use this method if you wish to use a different 
 651  
    * MarkupLanguage, WMLMarkupLanguage for example. 
 652  
    * Cannot be set in MelatiConfig as does not have a noarg constructor.
 653  
    * @param ml The ml to set.
 654  
    */
 655  
   public void setMarkupLanguage(MarkupLanguage ml) {
 656  147
     this.markupLanguage = ml;
 657  147
   }
 658  
   
 659  
   /**
 660  
    * Get a {@link MarkupLanguage} for use generating output from templates.
 661  
    * Defaults to HTMLMarkupLanguage.
 662  
    *
 663  
    * @return - a MarkupLanguage, defaulting to HTMLMarkupLanguage
 664  
    * @see org.melati.template.TempletLoader
 665  
    * @see org.melati.poem.PoemLocale
 666  
    */
 667  
   public MarkupLanguage getMarkupLanguage() {
 668  187
     if (markupLanguage == null) 
 669  22
       markupLanguage = new HTMLMarkupLanguage(this,
 670  
                                   config.getTempletLoader(),
 671  
                                   getPoemLocale());
 672  187
     return markupLanguage;