KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > opencms > flex > CmsFlexResponse


1 /*
2  * File : $Source: /usr/local/cvs/opencms/src/org/opencms/flex/CmsFlexResponse.java,v $
3  * Date : $Date: 2006/03/27 14:52:35 $
4  * Version: $Revision: 1.42 $
5  *
6  * This library is part of OpenCms -
7  * the Open Source Content Mananagement System
8  *
9  * Copyright (c) 2005 Alkacon Software GmbH (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 GmbH, 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 org.opencms.flex;
33
34 import org.opencms.main.CmsIllegalArgumentException;
35 import org.opencms.main.CmsLog;
36 import org.opencms.util.CmsDateUtil;
37 import org.opencms.util.CmsRequestUtil;
38
39 import java.io.BufferedWriter JavaDoc;
40 import java.io.ByteArrayOutputStream JavaDoc;
41 import java.io.IOException JavaDoc;
42 import java.io.OutputStreamWriter JavaDoc;
43 import java.io.PrintWriter JavaDoc;
44 import java.util.ArrayList JavaDoc;
45 import java.util.HashMap JavaDoc;
46 import java.util.Iterator JavaDoc;
47 import java.util.List JavaDoc;
48 import java.util.Map JavaDoc;
49
50 import javax.servlet.ServletOutputStream JavaDoc;
51 import javax.servlet.http.Cookie JavaDoc;
52 import javax.servlet.http.HttpServletResponse JavaDoc;
53 import javax.servlet.http.HttpServletResponseWrapper JavaDoc;
54
55 import org.apache.commons.logging.Log;
56
57 /**
58  * Wrapper class for a HttpServletResponse, required in order to process JSPs from the OpenCms VFS.<p>
59  *
60  * This class wrapps the standard HttpServletResponse so that it's output can be delivered to
61  * the CmsFlexCache.<p>
62  *
63  * @author Alexander Kandzior
64  *
65  * @version $Revision: 1.42 $
66  *
67  * @since 6.0.0
68  */

69 public class CmsFlexResponse extends HttpServletResponseWrapper JavaDoc {
70
71     /**
72      * Wrapped implementation of the ServletOutputStream.<p>
73      *
74      * This implementation writes to an internal buffer and optionally to another
75      * output stream at the same time.
76      * It should be fully transparent to the standard ServletOutputStream.<p>
77      */

78     private class CmsServletOutputStream extends ServletOutputStream JavaDoc {
79
80         /** The optional output stream to write to. */
81         private ServletOutputStream JavaDoc m_servletStream;
82
83         /** The internal stream buffer. */
84         private ByteArrayOutputStream JavaDoc m_stream;
85
86         /**
87          * Constructor that must be used if the stream should write
88          * only to a buffer.<p>
89          */

90         public CmsServletOutputStream() {
91
92             m_servletStream = null;
93             clear();
94         }
95
96         /**
97          * Constructor that must be used if the stream should write
98          * to a buffer and to another stream at the same time.<p>
99          *
100          * @param servletStream The stream to write to
101          */

102         public CmsServletOutputStream(ServletOutputStream JavaDoc servletStream) {
103
104             m_servletStream = servletStream;
105             clear();
106         }
107
108         /**
109          * Clears the buffer by initializing the buffer with a new stream.<p>
110          */

111         public void clear() {
112
113             m_stream = new java.io.ByteArrayOutputStream JavaDoc(1024);
114         }
115
116         /**
117          * @see java.io.OutputStream#close()
118          */

119         public void close() throws IOException JavaDoc {
120
121             if (m_stream != null) {
122                 m_stream.close();
123             }
124             if (m_servletStream != null) {
125                 m_servletStream.close();
126             }
127             super.close();
128         }
129
130         /**
131          * @see java.io.OutputStream#flush()
132          */

133         public void flush() throws IOException JavaDoc {
134
135             if (LOG.isDebugEnabled()) {
136                 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXRESPONSE_FLUSHED_1, m_servletStream));
137             }
138             if (m_servletStream != null) {
139                 m_servletStream.flush();
140             }
141         }
142
143         /**
144          * Provides access to the bytes cached in the buffer.<p>
145          *
146          * @return the cached bytes from the buffer
147          */

148         public byte[] getBytes() {
149
150             return m_stream.toByteArray();
151         }
152
153         /**
154          * @see java.io.OutputStream#write(byte[], int, int)
155          */

156         public void write(byte[] b, int off, int len) throws IOException JavaDoc {
157
158             m_stream.write(b, off, len);
159             if (m_servletStream != null) {
160                 m_servletStream.write(b, off, len);
161             }
162         }
163
164         /**
165          * @see java.io.OutputStream#write(int)
166          */

