Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: com/opencms/flex/cache/CmsFlexResponse.java


1   /*
2    * File   : $Source: /usr/local/cvs/opencms/src/com/opencms/flex/cache/Attic/CmsFlexResponse.java,v $
3    * Date   : $Date: 2003/05/13 13:18:20 $
4    * Version: $Revision: 1.16.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.cache;
33  
34  import com.opencms.core.A_OpenCms;
35  
36  import java.io.BufferedWriter;
37  import java.io.IOException;
38  import java.io.OutputStreamWriter;
39  import java.io.PrintWriter;
40  import java.util.ArrayList;
41  import java.util.List;
42  import java.util.Map;
43  
44  import javax.servlet.ServletOutputStream;
45  import javax.servlet.http.HttpServletResponse;
46  import javax.servlet.http.HttpServletResponseWrapper;
47  
48  /**
49   * Wrapper class for a HttpServletResponse.<p>
50   *
51   * This class wrapps the standard HttpServletResponse so that it's output can be delivered to
52   * the CmsFlexCache.
53   *
54   * @author  Alexander Kandzior (a.kandzior@alkacon.com)
55   * @version $Revision: 1.16.2.1 $
56   */
57  public class CmsFlexResponse extends HttpServletResponseWrapper {
58      
59      /** The wrapped ServletResponse */
60      private HttpServletResponse m_res = null;    
61      
62      /** The cached entry that is constructed from this response */
63      private CmsFlexCacheEntry m_cachedEntry = null;
64      
65      /** A special wrapper class for a ServletOutputStream */
66      private CmsFlexResponse.CmsServletOutputStream m_out;
67      
68      /** The CmsFlexController for this response */
69      private CmsFlexController m_controller = null;
70          
71      /** A printwriter that writes in the m_out stream */
72      private java.io.PrintWriter m_writer = null;
73      
74      /** Indicates that the OutputStream m_out should write ONLY in the buffer */
75      private boolean m_writeOnlyToBuffer;
76      
77      /** Indicates that parent stream is writing only in the buffer */
78      private boolean m_parentWritesOnlyToBuffer;
79      
80      /** A list of include calls that origin from this page, i.e. these are sub elements of this element */
81      private List m_includeList = null;
82      
83      /** A list of parameters that belong to the include calls */
84      private List m_includeListParameters = null;
85      
86      /** A list of results from the inclusions, needed because of JSP buffering */
87      private List m_includeResults = null;
88      
89      /** Byte array used for "cached leafs" optimization */
90      private byte[] m_cacheBytes = null;
91      
92      /** The CmsFlexCacheKey for this response */
93      private CmsFlexCacheKey m_key = null;
94  
95      /** Map to save all response headers (including sub-elements) in */
96      private Map m_headers;
97      
98      /** Map to save response headers belonging to a single include call in */
99      private Map m_buffer_headers;
100 
101     /** Indicates if this response is suspended (probably because of a redirect) */
102     private boolean m_suspended = false;
103 
104     /** String to hold a buffered redirect target */
105     private String m_buffer_redirect = null;        
106 
107     /** Indicates if caching is required, will always be true if m_writeOnlyToBuffer is true */
108     private boolean m_cachingRequired = false;       
109     
110     /** Indicates if this element is currently in include mode, i.e. processing a sub-element */
111     private boolean m_includeMode = false;
112     
113     /** Static string to indicate a header is "set" in the header maps */
114     public static final String C_SETHEADER = "[setHeader]";
115                 
116     /** Flag for debugging output */
117     private static final boolean DEBUG = false;
118     
119     /** The cache delimiter char */
120     public static final char C_FLEX_CACHE_DELIMITER = (char)0;
121     
122     /** The encoding to use for the response */
123     private String m_encoding;
124     
125     /** Flag to indicate if this is the top level element or an included sub - element */ 
126     private boolean m_isTopElement;     
127     
128     /** 
129      * Constructor for the CmsFlexResponse,
130      * this variation is usually used for the "Top" response.<p>
131      *
132      * @param res the HttpServletResponse to wrap
133      * @param controller the controller to use
134      * @param streaming indicates if streaming should be enabled or not
135      * @param isTopElement indicates if this is the top element of an include cascade
136      */
137     public CmsFlexResponse(HttpServletResponse res, CmsFlexController controller, boolean streaming, boolean isTopElement) {
138         super(res);
139         m_res = res;
140         m_controller = controller;
141         m_out = null;
142         m_encoding = controller.getCmsObject().getRequestContext().getEncoding();
143         m_isTopElement = isTopElement;
144         m_parentWritesOnlyToBuffer = ! streaming;
145         setOnlyBuffering(m_parentWritesOnlyToBuffer);
146         m_headers = new java.util.HashMap(37);
147         m_buffer_headers = new java.util.HashMap(17);        
148     }  
149     
150     /**
151      * Constructor for the CmsFlexResponse,
152      * this variation one is usually used to wrap responses for further include calls in OpenCms.<p>
153      *
154      * @param res the CmsFlexResponse to wrap     
155      * @param controller the controller to use
156      */
157     public CmsFlexResponse(HttpServletResponse res, CmsFlexController controller) {
158         super(res);
159         m_res = res;
160         m_controller = controller;
161         m_out = null;
162         m_encoding = controller.getCurrentResponse().getEncoding();
163         m_isTopElement = controller.getCurrentResponse().isTopElement();        
164         m_parentWritesOnlyToBuffer = controller.getCurrentResponse().hasIncludeList();
165         setOnlyBuffering(m_parentWritesOnlyToBuffer);
166         m_headers = new java.util.HashMap(37);
167         m_buffer_headers = new java.util.HashMap(17);
168     }    
169     
170     /**
171      * Returns the value of the encoding used for this response.<p>
172      * 
173      * @return the value of the encoding used for this response
174      */
175     public String getEncoding() {
176         return m_encoding;
177     }
178     
179     /**
180      * Returns <code>true</code> if this response has been constructed for the 
181      * top level element of this request, <code>false</code> if it was 
182      * constructed for an included sub-element.<p>
183      * 
184      * @return <code>true</code> if this response has been constructed for the 
185      * top level element of this request, <code>false</code> if it was 
186      * constructed for an included sub-element.
187      */
188     public boolean isTopElement() {
189         return m_isTopElement;        
190     }
191     
192     /** 
193      * Sets buffering status of the response.<p>
194      * 
195      * This must be done before the first output is written.
196      * Buffering is needed to process elements that can not be written
197      * directly to the output stream because their sub - elements have to
198      * be processed seperatly. Which is so far true only for JSP pages.<p>
199      *
200      * If buffering is on, nothing is written to the output stream
201      * even if streaming for this resonse is enabled.<p>
202      *
203      * @param value the value to set
204      */
205     public void setOnlyBuffering(boolean value) {
206         m_writeOnlyToBuffer = value;
207         
208         if (m_writeOnlyToBuffer) {
209             setCmsCachingRequired(true);
210         }      
211     }
212 
213     /**
214      * Set caching status for this reponse.<p>
215      * 
216      * Will always be set to "true" if setOnlyBuffering() is set to "true".
217      * Currently this is an optimization for non - JSP elements that 
218      * are known not to be cachable.<p>
219      *
220      * @param value the value to set     
221      */
222     void setCmsCachingRequired(boolean value) {  
223         m_cachingRequired = value || m_writeOnlyToBuffer;
224     }    
225 
226     /**
227      * This flag indicates to the response if it is in "include mode" or not.<p>
228      * 
229      * This is important in case a cache entry is constructed, 
230      * since the cache entry must not consist of output or headers of the
231      * included elements.<p>
232      *
233      * @param value the value to set     
234      */
235     void setCmsIncludeMode(boolean value) {
236         m_includeMode = value;
237     }
238     
239     /** 
240      * This flag indicates if the response is suspended or not.<p>
241      * 
242      * A suspended response mut not write further output to any stream or
243      * process a cache entry for itself.<p>
244      *
245      * Currently, a response is only suspended if it is redirected.<p>
246      *
247      * @return true if the response is suspended, false otherwise
248      */
249     public boolean isSuspended() {
250         return m_suspended;
251     }
252     
253     /**
254      * Sets the suspended status of the response, and also sets
255      * the suspend status of all responses wrapping this response.<p>
256      * 
257      * A suspended response mut not write further output to any stream or
258      * process a cache entry for itself.<p>
259      *
260      * @param value the value to set     
261      */
262     void setSuspended(boolean value) {
263         m_suspended = value;
264     }
265         
266     /**
267      * Provides access to the header cache of the top wrapper.<p>
268      *
269      * @return the Map of cached headers
270      */
271     public Map getHeaders() {
272         return m_headers;
273     }    
274         
275     /**
276      * Adds some bytes to the list of include results.<p>
277      * 
278      * Should be used only in inclusion-scenarios 
279      * like the JSP cms:include tag processing.<p>
280      * 
281      * @param result the byte array to add
282      */
283     void addToIncludeResults(byte[] result) {
284         if (m_includeResults == null) {
285             m_includeResults = new ArrayList(10);
286         }
287         m_includeResults.add(result);
288     }
289         
290     
291     /** 
292      * Adds an inclusion target to the list of include results.<p>
293      * 
294      * Should be used only in inclusion-scenarios
295      * like the JSP cms:include tag processing.<p>
296      *
297      * @param target the include target name to add
298      */
299     public void addToIncludeList(String target, Map parameterMap) {
300         if (m_includeList == null) {
301             m_includeList = new ArrayList(10);
302             m_includeListParameters = new ArrayList(10);
303         }        
304         m_includeListParameters.add(parameterMap);
305         m_includeList.add(target);
306     }
307     
308     /**
309      * Is used to check if the response has an include list, 
310      * which indicates a) it is probalbly processing a JSP element 
311      * and b) it can never be streamed and alwys must be buffered.<p>
312      *
313      * @return true if this response has an include list, false otherwise
314      */
315     boolean hasIncludeList() {
316         return m_includeList != null;
317     }    
318 
319     /**
320      * This method is needed to process pages that can NOT be analyzed
321      * directly during delivering (like JSP) because they write to 
322      * their own buffer.<p>
323      *
324      * So in this case, we don't actually write output of
325      * include calls to the stream.
326      * Where there are include calls we write a C_FLEX_CACHE_DELIMITER char on the stream to indicate
327      * that at this point the output of the include must be placed later.
328      * The include targets (resource names) are then saved in the m_includeList.<p>
329      *
330      * This method must be called after the complete page has been processed.
331      * It will contain the output of the page only (no includes), 
332      * with C_FLEX_CACHE_DELIMITER chars were the include calls should be placed. 
333      * What we do here is analyze the output and cut it in parts 
334      * of byte[] arrays which then are saved in the resulting cache entry.
335      * For the includes, we just save the name of the resource in
336      * the cache entry.<p>
337      *  
338      * If caching is disabled this method is just not called.<p>
339      */
340     private void processIncludeList() {
341         byte[] result = getWriterBytes();            
342         if (! hasIncludeList()) {
343             // No include list, so no includes and we just use the bytes as they are in one block
344             m_cachedEntry.add(result);
345         } else {
346             // Process the include list
347             int max = result.length;
348             int pos = 0;
349             int last = 0;
350             int size = 0;
351             int count = 0;
352             // Work through result and split this with include list calls
353             java.util.Iterator i = m_includeList.iterator();
354             java.util.Iterator j = m_includeListParameters.iterator();
355             while (i.hasNext() && (pos<max)) {
356                 // Look for the first C_FLEX_CACHE_DELIMITER char
357                 while ((pos<max) && (result[pos] != C_FLEX_CACHE_DELIMITER)) {
358                     pos++;
359                 }
360                 if (result[pos] == C_FLEX_CACHE_DELIMITER) {      
361                     count++;
362                     // A byte value of C_FLEX_CACHE_DELIMITER in our (string) output list indicates that the next include call
363                     // must be placed here
364                     size = pos - last;
365                     if (size > 0) {
366                         // If not (it might be 0) there would be 2 include calls back 2 back
367                         byte[] piece = new byte[size];
368                         System.arraycopy(result, last, piece, 0, size);
369                         // Add the byte array to the cache entry
370                         m_cachedEntry.add(piece);
371                     }
372                     last = ++pos;
373                     // Add an include call to the cache entry
374                     m_cachedEntry.add((String)i.next(), (java.util.HashMap)j.next());
375                 }
376             } 
377             // Is there something behind the last include call?
378             if (pos<max) {
379                 // Yes!
380                 size = max - pos;
381                 byte[] piece = new byte[size];
382                 System.arraycopy(result, pos, piece, 0, size);                
383                 m_cachedEntry.add(piece);
384             }           
385             if (! i.hasNext()) {
386                 // Delete the include list if all include calls are handled
387                 m_includeList = null;
388                 m_includeListParameters = null;
389             } else {
390                 // If something is left, remove the processed entries
391                 m_includeList = m_includeList.subList(count, m_includeList.size());
392                 m_includeListParameters = m_includeListParameters.subList(count, m_includeListParameters.size());
393             }
394         }
395     }
396     
397     /** 
398      * This delivers cached sub-elements back to the stream.
399      * Needed to overcome JSP buffering.<p>
400      *
401      * @param res the response to write the cached results to
402      * @throws IOException in case something goes wrong writing to the responses output stream
403      */
404     private void writeCachedResultToStream(HttpServletResponse res) throws IOException {        
405         java.util.List elements = m_cachedEntry.elements();
406         int count = 0;
407         if (elements != null) {
408             java.util.Iterator i = elements.iterator();
409             while (i.hasNext()) {
410                 Object o = i.next();                
411                 if (o instanceof byte[]) {
412                     res.getOutputStream().write((byte[])o);
413                 } else {
414                     if ((m_includeResults != null) && (m_includeResults.size() > count)) {
415                         // Make sure that we don't run behind end of list (should never happen, though)
416                         res.getOutputStream().write((byte[])m_includeResults.get(count));
417                         count++;
418                     }
419                     // Skip next entry, which is the parameter list for this incluce call
420                     o = i.next();
421                 }
422             }
423         }
424     }
425     
426     /**
427      * Generates a CmsFlexCacheEntry from the current response using the 
428      * stored include results.<p>
429      * 
430      * In case the results were written only to the buffer until now, 
431      * they are now re-written on the output stream, with all included 
432      * elements.<p>
433      *
434      * @throws IOException tn case something goes wrong while writing to the output stream
435      * @return  the generated cache entry
436      */    
437     CmsFlexCacheEntry processCacheEntry() throws IOException {    
438         if (isSuspended() && (m_buffer_redirect == null)) 
439             // An included element redirected this response, no cache entry must be produced
440             return null;
441         
442         if (m_cachingRequired) {
443             // Cache entry must only be calculated if it's actually needed (always true if we write only to buffer)
444             m_cachedEntry = new CmsFlexCacheEntry();    
445             if (m_buffer_redirect != null) {
446                 // Only set et cached redirect target
447                 m_cachedEntry.setRedirect(m_buffer_redirect);
448             } else {
449                 // Add cached headers
450                 m_cachedEntry.addHeaders(m_buffer_headers);
451                 // Add cached output 
452                 if (m_includeList != null) {
453                     // Probably JSP: We must analyze out stream for includes calls
454                     // Also, m_writeOnlyToBuffer must be "true" or m_includeList can not be != null
455                     processIncludeList();   
456                 } else {
457                     // Output is delivered directly, no include call parsing required
458                     m_cachedEntry.add(getWriterBytes());
459                 }
460             }
461             m_cachedEntry.complete();
462         }        
463         // In case the output was only bufferd we have to re-write it to the "right" stream       
464         if (m_writeOnlyToBuffer) {
465             
466             // Since we are processing a cache entry caching is not required
467             m_cachingRequired = false;    
468                         
469             if (m_buffer_redirect != null) {
470                 // Send buffered redirect, will trigger redirect of top response
471                 sendRedirect(m_buffer_redirect);
472             } else {  
473                 // Process the output               
474                 if (m_parentWritesOnlyToBuffer) {                                               
475                     // Write results back to own stream, headers are already in buffer
476                     if (m_out != null) {
477                         try {
478                             m_out.clear(); 
479                         } catch (Exception e) {
480                             if (DEBUG) System.err.println("FlexResponse: caught exception while calling m_out.clear() in processCacheEntry()\nException: " + e);                            
481                         }
482                     } else {
483                         if (DEBUG) System.err.println("FlexResponse: m_out == null in processCacheEntry()");
484                     }
485                     writeCachedResultToStream(this);                 
486                 } else {
487                     // We can use the parent stream
488                     processHeaders(m_headers, m_res);
489                     writeCachedResultToStream(m_res);  
490                 }
491             }
492         }        
493         return m_cachedEntry;
494     }
495     
496     /**
497      * Returns the bytes that have been written on the current writers output stream.<p>
498      *
499      * @return the bytes that have been written on the current writers output stream
500      */    
501     public byte[] getWriterBytes() {
502         if (isSuspended()) 
503             // No output whatsoever if the response is suspended
504             return new byte[0];
505         if (m_cacheBytes != null)
506             // Optimization for cached "leaf" nodes, here I re-use the array from the cache
507             return m_cacheBytes;
508         if (m_out == null) 
509             // No output was written so far, just return an empty array
510             return new byte[0];
511         if (m_writer != null) 
512             // Flush the writer in case something was written on it
513             m_writer.flush();
514         return m_out.getBytes();
515     }
516     
517     /**
518      * Initializes the current responses output stream 
519      * and the corrosponding print writer.<p>
520      *
521      * @throws IOException in case something goes wrong while initializing
522      */    
523     private void initStream() throws IOException {        
524         if (m_out == null) {
525             if (! m_writeOnlyToBuffer) {
526                 // We can use the parents output stream
527                 if (m_cachingRequired || (m_controller.getResponseQueueSize() > 1)) {
528                     // We are allowed to cache our results (probably to contruct a new cache entry)
529                     m_out = new CmsFlexResponse.CmsServletOutputStream(m_res.getOutputStream());        
530                 } else {
531                     // We are not allowed to cache so we just use the parents output stream
532                     m_out = (CmsFlexResponse.CmsServletOutputStream)m_res.getOutputStream();
533                 }
534             } else {
535                 // Construct a "buffer only" output stream
536                 m_out = new CmsFlexResponse.CmsServletOutputStream();
537             }
538         }
539         if (m_writer == null) {
540             // Encoding project:
541             // Create a PrintWriter that uses the OpenCms default encoding
542             m_writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(m_out, A_OpenCms.getDefaultEncoding())), false);
543         }
544     }
545     
546     /** 
547      * Writes some bytes to the current output stream,
548      * this method should be called from CmsFlexCacheEntry.service() only.<p>
549      *
550      * @param bytes an array of bytes
551      * @param useArray indicates that the byte array should be used directly
552      * @throws IOException in case something goes wrong while writing to the stream
553      */
554     void writeToOutputStream(byte[] bytes, boolean useArray) throws IOException {
555         if (isSuspended()) return;        
556         if (m_writeOnlyToBuffer) {
557             if (useArray) {
558                 // This cached entry has no sub-elements (it a "leaf") and so we can just use it's bytes
559                 m_cacheBytes = bytes;                                  
560             } else {
561                 if (m_out == null) initStream();
562                 // In this case the buffer will not write to the servlet stream, but to it's internal buffer only
563                 m_out.write(bytes);
564             }
565         } else {
566             if (DEBUG) System.err.println("FlexResponse.writeToOutputStream(): Writing directly to wrapped output stream!");
567             // The request is not buffered, so we can write directly to it's parents output stream 
568             m_res.getOutputStream().write(bytes);
569             m_res.getOutputStream().flush();
570         }    
571     }
572 
573     /**
574      * Returns the cache key for to this response.<p>
575      *
576      * @return the cache key for to this response
577      */
578     CmsFlexCacheKey getCmsCacheKey() {
579         return m_key;
580     }
581     
582     /** 
583      * Sets the cache key for this response, which is calculated
584      * from the provided parameters.<p>
585      *
586      * @param target the target resouce for which to create the cache key
587      * @param value the value of the cache property of the resource
588      * @param online indicates if this resource is online or offline
589      * @return the generated cache key
590      * @throws CmsException in case the value String had a parse error
591      */
592     CmsFlexCacheKey setCmsCacheKey(String target, String value, boolean online) throws com.opencms.core.CmsException {
593         m_key = new CmsFlexCacheKey(target, value, online);
594         if (m_key.hadParseError()) {
595             // We throw the exception here to make sure this response has a valid key (cache=never)
596             throw new com.opencms.core.CmsException(com.opencms.core.CmsException.C_FLEX_CACHE);            
597         }
598         return m_key;
599     }
600     
601     /**
602      * Sets the cache key for this response from 
603      * a pre-calculated cache key.<p>
604      *
605      * @param value the cache key to set
606      */
607     void setCmsCacheKey(CmsFlexCacheKey value) {
608         m_key = value;
609     }
610     
611     /**
612      * Helper method to add a value in the internal header list.<p>
613      *
614      * @param headers the headers to look up the value in
615      * @param name the name to look up
616      * @param value the value to set
617      */
618     private void addHeaderList(Map headers, String name, String value) {
619         ArrayList values = (ArrayList) headers.get(name);
620         if (values == null) {
621             values = new ArrayList();
622             headers.put(name, values);            
623         } 
624         values.add(value);        
625     }
626    
627     /**
628      * Helper method to set a value in the internal header list.
629      *
630      * @param headers the headers to set the value in
631      * @param name the name to set
632      * @param value the value to set
633      */
634     private void setHeaderList(Map headers, String name, String value) {
635         ArrayList values = new ArrayList();
636         values.add(C_SETHEADER + value);
637         headers.put(name, values);        
638     }
639     
640     /**
641      * Method overlodad from the standard HttpServletRequest API.<p>
642      *
643      * @see javax.servlet.ServletResponse#getWriter()
644      */
645     public java.io.PrintWriter getWriter() throws IOException {
646         if (m_writer == null) initStream();
647         return m_writer;
648     }
649     
650     /**
651      * Method overlodad from the standard HttpServletRequest API.<p>
652      *
653      * @see javax.servlet.ServletResponse#getOutputStream()
654      */
655     public javax.servlet.ServletOutputStream getOutputStream() throws IOException {
656         if (m_out == null) initStream();
657         return m_out;
658     }
659 
660     /**
661      * Method overlodad from the standard HttpServletRequest API.<p>
662      *
663      * @see javax.servlet.http.HttpServletResponse#sendRedirect(java.lang.String)
664      */
665     public void sendRedirect(String location) throws IOException {
666         // Ignore any redirects after the first one
667         if (isSuspended() && (! location.equals(m_buffer_redirect))) return;
668         if (DEBUG) System.err.println("FlexResponse: sendRedirect to target " + location);
669                 
670         if (m_cachingRequired && ! m_includeMode) {
671             m_buffer_redirect = location;
672         }
673         
674         if (! m_cachingRequired) {
675             // If caching is required a cached entry will be constructed first and redirect will
676             // be called after this is completed and stored in the cache
677             if (DEBUG) System.err.println("FlexResponse: getTopResponse.sendRedirect() to target " + location);
678             
679             m_controller.getTopResponse().sendRedirect(location);        
680         }
681         
682         m_controller.suspendFlexResponse();
683     }
684     
685     /**
686      * Process the headers stored in the provided map and add them to the response.<p>
687      * 
688      * @param headers the headers to add
689      * @param res the resonse to add the headers to
690      */
691     public static void processHeaders(Map headers, HttpServletResponse res) {
692         if (headers != null) {
693             java.util.Iterator i = headers.keySet().iterator();
694             while (i.hasNext()) {
695                 String key = (String)i.next();
696                 ArrayList l = (ArrayList)headers.get(key);  
697                 java.util.ListIterator j = l.listIterator(); 
698                 while (j.hasNext()) {
699                     if ((j.nextIndex() == 0) && (((String)l.get(0)).startsWith(C_SETHEADER)))  {
700                         String s = (String)j.next();
701                         res.setHeader(key, s.substring(C_SETHEADER.length()));
702                     } else {
703                         res.addHeader(key, (String)j.next());
704                     }
705                 }
706             }        
707         }          
708     }
709     
710     /**
711      * Method overlodad from the standard HttpServletRequest API.<p>
712      *
713      * @see javax.servlet.http.HttpServletResponse#setHeader(java.lang.String, java.lang.String)
714      */
715     public void setHeader(String name, String value) {
716         if (isSuspended()) return;
717 
718         if (m_cachingRequired && ! m_includeMode) {
719             setHeaderList(m_buffer_headers, name, value);
720             if (DEBUG) System.err.println("FlexResponse: setHeader(" + name + ", " + value + ") in element buffer");
721         }
722         
723         if (m_writeOnlyToBuffer) {
724             setHeaderList(m_headers, name, value);
725             if (DEBUG) System.err.println("FlexResponse: setHeader(" + name + ", " + value + ") in main header buffer");
726         } else {
727             if (DEBUG) System.err.println("FlexResponse: setHeader(" + name + ", " + value + ") passing to parent");
728             m_res.setHeader(name, value);
729         }
730     }
731 
732     /**
733      * Method overlodad from the standard HttpServletRequest API.<p>
734      *
735      * @see javax.servlet.http.HttpServletResponse#addHeader(java.lang.String, java.lang.String)
736      */   
737     public void addHeader(String name, String value) {
738         if (isSuspended()) return;
739 
740         if (m_cachingRequired && ! m_includeMode) {
741             addHeaderList(m_buffer_headers, name, value);
742             if (DEBUG) System.err.println("FlexResponse: addHeader(" + name + ", " + value + ") to element buffer");
743         }
744         
745         if (m_writeOnlyToBuffer) {
746             addHeaderList(m_headers, name, value);
747             if (DEBUG) System.err.println("FlexResponse: addHeader(" + name + ", " + value + ") to main header buffer");
748         } else {
749             if (DEBUG) System.err.println("FlexResponse: addHeader(" + name + ", " + value + ") passing to parent");
750             m_res.addHeader(name, value);
751         }
752     }
753     
754     /**
755      * Method overlodad from the standard HttpServletRequest API.<p>
756      *
757      * @see javax.servlet.http.HttpServletResponse#setDateHeader(java.lang.String, long)
758      */
759     public void setDateHeader(String name, long date) {
760         java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", java.util.Locale.US);
761         setHeader(name, format.format(new java.util.Date(date)));
762     }
763 
764     /**
765      * Method overlodad from the standard HttpServletRequest API.<p>
766      *
767      * @see javax.servlet.http.HttpServletResponse#addDateHeader(java.lang.String, long)
768      */
769     public void addDateHeader(String name, long date) {
770         java.text.SimpleDateFormat format = new java.text.SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss zzz", java.util.Locale.US);
771         addHeader(name, format.format(new java.util.Date(date)));
772     }
773 
774     /**
775      * Method overlodad from the standard HttpServletRequest API.<p>
776      *
777      * @see javax.servlet.http.HttpServletResponse#setIntHeader(java.lang.String, int)
778      */   
779     public void setIntHeader(String name, int value) {
780         setHeader(name, "" + value);       
781     }
782 
783     /**
784      * Method overlodad from the standard HttpServletRequest API.<p>
785      *
786      * @see javax.servlet.http.HttpServletResponse#addIntHeader(java.lang.String, int)
787      */    
788     public void addIntHeader(String name, int value) {
789         addHeader(name, "" + value);
790     }    
791 
792     /**
793      * Method overlodad from the standard HttpServletRequest API.<p>
794      *
795      * @see javax.servlet.ServletResponse#setContentType(java.lang.String)
796      */
797     public void setContentType(String type) {
798         if (DEBUG) System.err.println("FlexResponse: setContentType(" +type + ") called");
799         // TODO: Check setContentType() implementation
800         // If this is not the "Top-Level" element ignore all settings of content type    
801         // If this is not done an included JSP could reset the type with some unwanted defaults    
802         if (! m_isTopElement) return;
803         /*       
804         if (type != null) {
805             // ensure that the encoding set by OpenCms is not overwritten by the default form the JSP            
806             type = type.toLowerCase();
807             int i = type.indexOf("charset");
808             if (type.startsWith("text") && (i > 0)) {
809                 StringBuffer buf = new StringBuffer();
810                 buf.append(type.substring(0, i));
811                 buf.append("charset=");
812                 buf.append(m_encoding);
813                 type = new String(buf);
814                 if (DEBUG) System.err.println("FlexResponse: setContentType() changed type to " +type);
815             }            
816         }
817         m_res.setContentType(type);
818         */        
819     }
820     
821     /**
822      * Wrapped implementation of the ServletOutputStream.<p>
823      * 
824      * This implementation writes to an internal buffer and optionally to another 
825      * output stream at the same time.
826      * It should be fully transparent to the standard ServletOutputStream.<p>
827      */    
828     private class CmsServletOutputStream extends ServletOutputStream {
829 
830         /** The internal steam buffer */
831         private java.io.ByteArrayOutputStream stream = null;
832         
833         /** The optional output stream to write to */
834         private javax.servlet.ServletOutputStream servletStream = null;
835         
836         /** Debug flag */
837         private static final boolean DEBUG = false;
838 
839         /**
840          * Constructor that must be used if the stream should write 
841          * only to a buffer.<p>
842          */
843         public CmsServletOutputStream() {
844             this.servletStream = null;
845             clear();
846         }   
847 
848         /**
849          * Constructor that must be used if the stream should write 
850          * to a buffer and to another stream at the same time.<p>
851          *
852          * @param servletStream The stream to write to
853          */        
854         public CmsServletOutputStream(ServletOutputStream servletStream) {
855             this.servletStream = servletStream;
856             clear();                  
857         }   
858                 
859         /**
860          * @see java.io.OutputStream#write(int)
861          */
862         public void write(int b) throws IOException {
863             stream.write(b);
864             if (servletStream != null) servletStream.write(b);
865         }
866         
867         /**
868          * @see java.io.OutputStream#write(byte[], int, int)
869          */
870         public void write(byte[] b, int off, int len) throws IOException {
871             stream.write(b, off, len);
872             if (servletStream != null) servletStream.write(b, off, len);
873         }
874         
875         /**
876          * Writes an array of bytes only to the included servlet stream,
877          * not to the buffer.<p>
878          *
879          * @param b The bytes to write to the stream
880          * @throws IOException In case the write() operation on the included servlet stream raises one
881          */
882         public void writeToServletStream(byte[] b) throws IOException {
883             if (servletStream != null) servletStream.write(b);
884         }
885         
886         /**
887          * @see java.io.OutputStream#flush()
888          */
889         public void flush() throws IOException {
890             if (DEBUG) System.err.println("CmsServletOutputStream: flush() called! servletStream=" + servletStream);
891             if (servletStream != null) servletStream.flush();
892         }
893                 
894         /**
895          * Provides access to the bytes cached in the buffer.<p>
896          *
897          * @return the cached bytes from the buffer
898          */
899         public byte[] getBytes() {
900             return stream.toByteArray();
901         }
902         
903         /**
904          * Clears the buffer by initializing the buffer with a new stream.<p>
905          */
906         public void clear() {
907             stream = new java.io.ByteArrayOutputStream(1024);
908         }        
909     }    
910     
911 }