1 /*
2 * $Source: /usr/cvsroot/melati/melati/src/main/java/org/melati/Melati.java,v $
3 * $Revision: 1.131 $
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 private Database database = null;
124 private Table table = null;
125 private Persistent object = null;
126 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 private boolean flushing = false;
137 // are we buffering the output
138 private boolean buffered= true;
139 // the output writer
140 private MelatiWriter writer;
141
142 private static final int maxLocales = 10;
143 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 HttpServletResponse response) {
157 this.request = request;
158 this.response = response;
159 this.config = config;
160 }
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 public Melati(MelatiConfig config, MelatiWriter writer) {
171 this.config = config;
172 this.writer = writer;
173 }
174
175 /**
176 * Get the servlet request object.
177 *
178 * @return the Servlet Request
179 */
180
181 public HttpServletRequest getRequest() {
182 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 this.request = request;
194 }
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 this.response = response;
203 }
204
205 /**
206 * Get the servlet response object.
207 *
208 * @return - the Servlet Response
209 */
210
211 public HttpServletResponse getResponse() {
212 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 this.poemContext = context;
229 if (poemContext.getLogicalDatabase() != null)
230 database = LogicalDatabase.getDatabase(poemContext.getLogicalDatabase());
231 }
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 if (poemContext.getTable() != null && database != null)
244 table = database.getTable(poemContext.getTable());
245 if (poemContext.getTroid() != null && table != null)
246 object = table.getObject(poemContext.getTroid().intValue());
247 }
248
249
250 /**
251 * Get the PoemContext for this Request.
252 *
253 * @return - the PoemContext for this Request
254 */
255 public PoemContext getPoemContext() {
256 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 return database;
267 }
268
269 /**
270 * @return the name of the Database
271 */
272 public String getDatabaseName() {
273 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 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 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 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 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 templateEngine = te;
325 }
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 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 templateContext = tc;
344 }
345
346 /**
347 * Get the TemplateContext used for this Request.
348 *
349 * @return - the template context being used
350 */
351 public TemplateContext getTemplateContext() {
352 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 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 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 String pathInfo = request.getPathInfo();
380 if (pathInfo == null || pathInfo.length() < 1) return new String[0];
381 pathInfo = pathInfo.substring(1);
382 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 arguments = args;
392 }
393
394 /**
395 * Get the Arguments array.
396 *
397 * @return the arguments array
398 */
399 public String[] getArguments() {
400 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 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 Constructor c = Class.forName(className).getConstructor(new Class[] {this.getClass()});
422 util = c.newInstance(new Object[] {this});
423 } catch (Exception e) {
424 throw new MelatiBugMelatiException("Class " + className +
425 " cannot be instantiated; is PoemContext set?", e);
426 }
427 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 StringBuffer url = new StringBuffer();
438 HttpUtil.appendRelativeZoneURL(url, getRequest());
439 url.append('/');
440 url.append(MelatiConfig.getLogoutPageServletClassName());
441 url.append('/');
442 url.append(poemContext.getLogicalDatabase());
443 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 StringBuffer url = new StringBuffer();
454 HttpUtil.appendRelativeZoneURL(url, getRequest());
455 url.append('/');
456 url.append(MelatiConfig.getLoginPageServletClassName());
457 url.append('/');
458 url.append(poemContext.getLogicalDatabase());
459 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 return HttpUtil.zoneURL(getRequest());
470 }
471
472 /**
473 * @return the relative url for the Servlet Zone of the current request
474 */
475 public String getRelativeZoneURL() {
476 return HttpUtil.getRelativeRequestURL(getRequest());
477 }
478 /**
479 * Get the URL for this request.
480 * Not used in Melati.
481 *
482 * @return - the URL for this request
483 * @see org.melati.util.HttpUtil#servletURL
484 */
485 public String getServletURL() {
486 return HttpUtil.servletURL(getRequest());
487 }
488
489 /**
490 * Get the URL for the JavascriptLibrary.
491 * Convenience method.
492 *
493 * @return - the URL for the JavascriptLibrary
494 * @see org.melati.MelatiConfig#getJavascriptLibraryURL
495 */
496 public String getJavascriptLibraryURL() {
497 return config.getJavascriptLibraryURL();
498 }
499
500 /**
501 * Returns a PoemLocale object based on the Accept-Language header
502 * of this request.
503 *
504 * If we are using Melati outside of a servlet context then the
505 * configured locale is returned.
506 *
507 * @return a PoemLocale object
508 */
509 public PoemLocale getPoemLocale() {
510 HttpServletRequest r = getRequest();
511 PoemLocale ml = null;
512 if (r != null) {
513 String acceptLanguage = r.getHeader("Accept-Language");
514 if (acceptLanguage != null)
515 ml = getPoemLocale(acceptLanguage);
516 }
517 return ml != null ? ml : MelatiConfig.getPoemLocale();
518 }
519
520 /**
521 * Returns a PoemLocale based on a language tag. Locales are cached for
522 * future use.
523 *
524 * @param languageHeader
525 * A language header from RFC 3282
526 * @return a PoemLocale based on a language tag or null if not found
527 * See http://www.w3.org/Protocols/rfc2616/rfc2616-sec14.html
528 */
529 public static PoemLocale getPoemLocale(String languageHeader) {
530
531 // language headers may have multiple language tags sperated by ,
532 String tags[] = StringUtils.split(languageHeader, ',');
533 PoemLocale ml = null;
534
535 // loop through until we find a tag we like
536 for (int i = 0; i < tags.length; i++) {
537 String tag = tags[i];
538
539 // remove quality value if it exists.
540 // we'll just try them in order
541 int indexSemicolon = tag.indexOf(';');
542 if (indexSemicolon != -1)
543 tag = tag.substring(0, indexSemicolon);
544
545 String lowerTag = tag.trim().toLowerCase();
546
547 // try our cache
548 ml = (PoemLocale)localeHash.get(lowerTag);
549 if (ml != null)
550 return ml;
551
552 // try creating a locale from this tag
553 ml = PoemLocale.fromLanguageTag(lowerTag);
554 if (ml != null) {
555 localeHash.put(lowerTag, ml);
556 return ml;
557 }
558 }
559
560 return null;
561 }
562
563 /**
564 * Suggest a response character encoding and if necessary choose a
565 * request encoding.
566 * <p>
567 * If the request encoding is provided then we choose a response
568 * encoding to meet our preferences on the assumption that the
569 * client will also indicate next time what its request
570 * encoding is.
571 * The result can optionally be set in code or possibly in
572 * templates using {@link #setResponseContentType(String)}.
573 * <p>
574 * Otherwise we tread carefully. We assume that the encoding is
575 * the first supported encoding of the client's preferences for
576 * responses, as indicated by Accept-Charsets, and avoid giving
577 * it any reason to change.
578 * <p>
579 * Actually, the server preference is a bit dodgy for
580 * the response because if it does persuade the client to
581 * change encodings and future requests include query strings
582 * that we are providing now then we may end up with the
583 * query strings being automatically decoded using the wrong
584 * encoding by request.getParameter(). But by the time we
585 * end up with values in such parameters the client and
586 * server will probably have settled on particular encodings.
587 */
588 public void establishCharsets() throws CharsetException {
589
590 AcceptCharset ac;
591 String acs = request.getHeader("Accept-Charset");
592 //assert acs == null || acs.trim().length() > 0 :
593 // "Accept-Charset should not be empty but can be absent";
594 // Having said that we don't want to split hairs once debugged
595 if (acs != null && acs.trim().length() == 0) {
596 acs = null;
597 }
598 try {
599 ac = new AcceptCharset(acs, config.getPreferredCharsets());
600 }
601 catch (HttpHeader.HttpHeaderException e) {
602 throw new CharsetException(
603 "An error was detected in your HTTP request header, " +
604 "response code: " +
605 HttpServletResponse.SC_BAD_REQUEST +
606 ": \"" + acs + '"', e);
607 }
608 if (request.getCharacterEncoding() == null) {
609 responseCharset = ac.clientChoice();
610 try {
611 request.setCharacterEncoding(responseCharset);
612 }
613 catch (UnsupportedEncodingException e) {
614 throw new MelatiBugMelatiException("This should already have been checked by AcceptCharset", e);
615 }
616 } else {
617 responseCharset = ac.serverChoice();
618 }
619 }
620
621 /**
622 * Suggested character encoding for use in responses.
623 */
624 protected String responseCharset = null;
625
626
627 /**
628 * Sets the content type for use in the response.
629 * <p>
630 * Use of this method is optional and only makes sense in a
631 * Servlet context. If the response is null then this is a no-op.
632 * <p>
633 * If the type starts with "text/" and does not contain a semicolon
634 * and a good response character set has been established based on
635 * the request Accept-Charset header and server preferences, then this
636 * and semicolon separator are automatically appended to the type.
637 * <p>
638 * Whether this function should be called at all may depend on
639 * the application and templates.
640 * <p>
641 * It should be called before any calls to {@link #getEncoding()}
642 * and before writing the response.
643 *
644 * @see #establishCharsets()
645 */
646 public void setResponseContentType(String type) {
647 contentType = type;
648 if (responseCharset != null && type.startsWith("text/")
649 && type.indexOf(";") == -1) {
650 contentType += "; charset=" + responseCharset;
651 }
652 if (response != null) {
653 response.setContentType(contentType);
654 System.err.println("Setting response:" + contentType);
655 }
656 }
657 protected String contentType = null;
658 /**
659 * @return the contentType
660 */
661 public String getContentType() {
662 return contentType;
663 }
664
665
666 /**
667 * Use this method if you wish to use a different
668 * MarkupLanguage, WMLMarkupLanguage for example.
669 * Cannot be set in MelatiConfig as does not have a noarg constructor.
670 * @param ml The ml to set.
671 */
672 public void setMarkupLanguage(MarkupLanguage ml) {
673 this.markupLanguage = ml;
674 }
675
676 /**
677 * Get a {@link MarkupLanguage} for use generating output from templates.
678 * Defaults to HTMLMarkupLanguage.
679 *
680 * @return - a MarkupLanguage, defaulting to HTMLMarkupLanguage
681 * @see org.melati.template.TempletLoader
682 * @see org.melati.poem.PoemLocale
683 */
684 public MarkupLanguage getMarkupLanguage() {
685 if (markupLanguage == null)
686 markupLanguage = new HTMLMarkupLanguage(this,
687 config.getTempletLoader(),
688 getPoemLocale());
689 return markupLanguage;
690 }
691
692 /**
693 * Get a HTMLMarkupLanguage.
694 * Retained for backward compatibility as there are a lot
695 * of uses in templates.
696 *
697 * @return - a HTMLMarkupLanguage
698 */
699 public HTMLMarkupLanguage getHTMLMarkupLanguage() {
700 return (HTMLMarkupLanguage)getMarkupLanguage();
701 }
702
703 /**
704 * The URL of the servlet request associated with this <TT>Melati</TT>, with
705 * a modified or added form parameter setting (query string component).
706 *
707 * @param field The name of the form parameter
708 * @param value The new value for the parameter (unencoded)
709 * @return The request URL with <TT>field=value</TT>. If there is
710 * already a binding for <TT>field</TT> in the query string
711 * it is replaced, not duplicated. If there is no query
712 * string, one is added.
713 * @see org.melati.servlet.Form
714 */
715 public String sameURLWith(String field, String value) {
716 return Form.sameURLWith(getRequest(), field, value);
717 }
718
719 /**
720 * The URL of the servlet request associated with this <TT>Melati</TT>, with
721 * a modified or added form flag setting (query string component).
722 *
723 * @param field The name of the form parameter
724 * @return The request URL with <TT>field=1</TT>. If there is
725 * already a binding for <TT>field</TT> in the query string
726 * it is replaced, not duplicated. If there is no query
727 * string, one is added.
728 * @see org.melati.servlet.Form
729 */
730 public String sameURLWith(String field) {
731 return sameURLWith(field, "1");
732 }
733
734 /**
735 * The URL of the servlet request associated with this <TT>Melati</TT>.
736 *
737 * @return a string
738 */
739 public String getSameURL() {
740 String qs = getRequest().getQueryString();
741 return getRequest().getRequestURI() + (qs == null ? "" : '?' + qs);
742 }
743
744 /**
745 * Turn off buffering of the output stream.
746 *
747 * By default, melati will buffer the output, which will not be written
748 * to the output stream until you call melati.write();
749 *
750 * Buffering allows us to catch AccessPoemExceptions and redirect the user
751 * to the login page. This could not be done if any bytes had already been written
752 * to the client.
753 *
754 * @see org.melati.test.FlushingServletTest
755 * @throws IOException if a writer has already been selected
756 */
757 public void setBufferingOff() throws IOException {
758 if (writer != null)
759 throw new IOException("You have already requested a Writer, " +
760 "and can't change it's properties now");
761 buffered = false;
762 }
763
764 /**
765 * Turn on flushing of the output stream.
766 *
767 * @throws IOException if there is a problem with the writer
768 */
769 public void setFlushingOn() throws IOException {
770 if (writer != null)
771 throw new IOException("You have already requested a Writer, " +
772 "and can't change it's properties now");
773 flushing = true;
774 }
775
776 /**
777 * Return the encoding that is used for URL encoded query
778 * strings.
779 * <p>
780 * The requirement here is that parameters can be encoded in
781 * query strings included in URLs in the body of responses.
782 * User interaction may result in subsequent requests with such
783 * a URL. The HTML spec. describes encoding of non-alphanumeric
784 * ASCII using % and ASCII hex codes and, in the case of forms.
785 * says the client may use the response encoding by default.
786 * Sun's javadoc for <code>java.net.URLEncoder</code>
787 * recommends UTF-8 but the default is the Java platform
788 * encoding. Most significantly perhaps,
789 * org.mortbay.http.HttpRequest uses the request encoding.
790 * We should check that this is correct in the servlet specs.
791 * <p>
792 * So we assume that the servlet runner may dictate the
793 * encoding that will work for multi-national characters in
794 * field values encoded in URL's (but not necessarily forms).
795 * <p>
796 * If the request encoding is used then we have to try and
797 * predict it. It will be the same for a session unless a client
798 * has some reason to change it. E.g. if we respond to a request
799 * in a different encoding and the client is influenced.
800 * (See {@link #establishCharsets()}.
801 * But that is only a problem if the first or second request
802 * in a session includes field values encoded in the URL and
803 * user options include manually entering the same in a form
804 * or changing their browser configuration.
805 * Or we can change the server configuration.
806 * <p>
807 * It would be better if we had control over what encoding
808 * the servlet runner used to decode parameters.
809 * Perhaps one day we will.
810 * <p>
811 * So this method implements the current policy and currently
812 * returns the current request encoding.
813 * It assumes {@link #establishCharsets()} has been called to
814 * set the request encoding if necessary.
815 *
816 * @return the character encoding
817 * @see #establishCharsets()
818 * see also org.melati.admin.Admin#selection(ServletTemplateContext, Melati)
819 */
820 public String getURLQueryEncoding() {
821 return request.getCharacterEncoding();
822 }
823
824 /**
825 * Convenience method to URL encode a URL query string.
826 *
827 * See org.melati.admin.Admin#selection(ServletTemplateContext, Melati)
828 */
829 /**
830 * @param string the String to encode
831 * @return the encoded string
832 */
833 public String urlEncode(String string) {
834 try {
835 return UTF8URLEncoder.encode(string, getURLQueryEncoding());
836 }
837 catch (UnexpectedExceptionException e) {
838 // Thrown if the encoding is not supported
839 return string;
840 }
841 }
842
843 /**
844 * Return the encoding that is used for writing.
845 * <p>
846 * This should always return an encoding and it should be the same
847 * for duration of use of an instance.
848 *
849 * @return Response encoding or a default in stand alone mode
850 * @see #setResponseContentType(String)
851 */
852 public String getEncoding() {
853 if (encoding == null)
854 encoding = response == null ? DEFAULT_ENCODING :
855 response.getCharacterEncoding();
856 return encoding;
857 }
858
859 /**
860 * Get a Writer for this request.
861 *
862 * If you have not accessed the Writer, it is reasonable to assume that
863 * nothing has been written to the output stream.
864 *
865 * @return - one of:
866 *
867 * - the Writer that was used to construct the Melati
868 * - the Writer associated with the Servlet Response
869 * - a buffered Writer
870 * - a ThrowingPrintWriter
871 * @throws IOException if there is a problem with the writer
872 */
873 public MelatiWriter getWriter() throws IOException {
874 if (writer == null) writer = createWriter();
875 return writer;
876 }
877
878 /**
879 * @param writerP the MelatiWriter to set
880 */
881 public void setWriter(MelatiWriter writerP) {
882 writer = writerP;
883 }
884 /**
885 * Get a StringWriter.
886 *
887 * @return - one of:
888 *
889 * - a MelatiStringWriter from the template engine
890 * - a new MelatiStringWriter if template engine not set
891 *
892 */
893 public MelatiWriter getStringWriter() {
894 if (templateEngine == null) {
895 return new MelatiStringWriter();
896 }
897 return templateEngine.getStringWriter();
898 }
899
900 /**
901 * Used in a servlet setting, where the class was not constructed with
902 * output set.
903 * @return a response writer
904 * @throws IOException
905 */
906 private MelatiWriter createWriter() throws IOException {
907 // first effort is to use the writer supplied by the template engine
908 MelatiWriter writerL = null;
909 if (response != null) {
910 if (templateEngine != null &&
911 templateEngine instanceof ServletTemplateEngine) {
912 writerL = ((ServletTemplateEngine)templateEngine).getServletWriter(response, buffered);
913 } else {
914 if (buffered) {
915 writerL = new MelatiBufferedWriter(response.getWriter());
916 } else {
917 writerL = new MelatiSimpleWriter(response.getWriter());
918 }
919 }
920 if (flushing) writerL.setFlushingOn();
921 } else
922 throw new MelatiBugMelatiException("Method createWriter called when response was null.");
923 return writerL;
924 }
925
926 /**
927 * Write the buffered output to the Writer
928 * we also need to stop the flusher if it has started.
929 *
930 * @throws IOException if there is a problem with the writer
931 */
932 public void write() throws IOException {
933 // only write stuff if we have previously got a writer
934 if (writer != null) writer.close();
935 }
936
937 /**
938 * This allows an Exception to be handled inline during Template expansion
939 * for example, if you would like to render AccessPoemExceptions to a
940 * String to be displayed on the page that is returned to the client.
941 *
942 * @see org.melati.template.MarkupLanguage#rendered(Object)
943 * @see org.melati.poem.TailoredQuery
944 */
945 public void setPassbackExceptionHandling() {
946 templateContext.setPassbackExceptionHandling();
947 }
948
949 /**
950 * The normal state of afairs: an exception is thrown and
951 * it is handled by the servlet.
952 */
953 public void setPropagateExceptionHandling() {
954 templateContext.setPropagateExceptionHandling();
955 }
956 /**
957 * Get a User for this request (if they are logged in).
958 * NOTE POEM studiously assumes there isn't necessarily a user, only
959 * an AccessToken
960 * @return - a User for this request
961 */
962 public User getUser() {
963 try {
964 return (User)PoemThread.accessToken();
965 }
966 catch (NotInSessionPoemException e) {
967 return null;
968 }
969 catch (ClassCastException e) {
970 // If the AccessToken is the RootAccessToken
971 return null;
972 }
973 }
974
975 /**
976 * Establish if field is a ReferencePoemType field.
977 *
978 * @param field
979 * the field to check
980 * @return whether it is a reference poem type
981 */
982 public boolean isReferencePoemType(Field field) {
983 return field.getType() instanceof ReferencePoemType;
984 }
985
986 /**
987 * Find a db specific template if it exists, otherwise a non-specific one,
988 * searching through all template paths.
989 *
990 * @param key fileName of template, without extension
991 * @return full resource name
992 */
993 public String templateName(String key) {
994 String templateName = null;
995 try {
996 TemplateEngine te = getTemplateEngine();
997 if (te == null)
998 throw new MelatiBugMelatiException("Template engine null");
999 Database db = getDatabase();
1000 templateName = te.getTemplateName(key, db == null ? null : db.getName());
1001 } catch (Exception e) {
1002 throw new MelatiBugMelatiException("Problem getting template named " + key +
1003 " :" + e.toString(), e);
1004 }
1005 return templateName;
1006 }
1007
1008
1009
1010 }