167         public void write(int b) throws IOException JavaDoc {
168
169             m_stream.write(b);
170             if (m_servletStream != null) {
171                 m_servletStream.write(b);
172             }
173         }
174
175         /**
176          * Writes an array of bytes only to the included servlet stream,
177          * not to the buffer.<p>
178          *
179          * @param b The bytes to write to the stream
180          * @throws IOException In case the write() operation on the included servlet stream raises one
181          */

182         public void writeToServletStream(byte[] b) throws IOException JavaDoc {
183
184             if (m_servletStream != null) {
185                 m_servletStream.write(b);
186             }
187         }
188     }
189
190     /** The cache delimiter char. */
191     public static final char FLEX_CACHE_DELIMITER = (char)0;
192
193     /** Static string to indicate a header is "set" in the header maps. */
194     public static final String JavaDoc SET_HEADER = "[setHeader]";
195
196     /** The log object for this class. */
197     protected static final Log LOG = CmsLog.getLog(CmsFlexResponse.class);
198
199     /** Map to save response headers belonging to a single include call in .*/
200     private Map JavaDoc m_bufferHeaders;
201
202     /** String to hold a buffered redirect target. */
203     private String JavaDoc m_bufferRedirect;
204
205     /** Byte array used for "cached leafs" optimization. */
206     private byte[] m_cacheBytes;
207
208     /** The cached entry that is constructed from this response. */
209     private CmsFlexCacheEntry m_cachedEntry;
210
211     /** Indicates if caching is required, will always be true if m_writeOnlyToBuffer is true. */
212     private boolean m_cachingRequired;
213
214     /** The CmsFlexController for this response. */
215     private CmsFlexController m_controller;
216
217     /** The encoding to use for the response. */
218     private String JavaDoc m_encoding;
219
220     /** Map to save all response headers (including sub-elements) in. */
221     private Map JavaDoc m_headers;
222
223     /** A list of include calls that origin from this page, i.e. these are sub elements of this element. */
224     private List JavaDoc m_includeList;
225
226     /** A list of parameters that belong to the include calls. */
227     private List JavaDoc m_includeListParameters;
228
229     /** Indicates if this element is currently in include mode, i.e. processing a sub-element. */
230     private boolean m_includeMode;
231
232     /** A list of results from the inclusions, needed because of JSP buffering. */
233     private List JavaDoc m_includeResults;
234
235     /** Flag to indicate if this is the top level element or an included sub - element. */
236     private boolean m_isTopElement;
237
238     /** The CmsFlexCacheKey for this response. */
239     private CmsFlexCacheKey m_key;
240
241     /** A special wrapper class for a ServletOutputStream. */
242     private CmsFlexResponse.CmsServletOutputStream m_out;
243
244     /** Indicates that parent stream is writing only in the buffer. */
245     private boolean m_parentWritesOnlyToBuffer;
246
247     /** The wrapped ServletResponse. */
248     private HttpServletResponse JavaDoc m_res;
249
250     /** Indicates if this response is suspended (probably because of a redirect). */
251     private boolean m_suspended;
252
253     /** State bit indicating whether content type has been set, type may only be set once according to spec. */
254     private boolean m_typeSet;
255
256     /** Indicates that the OutputStream m_out should write ONLY in the buffer. */
257     private boolean m_writeOnlyToBuffer;
258
259     /** A printwriter that writes in the m_out stream. */
260     private java.io.PrintWriter JavaDoc m_writer;
261
262     /**
263      * Constructor for the CmsFlexResponse,
264      * this variation one is usually used to wrap responses for further include calls in OpenCms.<p>
265      *
266      * @param res the CmsFlexResponse to wrap
267      * @param controller the controller to use
268      */

269     public CmsFlexResponse(HttpServletResponse JavaDoc res, CmsFlexController controller) {
270
271         super(res);
272         m_res = res;
273         m_controller = controller;
274         m_encoding = controller.getCurrentResponse().getEncoding();
275         m_isTopElement = controller.getCurrentResponse().isTopElement();
276         m_parentWritesOnlyToBuffer = controller.getCurrentResponse().hasIncludeList() && !controller.isForwardMode();
277         setOnlyBuffering(m_parentWritesOnlyToBuffer);
278         m_headers = new HashMap JavaDoc(16);
279         m_bufferHeaders = new HashMap JavaDoc(8);
280     }
281
282     /**
283      * Constructor for the CmsFlexResponse,
284      * this variation is usually used for the "top" response.<p>
285      *
286      * @param res the HttpServletResponse to wrap
287      * @param controller the controller to use
288      * @param streaming indicates if streaming should be enabled or not
289      * @param isTopElement indicates if this is the top element of an include cascade
290      */

