Source code: com/opencms/flex/CmsJspLoader.java
1 /*
2 * File : $Source: /usr/local/cvs/opencms/src/com/opencms/flex/Attic/CmsJspLoader.java,v $
3 * Date : $Date: 2003/05/13 13:18:20 $
4 * Version: $Revision: 1.24.2.1 $
5 *
6 * This library is part of OpenCms -
7 * the Open Source Content Mananagement System
8 *
9 * Copyright (C) 2002 - 2003 Alkacon Software (http://www.alkacon.com)
10 *
11 * This library is free software; you can redistribute it and/or
12 * modify it under the terms of the GNU Lesser General Public
13 * License as published by the Free Software Foundation; either
14 * version 2.1 of the License, or (at your option) any later version.
15 *
16 * This library is distributed in the hope that it will be useful,
17 * but WITHOUT ANY WARRANTY; without even the implied warranty of
18 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
19 * Lesser General Public License for more details.
20 *
21 * For further information about Alkacon Software, please see the
22 * company website: http://www.alkacon.com
23 *
24 * For further information about OpenCms, please see the
25 * project website: http://www.opencms.org
26 *
27 * You should have received a copy of the GNU Lesser General Public
28 * License along with this library; if not, write to the Free Software
29 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
30 */
31
32 package com.opencms.flex;
33
34 import com.opencms.boot.I_CmsLogChannels;
35 import com.opencms.core.A_OpenCms;
36 import com.opencms.core.CmsException;
37 import com.opencms.core.CmsExportRequest;
38 import com.opencms.core.I_CmsConstants;
39 import com.opencms.file.CmsFile;
40 import com.opencms.file.CmsObject;
41 import com.opencms.file.CmsRequestContext;
42 import com.opencms.file.CmsResource;
43 import com.opencms.flex.cache.CmsFlexCache;
44 import com.opencms.flex.cache.CmsFlexController;
45 import com.opencms.flex.cache.CmsFlexRequest;
46 import com.opencms.flex.cache.CmsFlexResponse;
47 import com.opencms.launcher.I_CmsLauncher;
48 import com.opencms.util.Encoder;
49 import com.opencms.util.Utils;
50
51 import java.io.ByteArrayOutputStream;
52 import java.io.DataInputStream;
53 import java.io.File;
54 import java.io.FileNotFoundException;
55 import java.io.FileOutputStream;
56 import java.io.IOException;
57 import java.io.OutputStream;
58 import java.net.HttpURLConnection;
59 import java.net.URL;
60 import java.util.Enumeration;
61 import java.util.HashSet;
62 import java.util.Iterator;
63 import java.util.Set;
64 import java.util.StringTokenizer;
65 import java.util.Vector;
66
67 import javax.servlet.ServletException;
68 import javax.servlet.ServletRequest;
69 import javax.servlet.ServletResponse;
70 import javax.servlet.http.HttpServletRequest;
71 import javax.servlet.http.HttpServletResponse;
72
73 /**
74 * The JSP loader which enables the execution of JSP in OpenCms.<p>
75 *
76 * It does NOT extend {@link com.opencms.launcher.A_CmsLauncher}, since JSP are not related
77 * to the OpenCms Template mechanism. However, it implements the
78 * launcher interface so that JSP can be sub-elements in XMLTemplace pages.
79 *
80 * @author Alexander Kandzior (a.kandzior@alkacon.com)
81 *
82 * @version $Revision: 1.24.2.1 $
83 * @since FLEX alpha 1
84 *
85 * @see I_CmsResourceLoader
86 * @see com.opencms.launcher.I_CmsLauncher
87 */
88 public class CmsJspLoader implements I_CmsLauncher, I_CmsResourceLoader {
89
90 /** The directory to store the generated JSP pages in (absolute path) */
91 private static String m_jspRepository = null;
92
93 /** The directory to store the generated JSP pages in (relative path in web application */
94 private static String m_jspWebAppRepository = null;
95
96 /** The CmsFlexCache used to store generated cache entries in */
97 private static CmsFlexCache m_cache;
98
99 /** Export URL for JSP pages */
100 private static String m_jspExportUrl;
101
102 /** Flag to indicate if error pages are mared a "commited" */
103 // TODO: This is a hack, investigate this issue with different runtime environments
104 private static boolean m_errorPagesAreNotCommited = false; // should work for Tomcat 4.1
105
106 /** Special JSP directive tag start (<code><%@</code>)*/
107 public static final String C_DIRECTIVE_START = "<%@";
108
109 /** Special JSP directive tag start (<code>%></code>)*/
110 public static final String C_DIRECTIVE_END ="%>";
111
112 /** Encoding to write JSP files to disk (<code>ISO-8859-1</code>) */
113 public static final String C_DEFAULT_JSP_ENCODING = "ISO-8859-1";
114
115 /** Extension for JSP managed by OpenCms (<code>.jsp</code>) */
116 public static final String C_JSP_EXTENSION = ".jsp";
117
118 // Static export related stuff
119 /** Parameter constant to indicate that the export is requested */
120 private static final String C_EXPORT_PARAM = "_flex_export";
121
122 /** Parameter constant to indicate a body previously discovered in an XMLTemplate */
123 private static final String C_EXPORT_BODY = "_flex_export_body";
124
125 /** Parameter constant to indicate encoding used in calling template */
126 private static final String C_EXPORT_ENCODING = "_flex_export_encoding";
127
128 /** Header constant to indicate the found links in the response return headers */
129 private static final String C_EXPORT_HEADER = "_flex_export_links";
130
131 /** Separator constant to separate return headers */
132 private static final String C_EXPORT_HEADER_SEP = "/";
133
134 /** Name of export URL runtime property */
135 public static final String C_LOADER_JSPEXPORTURL = "flex.jsp.exporturl";
136
137 /** Name of "error pages are commited or not" runtime property*/
138 public static final String C_LOADER_ERRORPAGECOMMIT = "flex.jsp.errorpagecommit";
139
140 /** Flag for debugging output. Set to 9 for maximum verbosity. */
141 private static final int DEBUG = 0;
142
143 /**
144 * The constructor of the class is empty, the initial instance will be
145 * created by the launcher manager upon startup of OpenCms.<p>
146 *
147 * To initilize the fields in this class, the <code>setOpenCms()</code>
148 * method will be called by the launcher.
149 *
150 * @see com.opencms.launcher.CmsLauncherManager
151 * @see #setOpenCms(A_OpenCms openCms)
152 */
153 public CmsJspLoader() {
154 // NOOP
155 }
156
157 // ---------------------------- Implementation of interface com.opencms.launcher.I_CmsLauncher
158
159 /**
160 * This is part of the I_CmsLauncher interface, but for JSP so far this
161 * is a NOOP.
162 */
163 public void clearCache() {
164 // NOOP
165 }
166
167 /**
168 * This is part of the I_CmsLauncher interface,
169 * used here to call the init() method.
170 *
171 * @see #init(A_OpenCms openCms)
172 */
173 public void setOpenCms(A_OpenCms openCms) {
174 init(openCms);
175 }
176
177 /**
178 * Returns the ID that indicates the type of the launcher.
179 *
180 * The IDs for all launchers of the core distributions are constants
181 * in the I_CmsLauncher interface.
182 * The value returned is <code>com.opencms.launcher.I_CmsLauncher.C_TYPE_JSP</code>.
183 *
184 * @return launcher ID
185 *
186 * @see com.opencms.launcher.I_CmsLauncher
187 */
188 public int getLauncherId() {
189 return com.opencms.launcher.I_CmsLauncher.C_TYPE_JSP;
190 }
191
192 /**
193 * Start launch method called by the OpenCms system to show a resource,
194 * this basically processes the resource and returns the output.<p>
195 *
196 * This is part of the Launcher interface.
197 * All requests will be forwarded to the <code>load()</code> method of this
198 * class. That forms the link between the Launcher and Loader interfaces.<p>
199 *
200 * Exceptions thrown in the <code>load()</code> method of this loader
201 * will be handled here, usually by wrapping them in a CmsException
202 * that will then be shown in the OpenCms error dialog.
203 *
204 * @param cms CmsObject Object for accessing system resources.
205 * @param file CmsFile Object with the selected resource to be shown.
206 * @param startTemplateClass Name of the template class to start with.
207 * @param openCms a instance of A_OpenCms for redirect-needs
208 * @throws CmsException all exeptions in the load process of a JSP will be caught here and wrapped to a CmsException
209 *
210 * @see com.opencms.launcher.I_CmsLauncher
211 * @see #load(CmsObject cms, CmsFile file, HttpServletRequest req, HttpServletResponse res)
212 */
213 public void initlaunch(CmsObject cms, CmsFile file, String startTemplateClass, A_OpenCms openCms) throws CmsException {
214 HttpServletRequest req;
215 HttpServletResponse res;
216
217 if (cms.getRequestContext().getRequest() instanceof CmsExportRequest) {
218 // request is an export request
219 if (DEBUG > 1) System.err.println("FlexJspLoader: Export requested for " + file.getAbsolutePath());
220 // get the contents of the exported page
221 byte[] export = exportJsp(cms, file);
222 // try to write the result to the current output stream
223 try {
224 OutputStream output = cms.getRequestContext().getResponse().getOutputStream();
225 output.write(export);
226 } catch (IOException e) {
227 throw new CmsException("IOException writing contents of exported JSP for URI " + cms.getRequestContext().getUri(),
228 CmsException.C_FLEX_LOADER, e);
229 }
230 } else {
231 // wrap request and response
232 req = (HttpServletRequest)cms.getRequestContext().getRequest().getOriginalRequest();
233 res = (HttpServletResponse)cms.getRequestContext().getResponse().getOriginalResponse();
234 // check if this is an export request
235 int oldMode = exportCheckMode(cms, req);
236 // load and process the JSP
237 try {
238 // load the resource
239 load(cms, file, req, res);
240 } catch (Exception e) {
241 // all Exceptions are caught here and get translated to a CmsException for display in the OpenCms error dialog
242 if (DEBUG > 1) System.err.println("Error in Flex loader: " + e + Utils.getStackTrace(e));
243 throw new CmsException("Error in Flex loader", CmsException.C_FLEX_LOADER, e, true);
244 } finally {
245 exportResetMode(cms, oldMode);
246 }
247 }
248 }
249
250 // ---------------------------- Static export related stuff
251
252 /**
253 * Checks if the request parameter C_EXPORT_PARAM is set, if so sets the CmsObject
254 * working mode to C_MODUS_EXPORT.
255 *
256 * @param cms provides the current cms context
257 * @param req the current request
258 * @return int the mode previously set in the CmsObject
259 */
260 private int exportCheckMode(CmsObject cms, HttpServletRequest req) {
261 int oldMode = cms.getMode();
262 String exportUri = req.getParameter(C_EXPORT_PARAM);
263 if (exportUri != null) {
264 if (! exportUri.equals(cms.getRequestContext().getUri())) {
265 // URI is not the same, so this is a sub - element
266 cms.getRequestContext().setUri(exportUri);
267 }
268 cms.setMode(CmsObject.C_MODUS_EXPORT);
269 }
270 // check body
271 String body = req.getParameter(C_EXPORT_BODY);
272 if (body != null) {
273 cms.getRequestContext().setAttribute(I_CmsConstants.C_XML_BODY_ELEMENT, body);
274 }
275 // check encoding
276 String encoding = req.getParameter(C_EXPORT_ENCODING);
277 if (encoding != null) {
278 cms.getRequestContext().setEncoding(encoding);
279 }
280 return oldMode;
281 }
282
283 /**
284 * Restores the mode stored in the <code>oldMode</code> paameter to the CmsObject.
285 *
286 * @param cms provides the current cms context
287 * @param oldMode the old mode to restore in the CmsObject
288 */
289 private void exportResetMode(CmsObject cms, int oldMode) {
290 cms.setMode(oldMode);
291 }
292
293 /**
294 * Returns the links found in the currently processed page as response headers,
295 * so that the static export can pick them up later.
296 *
297 * @param cms provides the current cms context
298 * @param res the response to set the headers in
299 */
300 private void exportSetLinkHeader(CmsObject cms, HttpServletResponse res) {
301 // get the links found on the page from the current request context
302 Vector v = cms.getRequestContext().getLinkVector();
303 // making the vector a set removes the duplicate entries
304 Set s = new HashSet(v);
305 StringBuffer links = new StringBuffer(s.size() * 64);
306 Iterator i = s.iterator();
307 // build a string out of the found links
308 while (i.hasNext()) {
309 links.append(Encoder.encode((String)i.next()));
310 if (i.hasNext()) links.append(C_EXPORT_HEADER_SEP);
311 }
312 // set the export header and we are finished
313 res.setHeader(C_EXPORT_HEADER, new String(links));
314 }
315
316 /**
317 * Perform an export of the requested JSP page.<p>
318 *
319 * The export of a JSP is done in the following way:
320 * <ul>
321 * <li>A HttpURLConnection is openend to the address configured in the runtime property with
322 * the name {@link #C_LOADER_JSPEXPORTURL}, which usually should be the current OpenCms server.
323 * <li>The URI of the <code>file</code> is appended to the connection as path information, so
324 * this will be the page requested and exported.
325 * <li>All current request parameters are encoded and also added to the request as parameters.
326 * <li>The currently requested URI is also appended as value of the special parameter {@link
327 * #C_EXPORT_PARAM}.
328 * <li>When processing this special request, the mode of the <code>CmsObject</code> will be
329 * set to <code>C_MODUS_EXPORT</code>, which is the required mode if you want to generate
330 * the result for an export.
331 * <li>All links found while processing the exported JSP will be written in a special header
332 * of the response, called {@link #C_EXPORT_HEADER}.
333 * <li>The response result will be checked for the headers and all links found will be added to
334 * the link vector of the currently processed page.
335 * <li>The content of the resonse will be read into a byte array and returned as result of this
336 * call.
337 * </ul>
338 *
339 * @param cms provides the current cms context
340 * @param file the JSP file requested
341 * @return the contents of the JSP page for the export
342 */
343 private byte[] exportJsp(CmsObject cms, CmsFile file) throws CmsException {
344
345 // check if we are properly initialized
346 if (m_jspExportUrl == null) {
347 throw new CmsException("JSP export URL not set, can not export JSP", CmsException.C_FLEX_LOADER);
348 }
349
350 ByteArrayOutputStream bytes = null;
351 CmsRequestContext context = cms.getRequestContext();
352
353 // generate export URL
354 StringBuffer exportUrl = new StringBuffer(m_jspExportUrl);
355 exportUrl.append(file.getAbsolutePath());
356 exportUrl.append("?");
357
358 // add parameters to export call
359 Enumeration params = context.getRequest().getParameterNames();
360 while (params.hasMoreElements()) {
361 String key = (String)params.nextElement();
362 String values[] = (String[])context.getRequest().getParameterValues(key);
363 for (int i=0; i<values.length; i++) {
364 exportUrl.append(key);
365 exportUrl.append("=");
366 exportUrl.append(Encoder.encode(values[i]));
367 exportUrl.append("&");
368 }
369 }
370 // add the export parameter to the request
371 exportUrl.append(C_EXPORT_PARAM);
372 exportUrl.append("=");
373 exportUrl.append(cms.getRequestContext().getUri());
374 // add the original requested body file to the request
375 String body = (String)cms.getRequestContext().getAttribute(I_CmsConstants.C_XML_BODY_ELEMENT);
376 if (body != null) {
377 exportUrl.append("&");
378 exportUrl.append(C_EXPORT_BODY);
379 exportUrl.append("=");
380 exportUrl.append(Encoder.encode(body));
381 }
382 // add the encoding used for the output page to the request
383 String encoding = cms.getRequestContext().getEncoding();
384 exportUrl.append("&");
385 exportUrl.append(C_EXPORT_ENCODING);
386 exportUrl.append("=");
387 exportUrl.append(Encoder.encode(encoding));
388
389 if (DEBUG > 2) System.err.println("CmsJspLoader.exportJsp(): JSP export URL is " + exportUrl);
390
391 // perform the export with an URLConnection
392 URL export;
393 HttpURLConnection urlcon;
394 DataInputStream input;
395
396 try {
397 export = new URL(new String(exportUrl));
398 urlcon = (HttpURLConnection) export.openConnection();
399 // set request type to POST
400 urlcon.setRequestMethod("POST");
401 HttpURLConnection.setFollowRedirects(false);
402 // input and output stream
403 input = new DataInputStream(urlcon.getInputStream());
404 bytes = new ByteArrayOutputStream(urlcon.getContentLength()>0?urlcon.getContentLength():1024);
405 } catch (Exception e) {
406 // all exceptions here will be IO related
407 throw new CmsException("IO related error while exporting JSP for URI " + cms.getRequestContext().getUri(),
408 CmsException.C_FLEX_LOADER, e);
409 }
410
411 // check if links are present in the exported page
412 String cmslinks = urlcon.getHeaderField(C_EXPORT_HEADER);
413 if (cmslinks != null) {
414 // add all the links to the current cms context
415 StringTokenizer tok = new StringTokenizer(cmslinks, C_EXPORT_HEADER_SEP);
416 while (tok.hasMoreTokens()) {
417 String link = Encoder.decode(tok.nextToken(), "UTF-8", true);
418 cms.getRequestContext().addLink(link);
419 if (DEBUG > 3) System.err.println("CmsJspLoader.exportJsp(): Extracted link " + link);
420 }
421 }
422 // now read the page content and write it to the byte array
423 try {
424 int b;
425 while ((b = input.read()) > 0) {
426 bytes.write(b);
427 }
428 } catch (IOException e) {
429 throw new CmsException("IO error writing bytes to buffer exporting JSP for URI " + cms.getRequestContext().getUri(),
430 CmsException.C_FLEX_LOADER, e);
431 }
432
433 return bytes.toByteArray();
434 }
435
436 // ---------------------------- Implementation of interface com.opencms.flex.I_CmsResourceLoader
437
438 /** Destroy this ResourceLoder, this is a NOOP so far. */
439 public void destroy() {
440 // NOOP
441 }
442
443 /**
444 * Return a String describing the ResourceLoader,
445 * which is <code>"The OpenCms default resource loader for JSP"</code>
446 *
447 * @return a describing String for the ResourceLoader
448 */
449 public String getResourceLoaderInfo() {
450 return "The OpenCms default resource loader for JSP";
451 }
452
453 /**
454 * Initialize the ResourceLoader,
455 * here the configuration for the JSP repository (directories used) is set.
456 *
457 * @param openCms An OpenCms object to use for initalizing.
458 */
459 public void init(A_OpenCms openCms) {
460 m_jspRepository = com.opencms.boot.CmsBase.getBasePath();
461 if (m_jspRepository.indexOf("WEB-INF") >= 0) {
462 // Should always be true, just make sure we don't generate an exception in untested environments
463 m_jspRepository = m_jspRepository.substring(0, m_jspRepository.indexOf("WEB-INF")-1);
464 }
465 source.org.apache.java.util.Configurations c = openCms.getConfiguration();
466 m_jspWebAppRepository = c.getString("flex.jsp.repository", "/WEB-INF/jsp");
467 m_jspRepository += m_jspWebAppRepository.replace('/', File.separatorChar);
468 if (! m_jspRepository.endsWith(File.separator)) m_jspRepository += File.separator;
469 if (DEBUG > 0) System.err.println("JspLoader: Setting jsp repository to " + m_jspRepository);
470 // Get the cache from the runtime properties
471 m_cache = (CmsFlexCache)A_OpenCms.getRuntimeProperty(C_LOADER_CACHENAME);
472 // Get the export URL from the runtime properties
473 m_jspExportUrl = (String)A_OpenCms.getRuntimeProperty(C_LOADER_JSPEXPORTURL);
474 if (I_CmsLogChannels.C_LOGGING && A_OpenCms.isLogging(I_CmsLogChannels.C_FLEX_LOADER)) {
475 A_OpenCms.log(I_CmsLogChannels.C_FLEX_LOADER, "Initialized!");
476 A_OpenCms.log(I_CmsLogChannels.C_FLEX_LOADER, "JSP repository (absolute path): " + m_jspRepository);
477 A_OpenCms.log(I_CmsLogChannels.C_FLEX_LOADER, "JSP repository (web application path): " + m_jspWebAppRepository);
478 A_OpenCms.log(I_CmsLogChannels.C_FLEX_LOADER, "JSP export URL: " + m_jspExportUrl);
479 }
480 // Get the "error pages are commited or not" flag from the runtime properties
481 Boolean errorPagesAreNotCommited = (Boolean)A_OpenCms.getRuntimeProperty(C_LOADER_ERRORPAGECOMMIT);
482 if (errorPagesAreNotCommited != null) m_errorPagesAreNotCommited = errorPagesAreNotCommited.booleanValue();
483 }
484
485 /**
486 * Set's the JSP export URL.<p>
487 *
488 * This is required after <code>init()</code> called if the URL was not set in <code>opencms.
489 * properties</code>.
490 *
491 * @param url the JSP export URL
492 */
493 public static void setJspExportUrl(String value) {
494 m_jspExportUrl = value;
495 }
496
497 /**
498 * Basic top-page processing method for this I_CmsResourceLoader,
499 * this method is called by <code>initlaunch()</code> if a JSP is requested and
500 * the original request was from the launcher manager.
501 *
502 * @param cms The initialized CmsObject which provides user permissions
503 * @param file The requested OpenCms VFS resource
504 * @param req The original servlet request
505 * @param res The original servlet response
506 *
507 * @throws ServletException might be thrown in the process of including the JSP
508 * @throws IOException might be thrown in the process of including the JSP
509 *
510 * @see I_CmsResourceLoader
511 * @see #initlaunch(CmsObject cms, CmsFile file, String startTemplateClass, A_OpenCms openCms)
512 */
513 public void load(CmsObject cms, CmsFile file, HttpServletRequest req, HttpServletResponse res)
514 throws ServletException, IOException {
515
516 long timer1 = 0;
517 if (DEBUG > 0) {
518 timer1 = System.currentTimeMillis();
519 System.err.println("========== JspLoader loading: " + file.getAbsolutePath());
520 System.err.println("JspLoader.load() cms uri is: " + cms.getRequestContext().getUri());
521 }
522
523 boolean streaming = false;
524 boolean bypass = false;
525
526 // check if export mode is active, if so "streaming" must be deactivated
527 boolean exportmode = (cms.getMode() == CmsObject.C_MODUS_EXPORT);
528
529 try {
530 // Read caching property from requested VFS resource
531 String stream = cms.readProperty(file.getAbsolutePath(), I_CmsResourceLoader.C_LOADER_STREAMPROPERTY);
532 if (stream != null) {
533 if ("yes".equalsIgnoreCase(stream) || "true".equalsIgnoreCase(stream)) {
534 // streaming not allowed in export mode
535 streaming = !exportmode;
536 } else if ("bypass".equalsIgnoreCase(stream) || "bypasscache".equalsIgnoreCase(stream)) {
537 // bypass not allowed in export mode
538 bypass = !exportmode;
539 }
540 }
541 } catch (CmsException e) {
542 throw new ServletException("FlexJspLoader: Error while loading stream properties for " + file.getAbsolutePath() + "\n" + e, e);
543 }
544
545 if (DEBUG > 1) System.err.println("========== JspLoader stream=" + streaming + " bypass=" + bypass);
546
547 CmsFlexController controller = (CmsFlexController)req.getAttribute(CmsFlexController.ATTRIBUTE_NAME);
548
549 CmsFlexRequest f_req;
550 CmsFlexResponse f_res;
551
552 if (controller != null) {
553 // re-use currently wrapped request / response
554 f_req = controller.getCurrentRequest();
555 f_res = controller.getCurrentResponse();
556 } else {
557 // create new request / response wrappers
558 controller = new CmsFlexController(cms, file, m_cache, req, res);
559 req.setAttribute(CmsFlexController.ATTRIBUTE_NAME, controller);
560 f_req = new CmsFlexRequest(req, controller);
561 f_res = new CmsFlexResponse(res, controller, streaming, true);
562 controller.pushRequest(f_req);
563 controller.pushResponse(f_res);
564 }
565
566 if (bypass) {
567 // Bypass Flex cache for this page (this solves some compatibility issues in BEA Weblogic)
568 if (DEBUG > 1) System.err.println("JspLoader.load() bypassing cache for file " + file.getAbsolutePath());
569 // Update the JSP first if neccessary
570 String target = updateJsp(cms, file, f_req, controller, new HashSet(11));
571 // Dispatch to external JSP
572 req.getRequestDispatcher(target).forward(f_req, res);
573 if (DEBUG > 1) System.err.println("JspLoader.load() cache was bypassed!");
574 } else {
575 // Flex cache not bypassed
576 try {
577 f_req.getRequestDispatcher(file.getAbsolutePath()).include(f_req, f_res);
578 } catch (java.net.SocketException e) {
579 // Uncritical, might happen if client (browser) does not wait until end of page delivery
580 if (DEBUG > 1) System.err.println("JspLoader.load() ignoring SocketException " + e);
581 }
582 if (! streaming && ! f_res.isSuspended()) {
583 try {
584 if (! res.isCommitted() || m_errorPagesAreNotCommited) {
585 // If a JSP errorpage was triggered the response will be already committed here
586 byte[] result = f_res.getWriterBytes();
587
588 // Encoding project:
589 // The byte array will internally be encoded in the OpenCms
590 // default encoding. In case another encoding is set
591 // in the 'content-encoding' property of the file,
592 // we need to re-encode the output here.
593 result = Encoder.changeEncoding(result, A_OpenCms.getDefaultEncoding(), cms.getRequestContext().getEncoding());
594
595 // Check for export request links
596 if (exportmode) {
597 exportSetLinkHeader(cms, f_res);
598 }
599
600 // Process headers and write output
601 res.setContentLength(result.length);
602 CmsFlexResponse.processHeaders(f_res.getHeaders(), res);
603 res.getOutputStream().write(result);
604 res.getOutputStream().flush();
605 } else if (DEBUG > 1) {
606 System.err.println("JspLoader.load() resource is already commited!");
607 }
608 } catch (IllegalStateException e) {
609 // Uncritical, might happen if JSP error page was used
610 if (DEBUG > 1) System.err.println("JspLoader.load() ignoring IllegalStateException " + e);
611 } catch (java.net.SocketException e) {
612 // Uncritical, might happen if client (browser) does not wait until end of page delivery
613 if (DEBUG > 1) System.err.println("JspLoader.load() ignoring SocketException " + e);
614 }
615 }
616 }
617
618 if (DEBUG > 0) {
619 long timer2 = System.currentTimeMillis() - timer1;
620 System.err.println("========== JspLoader time delivering JSP for " + file.getAbsolutePath() + ": " + timer2 + "ms");
621 }
622 }
623
624 /**
625 * Method to enable JSPs to be used as sub-elements in XMLTemplates.
626 *
627 * @param cms The initialized CmsObject which provides user permissions
628 * @param file The requested OpenCms VFS resource
629 *
630 * @throws CmsException In case the Loader can not process the requested resource
631 *
632 * @see CmsJspTemplate
633 */
634 public byte[] loadTemplate(CmsObject cms, CmsFile file)
635 throws CmsException {
636
637 byte[] result = null;
638
639 long timer1 = 0;
640 if (DEBUG > 0) {
641 timer1 = System.currentTimeMillis();
642 System.err.println("========== JspLoader (Template) loading: " + file.getAbsolutePath());
643 }
644
645 if (cms.getRequestContext().getRequest() instanceof CmsExportRequest) {
646 if (DEBUG > 1) System.err.println("FlexJspLoader.loadTemplate(): Export requested for " + file.getAbsolutePath());
647 // export the JSP
648 result = exportJsp(cms, file);
649 } else {
650 HttpServletRequest req = (HttpServletRequest)cms.getRequestContext().getRequest().getOriginalRequest();
651 HttpServletResponse res = (HttpServletResponse)cms.getRequestContext().getResponse().getOriginalResponse();
652
653 CmsFlexController controller = (CmsFlexController)req.getAttribute(CmsFlexController.ATTRIBUTE_NAME);
654
655 CmsFlexRequest f_req;
656 CmsFlexResponse f_res;
657
658 if (controller != null) {
659 // re-use currently wrapped request / response
660 f_req = controller.getCurrentRequest();
661 f_res = controller.getCurrentResponse();
662 } else {
663 // create new request / response wrappers
664 controller = new CmsFlexController(cms, file, m_cache, req, res);
665 req.setAttribute(CmsFlexController.ATTRIBUTE_NAME, controller);
666 f_req = new CmsFlexRequest(req, controller);
667 f_res = new CmsFlexResponse(res, controller, false, false);
668 controller.pushRequest(f_req);
669 controller.pushResponse(f_res);
670 }
671
672 try {
673 f_req.getRequestDispatcher(file.getAbsolutePath()).include(f_req, f_res);
674 } catch (java.net.SocketException e) {
675 // Uncritical, might happen if client (browser) does not wait until end of page delivery
676 if (DEBUG > 1) System.err.println("JspLoader.loadTemplate() ignoring SocketException " + e);
677 } catch (Exception e) {
678 System.err.println("Error in CmsJspLoader.loadTemplate() while loading: " + e.toString());
679 if (DEBUG > 0) System.err.println(com.opencms.util.Utils.getStackTrace(e));
680 throw new CmsException("Error in CmsJspLoader.loadTemplate() while loading " + file.getAbsolutePath() + "\n" + e, CmsException.C_LAUNCH_ERROR, e);
681 }
682
683 if (! f_res.isSuspended()) {
684 try {
685 if ((res == null) || (! res.isCommitted())) {
686 // If a JSP errorpage was triggered the response will be already committed here
687 result = f_res.getWriterBytes();
688 // Encoding project:
689 // The byte array will internally be encoded in the OpenCms
690 // default encoding. In case another encoding is set
691 // in the 'content-encoding' property of the file,
692 // we need to re-encode the output here
693 result = Encoder.changeEncoding(result, A_OpenCms.getDefaultEncoding(), cms.getRequestContext().getEncoding());
694 }
695 } catch (IllegalStateException e) {
696 // Uncritical, might happen if JSP error page was used
697 if (DEBUG > 1) System.err.println("JspLoader.loadTemplate() ignoring IllegalStateException " + e);
698 } catch (Exception e) {
699 System.err.println("Error in CmsJspLoader.loadTemplate() while writing buffer to final stream: " + e.toString());
700 if (DEBUG > 0) System.err.println(com.opencms.util.Utils.getStackTrace(e));
701 throw new CmsException("Error in CmsJspLoader.loadTemplate() while writing buffer to final stream for " + file.getAbsolutePath() + "\n" + e, CmsException.C_LAUNCH_ERROR, e);
702 }
703 }
704 }
705
706 if (DEBUG > 0) {
707 long timer2 = System.currentTimeMillis() - timer1;
708 System.err.println("========== JspLoader (Template) time delivering JSP for " + file.getAbsolutePath() + ": " + timer2 + "ms");
709 }
710
711 return result;
712 }
713
714 /**
715 * Translates the JSP file name for a OpenCms VFS resourcn
716 * to the name used in the "real" file system.<p>
717 *
718 * The name given must be a absolute URI in the OpenCms VFS,
719 * e.g. CmsFile.getAbsolutePath()
720 *
721 * @param name The file to calculate the JSP name for
722 * @return The JSP name for the file
723 */
724 public static String getJspName(String name) {
725 return name + C_JSP_EXTENSION;
726 }
727
728 /**
729 * Returns the uri for a given JSP in the "real" file system,
730 * i.e. the path in the file
731 * system relative to the web application directory.
732 *
733 * @param name The name of the JSP file
734 * @param online Flag to check if this is request is online or not
735 * @return The full uri to the JSP
736 */
737 public static String getJspUri(String name, boolean online) {
738 return m_jspWebAppRepository + (online?"/online":"/offline") + getJspName(name);
739 }
740
741 /**
742 * Returns the absolute path in the "real" file system for a given JSP.
743 *
744 * @param name The name of the JSP file
745 * @param online Flag to check if this is request is online or not
746 * @return The full path to the JSP
747 */
748 public static String getJspPath(String name, boolean online) {
749 return m_jspRepository + (online?"online":"offline") + name;
750 }
751
752 /**
753 * Returns the absolute path in the "real" file system for the JSP repository
754 * toplevel directory.
755 *
756 * @return The full path to the JSP repository
757 */
758 public static String getJspRepository() {
759 return m_jspRepository;
760 }
761
762 /**
763 * Updates a JSP page in the "real" file system in case the VFS resource has changed.<p>
764 *
765 * Also processes the <code><%@ cms %></code> tags before the JSP is written to the real FS.
766 * Also recursivly updates all files that are referenced by a <code><%@ cms %></code> tag
767 * on this page to make sure the file actually exists in the real FS.
768 * All <code><%@ include %></code> tags are parsed and the name in the tag is translated
769 * from the OpenCms VFS path to the path in the real FS.
770 * The same is done for filenames in <code><%@ page errorPage=... %></code> tags.
771 *
772 * @param cms Used to access the OpenCms VFS
773 * @param file The reqested JSP file resource in the VFS
774 * @param req The current request
775 * @param res The current response
776 * @param updates A Set containing all JSP pages that have been already updated
777 *
778 * @return The file name of the updated JSP in the "real" FS
779 *
780 * @throws ServletException might be thrown in the process of including the JSP
781 * @throws IOException might be thrown in the process of including the JSP
782 */
783 private synchronized String updateJsp(CmsObject cms, CmsResource file, ServletRequest req, CmsFlexController controller, Set updates)
784 throws IOException, ServletException {
785
786 String jspTargetName = getJspName(file.getAbsolutePath());
787
788 // check for inclusion loops
789 if (updates.contains(jspTargetName)) return null;
790 updates.add(jspTargetName);
791
792 String jspPath = getJspPath(jspTargetName, controller.getCurrentRequest().isOnline());
793
794 File d = new File(jspPath).getParentFile();
795 if ((d == null) || (d.exists() && ! (d.isDirectory() && d.canRead()))) {
796 if (I_CmsLogChannels.C_LOGGING && A_OpenCms.isLogging(I_CmsLogChannels.C_OPENCMS_CRITICAL))
797 A_OpenCms.log(I_CmsLogChannels.C_OPENCMS_CRITICAL, "Could not access directory for " + jspPath);
798 throw new ServletException("JspLoader: Could not access directory for " + jspPath);
799 }
800
801 if (! d.exists()) {
802 // create directory structure
803 d.mkdirs();
804 }
805
806 boolean mustUpdate = false;
807
808 File f = new File(jspPath);
809 if (!f.exists()) {
810 // File does not exist in FS
811 mustUpdate = true;
812 } else if (f.lastModified() <= file.getDateLastModified()) {
813 // File in FS is older then file in VFS
814 mustUpdate = true;
815 } else if (controller.getCurrentRequest().isDoRecompile()) {
816 // Recompile is forced with parameter
817 mustUpdate = true;
818 }
819
820 String jspfilename = getJspUri(file.getAbsolutePath(), controller.getCurrentRequest().isOnline());
821
822 if (mustUpdate) {
823 if (DEBUG > 2) System.err.println("JspLoader writing new file: " + jspfilename);
824 byte[] contents = null;
825 String jspEncoding = null;
826 try {
827 contents = cms.readFile(file.getAbsolutePath()).getContents();
828 // Encoding project:
829 // Check the JSP "content-encoding" property
830 jspEncoding = cms.readProperty(file.getAbsolutePath(), I_CmsConstants.C_PROPERTY_CONTENT_ENCODING, false);
831 if (jspEncoding == null) jspEncoding = C_DEFAULT_JSP_ENCODING;
832 jspEncoding = jspEncoding.trim().toUpperCase();
833 } catch (CmsException e) {
834 throw new ServletException("JspLoader: Could not read contents for file '" + file.getAbsolutePath() + "'", e);
835 }
836
837 try {
838 FileOutputStream fs = new FileOutputStream(f);
839 // Encoding project:
840 // We need to use some encoding to convert bytes to String
841 // corectly. Internally a JSP will always be stored in the
842 // system default encoding since they are just a variation of
843 // the "plain" resource type.
844 String page = new String(contents, A_OpenCms.getDefaultEncoding());
845 StringBuffer buf = new StringBuffer(contents.length);
846
847 int p0 = 0, i2 = 0, slen = C_DIRECTIVE_START.length(), elen = C_DIRECTIVE_END.length();
848 // Check if any jsp name references occur in the file
849 int i1 = page.indexOf(C_DIRECTIVE_START);
850 while (i1 >= 0) {
851 // Parse the file and replace jsp name references
852 i2 = page.indexOf(C_DIRECTIVE_END, i1 + slen);
853 if (i2 > i1) {
854 String directive = page.substring(i1 + slen, i2);
855 if (DEBUG > 2) System.err.println("JspLoader: Detected " + C_DIRECTIVE_START + directive + C_DIRECTIVE_END);
856
857 int t1=0, t2=0, t3=0, t4=0, t5=0, t6=slen, t7=0;
858 while (directive.charAt(t1) == ' ') t1++;
859 String filename = null;
860 if (directive.startsWith("include", t1)) {
861 if (DEBUG > 2) System.err.println("JspLoader: Detected 'include' directive!");
862 t2 = directive.indexOf("file", t1 + 7);
863 t5 = 6;
864 } else if (directive.startsWith("page", t1)) {
865 if (DEBUG > 2) System.err.println("JspLoader: Detected 'page' directive!");
866 t2 = directive.indexOf("errorPage", t1 + 4);
867 t5 = 11;
868 } else if (directive.startsWith("cms", t1)) {
869 if (DEBUG > 2) System.err.println("JspLoader: Detected 'cms' directive!");
870 t2 = directive.indexOf("file", t1 + 3);
871 t5 = 4; t6 = 0; t7 = elen;
872 }
873
874 if (t2 > 0) {
875 String sub = directive.substring(t2 + t5);
876 char c1 = sub.charAt(t3);
877 while ((c1 == ' ') || (c1 == '=') || (c1 == '"')) c1 = sub.charAt(++t3);
878 t4 = t3;
879 while (c1 != '"') c1 = sub.charAt(++t4);
880 if (t4 > t3) filename=sub.substring(t3,t4);
881 if (DEBUG > 2) System.err.println("JspLoader: File given in directive is: " + filename);
882 }
883
884 if (filename != null) {
885 // a file was found, changes have to be made
886 String pre = ((t7 == 0)?directive.substring(0,t2+t3+t5):""); ;
887 String suf = ((t7 == 0)?directive.substring(t2+t3+t5+filename.length()):"");
888 // Now try to update the referenced file
889 String absolute = controller.getCurrentRequest().toAbsolute(filename);
890 if (DEBUG > 2) System.err.println("JspLoader: Absolute location=" + absolute);
891 String jspname = null;
892 try {
893 // Make sure the jsp referenced file is generated
894 CmsResource jsp = cms.readFileHeader(absolute);
895 updateJsp(cms, jsp, req, controller, updates);
896 jspname = getJspUri(jsp.getAbsolutePath(), controller.getCurrentRequest().isOnline());
897 } catch (Exception e) {
898 jspname = null;
899 if (DEBUG > 2) System.err.println("JspLoader: Error while creating jsp file " + absolute + "\n" + e);
900 }
901 if (jspname != null) {
902 // Only change something in case no error had occured
903 if (DEBUG > 2) System.err.println("JspLoader: Name of jsp file is " + jspname);
904 directive = pre + jspname + suf;
905 if (DEBUG > 2) System.err.println("JspLoader: Changed directive to " + C_DIRECTIVE_START + directive + C_DIRECTIVE_END);
906 }
907 }
908
909 buf.append(page.substring(p0, i1 + t6));
910 buf.append(directive);
911 p0 = i2 + t7;
912 i1 = page.indexOf(C_DIRECTIVE_START, p0);
913 }
914 }
915 if (i2 > 0) {
916 buf.append(page.substring(p0, page.length()));
917 // Encoding project:
918 // Now we are ready to store String data in file system.
919 // To convert String to bytes we also need to provide
920 // some encoding. The default (by the JSP standard) encoding
921 // for JSP is ISO-8859-1.
922 contents = buf.toString().getBytes(jspEncoding);
923 } else {
924 // Encoding project:
925 // Contents of original file where not modified,
926 // just translate to the required JSP encoding (if necessary)
927 contents = Encoder.changeEncoding(contents, A_OpenCms.getDefaultEncoding(), jspEncoding);
928 }
929 fs.write(contents);
930 fs.close();
931
932 if (I_CmsLogChannels.C_LOGGING && A_OpenCms.isLogging(I_CmsLogChannels.C_OPENCMS_INFO))
933 A_OpenCms.log(I_CmsLogChannels.C_OPENCMS_INFO, "Updated JSP file \"" + jspfilename + "\" for resource \"" + file.getAbsolutePath() + "\"") ;
934 } catch (FileNotFoundException e) {
935 throw new ServletException("JspLauncher: Could not write to file '" + f.getName() + "'\n" + e, e);
936 }
937 }
938 return jspfilename;
939 }
940
941 /**
942 * Does the job of including the JSP,
943 * this method should usually be called from a <code>CmsFlexRequestDispatcher</code> only.<p>
944 *
945 * This method is called directly if the element is
946 * called as a sub-element from another I_CmsResourceLoader.<p>
947 *
948 * One of the tricky issues is the correct cascading of the Exceptions,
949 * so that you are able to identify the true origin of the problem.
950 * This ia achived by imprinting a String C_EXCEPTION_PREFIX to the
951 * exception message.
952 *
953 * @param cms used to access the OpenCms VFS
954 * @param file the reqested JSP file resource in the VFS
955 * @param req the current request
956 * @param res the current response
957 *
958 * @throws ServletException might be thrown in the process of including the JSP
959 * @throws IOException might be thrown in the process of including the JSP
960 *
961 * @see com.opencms.flex.cache.CmsFlexRequestDispatcher
962 */
963 public void service(CmsObject cms, CmsResource file, ServletRequest req, ServletResponse res)
964 throws ServletException, IOException {
965 try {
966 CmsFlexController controller = (CmsFlexController)req.getAttribute(CmsFlexController.ATTRIBUTE_NAME);
967 // Get JSP target name on "real" file system
968 String target = updateJsp(cms, file, req, controller, new HashSet(11));
969 // Important: Indicate that all output must be buffered
970 controller.getCurrentResponse().setOnlyBuffering(true);
971 // Dispatch to external file
972 controller.getCurrentRequest().getRequestDispatcherToExternal(file.getAbsolutePath(), target).include(req, res);
973 } catch (ServletException e) {
974 // Check if this Exception has already been marked
975 String msg = e.getMessage();
976 if (DEBUG > 1) System.err.println("JspLauncher: Caught ServletException " + e );
977 if ((msg != null) && msg.startsWith(C_LOADER_EXCEPTION_PREFIX)) throw e;
978 // Not marked, imprint current JSP file and stack trace
979 throw new ServletException(C_LOADER_EXCEPTION_PREFIX + " '" + file.getAbsolutePath() + "'\n\nRoot cause:\n" + Utils.getStackTrace(e) + "\n--------------- End of root cause.\n", e);
980 } catch (Exception e) {
981 // Imprint current JSP file and stack trace
982 throw new ServletException(C_LOADER_EXCEPTION_PREFIX + " '" + file.getAbsolutePath() + "'\n\nRoot cause:\n" + Utils.getStackTrace(e) + "\n--------------- End of root cause.\n", e);
983 }
984 }
985 }