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