001    /**
002     * Copyright (c) 2000-2013 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                                    "Unable to obtain OutputStream because Writer is already in " +
160                                            "use");
161                    }
162    
163                    if (_servletOutputStream != null) {
164                            return _servletOutputStream;
165                    }
166    
167                    resetBuffer(true);
168    
169                    _unsyncByteArrayOutputStream = new UnsyncByteArrayOutputStream();
170    
171                    _servletOutputStream = new ServletOutputStreamAdapter(
172                            _unsyncByteArrayOutputStream);
173    
174                    calledGetOutputStream = true;
175    
176                    return _servletOutputStream;
177            }
178    
179            /**
180             * @see #getCharBuffer()
181             */
182            public String getString() throws IOException {
183                    if (_charBuffer != null) {
184                            return _charBuffer.toString();
185                    }
186    
187                    if (_byteBuffer != null) {
188                            CharBuffer charBuffer = CharsetDecoderUtil.decode(
189                                    getCharacterEncoding(), _byteBuffer.duplicate());
190    
191                            return charBuffer.toString();
192                    }
193    
194                    _flushInternalBuffer();
195    
196                    if (_unsyncStringWriter != null) {
197                            return _unsyncStringWriter.toString();
198                    }
199    
200                    if (_unsyncByteArrayOutputStream != null) {
201                            ByteBuffer byteBuffer =
202                                    _unsyncByteArrayOutputStream.unsafeGetByteBuffer();
203    
204                            CharBuffer charBuffer = CharsetDecoderUtil.decode(
205                                    getCharacterEncoding(), byteBuffer);
206    
207                            return charBuffer.toString();
208                    }
209    
210                    return StringPool.BLANK;
211            }
212    
213            /**
214             * @see #getCharBuffer()
215             */
216            public StringBundler getStringBundler() throws IOException {
217                    if (_charBuffer != null) {
218                            StringBundler sb = new StringBundler(1);
219    
220                            sb.append(_charBuffer.toString());
221    
222                            return sb;
223                    }
224    
225                    if (_byteBuffer != null) {
226                            CharBuffer charBuffer = CharsetDecoderUtil.decode(
227                                    getCharacterEncoding(), _byteBuffer.duplicate());
228    
229                            StringBundler sb = new StringBundler(1);
230    
231                            sb.append(charBuffer.toString());
232    
233                            return sb;
234                    }
235    
236                    _flushInternalBuffer();
237    
238                    if (_unsyncStringWriter != null) {
239                            return _unsyncStringWriter.getStringBundler();
240                    }
241    
242                    if (_unsyncByteArrayOutputStream != null) {
243                            ByteBuffer byteBuffer =
244                                    _unsyncByteArrayOutputStream.unsafeGetByteBuffer();
245    
246                            CharBuffer charBuffer = CharsetDecoderUtil.decode(
247                                    getCharacterEncoding(), byteBuffer);
248    
249                            StringBundler sb = new StringBundler(1);
250    
251                            sb.append(charBuffer.toString());
252    
253                            return sb;
254                    }
255    
256                    return new StringBundler(1);
257            }
258    
259            @Override
260            public PrintWriter getWriter() {
261                    if (calledGetOutputStream) {
262                            throw new IllegalStateException(
263                                    "Cannot obtain Writer because OutputStream is already in use");
264                    }
265    
266                    if (_printWriter != null) {
267                            return _printWriter;
268                    }
269    
270                    resetBuffer(true);
271    
272                    _unsyncStringWriter = new UnsyncStringWriter();
273    
274                    _printWriter = UnsyncPrintWriterPool.borrow(_unsyncStringWriter);
275    
276                    calledGetWriter = true;
277    
278                    return _printWriter;
279            }
280    
281            public boolean isByteMode() {
282                    if ((_byteBuffer != null) || (_unsyncByteArrayOutputStream != null)) {
283                            return true;
284                    }
285    
286                    return false;
287            }
288    
289            public boolean isCharMode() {
290                    if ((_charBuffer != null) || (_unsyncStringWriter != null)) {
291                            return true;
292                    }
293    
294                    return false;
295            }
296    
297            public void outputBuffer() throws IOException {
298                    _flushInternalBuffer();
299    
300                    HttpServletResponse response = (HttpServletResponse)getResponse();
301    
302                    if ((_byteBuffer != null) || calledGetOutputStream) {
303                            ServletResponseUtil.write(response, getByteBuffer());
304                    }
305                    else if ((_charBuffer != null) || calledGetWriter) {
306                            ServletResponseUtil.write(response, getCharBuffer());
307                    }
308            }
309    
310            @Override
311            public void setBufferSize(int bufferSize) {
312                    if (isCommitted()) {
313                            throw new IllegalStateException("Set buffer size after commit");
314                    }
315    
316                    // Buffered response cannot accept buffer size because it has an
317                    // internal buffer that grows as needed
318    
319            }
320    
321            public void setByteBuffer(ByteBuffer byteBuffer) {
322                    resetBuffer(true);
323    
324                    _byteBuffer = byteBuffer;
325    
326                    if (byteBuffer != null) {
327                            _servletOutputStream = new ServletOutputStreamAdapter(
328                                    new DummyOutputStream());
329    
330                            calledGetOutputStream = true;
331                    }
332            }
333    
334            public void setCharBuffer(CharBuffer charBuffer) {
335                    resetBuffer(true);
336    
337                    _charBuffer = charBuffer;
338    
339                    if (charBuffer != null) {
340                            _printWriter = UnsyncPrintWriterPool.borrow(new DummyWriter());
341    
342                            calledGetWriter = true;
343                    }
344            }
345    
346            @Override
347            public void setContentLength(int contentLength) {
348    
349                    // Buffered response cannot accept content length because content post
350                    // processing may cause length change
351    
352            }
353    
354            public void setString(String string) {
355                    setCharBuffer(CharBuffer.wrap(string));
356            }
357    
358            @Override
359            protected void resetBuffer(boolean nullOutReferences) {
360                    if (nullOutReferences) {
361                            calledGetOutputStream = false;
362                            calledGetWriter = false;
363    
364                            _printWriter = null;
365                            _servletOutputStream = null;
366                            _unsyncByteArrayOutputStream = null;
367                            _unsyncStringWriter = null;
368                    }
369                    else {
370                            if (_unsyncByteArrayOutputStream != null) {
371                                    _unsyncByteArrayOutputStream.reset();
372                            }
373    
374                            if (_unsyncStringWriter != null) {
375                                    _unsyncStringWriter.reset();
376                            }
377    
378                            if (_byteBuffer != null) {
379                                    _servletOutputStream = null;
380    
381                                    calledGetOutputStream = false;
382                            }
383    
384                            if (_charBuffer != null) {
385                                    _printWriter = null;
386    
387                                    calledGetWriter = false;
388                            }
389                    }
390    
391                    _byteBuffer = null;
392                    _charBuffer = null;
393            }
394    
395            private void _flushInternalBuffer() throws IOException {
396                    if (calledGetOutputStream) {
397                            _servletOutputStream.flush();
398                    }
399                    else if (calledGetWriter) {
400                            _printWriter.flush();
401                    }
402            }
403    
404            private static ByteBuffer _emptyByteBuffer = ByteBuffer.allocate(0);
405            private static CharBuffer _emptyCharBuffer = CharBuffer.allocate(0);
406    
407            private ByteBuffer _byteBuffer;
408            private CharBuffer _charBuffer;
409            private PrintWriter _printWriter;
410            private ServletOutputStream _servletOutputStream;
411            private UnsyncByteArrayOutputStream _unsyncByteArrayOutputStream;
412            private UnsyncStringWriter _unsyncStringWriter;
413    
414    }