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.util.CharPool;
018    import com.liferay.portal.kernel.util.StringBundler;
019    import com.liferay.portal.kernel.util.StringPool;
020    
021    import java.io.IOException;
022    import java.io.PrintWriter;
023    import java.io.Serializable;
024    
025    import java.util.ArrayList;
026    import java.util.Collection;
027    import java.util.Collections;
028    import java.util.HashMap;
029    import java.util.HashSet;
030    import java.util.List;
031    import java.util.Locale;
032    import java.util.Map;
033    import java.util.Set;
034    
035    import javax.servlet.ServletOutputStream;
036    import javax.servlet.ServletResponse;
037    import javax.servlet.http.Cookie;
038    import javax.servlet.http.HttpServletResponse;
039    import javax.servlet.http.HttpServletResponseWrapper;
040    
041    /**
042     * @author Shuyang Zhou
043     */
044    public class MetaInfoCacheServletResponse extends HttpServletResponseWrapper {
045    
046            @SuppressWarnings("deprecation")
047            public static void finishResponse(
048                            MetaData metaInfoDataBag, HttpServletResponse response)
049                    throws IOException {
050    
051                    if (response.isCommitted()) {
052                            return;
053                    }
054    
055                    for (Map.Entry<String, Set<Header>> entry :
056                                    metaInfoDataBag._headers.entrySet()) {
057    
058                            String key = entry.getKey();
059    
060                            boolean first = true;
061    
062                            for (Header header : entry.getValue()) {
063                                    if (first) {
064                                            header.setToResponse(key, response);
065    
066                                            first = false;
067                                    }
068                                    else {
069                                            header.addToResponse(key, response);
070                                    }
071                            }
072                    }
073    
074                    if (metaInfoDataBag._location != null) {
075                            response.sendRedirect(metaInfoDataBag._location);
076                    }
077                    else if (metaInfoDataBag._error) {
078                            response.sendError(
079                                    metaInfoDataBag._status, metaInfoDataBag._errorMessage);
080                    }
081                    else {
082                            if (metaInfoDataBag._charsetName != null) {
083                                    response.setCharacterEncoding(metaInfoDataBag._charsetName);
084                            }
085    
086                            if (metaInfoDataBag._contentLength != -1) {
087                                    response.setContentLength(metaInfoDataBag._contentLength);
088                            }
089    
090                            if (metaInfoDataBag._contentType != null) {
091                                    response.setContentType(metaInfoDataBag._contentType);
092                            }
093    
094                            if (metaInfoDataBag._locale != null) {
095                                    response.setLocale(metaInfoDataBag._locale);
096                            }
097    
098                            if (metaInfoDataBag._status != SC_OK) {
099                                    response.setStatus(
100                                            metaInfoDataBag._status, metaInfoDataBag._statusMessage);
101                            }
102                    }
103            }
104    
105            public MetaInfoCacheServletResponse(HttpServletResponse response) {
106                    super(response);
107            }
108    
109            @Override
110            public void addCookie(Cookie cookie) {
111    
112                    // The correct header name should be "Set-Cookie". Otherwise, the method
113                    // containsHeader will not able to detect cookies with the correct
114                    // header name.
115    
116                    Set<Header> values = _metaData._headers.get(HttpHeaders.SET_COOKIE);
117    
118                    if (values == null) {
119                            values = new HashSet<Header>();
120    
121                            _metaData._headers.put(HttpHeaders.SET_COOKIE, values);
122                    }
123    
124                    Header header = new Header(cookie);
125    
126                    values.add(header);
127    
128                    super.addCookie(cookie);
129            }
130    
131            @Override
132            public void addDateHeader(String name, long value) {
133                    Set<Header> values = _metaData._headers.get(name);
134    
135                    if (values == null) {
136                            values = new HashSet<Header>();
137    
138                            _metaData._headers.put(name, values);
139                    }
140    
141                    Header header = new Header(value);
142    
143                    values.add(header);
144    
145                    super.addDateHeader(name, value);
146            }
147    
148            @Override
149            public void addHeader(String name, String value) {
150                    if (name.equals(HttpHeaders.CONTENT_TYPE)) {
151                            setContentType(value);
152    
153                            return;
154                    }
155    
156                    Set<Header> values = _metaData._headers.get(name);
157    
158                    if (values == null) {
159                            values = new HashSet<Header>();
160    
161                            _metaData._headers.put(name, values);
162                    }
163    
164                    Header header = new Header(value);
165    
166                    values.add(header);
167    
168                    super.addHeader(name, value);
169            }
170    
171            @Override
172            public void addIntHeader(String name, int value) {
173                    Set<Header> values = _metaData._headers.get(name);
174    
175                    if (values == null) {
176                            values = new HashSet<Header>();
177    
178                            _metaData._headers.put(name, values);
179                    }
180    
181                    Header header = new Header(value);
182    
183                    values.add(header);
184    
185                    super.addIntHeader(name, value);
186            }
187    
188            @Override
189            public boolean containsHeader(String name) {
190                    return _metaData._headers.containsKey(name);
191            }
192    
193            public void finishResponse() throws IOException {
194                    HttpServletResponse response = (HttpServletResponse)getResponse();
195    
196                    finishResponse(_metaData, response);
197    
198                    _committed = true;
199            }
200    
201            @Override
202            @SuppressWarnings("unused")
203            public void flushBuffer() throws IOException {
204                    _committed = true;
205            }
206    
207            @Override
208            public int getBufferSize() {
209                    return _metaData._bufferSize;
210            }
211    
212            @Override
213            public String getCharacterEncoding() {
214    
215                    // We are supposed to default to ISO-8859-1 based on the Servlet
216                    // specification. However, most application servers honors the system
217                    // property "file.encoding". Using the system default character gives us
218                    // better application server compatibility.
219    
220                    if (_metaData._charsetName == null) {
221                            return StringPool.DEFAULT_CHARSET_NAME;
222                    }
223    
224                    return _metaData._charsetName;
225            }
226    
227            @Override
228            public String getContentType() {
229                    String contentType = _metaData._contentType;
230    
231                    if ((contentType != null) && (_metaData._charsetName != null)) {
232                            contentType = contentType.concat("; charset=").concat(
233                                    _metaData._charsetName);
234                    }
235    
236                    return contentType;
237            }
238    
239            /**
240             * When the header for this given name is "Cookie", the return value cannot
241             * be used for the "Set-Cookie" header. The string representation for
242             * "Cookie" is application server specific. The only safe way to add the
243             * header is to call {@link HttpServletResponse#addCookie(Cookie)}.
244             */
245            @Override
246            public String getHeader(String name) {
247                    Set<Header> values = _metaData._headers.get(name);
248    
249                    if (values == null) {
250                            return null;
251                    }
252    
253                    Header header = values.iterator().next();
254    
255                    return header.toString();
256            }
257    
258            @Override
259            public Collection<String> getHeaderNames() {
260                    return _metaData._headers.keySet();
261            }
262    
263            public Map<String, Set<Header>> getHeaders() {
264                    return _metaData._headers;
265            }
266    
267            /**
268             * When the header for this given name is "Cookie", the return value cannot
269             * be used for the "Set-Cookie" header. The string representation for
270             * "Cookie" is application server specific. The only safe way to add the
271             * header is to call {@link HttpServletResponse#addCookie(Cookie)}.
272             */
273            @Override
274            public Collection<String> getHeaders(String name) {
275                    Set<Header> values = _metaData._headers.get(name);
276    
277                    if (values == null) {
278                            return Collections.emptyList();
279                    }
280    
281                    List<String> stringValues = new ArrayList<String>();
282    
283                    for (Header header : values) {
284                            stringValues.add(header.toString());
285                    }
286    
287                    return stringValues;
288            }
289    
290            @Override
291            public Locale getLocale() {
292                    return _metaData._locale;
293            }
294    
295            public MetaData getMetaData() {
296                    return _metaData;
297            }
298    
299            @Override
300            public ServletOutputStream getOutputStream() throws IOException {
301                    calledGetOutputStream = true;
302    
303                    return super.getOutputStream();
304            }
305    
306            @Override
307            public int getStatus() {
308                    return _metaData._status;
309            }
310    
311            @Override
312            public PrintWriter getWriter() throws IOException {
313                    calledGetWriter = true;
314    
315                    return super.getWriter();
316            }
317    
318            @Override
319            public boolean isCommitted() {
320                    ServletResponse servletResponse = getResponse();
321    
322                    return _committed || servletResponse.isCommitted();
323            }
324    
325            @Override
326            public void reset() {
327                    if (isCommitted()) {
328                            throw new IllegalStateException("Reset after commit");
329                    }
330    
331                    // No need to reset _error, _errorMessage and _location, because setting
332                    // them requires commit.
333    
334                    _metaData._charsetName = null;
335                    _metaData._contentLength = -1;
336                    _metaData._contentType = null;
337                    _metaData._headers.clear();
338                    _metaData._locale = null;
339                    _metaData._status = SC_OK;
340                    _metaData._statusMessage = null;
341    
342                    // calledGetOutputStream and calledGetWriter should be cleared by
343                    // resetBuffer() in subclass.
344    
345                    resetBuffer();
346    
347                    super.reset();
348            }
349    
350            @Override
351            public void resetBuffer() {
352                    if (isCommitted()) {
353                            throw new IllegalStateException("Reset buffer after commit");
354                    }
355    
356                    resetBuffer(false);
357            }
358    
359            @Override
360            public void sendError(int status) throws IOException {
361                    sendError(status, null);
362            }
363    
364            @Override
365            public void sendError(int status, String errorMessage) throws IOException {
366                    if (isCommitted()) {
367                            throw new IllegalStateException("Send error after commit");
368                    }
369    
370                    _metaData._error = true;
371                    _metaData._errorMessage = errorMessage;
372                    _metaData._status = status;
373    
374                    resetBuffer();
375    
376                    _committed = true;
377    
378                    super.sendError(status, errorMessage);
379            }
380    
381            @Override
382            public void sendRedirect(String location) throws IOException {
383                    if (isCommitted()) {
384                            throw new IllegalStateException("Send redirect after commit");
385                    }
386    
387                    resetBuffer(true);
388    
389                    _metaData._location = location;
390    
391                    _committed = true;
392    
393                    super.sendRedirect(location);
394            }
395    
396            @Override
397            public void setBufferSize(int bufferSize) {
398                    if (isCommitted()) {
399                            throw new IllegalStateException("Set buffer size after commit");
400                    }
401    
402                    _metaData._bufferSize = bufferSize;
403    
404                    super.setBufferSize(bufferSize);
405            }
406    
407            @Override
408            public void setCharacterEncoding(String charsetName) {
409                    if (isCommitted()) {
410                            return;
411                    }
412    
413                    if (calledGetWriter) {
414                            return;
415                    }
416    
417                    if (charsetName == null) {
418                            return;
419                    }
420    
421                    _metaData._charsetName = charsetName;
422    
423                    super.setCharacterEncoding(charsetName);
424            }
425    
426            @Override
427            public void setContentLength(int contentLength) {
428                    if (isCommitted()) {
429                            return;
430                    }
431    
432                    _metaData._contentLength = contentLength;
433    
434                    super.setContentLength(contentLength);
435            }
436    
437            @Override
438            public void setContentType(String contentType) {
439                    if (isCommitted()) {
440                            return;
441                    }
442    
443                    if (contentType == null) {
444                            return;
445                    }
446    
447                    int index = contentType.indexOf(CharPool.SEMICOLON);
448    
449                    if (index != -1) {
450                            String firstPart = contentType.substring(0, index);
451    
452                            _metaData._contentType = firstPart.trim();
453    
454                            index = contentType.indexOf("charset=");
455    
456                            if (index != -1) {
457                                    String charsetName = contentType.substring(index + 8);
458    
459                                    charsetName = charsetName.trim();
460    
461                                    setCharacterEncoding(charsetName);
462                            }
463                            else {
464                                    _metaData._charsetName = null;
465                            }
466                    }
467                    else {
468                            _metaData._contentType = contentType;
469    
470                            _metaData._charsetName = null;
471                    }
472    
473                    super.setContentType(contentType);
474            }
475    
476            @Override
477            public void setDateHeader(String name, long value) {
478                    Set<Header> values = new HashSet<Header>();
479    
480                    _metaData._headers.put(name, values);
481    
482                    Header header = new Header(value);
483    
484                    values.add(header);
485    
486                    super.setDateHeader(name, value);
487            }
488    
489            @Override
490            public void setHeader(String name, String value) {
491                    if (name.equals(HttpHeaders.CONTENT_TYPE)) {
492                            setContentType(value);
493    
494                            return;
495                    }
496    
497                    Set<Header> values = new HashSet<Header>();
498    
499                    _metaData._headers.put(name, values);
500    
501                    Header header = new Header(value);
502    
503                    values.add(header);
504    
505                    super.setHeader(name, value);
506            }
507    
508            @Override
509            public void setIntHeader(String name, int value) {
510                    Set<Header> values = new HashSet<Header>();
511    
512                    _metaData._headers.put(name, values);
513    
514                    Header header = new Header(value);
515    
516                    values.add(header);
517    
518                    super.setIntHeader(name, value);
519            }
520    
521            @Override
522            public void setLocale(Locale locale) {
523                    if (isCommitted()) {
524                            return;
525                    }
526    
527                    _metaData._locale = locale;
528    
529                    super.setLocale(locale);
530            }
531    
532            @Override
533            public void setStatus(int status) {
534                    setStatus(status, null);
535            }
536    
537            /**
538             * @deprecated
539             */
540            @Override
541            public void setStatus(int status, String statusMessage) {
542                    if (isCommitted()) {
543                            return;
544                    }
545    
546                    _metaData._status = status;
547                    _metaData._statusMessage = statusMessage;
548    
549                    super.setStatus(status, statusMessage);
550            }
551    
552            @Override
553            public String toString() {
554                    StringBundler sb = new StringBundler(23);
555    
556                    sb.append("{bufferSize=");
557                    sb.append(_metaData._bufferSize);
558                    sb.append(", charsetName=");
559                    sb.append(_metaData._charsetName);
560                    sb.append(", committed=");
561                    sb.append(_committed);
562                    sb.append(", contentLength=");
563                    sb.append(_metaData._contentLength);
564                    sb.append(", contentType=");
565                    sb.append(_metaData._contentType);
566                    sb.append(", error=");
567                    sb.append(_metaData._error);
568                    sb.append(", errorMessage=");
569                    sb.append(_metaData._errorMessage);
570                    sb.append(", headers=");
571                    sb.append(_metaData._headers);
572                    sb.append(", location=");
573                    sb.append(_metaData._location);
574                    sb.append(", locale=");
575                    sb.append(_metaData._locale);
576                    sb.append(", status=");
577                    sb.append(_metaData._status);
578                    sb.append("}");
579    
580                    return sb.toString();
581            }
582    
583            public static class MetaData implements Serializable {
584    
585                    private int _bufferSize;
586                    private String _charsetName;
587                    private int _contentLength = -1;
588                    private String _contentType;
589                    private boolean _error;
590                    private String _errorMessage;
591                    private Map<String, Set<Header>> _headers =
592                            new HashMap<String, Set<Header>>();
593                    private Locale _locale;
594                    private String _location;
595                    private int _status = SC_OK;
596                    private String _statusMessage;
597    
598            }
599    
600            /**
601             * Stub method for subclass to provide buffer resetting logic.
602             *
603             * @param nullOutReferences whether to reset flags. It is not directly used
604             *        by this class. Subclasses with an actual buffer may behave
605             *        differently depending on the value of this parameter.
606             */
607            protected void resetBuffer(boolean nullOutReferences) {
608                    super.resetBuffer();
609            }
610    
611            protected boolean calledGetOutputStream;
612            protected boolean calledGetWriter;
613    
614            private boolean _committed;
615            private MetaData _metaData = new MetaData();
616    
617    }