001    /**
002     * Copyright (c) 2000-2012 Liferay, Inc. All rights reserved.
003     *
004     * This library is free software; you can redistribute it and/or modify it under
005     * the terms of the GNU Lesser General Public License as published by the Free
006     * Software Foundation; either version 2.1 of the License, or (at your option)
007     * any later version.
008     *
009     * This library is distributed in the hope that it will be useful, but WITHOUT
010     * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
011     * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
012     * details.
013     */
014    
015    package com.liferay.portal.kernel.servlet;
016    
017    import com.liferay.portal.kernel.io.DummyOutputStream;
018    import com.liferay.portal.kernel.io.DummyWriter;
019    import com.liferay.portal.kernel.io.unsync.UnsyncByteArrayOutputStream;
020    import com.liferay.portal.kernel.io.unsync.UnsyncStringWriter;
021    import com.liferay.portal.kernel.nio.charset.CharsetDecoderUtil;
022    import com.liferay.portal.kernel.nio.charset.CharsetEncoderUtil;
023    import com.liferay.portal.kernel.util.StringBundler;
024    import com.liferay.portal.kernel.util.StringPool;
025    import com.liferay.portal.kernel.util.UnsyncPrintWriterPool;
026    
027    import java.io.IOException;
028    import java.io.PrintWriter;
029    
030    import java.nio.ByteBuffer;
031    import java.nio.CharBuffer;
032    
033    import javax.servlet.ServletOutputStream;
034    import javax.servlet.http.HttpServletResponse;
035    
036    /**
037     * @author Shuyang Zhou
038     */
039    public class BufferCacheServletResponse extends MetaInfoCacheServletResponse {
040    
041            public BufferCacheServletResponse(HttpServletResponse response) {
042                    super(response);
043            }
044    
045            /**
046             * This method is very expensive when used in char mode because it has to
047             * encode every char to byte in order to calculate the final byte size.
048             *
049             * @return used buffer size in byte.
050             */
051            @Override
052            public int getBufferSize() {
053                    if (_byteBuffer != null) {
054                            return _byteBuffer.limit();
055                    }
056    
057                    if (_charBuffer != null) {
058                            ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
059                                    getCharacterEncoding(), _charBuffer.duplicate());
060    
061                            return byteBuffer.limit();
062                    }
063    
064                    try {
065                            _flushInternalBuffer();
066                    }
067                    catch (IOException ioe) {
068                            throw new RuntimeException(ioe);
069                    }
070    
071                    if (_unsyncByteArrayOutputStream != null) {
072                            return _unsyncByteArrayOutputStream.size();
073                    }
074    
075                    if (_unsyncStringWriter != null) {
076                            String content = _unsyncStringWriter.toString();
077    
078                            ByteBuffer byteBuffer = CharsetEncoderUtil.encode(
079                                    getCharacterEncoding(), content);
080    
081                            return byteBuffer.limit();
082                    }
083    
084                    return 0;
085            }
086    
087            /**
088             * In char mode, this method will encode every char to byte using the
089             * character set from {@link #getCharacterEncoding()}. Generally, this
090             * method should be called after the last text based post-processing.
091             * Otherwise, you may need decode the byte back to char again which will
092             * result in a lot of unnecessary CPU overhead.
093             */
094            public ByteBuffer getByteBuffer() throws IOException {
095                    if (_byteBuffer != null) {
096                            return _byteBuffer;
097                    }
098    
099                    if (_charBuffer != null) {
100                            return CharsetEncoderUtil.encode(
101                                    getCharacterEncoding(), _charBuffer.duplicate());
102                    }
103    
104                    _flushInternalBuffer();
105    
106                    if (_unsyncByteArrayOutputStream != null) {
107                            return _unsyncByteArrayOutputStream.unsafeGetByteBuffer();
108                    }
109    
110                    if (_unsyncStringWriter != null) {
111                            String content = _unsyncStringWriter.toString();
112    
113                            return CharsetEncoderUtil.encode(getCharacterEncoding(), content);
114                    }
115    
116                    return _emptyByteBuffer;
117            }
118    
119            /**
120             * In char mode, this method will encode every byte to char using the
121             * character set from {@link #getCharacterEncoding()}. Make sure the byte
122             * data is actually encoded chars. Otherwise, the decoding will generate
123             * meaningless data, or worse, even encode the output back and the exact
124             * same character set may not able to retrieve the exact same byte data. For
125             * example, decode an image byte data to char, then encode the chars back to
126             * byte with same character set, will most likely create a corrupted image.
127             */
128            public CharBuffer getCharBuffer() throws IOException {
129                    if (_charBuffer != null) {
130                            return _charBuffer;
131                    }
132    
133                    if (_byteBuffer != null) {
134                            return CharsetDecoderUtil.decode(
135                                    getCharacterEncoding(), _byteBuffer.duplicate());
136                    }
137    
138                    _flushInternalBuffer();
139    
140                    if (_unsyncStringWriter != null) {
141                            return CharBuffer.wrap(_unsyncStringWriter.toString());
142                    }
143    
144                    if (_unsyncByteArrayOutputStream != null) {
145                            ByteBuffer byteBuffer =
146                                    _unsyncByteArrayOutputStream.unsafeGetByteBuffer();
147    
148                            return CharsetDecoderUtil.decode(
149                                    getCharacterEncoding(), byteBuffer);
150                    }
151    
152                    return _emptyCharBuffer;
153            }
154    
155            @Override
156            public ServletOutputStream getOutputStream() {
157                    if (calledGetWriter) {
158                            throw new IllegalStateException(
159                                    "Cannot obtain OutputStream because Writer is already in use");
160                    }
161    
162                    if (_servletOutputStream != null) {
163                            return _servletOutputStream;
164                    }
165    
166                    resetBuffer(true);
167    
168                    _unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream();
169    
170                    _servletOutputStream = new ServletOutputStreamAdapter(
171                            _unsyncByteArrayOutputStream);
172    
173                    calledGetOutputStream = true;
174    
175                    return _servletOutputStream;
176            }
177    
178            /**
179             * @see #getCharBuffer()
180             */
181            public String getString() throws IOException {
182                    if (_charBuffer != null) {
183                            return _charBuffer.toString();
184                    }
185    
186                    if (_byteBuffer != null) {
187                            CharBuffer charBuffer = CharsetDecoderUtil.decode(
188                                    getCharacterEncoding(), _byteBuffer.duplicate());
189    
190                            return charBuffer.toString();
191                    }
192    
193                    _flushInternalBuffer();
194    
195                    if (_unsyncStringWriter != null) {
196                            return _unsyncStringWriter.toString();
197                    }
198    
199                    if (_unsyncByteArrayOutputStream != null) {
200                            ByteBuffer byteBuffer =
201                                    _unsyncByteArrayOutputStream.unsafeGetByteBuffer();
202    
203                            CharBuffer charBuffer = CharsetDecoderUtil.decode(
204                                    getCharacterEncoding(), byteBuffer);
205    
206                            return charBuffer.toString();
207                    }
208    
209                    return StringPool.BLANK;
210            }
211    
212            /**
213             * @see #getCharBuffer()
214             */
215            public StringBundler getStringBundler() throws IOException {
216                    if (_charBuffer != null) {
217                            StringBundler sb = new StringBundler(1);
218    
219                            sb.append(_charBuffer.toString());
220    
221                            return sb;
222                    }
223    
224                    if (_byteBuffer != null) {
225                            CharBuffer charBuffer = CharsetDecoderUtil.decode(
226                                    getCharacterEncoding(), _byteBuffer.duplicate());
227    
228                            StringBundler sb = new StringBundler(1);
229    
230                            sb.append(charBuffer.toString());
231    
232                            return sb;
233                    }
234    
235                    _flushInternalBuffer();
236    
237                    if (_unsyncStringWriter != null) {
238                            return _unsyncStringWriter.getStringBundler();
239                    }
240    
241                    if (_unsyncByteArrayOutputStream != null) {
242                            ByteBuffer byteBuffer =
243                                    _unsyncByteArrayOutputStream.unsafeGetByteBuffer();
244    
245                            CharBuffer charBuffer = CharsetDecoderUtil.decode(
246                                    getCharacterEncoding(), byteBuffer);
247    
248                            StringBundler sb = new StringBundler(1);
249    
250                            sb.append(charBuffer.toString());
251    
252                            return sb;
253                    }
254    
255                    return new StringBundler(1);
256            }
257    
258            @Override
259            public PrintWriter getWriter() {
260                    if (calledGetOutputStream) {
261                            throw new IllegalStateException(
262                                    "Cannot obtain Writer because OutputStream is already in use");
263                    }
264    
265                    if (_printWriter != null) {
266                            return _printWriter;
267                    }
268    
269                    resetBuffer(true);
270    
271                    _unsyncStringWriter = new UnsyncStringWriter();
272    
273                    _printWriter = UnsyncPrintWriterPool.borrow(_unsyncStringWriter);
274    
275                    calledGetWriter = true;
276    
277                    return _printWriter;
278            }
279    
280            public boolean isByteMode() {
281                    if ((_byteBuffer != null) || (_unsyncByteArrayOutputStream != null)) {
282                            return true;
283                    }
284    
285                    return false;
286            }
287    
288            public boolean isCharMode() {
289                    if ((_charBuffer != null) || (_unsyncStringWriter != null)) {
290                            return true;
291                    }
292    
293                    return false;
294            }
295    
296            public void outputBuffer() throws IOException {
297                    _flushInternalBuffer();
298    
299                    HttpServletResponse response = (HttpServletResponse)getResponse();
300    
301                    if ((_byteBuffer != null) || calledGetOutputStream) {
302                            ServletResponseUtil.write(response, getByteBuffer());
303                    }
304                    else if ((_charBuffer != null) || calledGetWriter) {
305                            ServletResponseUtil.write(response, getCharBuffer());
306                    }
307            }
308    
309            @Override
310            public void setBufferSize(int bufferSize) {
311                    if (isCommitted()) {
312                            throw new IllegalStateException("Set buffer size after commit");
313                    }
314    
315                    // Buffered response cannot accept buffer size because it has an
316                    // internal buffer that grows as needed
317    
318            }
319    
320            public void setByteBuffer(ByteBuffer byteBuffer) {
321                    resetBuffer(true);
322    
323                    _byteBuffer = byteBuffer;
324    
325                    if (byteBuffer != null) {
326                            _servletOutputStream = new ServletOutputStreamAdapter(
327                                    new DummyOutputStream());
328    
329                            calledGetOutputStream = true;
330                    }
331            }
332    
333            public void setCharBuffer(CharBuffer charBuffer) {
334                    resetBuffer(true);
335    
336                    _charBuffer = charBuffer;
337    
338                    if (charBuffer != null) {
339                            _printWriter = UnsyncPrintWriterPool.borrow(new DummyWriter());
340    
341                            calledGetWriter = true;
342                    }
343            }
344    
345            @Override
346            public void setContentLength(int contentLength) {
347    
348                    // Buffered response cannot accept content length because content post
349                    // processing may cause length change
350    
351            }
352    
353            public void setString(String string) {
354                    setCharBuffer(CharBuffer.wrap(string));
355            }
356    
357            @Override
358            protected void resetBuffer(boolean nullOutReferences) {
359                    if (nullOutReferences) {
360                            calledGetOutputStream = false;
361                            calledGetWriter = false;
362    
363                            _printWriter = null;
364                            _servletOutputStream = null;
365                            _unsyncByteArrayOutputStream = null;
366                            _unsyncStringWriter = null;
367                    }
368                    else {
369                            if (_unsyncByteArrayOutputStream != null) {
370                                    _unsyncByteArrayOutputStream.reset();
371                            }
372    
373                            if (_unsyncStringWriter != null) {
374                                    _unsyncStringWriter.reset();
375                            }
376    
377                            if (_byteBuffer != null) {
378                                    _servletOutputStream = null;
379    
380                                    calledGetOutputStream = false;
381                            }
382    
383                            if (_charBuffer != null) {
384                                    _printWriter = null;
385    
386                                    calledGetWriter = false;
387                            }
388                    }
389    
390                    _byteBuffer = null;
391                    _charBuffer = null;
392            }
393    
394            private void _flushInternalBuffer() throws IOException {
395                    if (calledGetOutputStream) {
396                            _servletOutputStream.flush();
397                    }
398                    else if (calledGetWriter) {
399                            _printWriter.flush();
400                    }
401            }
402    
403            private static ByteBuffer _emptyByteBuffer = ByteBuffer.allocate(0);
404            private static CharBuffer _emptyCharBuffer = CharBuffer.allocate(0);
405    
406            private ByteBuffer _byteBuffer;
407            private CharBuffer _charBuffer;
408            private PrintWriter _printWriter;
409            private ServletOutputStream _servletOutputStream;
410            private UnsyncByteArrayOutputStream _unsyncByteArrayOutputStream;
411            private UnsyncStringWriter _unsyncStringWriter;
412    
413    }