View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.jetspeed.resource;
18  
19  import java.io.ByteArrayOutputStream;
20  import java.io.CharArrayWriter;
21  import java.io.IOException;
22  import java.io.PrintWriter;
23  import java.util.ArrayList;
24  import java.util.HashMap;
25  import java.util.Iterator;
26  import java.util.Locale;
27  import java.util.Map.Entry;
28  
29  import javax.servlet.ServletOutputStream;
30  import javax.servlet.http.Cookie;
31  import javax.servlet.http.HttpServletResponse;
32  import javax.servlet.http.HttpServletResponseWrapper;
33  
34  /***
35   * <p>
36   * BufferedHttpServletResponse fully captures all HttpServletResponse interactions to be flushed out later.
37   * This wrapper is specifically written to allow included servlets to set headers, cookies, encoding etc. which isn't allowed by
38   * the servlet specification on included responses.
39   * </p>
40   * <p>
41   * Call flush(HttpServletResponse) after the include has returned to flush out the buffered data, headers and state.
42   * </p>
43   * <p>
44   * Note: the only method not fully supported by this buffered version is getCharacterEncoding(). Setting characterEncoding through
45   * setContentType or setLocale on this class won't be reflected in the return value from getCharacterEncoding(), and calling getWriter()
46   * won't set it either although calling setLocale, setContentType or setCharacterEncoding (servlet api 2.4+) after that will be ignored.
47   * But, when this object is flused to a (real) response, the contentType, locale and/or characterEncoding recorded will be set on the
48   * target response then.
49   * </p>
50   * 
51   * @author <a href="mailto:ate@douma.nu">Ate Douma</a>
52   * @version $Id: BufferedHttpServletResponse.java 544024 2007-06-04 00:59:09Z ate $
53   */
54  public class BufferedHttpServletResponse extends HttpServletResponseWrapper
55  {
56      private static class CharArrayWriterBuffer extends CharArrayWriter
57      {
58          public char[] getBuffer()
59          {
60              return buf;
61          }
62          
63          public int getCount()
64          {
65              return count;
66          }
67      }
68      
69      private ByteArrayOutputStream byteOutputBuffer;
70      private CharArrayWriterBuffer charOutputBuffer;
71      private ServletOutputStream outputStream;
72      private PrintWriter printWriter;
73      private HashMap headers;
74      private ArrayList cookies;
75      private int errorCode;
76      private int statusCode;
77      private String errorMessage;
78      private String redirectLocation;
79      private boolean committed;
80      private boolean hasStatus;
81      private boolean hasError;
82      private Locale locale;
83      private boolean closed;
84      private String characterEncoding;
85      private int contentLength = -1;
86      private String contentType;
87      private boolean flushed;
88      
89      public BufferedHttpServletResponse(HttpServletResponse response)
90      {
91          super(response);
92      }
93      
94      public void flush(HttpServletResponse response) throws IOException
95      {
96          if (flushed)
97          {
98              throw new IllegalStateException("Already flushed");            
99          }
100         flushed = true;
101         
102         if (locale != null)
103         {
104             response.setLocale(locale);
105         }        
106         if (contentType != null)
107         {
108             response.setContentType(contentType);
109         }
110         if (characterEncoding != null)
111         {
112             // setCharacterEncoding only available on Servlet Spec 2.4+
113             try
114             {
115                 response.getClass().getMethod("setCharacterEncoding", new Class[]{String.class}).invoke(response, new Object[]{characterEncoding});
116             }
117             catch (NoSuchMethodException nsme)
118             {
119                 // servlet spec 2.3
120             }
121             catch (Exception e)
122             {
123                 throw new RuntimeException(e);
124             }
125         }
126         if (cookies != null)
127         {
128             for (int i=0,size=cookies.size(); i<size; i++)
129             {
130                 response.addCookie((Cookie)cookies.get(i));
131             }
132             cookies = null;
133         }
134         if (headers != null)
135         {
136             Iterator iter = headers.entrySet().iterator();
137             while (iter.hasNext())
138             {
139                 Entry e = (Entry)iter.next();
140                 String name = (String)e.getKey();
141                 ArrayList values = (ArrayList)e.getValue();
142                 for (int i=0, size=values.size(); i < size; i++ )
143                 {
144                     Object value = values.get(i);
145                     if (value instanceof Integer)
146                     {
147                         response.addIntHeader(name, ((Integer)value).intValue());
148                     }
149                     else if (value instanceof Long)
150                     {
151                         response.addDateHeader(name, ((Long)value).longValue());
152                     }
153                     else
154                     {
155                         response.addHeader(name, (String)value);
156                     }
157                 }
158             }
159             headers = null;
160         }
161         if (contentLength > -1)
162         {
163             response.setContentLength(contentLength);
164         }
165         if (hasStatus)
166         {
167             response.setStatus(statusCode);
168         }
169         if (hasError)
170         {
171             response.sendError(errorCode, errorMessage);            
172         }
173         else if (redirectLocation != null)
174         {
175             response.sendRedirect(redirectLocation);
176         }
177         else
178         {
179             if (outputStream != null)
180             {
181                 if (!closed)
182                 {
183                     outputStream.flush();
184                 }
185                 ServletOutputStream realOutputStream = response.getOutputStream();
186                 int len = byteOutputBuffer.size();
187                 if (contentLength > -1 && contentLength < len)
188                 {
189                     len = contentLength;
190                 }
191                 if (len > 0)
192                 {
193                     realOutputStream.write(byteOutputBuffer.toByteArray(), 0, len);
194                 }
195                 outputStream.close();
196                 outputStream = null;
197                 byteOutputBuffer = null;
198             }
199             else if (printWriter != null)
200             {
201                 if (!closed)
202                 {
203                     printWriter.flush();
204                     if ( charOutputBuffer.getCount() > 0)
205                     {
206                         response.getWriter().write(charOutputBuffer.getBuffer(), 0, charOutputBuffer.getCount());
207                     }
208                     printWriter.close();
209                     
210                     printWriter = null;
211                     charOutputBuffer = null;
212                 }
213             }
214             
215         }
216     }
217     
218     private ArrayList getHeaderList(String name, boolean create)
219     {
220         if ( headers == null )
221         {
222             headers = new HashMap();
223         }
224         ArrayList headerList = (ArrayList)headers.get(name);
225         if ( headerList == null && create )
226         {
227             headerList = new ArrayList();
228             headers.put(name,headerList);
229         }
230         return headerList;
231     }
232     
233     private void failIfCommitted()
234     {
235         if (committed)
236         {
237             throw new IllegalStateException("Response is already committed");
238         }
239     }
240     
241     /* (non-Javadoc)
242      * @see javax.servlet.http.HttpServletResponseWrapper#addCookie(javax.servlet.http.Cookie)
243      */
244     public void addCookie(Cookie cookie)
245     {
246         if ( !committed )
247         {
248             if ( cookies == null )
249             {
250                 cookies = new ArrayList();
251             }
252             cookies.add(cookie);
253         }
254     }
255 
256     /* (non-Javadoc)
257      * @see javax.servlet.http.HttpServletResponseWrapper#addDateHeader(java.lang.String, long)
258      */
259     public void addDateHeader(String name, long date)
260     {
261         if (!committed)
262         {
263             ArrayList headerList = getHeaderList(name, true);
264             headerList.add(new Long(date));
265         }
266     }
267 
268     /* (non-Javadoc)
269      * @see javax.servlet.http.HttpServletResponseWrapper#addHeader(java.lang.String, java.lang.String)
270      */
271     public void addHeader(String name, String value)
272     {
273         if (!committed)
274         {
275             ArrayList headerList = getHeaderList(name, true);
276             headerList.add(value);
277         }
278     }
279 
280     /* (non-Javadoc)
281      * @see javax.servlet.http.HttpServletResponseWrapper#addIntHeader(java.lang.String, int)
282      */
283     public void addIntHeader(String name, int value)
284     {
285         if (!committed)
286         {
287             ArrayList headerList = getHeaderList(name, true);
288             headerList.add(new Integer(value));
289         }
290     }
291 
292     /* (non-Javadoc)
293      * @see javax.servlet.http.HttpServletResponseWrapper#containsHeader(java.lang.String)
294      */
295     public boolean containsHeader(String name)
296     {
297         return getHeaderList(name, false) != null;
298     }
299 
300     /* (non-Javadoc)
301      * @see javax.servlet.http.HttpServletResponseWrapper#sendError(int, java.lang.String)
302      */
303     public void sendError(int errorCode, String errorMessage) throws IOException
304     {
305         failIfCommitted();
306         committed = true;
307         closed = true;
308         hasError = true;
309         this.errorCode = errorCode;
310         this.errorMessage = errorMessage;
311     }
312 
313     /* (non-Javadoc)
314      * @see javax.servlet.http.HttpServletResponseWrapper#sendError(int)
315      */
316     public void sendError(int errorCode) throws IOException
317     {
318         sendError(errorCode, null);
319     }
320 
321     /* (non-Javadoc)
322      * @see javax.servlet.http.HttpServletResponseWrapper#sendRedirect(java.lang.String)
323      */
324     public void sendRedirect(String redirectLocation) throws IOException
325     {
326         failIfCommitted();
327         closed = true;
328         committed = true;
329         this.redirectLocation = redirectLocation;
330     }
331 
332     /* (non-Javadoc)
333      * @see javax.servlet.http.HttpServletResponseWrapper#setDateHeader(java.lang.String, long)
334      */
335     public void setDateHeader(String name, long date)
336     {
337         if (!committed)
338         {
339             ArrayList headerList = getHeaderList(name, true);
340             headerList.clear();
341             headerList.add(new Long(date));
342         }
343     }
344 
345     /* (non-Javadoc)
346      * @see javax.servlet.http.HttpServletResponseWrapper#setHeader(java.lang.String, java.lang.String)
347      */
348     public void setHeader(String name, String value)
349     {
350         if (!committed)
351         {
352             ArrayList headerList = getHeaderList(name, true);
353             headerList.clear();
354             headerList.add(value);
355         }
356     }
357 
358     /* (non-Javadoc)
359      * @see javax.servlet.http.HttpServletResponseWrapper#setIntHeader(java.lang.String, int)
360      */
361     public void setIntHeader(String name, int value)
362     {
363         if (!committed)
364         {
365             ArrayList headerList = getHeaderList(name, true);
366             headerList.clear();
367             headerList.add(new Integer(value));
368         }
369     }
370 
371     /* (non-Javadoc)
372      * @see javax.servlet.http.HttpServletResponseWrapper#setStatus(int, java.lang.String)
373      */
374     public void setStatus(int statusCode, String message)
375     {
376         throw new UnsupportedOperationException("This method is deprecated and no longer available");
377     }
378 
379     /* (non-Javadoc)
380      * @see javax.servlet.http.HttpServletResponseWrapper#setStatus(int)
381      */
382     public void setStatus(int statusCode)
383     {
384         if (!committed)
385         {
386             this.statusCode = statusCode;
387             this.hasStatus = true;
388             resetBuffer();
389         }
390     }
391 
392     /* (non-Javadoc)
393      * @see javax.servlet.ServletResponseWrapper#flushBuffer()
394      */
395     public void flushBuffer() throws IOException
396     {
397         if (!closed)
398         {
399             committed = true;
400         }
401     }
402 
403     /* (non-Javadoc)
404      * @see javax.servlet.ServletResponseWrapper#getBufferSize()
405      */
406     public int getBufferSize()
407     {
408         return Integer.MAX_VALUE;
409     }
410 
411     /* (non-Javadoc)
412      * @see javax.servlet.ServletResponseWrapper#getCharacterEncoding()
413      */
414     public String getCharacterEncoding()
415     {
416         return characterEncoding != null ? characterEncoding : "ISO-8859-1";
417     }
418 
419     /* (non-Javadoc)
420      * @see javax.servlet.ServletResponseWrapper#getLocale()
421      */
422     public Locale getLocale()
423     {
424         return locale != null ? locale : super.getLocale();
425     }
426 
427     /* (non-Javadoc)
428      * @see javax.servlet.ServletResponseWrapper#getOutputStream()
429      */
430     public ServletOutputStream getOutputStream() throws IOException
431     {
432         if (outputStream == null)
433         {
434             if (printWriter != null)
435             {
436                 throw new IllegalStateException("getWriter() has already been called on this response");
437             }
438             byteOutputBuffer = new ByteArrayOutputStream();
439             outputStream = new ServletOutputStream()
440             {
441                 public void write(int b) throws IOException
442                 {
443                     if (!closed)
444                     {
445                         byteOutputBuffer.write(b);
446                         if (contentLength>-1 && byteOutputBuffer.size()>=contentLength)
447                         {
448                             committed = true;
449                             closed = true;
450                         }
451                     }
452                 }
453             };
454         }
455         return outputStream;
456     }
457 
458     /* (non-Javadoc)
459      * @see javax.servlet.ServletResponseWrapper#getWriter()
460      */
461     public PrintWriter getWriter() throws IOException
462     {
463         if (printWriter == null)
464         {
465             if (outputStream != null)
466             {
467                 throw new IllegalStateException("getOutputStream() has already been called on this response");
468             }
469             charOutputBuffer = new CharArrayWriterBuffer();
470             printWriter = new PrintWriter(charOutputBuffer);
471         }
472         return printWriter;
473     }
474 
475     /* (non-Javadoc)
476      * @see javax.servlet.ServletResponseWrapper#isCommitted()
477      */
478     public boolean isCommitted()
479     {
480         return committed;
481     }
482 
483     /* (non-Javadoc)
484      * @see javax.servlet.ServletResponseWrapper#reset()
485      */
486     public void reset()
487     {
488         resetBuffer(); // fails if committed
489         headers = null;
490         cookies = null;
491         hasStatus = false;
492         contentLength = -1;
493         if (printWriter == null)
494         {
495             contentType = null;
496             characterEncoding = null;
497             locale = null;
498         }
499     }
500 
501     /* (non-Javadoc)
502      * @see javax.servlet.ServletResponseWrapper#resetBuffer()
503      */
504     public void resetBuffer()
505     {
506         failIfCommitted();
507         if (outputStream != null)
508         {
509             try { outputStream.flush(); } catch (Exception e){}
510             byteOutputBuffer.reset();
511         }
512         else if (printWriter != null)
513         {
514             printWriter.flush();
515             charOutputBuffer.reset();
516         }
517     }
518 
519     /* (non-Javadoc)
520      * @see javax.servlet.ServletResponseWrapper#setBufferSize(int)
521      */
522     public void setBufferSize(int size)
523     {
524         failIfCommitted();
525         if ( (charOutputBuffer != null && charOutputBuffer.size() > 0)
526                 || (byteOutputBuffer != null && byteOutputBuffer.size() > 0) )
527         {
528             throw new IllegalStateException("Content has already been written");
529         }
530     }
531 
532     /* (non-Javadoc)
533      * @see javax.servlet.ServletResponseWrapper#setCharacterEncoding(java.lang.String)
534      */
535     public void setCharacterEncoding(String charset)
536     {
537         if (charset != null && !committed && printWriter == null)
538         {
539             characterEncoding = charset;
540         }
541     }
542 
543     /* (non-Javadoc)
544      * @see javax.servlet.ServletResponseWrapper#setContentLength(int)
545      */
546     public void setContentLength(int len)
547     {
548         if (!committed && printWriter == null && len > 0)
549         {
550             contentLength = len;
551             if (outputStream != null)
552             {
553                 try { outputStream.flush(); } catch (Exception e){}
554             }
555             if ( !closed && byteOutputBuffer != null && byteOutputBuffer.size() >= len )
556             {
557                 committed = true;
558                 closed = true;
559             }
560         }
561     }
562 
563     /* (non-Javadoc)
564      * @see javax.servlet.ServletResponseWrapper#setContentType(java.lang.String)
565      */
566     public void setContentType(String type)
567     {
568         if (!committed)
569         {
570             contentType = type;
571             if (printWriter == null)
572             {
573                 // TODO: parse possible encoding for better return value from getCharacterEncoding()
574             }
575         }
576     }
577 
578     /* (non-Javadoc)
579      * @see javax.servlet.ServletResponseWrapper#setLocale(java.util.Locale)
580      */
581     public void setLocale(Locale locale)
582     {
583         if (!committed)
584         {
585             this.locale = locale;
586             /* NON-FIXABLE ISSUE: defaulting the characterEncoding from the Locale
587                This feature cannot be implemented/wrapped as it might depend on web.xml locale settings
588              */
589         }
590     }
591 }