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