##### Coverage Report - org.melati.Melati

Classes in this File | Line Coverage | Branch Coverage | Complexity
Melati |
97% | 196/202
91% | 66/72
| 2.0689655172413794;2.069

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