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 }