291     public CmsFlexResponse(
292         HttpServletResponse JavaDoc res,
293         CmsFlexController controller,
294         boolean streaming,
295         boolean isTopElement) {
296
297         super(res);
298         m_res = res;
299         m_controller = controller;
300         m_encoding = controller.getCmsObject().getRequestContext().getEncoding();
301         m_isTopElement = isTopElement;
302         m_parentWritesOnlyToBuffer = !streaming && !controller.isForwardMode();
303         setOnlyBuffering(m_parentWritesOnlyToBuffer);
304         m_headers = new HashMap JavaDoc(16);
305         m_bufferHeaders = new HashMap JavaDoc(8);
306     }
307
308     /**
309      * Process the headers stored in the provided map and add them to the response.<p>
310      *
311      * @param headers the headers to add
312      * @param res the resonse to add the headers to
313      */

314     public static void processHeaders(Map JavaDoc headers, HttpServletResponse JavaDoc res) {
315
316         if (headers != null) {
317             Iterator JavaDoc i = headers.keySet().iterator();
318             while (i.hasNext()) {
319                 String JavaDoc key = (String JavaDoc)i.next();
320                 List JavaDoc l = (List JavaDoc)headers.get(key);
321                 for (int j = 0; j < l.size(); j++) {
322                     if ((j == 0) && (((String JavaDoc)l.get(0)).startsWith(SET_HEADER))) {
323                         String JavaDoc s = (String JavaDoc)l.get(0);
324                         res.setHeader(key, s.substring(SET_HEADER.length()));
325                     } else {
326                         res.addHeader(key, (String JavaDoc)l.get(j));
327                     }
328                 }
329             }
330         }
331     }
332
333     /**
334      * Method overloaded from the standard HttpServletRequest API.<p>
335      *
336      * Cookies must be set directly as a header, otherwise they might not be set
337      * in the super class.<p>
338      *
339      * @see javax.servlet.http.HttpServletResponseWrapper#addCookie(javax.servlet.http.Cookie)
340      */

341     public void addCookie(Cookie JavaDoc cookie) {
342
343         if (cookie == null) {
344             throw new CmsIllegalArgumentException(Messages.get().container(Messages.ERR_ADD_COOKIE_0));
345         }
346
347         StringBuffer JavaDoc header = new StringBuffer JavaDoc(128);
348
349         // name and value
350
header.append(cookie.getName());
351         header.append('=');
352         header.append(cookie.getValue());
353
354         // add version 1 / RFC 2109 specific information
355
if (cookie.getVersion() == 1) {
356             header.append("; Version=1");
357
358             // comment
359
if (cookie.getComment() != null) {
360                 header.append("; Comment=");
361                 header.append(cookie.getComment());
362             }
363         }
364
365         // domain
366
if (cookie.getDomain() != null) {
367             header.append("; Domain=");
368             header.append(cookie.getDomain());
369         }
370
371         // max-age / expires
372
if (cookie.getMaxAge() >= 0) {
373             if (cookie.getVersion() == 0) {
374                 // old Netscape format
375
header.append("; Expires=");
376                 long time;
377                 if (cookie.getMaxAge() == 0) {
378                     time = 10000L;
379                 } else {
380                     time = System.currentTimeMillis() + (cookie.getMaxAge() * 1000L);
381                 }
382                 header.append(CmsDateUtil.getOldCookieDate(time));
383             } else {
384                 // new RFC 2109 format
385
header.append("; Max-Age=");
386                 header.append(cookie.getMaxAge());
387             }
388         }
389
390         // path
391
if (cookie.getPath() != null) {
392             header.append("; Path=");
393             header.append(cookie.getPath());
394         }
395
396         // secure
397
if (cookie.getSecure()) {
398             header.append("; Secure");
399         }
400
401         addHeader("Set-Cookie", header.toString());
402     }
403
404     /**
405      * Method overlodad from the standard HttpServletRequest API.<p>
406      *
407      * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long)
408      */

409     public void addDateHeader(String JavaDoc name, long date) {
410
411         addHeader(name, CmsDateUtil.getHeaderDate(date));
412     }
413
414     /**
415      * Method overlodad from the standard HttpServletRequest API.<p>
416      *
417      * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String)
418      */

419     public void addHeader(String JavaDoc name, String JavaDoc value) {
420
421         if (isSuspended()) {
422             return;
423         }
424
425         if (CmsRequestUtil.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
426             setContentType(value);
427             return;
428         }
429
430         if (m_cachingRequired && !m_includeMode) {
431             addHeaderList(m_bufferHeaders, name, value);
432             if (LOG.isDebugEnabled()) {
433                 LOG.debug(Messages.get().getBundle().key(
434                     Messages.LOG_FLEXRESPONSE_ADDING_HEADER_TO_ELEMENT_BUFFER_2,
435                     name,
436                     value));
437             }
438         }
439
440         if (m_writeOnlyToBuffer) {
441             addHeaderList(m_headers, name, value);
442             if (LOG.isDebugEnabled()) {
443                 LOG.debug(Messages.get().getBundle().key(
444                     Messages.LOG_FLEXRESPONSE_ADDING_HEADER_TO_HEADERS_2,
445                     name,
446                     value));
447             }
448         } else {
449             if (LOG.isDebugEnabled()) {
450                 LOG.debug(Messages.get().getBundle().key(
451                     Messages.LOG_FLEXRESPONSE_ADDING_HEADER_TO_PARENT_RESPONSE_2,
452                     name,
453                     value));
454             }
455             m_res.addHeader(name, value);
456         }
457     }
458
459     /**
460      * Method overlodad from the standard HttpServletRequest API.<p>
461      *
462      * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int)
463      */

