Source code: org/enhydra/xml/xmlc/servlet/XMLCContext.java
1 /*
2 * Enhydra Java Application Server Project
3 *
4 * The contents of this file are subject to the Enhydra Public License
5 * Version 1.1 (the "License"); you may not use this file except in
6 * compliance with the License. You may obtain a copy of the License on
7 * the Enhydra web site ( http://www.enhydra.org/ ).
8 *
9 * Software distributed under the License is distributed on an "AS IS"
10 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
11 * the License for the specific terms governing rights and limitations
12 * under the License.
13 *
14 * The Initial Developer of the Enhydra Application Server is Lutris
15 * Technologies, Inc. The Enhydra Application Server and portions created
16 * by Lutris Technologies, Inc. are Copyright Lutris Technologies, Inc.
17 * All Rights Reserved.
18 *
19 * Contributor(s):
20 *
21 * $Id: XMLCContext.java,v 1.1.2.8 2001/02/16 20:41:44 markd Exp $
22 */
23
24 package org.enhydra.xml.xmlc.servlet;
25
26 import java.io.IOException;
27 import java.io.StringWriter;
28 import java.io.PrintWriter;
29 import java.io.OutputStream;
30 import javax.servlet.ServletContext;
31 import javax.servlet.http.HttpServletRequest;
32 import javax.servlet.http.HttpServletResponse;
33 import javax.servlet.http.HttpServlet;
34 import org.enhydra.xml.io.DOMFormatter;
35 import org.enhydra.xml.io.OutputOptions;
36 import org.enhydra.xml.io.URLRewriter;
37 import org.enhydra.xml.dom.DOMStats;
38 import org.enhydra.xml.xmlc.XMLObject;
39 import org.enhydra.xml.xmlc.XMLCFactory;
40
41 /**
42 * Class to facility the use of XMLC in a HTTP servlet. This object contains
43 * a XMLC factory to create instances of XMLC document classes and methods to
44 * output documents.
45 *
46 * <P>
47 * An instance of this object is associated with the ServletContext object
48 * for an application (one per WebApp). The object is created on first use by
49 * ({@see #getContext getContext}. It provides a factory for creating XMLC
50 * document class with optional automatic reloading of out of date classes
51 * or recompilation and reloading of classes out-of-date relative to a source
52 * file. Also provided are methods to output a XMLC document as a HTTP response,
53 * with automatic setting of response content type.
54 * <P>
55 * Instance of this class are configured using the following Servlet
56 * context-wide init parameters:
57 * <UL>
58 * <LI> <tt>xmlcReloading</tt> - Control's the automatic reloading or
59 * recompilation of XMLC document classes. The following values are
60 * recognized.
61 * <UL>
62 * <LI> <tt>off</tt> - No automatic reloading or recompilation (default).
63 * <LI> <tt>reload</tt> - Automatic reloading of modified class files.
64 * <LI> <tt>recompile</tt> - Automatic recompilation of classes that are
65 * out of date relative to their source files and reloading
66 * of modified class files.
67 * </UL>
68 * <LI> <tt>xmlcSessionURLEncoding</tt> - Controls automatic encoding of session ID in
69 * URLs.
70 * <UL>
71 * <LI> <tt>auto</tt> - Automatically enable session URL encoding as needed
72 * (default)
73 * <LI> <tt>always</tt> - Always enable session URL encoding.
74 * <LI> <tt>never</tt> - Never enable session URL encoding.
75 * </UL>
76 * <LI> <tt>xmlcLogging</tt> - Control's what runtime information XMLC logs.
77 * The value is a space-seperated list of the following values:
78 * <UL>
79 * <LI> <tt>INFO</tt> - Log basic information about notable events, such as
80 * recompiling or reloading classes.
81 * <LI> <tt>DEBUG</tt> - Log debugging information. Mostly related to
82 * recompiling and reloading.
83 * <LI> <tt>STATS</tt> - Log statistics information useful in debugging
84 * performance problems. Currently writes information about each DOM
85 * that is written. This is especially useful in looking at the
86 * how much of a Lazy DOM has been expanded.
87 * </UL>
88 * The default is <tt>INFO</tt>.
89 * </UL>
90 *
91 * @see ServletContext
92 */
93 public class XMLCContext {
94 /**
95 * Value indicating that URL session encoding should be detected
96 * automatically based on if it was used for the current requested.
97 */
98 public static final SessionURLEncodingMode URL_ENCODING_AUTO
99 = new SessionURLEncodingMode("auto");
100
101 /**
102 * Value indicating that URL session encoding should always be used.
103 */
104 public static final SessionURLEncodingMode URL_ENCODING_ALWAYS
105 = new SessionURLEncodingMode("always");
106
107 /**
108 * Value indicating that URL session encoding should never be used.
109 */
110 public static final SessionURLEncodingMode URL_ENCODING_NEVER
111 = new SessionURLEncodingMode("never");
112
113 /**
114 * Enumerated constant indicating how URL session encoding is to
115 * be handled.
116 */
117 static public class SessionURLEncodingMode {
118 /** String name of the mode */
119 private final String fModeName;
120
121 /** Constructor */
122 private SessionURLEncodingMode(String modeName) {
123 fModeName = modeName;
124 }
125
126 /** Get the string value of the mode */
127 public String toString() {
128 return fModeName;
129 }
130 }
131
132 /**
133 * Name of ServletContext attribute that contains the instance of this class
134 * for a WebApp.
135 */
136 public static final String CONTEXT_ATTRIBUTE
137 = "http://www.enhydra.org/xmlc/XMLCContext";
138
139 /**
140 * The servlet context we are associated with.
141 */
142 private ServletContext fServletContext;
143
144 /*
145 * XMLC factory to use.
146 */
147 private XMLCFactory fFactory;
148
149 /*
150 * URL encoding of session key configuration.
151 */
152 private SessionURLEncodingMode fSessionURLEncodingMode;
153
154 /*
155 * Log various statistics.
156 */
157 private boolean fLogStats;
158
159 /**
160 * Constructor; instances can only be created through
161 * ({@see #getContext getContext}.
162 */
163 XMLCContext(ServletContext servletContext,
164 XMLCFactory factory,
165 SessionURLEncodingMode sessionURLEncodingMode,
166 boolean logStats) {
167 fServletContext = servletContext;
168 fFactory = factory;
169 fSessionURLEncodingMode = sessionURLEncodingMode;
170 fLogStats = logStats;
171 }
172
173 /**
174 * Get the XMLC factory object associated with the context.
175 */
176 public XMLCFactory getXMLCFactory() {
177 return fFactory;
178 }
179
180 /**
181 * Explictly set XMLC factory. The XMLC factory is normally defined base
182 * on the servlet configuration parameters. This allows the user to
183 * overide the standard factories.
184 */
185 public void setXMLCFactory(XMLCFactory factory) {
186 fFactory = factory;
187 }
188
189 /**
190 * Set the session URL encoding mode. This option is normally set from
191 * the servlet configuration parameters. This allows the explicit setting
192 * of this option.
193 *
194 * @param mode Constant indicating what mode to use
195 * @see SessionURLEncodingMode
196 */
197 public void setSessionURLEncoding(SessionURLEncodingMode mode) {
198 fSessionURLEncodingMode = mode;
199 }
200
201 /*
202 * Get the session URL encoding mode.
203 *
204 * @return The constant indicating what mode should be used for encoding.
205 * @see SessionURLEncodingMode
206 */
207 public SessionURLEncodingMode getSessionURLEncoding() {
208 return fSessionURLEncodingMode;
209 }
210
211 /**
212 * Determine if a session id should be encoded URLs.
213 */
214 private boolean shouldEncodeSessionId(HttpServletRequest request) {
215 return (((fSessionURLEncodingMode == URL_ENCODING_AUTO)
216 && request.isRequestedSessionIdFromURL()
217 && request.isRequestedSessionIdValid())
218 || (fSessionURLEncodingMode == URL_ENCODING_ALWAYS));
219 }
220
221 /**
222 * Setup the session URL encoder as required by configuration and
223 * request.
224 */
225 private URLRewriter setupURLRewriter(HttpServletRequest request,
226 final HttpServletResponse response) {
227 if (shouldEncodeSessionId(request)) {
228 return new URLRewriter() {
229 public String rewriteURL(String urlAttrValue) {
230 return response.encodeURL(urlAttrValue);
231 }
232 };
233 } else {
234 return null;
235 }
236 }
237
238 /**
239 * Set up the encoding using the encoding specified, OutputOptions,
240 * the response, or the XMLC default encoding. Modifies OutputOptions.
241 */
242 private void setupEncoding(HttpServletResponse response,
243 XMLObject document,
244 OutputOptions options) {
245
246 // If encoding is already specified, we don't override.
247 if (options.getEncoding() == null) {
248 // Use document default encoding.
249 String encoding = document.getEncoding();
250 if (encoding != null) {
251 options.setEncoding(encoding);
252 }
253 }
254 }
255
256 /**
257 * Create an OutputOptions object for a document. Options are defaulted for
258 * the specified document. The object maybe then modified as needed to
259 * override the default values.
260 * <P>
261 * The following attributes are set in the object:
262 * <UL>
263 * <LI> encoding
264 * <LI> MIME type - Defaults for document.
265 * <LI> URLRewriter - Set if URL encoding of sessions is enabled.
266 * </UL>
267 *
268 * @param request The servlet request object. The request is required
269 * to determine if URL encoding should be done on the document.
270 * @param response The servlet response object. The response is required
271 * to set up the URL encoder.
272 * @param document The DOM object to be returned as response.
273 */
274 public OutputOptions createOutputOptions(HttpServletRequest request,
275 HttpServletResponse response,
276 XMLObject document) {
277 OutputOptions options = DOMFormatter.getDefaultOutputOptions(document);
278
279 setupEncoding(response, document, options);
280
281 // Get the default MIME type from object
282 options.setMIMEType(document.getMIMEType());
283
284 // Enable encoding of session id in URLs, if needed.
285 options.setURLRewriter(setupURLRewriter(request, response));
286 return options;
287 }
288
289 /**
290 * Write headers, disabling caching if other policy is not already
291 * set.
292 */
293 private void outputHeaders(HttpServletResponse response,
294 int docLength,
295 String mimeEncoding,
296 String mimeType) throws IOException {
297
298 // Set headers
299 response.setContentLength(docLength);
300 if (mimeType != null) {
301 if (mimeEncoding != null) {
302 response.setContentType(mimeType + "; charset=" + mimeEncoding);
303 } else {
304 response.setContentType(mimeType);
305 }
306 }
307
308 // Disable caching of page unless otherwise specified.
309 if (response.containsHeader("Cache-Control")) {
310 response.setHeader("Cache-Control", "no-cache");
311 }
312 if (response.containsHeader("Expires")) {
313 response.setHeader("Expires", "0");
314 }
315 }
316
317 /**
318 * Output document statistics.
319 */
320 private void logDocumentStats(XMLObject document) {
321 // Buffer results so it gets logged as a single entry
322 StringWriter buf = new StringWriter(2048);
323 PrintWriter writer = new PrintWriter(buf);
324 DOMStats.printStats("Write " + document.getClass().getName(),
325 document, 0, writer);
326 writer.flush();
327 fServletContext.log(buf.toString());
328 }
329
330 /**
331 * Do common operations for writing DOM document. Sets approriate HTTP
332 * header, including setting of cache-control headers, if not already set.
333 * Handles encoding conversion and output.
334 */
335 private void outputDocument(HttpServletResponse response,
336 XMLObject document,
337 OutputOptions outputOptions) throws IOException {
338
339 // Convert to bytes and output
340 DOMFormatter formatter = new DOMFormatter(outputOptions);
341 byte[] docBytes = formatter.toBytes(document);
342
343 outputHeaders(response, docBytes.length,
344 outputOptions.getMIMEEncoding(),
345 outputOptions.getMIMEType());
346
347 // Log if requested
348 if (fLogStats) {
349 logDocumentStats(document);
350 }
351
352 // Output
353 OutputStream out = response.getOutputStream();
354 out.write(docBytes);
355 out.flush();
356 }
357
358 /**
359 * Output an an XMLC document object (DOM). The document is formatted
360 * according to it's type. The MIME type of the response is automatically
361 * set.
362 *
363 * @param request The servlet request object. The request is required
364 * to determine if URL encoding should be done on the document.
365 * @param response The servlet response object. The
366 * <tt>Content-Type</tt> and <tt>Content-Length</tt> will be set from
367 * the document..
368 * @param outputFormat Object use to specify options controlling the
369 * formatting of the document.
370 * @param document The DOM object to be returned as response.
371 */
372 public void writeDOM(HttpServletRequest request,
373 HttpServletResponse response,
374 OutputOptions outputOptions,
375 XMLObject document) throws IOException {
376 // Copy options so as to not modify when changing encoding.
377 OutputOptions options = new OutputOptions(outputOptions);
378 setupEncoding(response, document, options);
379 outputDocument(response, document, options);
380 }
381
382 /**
383 * Output an an XMLC document object (DOM). The document is formatted
384 * according to it's type. The MIME type of the response is automatically
385 * set.
386 *
387 * @param request The servlet request object. The request is required
388 * to determine if URL encoding should be done on the document.
389 * @param response The servlet response object. The
390 * <tt>Content-Type</tt> and <tt>Content-Length</tt> will be set from
391 * the document..
392 * @param document The DOM object to be returned as response.
393 */
394 public void writeDOM(HttpServletRequest request,
395 HttpServletResponse response,
396 XMLObject document) throws IOException {
397 OutputOptions options
398 = createOutputOptions(request, response, document);
399 outputDocument(response, document, options);
400 }
401
402 /**
403 * Obtain the XMLCContext for the current application, creating it if
404 * it doesn't exist.
405 *
406 * @param servlet A servlet in the application. The class loader that loaded this
407 * servlet will be used to load XMLC document classes.
408 * @return The XMLC context object for the application.
409 */
410 public static XMLCContext getContext(HttpServlet servlet) {
411 ServletContext servletContext
412 = servlet.getServletConfig().getServletContext();
413 XMLCContext context = (XMLCContext)servletContext.getAttribute(CONTEXT_ATTRIBUTE);
414 if (context == null) {
415 synchronized (servletContext) {
416 // Check again now that we are synchronized
417 context = (XMLCContext)servletContext.getAttribute(CONTEXT_ATTRIBUTE);
418 if (context == null) {
419 context = new XMLCContextInit().createContext(servlet);
420 servletContext.setAttribute(CONTEXT_ATTRIBUTE, context);
421 }
422 }
423 }
424 return context;
425 }
426 }