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