464     public void addIntHeader(String JavaDoc name, int value) {
465
466         addHeader(name, String.valueOf(value));
467     }
468
469     /**
470      * Adds an inclusion target to the list of include results.<p>
471      *
472      * Should be used only in inclusion-scenarios
473      * like the JSP cms:include tag processing.<p>
474      *
475      * @param target the include target name to add
476      * @param parameterMap the map of parameters given with the include command
477      */

478     public void addToIncludeList(String JavaDoc target, Map JavaDoc parameterMap) {
479
480         if (m_includeList == null) {
481             m_includeList = new ArrayList JavaDoc(10);
482             m_includeListParameters = new ArrayList JavaDoc(10);
483         }
484         m_includeListParameters.add(parameterMap);
485         m_includeList.add(target);
486     }
487
488     /**
489      * Returns the value of the encoding used for this response.<p>
490      *
491      * @return the value of the encoding used for this response
492      */

493     public String JavaDoc getEncoding() {
494
495         return m_encoding;
496     }
497
498     /**
499      * Provides access to the header cache of the top wrapper.<p>
500      *
501      * @return the Map of cached headers
502      */

503     public Map JavaDoc getHeaders() {
504
505         return m_headers;
506     }
507
508     /**
509      * Method overlodad from the standard HttpServletRequest API.<p>
510      *
511      * @see javax.servlet.ServletResponse#getOutputStream()
512      */

513     public ServletOutputStream JavaDoc getOutputStream() throws IOException JavaDoc {
514
515         if (m_out == null) {
516             initStream();
517         }
518         return m_out;
519     }
520
521     /**
522      * Method overlodad from the standard HttpServletRequest API.<p>
523      *
524      * @see javax.servlet.ServletResponse#getWriter()
525      */

526     public PrintWriter JavaDoc getWriter() throws IOException JavaDoc {
527
528         if (m_writer == null) {
529             initStream();
530         }
531         return m_writer;
532     }
533
534     /**
535      * Returns the bytes that have been written on the current writers output stream.<p>
536      *
537      * @return the bytes that have been written on the current writers output stream
538      */

539     public byte[] getWriterBytes() {
540
541         if (isSuspended()) {
542             // No output whatsoever if the response is suspended
543
return new byte[0];
544         }
545         if (m_cacheBytes != null) {
546             // Optimization for cached "leaf" nodes, here I re-use the array from the cache
547
return m_cacheBytes;
548         }
549         if (m_out == null) {
550             // No output was written so far, just return an empty array
551
return new byte[0];
552         }
553         if (m_writer != null) {
554             // Flush the writer in case something was written on it
555
m_writer.flush();
556         }
557         return m_out.getBytes();
558     }
559
560     /**
561      * This flag indicates if the response is suspended or not.<p>
562      *
563      * A suspended response mut not write further output to any stream or
564      * process a cache entry for itself.<p>
565      *
566      * Currently, a response is only suspended if it is redirected.<p>
567      *
568      * @return true if the response is suspended, false otherwise
569      */

570     public boolean isSuspended() {
571
572         return m_suspended;
573     }
574
575     /**
576      * Returns <code>true</code> if this response has been constructed for the
577      * top level element of this request, <code>false</code> if it was
578      * constructed for an included sub-element.<p>
579      *
580      * @return <code>true</code> if this response has been constructed for the
581      * top level element of this request, <code>false</code> if it was
582      * constructed for an included sub-element.
583      */

584     public boolean isTopElement() {
585
586         return m_isTopElement;
587     }
588
589     /**
590      * Method overlodad from the standard HttpServletRequest API.<p>
591      *
592      * @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String)
593      */

