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 }