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