594     public void sendRedirect(String JavaDoc location) throws IOException JavaDoc {
595
596         // Ignore any redirects after the first one
597
if (isSuspended() && (!location.equals(m_bufferRedirect))) {
598             return;
599         }
600         if (LOG.isDebugEnabled()) {
601             LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXRESPONSE_SENDREDIRECT_1, location));
602         }
603         if (m_cachingRequired && !m_includeMode) {
604             m_bufferRedirect = location;
605         }
606
607         if (!m_cachingRequired) {
608             // If caching is required a cached entry will be constructed first and redirect will
609
// be called after this is completed and stored in the cache
610
if (LOG.isDebugEnabled()) {
611                 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXRESPONSE_TOPRESPONSE_SENDREDIRECT_1, location));
612             }
613             if (LOG.isWarnEnabled()) {
614                 if (m_controller.getResponseStackSize() > 2) {
615                     // sendRedirect in a stacked response scenario, this may cause issues in some appservers
616
LOG.warn(Messages.get().getBundle().key(
617                         Messages.LOG_FLEXRESPONSE_REDIRECTWARNING_3,
618                         m_controller.getCmsResource().getRootPath(),
619                         m_controller.getCurrentRequest().getElementUri(),
620                         location));
621                 }
622             }
623             // use top response for redirect
624
HttpServletResponse JavaDoc topRes = m_controller.getTopResponse();
625             // add all headers found to make sure cookies can be set before redirect
626
processHeaders(getHeaders(), topRes);
627             topRes.sendRedirect(location);
628         }
629
630         m_controller.suspendFlexResponse();
631     }
632
633     /**
634      * Method overlodad from the standard HttpServletRequest API.<p>
635      *
636      * @see javax.servlet.ServletResponse#setContentType(java.lang.String)
637      */

638     public void setContentType(String JavaDoc type) {
639
640         if (LOG.isDebugEnabled()) {
641             LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXRESPONSE_SETTING_CONTENTTYPE_1, type));
642         }
643         // only if this is the "Top-Level" element, do set the content type
644
// otherwise an included JSP could reset the type with some unwanted defaults
645
if (!m_typeSet && m_isTopElement) {
646             // type must be set only once, otherwise some Servlet containers (not Tomcat) generate errors
647
m_typeSet = true;
648             super.setContentType(type);
649             return;
650         }
651     }
652
653     /**
654      * Method overlodad from the standard HttpServletRequest API.<p>
655      *
656      * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long)
657      */

658     public void setDateHeader(String JavaDoc name, long date) {
659
660         setHeader(name, CmsDateUtil.getHeaderDate(date));
661     }
662
663     /**
664      * Method overlodad from the standard HttpServletRequest API.<p>
665      *
666      * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String)
667      */

668     public void setHeader(String JavaDoc name, String JavaDoc value) {
669
670         if (isSuspended()) {
671             return;
672         }
673
674         if (CmsRequestUtil.HEADER_CONTENT_TYPE.equalsIgnoreCase(name)) {
675             setContentType(value);
676             return;
677         }
678
679         if (m_cachingRequired && !m_includeMode) {
680             setHeaderList(m_bufferHeaders, name, value);
681             if (LOG.isDebugEnabled()) {
682                 LOG.debug(Messages.get().getBundle().key(
683                     Messages.LOG_FLEXRESPONSE_SETTING_HEADER_IN_ELEMENT_BUFFER_2,
684                     name,
685                     value));
686             }
687         }
688
689         if (m_writeOnlyToBuffer) {
690             setHeaderList(m_headers, name, value);
691             if (LOG.isDebugEnabled()) {
692                 LOG.debug(Messages.get().getBundle().key(
693                     Messages.LOG_FLEXRESPONSE_SETTING_HEADER_IN_HEADERS_2,
694                     name,
695                     value));
696             }
697         } else {
698             if (LOG.isDebugEnabled()) {
699                 LOG.debug(Messages.get().getBundle().key(
700                     Messages.LOG_FLEXRESPONSE_SETTING_HEADER_IN_PARENT_RESPONSE_2,
701                     name,
702                     value));
703             }
704             m_res.setHeader(name, value);
705         }
706     }
707
708     /**
709      * Method overlodad from the standard HttpServletRequest API.<p>
710      *
711      * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int)
712      */

713     public void setIntHeader(String JavaDoc name, int value) {
714
715         setHeader(name, "" + value);
716     }
717
718     /**
719      * Sets buffering status of the response.<p>
720      *
721      * This must be done before the first output is written.
722      * Buffering is needed to process elements that can not be written
723      * directly to the output stream because their sub - elements have to
724      * be processed seperatly. Which is so far true only for JSP pages.<p>
725      *
726      * If buffering is on, nothing is written to the output stream
727      * even if streaming for this resonse is enabled.<p>
728      *
729      * @param value the value to set
730      */

731     public void setOnlyBuffering(boolean value) {
732
733         m_writeOnlyToBuffer = value && !m_controller.isForwardMode();
734
735         if (m_writeOnlyToBuffer) {
736             setCmsCachingRequired(true);
737         }
738     }
739
740     /**
741      * Adds some bytes to the list of include results.<p>
742      *
743      * Should be used only in inclusion-scenarios
744      * like the JSP cms:include tag processing.<p>
745      *
746      * @param result the byte array to add
747      */

748     void addToIncludeResults(byte[] result) {
749
750         if (m_includeResults == null) {
751             m_includeResults = new ArrayList JavaDoc(10);
752         }
753         m_includeResults.add(result);
754     }
755
756     /**
757      * Returns the cache key for to this response.<p>
758      *
759      * @return the cache key for to this response
760      */

761     CmsFlexCacheKey getCmsCacheKey() {
762
763         return m_key;
764     }
765
766     /**
767      * Is used to check if the response has an include list,
768      * which indicates a) it is probalbly processing a JSP element
769      * and b) it can never be streamed and alwys must be buffered.<p>
770      *
771      * @return true if this response has an include list, false otherwise
772      */

773     boolean hasIncludeList() {
774
775         return m_includeList != null;
776     }
777
778     /**
779      * Generates a CmsFlexCacheEntry from the current response using the
780      * stored include results.<p>
781      *
782      * In case the results were written only to the buffer until now,
783      * they are now re-written on the output stream, with all included
784      * elements.<p>
785      *
786      * @throws IOException tn case something goes wrong while writing to the output stream
787      * @return the generated cache entry
788      */

789     CmsFlexCacheEntry processCacheEntry() throws IOException JavaDoc {
790
791         if (isSuspended() && (m_bufferRedirect == null)) {
792             // an included element redirected this response, no cache entry must be produced
793
return null;
794         }
795         if (m_cachingRequired) {
796             // cache entry must only be calculated if it's actually needed (always true if we write only to buffer)
797
m_cachedEntry = new CmsFlexCacheEntry();
798             if (m_bufferRedirect != null) {
799                 // only set et cached redirect target
800
m_cachedEntry.setRedirect(m_bufferRedirect);
801             } else {
802                 // add cached headers
803
m_cachedEntry.addHeaders(m_bufferHeaders);
804                 // add cached output
805
if (m_includeList != null) {
806                     // probably JSP: we must analyze out stream for includes calls
807
// also, m_writeOnlyToBuffer must be "true" or m_includeList can not be != null
808
processIncludeList();
809                 } else {
810                     // output is delivered directly, no include call parsing required
811
m_cachedEntry.add(getWriterBytes());
812                 }
813             }
814             // update the "last modified" date for the cache entry
815
m_cachedEntry.complete();
816         }
817         // in case the output was only bufferd we have to re-write it to the "right" stream
818
if (m_writeOnlyToBuffer) {
819
820             // since we are processing a cache entry caching is not required
821
m_cachingRequired = false;
822
823             if (m_bufferRedirect != null) {
824                 // send buffered redirect, will trigger redirect of top response
825
sendRedirect(m_bufferRedirect);
826             } else {
827                 // process the output
828
if (m_parentWritesOnlyToBuffer) {
829                     // write results back to own stream, headers are already in buffer
830
if (m_out != null) {
831                         try {
832                             m_out.clear();
833                         } catch (Exception JavaDoc e) {
834                             if (LOG.isDebugEnabled()) {
835                                 LOG.debug(Messages.get().getBundle().key(
836                                     Messages.LOG_FLEXRESPONSE_ERROR_FLUSHING_OUTPUT_STREAM_1,
837                                     e));
838                             }
839                         }
840                     } else {
841                         if (LOG.isDebugEnabled()) {
842                             LOG.debug(Messages.get().getBundle().key(
843                                 Messages.LOG_FLEXRESPONSE_ERROR_OUTPUT_STREAM_NULL_0));
844                         }
845                     }
846                     writeCachedResultToStream(this);
847                 } else {
848                     // we can use the parent stream
849
processHeaders(m_headers, m_res);
850                     writeCachedResultToStream(m_res);
851                 }
852             }
853         }
854         return m_cachedEntry;
855     }
856
857     /**
858      * Sets the cache key for this response from
859      * a pre-calculated cache key.<p>
860      *
861      * @param value the cache key to set
862      */

863     void setCmsCacheKey(CmsFlexCacheKey value) {
864
865         m_key = value;
866     }
867
868     /**
869      * Sets the cache key for this response, which is calculated
870      * from the provided parameters.<p>
871      *
872      * @param resourcename the target resouce for which to create the cache key
873      * @param cacheDirectives the cache directives of the resource (value of the property "cache")
874      * @param online indicates if this resource is online or offline
875      *
876      * @return the generated cache key
877      * @throws CmsFlexCacheException in case the value String had a parse error
878      */

879     CmsFlexCacheKey setCmsCacheKey(String JavaDoc resourcename, String JavaDoc cacheDirectives, boolean online)
880     throws CmsFlexCacheException {
881
882         m_key = new CmsFlexCacheKey(resourcename, cacheDirectives, online);
883         if (m_key.hadParseError()) {
884             // We throw the exception here to make sure this response has a valid key (cache=never)
885
throw new CmsFlexCacheException(Messages.get().container(
886                 Messages.LOG_FLEXRESPONSE_PARSE_ERROR_IN_CACHE_KEY_2,
887                 cacheDirectives,
888                 resourcename));
889         }
890         return m_key;
891     }
892
893     /**
894      * Set caching status for this reponse.<p>
895      *
896      * Will always be set to <code>"true"</code> if setOnlyBuffering() is set to <code>"true"</code>.
897      * Currently this is an optimization for non - JSP elements that
898      * are known not to be cachable.<p>
899      *
900      * @param value the value to set
901      */

902     void setCmsCachingRequired(boolean value) {
903
904         m_cachingRequired = (value || m_writeOnlyToBuffer) && !m_controller.isForwardMode();
905     }
906
907     /**
908      * This flag indicates to the response if it is in "include mode" or not.<p>
909      *
910      * This is important in case a cache entry is constructed,
911      * since the cache entry must not consist of output or headers of the
912      * included elements.<p>
913      *
914      * @param value the value to set
915      */

916     void setCmsIncludeMode(boolean value) {
917
918         m_includeMode = value;
919     }
920
921     /**
922      * Sets the suspended status of the response, and also sets
923      * the suspend status of all responses wrapping this response.<p>
924      *
925      * A suspended response mut not write further output to any stream or
926      * process a cache entry for itself.<p>
927      *
928      * @param value the value to set
929      */

930     void setSuspended(boolean value) {
931
932         m_suspended = value;
933     }
934
935     /**
936      * Writes some bytes to the current output stream,
937      * this method should be called from CmsFlexCacheEntry.service() only.<p>
938      *
939      * @param bytes an array of bytes
940      * @param useArray indicates that the byte array should be used directly
941      * @throws IOException in case something goes wrong while writing to the stream
942      */

943     void writeToOutputStream(byte[] bytes, boolean useArray) throws IOException JavaDoc {
944
945         if (isSuspended()) {
946             return;
947         }
948         if (m_writeOnlyToBuffer) {
949             if (useArray) {
950                 // This cached entry has no sub-elements (it a "leaf") and so we can just use it's bytes
951
m_cacheBytes = bytes;
952             } else {
953                 if (m_out == null) {
954                     initStream();
955                 }
956                 // In this case the buffer will not write to the servlet stream, but to it's internal buffer only
957
m_out.write(bytes);
958             }
959         } else {
960             if (LOG.isDebugEnabled()) {
961                 LOG.debug(Messages.get().getBundle().key(Messages.LOG_FLEXRESPONSE_ERROR_WRITING_TO_OUTPUT_STREAM_0));
962             }
963             // The request is not buffered, so we can write directly to it's parents output stream
964
m_res.getOutputStream().write(bytes);
965             m_res.getOutputStream().flush();
966         }
967     }
968
969     /**
970      * Helper method to add a value in the internal header list.<p>
971      *
972      * @param headers the headers to look up the value in
973      * @param name the name to look up
974      * @param value the value to set
975      */

976     private void addHeaderList(Map JavaDoc headers, String JavaDoc name, String JavaDoc value) {
977
978         ArrayList JavaDoc values = (ArrayList JavaDoc)headers.get(name);
979         if (values == null) {
980             values = new ArrayList JavaDoc();
981             headers.put(name, values);
982         }
983         values.add(value);
984     }
985
986     /**
987      * Initializes the current responses output stream
988      * and the corrosponding print writer.<p>
989      *
990      * @throws IOException in case something goes wrong while initializing
991      */

992     private void initStream() throws IOException JavaDoc {
993
994         if (m_out == null) {
995             if (!m_writeOnlyToBuffer) {
996                 // we can use the parents output stream
997
if (m_cachingRequired || (m_controller.getResponseStackSize() > 1)) {
998                     // we are allowed to cache our results (probably to contruct a new cache entry)
999
m_out = new CmsFlexResponse.CmsServletOutputStream(m_res.getOutputStream());
1000                } else {
1001                    // we are not allowed to cache so we just use the parents output stream
1002
m_out = (CmsFlexResponse.CmsServletOutputStream)m_res.getOutputStream();
1003                }
1004            } else {
1005                // construct a "buffer only" output stream
1006
m_out = new CmsFlexResponse.CmsServletOutputStream();
1007            }
1008        }
1009        if (m_writer == null) {
1010            // create a PrintWriter that uses the encoding required for the request context
1011
m_writer = new PrintWriter JavaDoc(new BufferedWriter JavaDoc(new OutputStreamWriter JavaDoc(m_out, m_encoding)), false);
1012        }
1013    }
1014
1015    /**
1016     * This method is needed to process pages that can NOT be analyzed
1017     * directly during delivering (like JSP) because they write to
1018     * their own buffer.<p>
1019     *
1020     * In this case, we don't actually write output of include calls to the stream.
1021     * Where there are include calls we write a <code>{@link #FLEX_CACHE_DELIMITER}</code> char on the stream
1022     * to indicate that at this point the output of the include must be placed later.
1023     * The include targets (resource names) are then saved in the m_includeList.<p>
1024     *
1025     * This method must be called after the complete page has been processed.
1026     * It will contain the output of the page only (no includes),
1027     * with <code>{@link #FLEX_CACHE_DELIMITER}</code> chars were the include calls should be placed.
1028     * What we do here is analyze the output and cut it in parts
1029     * of <code>byte[]</code> arrays which then are saved in the resulting cache entry.
1030     * For the includes, we just save the name of the resource in
1031     * the cache entry.<p>
1032     *
1033     * If caching is disabled this method is just not called.<p>
1034     */

1035    private void processIncludeList() {
1036
1037        byte[] result = getWriterBytes();
1038        if (!hasIncludeList()) {
1039            // no include list, so no includes and we just use the bytes as they are in one block
1040
m_cachedEntry.add(result);
1041            result = null;
1042        } else {
1043            // process the include list
1044
int max = result.length;
1045            int pos = 0;
1046            int last = 0;
1047            int size = 0;
1048            int count = 0;
1049
1050            // work through result and split this with include list calls
1051
int i = 0;
1052            int j = 0;
1053            while (i < m_includeList.size() && (pos < max)) {
1054                // look for the first FLEX_CACHE_DELIMITER char
1055
while ((pos < max) && (result[pos] != FLEX_CACHE_DELIMITER)) {
1056                    pos++;
1057                }
1058                if ((pos < max) && (result[pos] == FLEX_CACHE_DELIMITER)) {
1059                    count++;
1060                    // a byte value of C_FLEX_CACHE_DELIMITER in our (String) output list indicates
1061
// that the next include call must be placed here
1062
size = pos - last;
1063                    if (size > 0) {
1064                        // if not (it might be 0) there would be 2 include calls back 2 back
1065
byte[] piece = new byte[size];
1066                        System.arraycopy(result, last, piece, 0, size);
1067                        // add the byte array to the cache entry
1068
m_cachedEntry.add(piece);
1069                        piece = null;
1070                    }
1071                    last = ++pos;
1072                    // add an include call to the cache entry
1073
m_cachedEntry.add((String JavaDoc)m_includeList.get(i++), (Map JavaDoc)m_includeListParameters.get(j++));
1074                }
1075            }
1076            if (pos < max) {
1077                // there is content behind the last include call
1078
size = max - pos;
1079                byte[] piece = new byte[size];
1080                System.arraycopy(result, pos, piece, 0, size);
1081                m_cachedEntry.add(piece);
1082                piece = null;
1083            }
1084            result = null;
1085            if (i >= m_includeList.size()) {
1086                // clear the include list if all include calls are handled
1087
m_includeList = null;
1088                m_includeListParameters = null;
1089            } else {
1090                // if something is left, remove the processed entries
1091
m_includeList = m_includeList.subList(count, m_includeList.size());
1092                m_includeListParameters = m_includeListParameters.subList(count, m_includeListParameters.size());
1093            }
1094        }
1095    }
1096
1097    /**
1098     * Helper method to set a value in the internal header list.
1099     *
1100     * @param headers the headers to set the value in
1101     * @param name the name to set
1102     * @param value the value to set
1103     */

1104    private void setHeaderList(Map JavaDoc headers, String JavaDoc name, String JavaDoc value) {
1105
1106        ArrayList JavaDoc values = new ArrayList JavaDoc();
1107        values.add(SET_HEADER + value);
1108        headers.put(name, values);
1109    }
1110
1111    /**
1112     * This delivers cached sub-elements back to the stream.
1113     * Needed to overcome JSP buffering.<p>
1114     *
1115     * @param res the response to write the cached results to
1116     * @throws IOException in case something goes wrong writing to the responses output stream
1117     */

1118    private void writeCachedResultToStream(HttpServletResponse JavaDoc res) throws IOException JavaDoc {
1119
1120        List JavaDoc elements = m_cachedEntry.elements();
1121        int count = 0;
1122        if (elements != null) {
1123            for (int i = 0; i < elements.size(); i++) {
1124                Object JavaDoc o = elements.get(i);
1125                if (o instanceof byte[]) {
1126                    res.getOutputStream().write((byte[])o);
1127                } else {
1128                    if ((m_includeResults != null) && (m_includeResults.size() > count)) {
1129                        // make sure that we don't run behind end of list (should never happen, though)
1130
res.getOutputStream().write((byte[])m_includeResults.get(count));
1131                        count++;
1132                    }
1133                    // skip next entry, which is the parameter list for this incluce call
1134
i++;
1135                }
1136            }
1137        }
1138    }
1139}
Popular Tags