001    /**
002     * Copyright (c) 2000-present 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<>();
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<>();
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<>();
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<>();
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            public void finishResponse(boolean reapplyMetaData) throws IOException {
196                    HttpServletResponse response = (HttpServletResponse)getResponse();
197    
198                    if (reapplyMetaData) {
199                            finishResponse(_metaData, response);
200                    }
201    
202                    _committed = true;
203            }
204    
205            @Override
206            @SuppressWarnings("unused")
207            public void flushBuffer() throws IOException {
208                    _committed = true;
209            }
210    
211            @Override
212            public int getBufferSize() {
213                    return _metaData._bufferSize;
214            }
215    
216            @Override
217            public String getCharacterEncoding() {
218    
219                    // We are supposed to default to ISO-8859-1 based on the Servlet
220                    // specification. However, most application servers honors the system
221                    // property "file.encoding". Using the system default character gives us
222                    // better application server compatibility.
223    
224                    if (_metaData._charsetName == null) {
225                            return StringPool.DEFAULT_CHARSET_NAME;
226                    }
227    
228                    return _metaData._charsetName;
229            }
230    
231            @Override
232            public String getContentType() {
233                    String contentType = _metaData._contentType;
234    
235                    if ((contentType != null) && (_metaData._charsetName != null)) {
236                            contentType = contentType.concat("; charset=").concat(
237                                    _metaData._charsetName);
238                    }
239    
240                    return contentType;
241            }
242    
243            /**
244             * When the header for this given name is "Cookie", the return value cannot
245             * be used for the "Set-Cookie" header. The string representation for
246             * "Cookie" is application server specific. The only safe way to add the
247             * header is to call {@link HttpServletResponse#addCookie(Cookie)}.
248             */
249            @Override
250            public String getHeader(String name) {
251                    Set<Header> values = _metaData._headers.get(name);
252    
253                    if (values == null) {
254                            return null;
255                    }
256    
257                    Header header = values.iterator().next();
258    
259                    return header.toString();
260            }
261    
262            @Override
263            public Collection<String> getHeaderNames() {
264                    return _metaData._headers.keySet();
265            }
266    
267            public Map<String, Set<Header>> getHeaders() {
268                    return _metaData._headers;
269            }
270    
271            /**
272             * When the header for this given name is "Cookie", the return value cannot
273             * be used for the "Set-Cookie" header. The string representation for
274             * "Cookie" is application server specific. The only safe way to add the
275             * header is to call {@link HttpServletResponse#addCookie(Cookie)}.
276             */
277            @Override
278            public Collection<String> getHeaders(String name) {
279                    Set<Header> values = _metaData._headers.get(name);
280    
281                    if (values == null) {
282                            return Collections.emptyList();
283                    }
284    
285                    List<String> stringValues = new ArrayList<>();
286    
287                    for (Header header : values) {
288                            stringValues.add(header.toString());
289                    }
290    
291                    return stringValues;
292            }
293    
294            @Override
295            public Locale getLocale() {
296                    return _metaData._locale;
297            }
298    
299            public MetaData getMetaData() {
300                    return _metaData;
301            }
302    
303            @Override
304            public ServletOutputStream getOutputStream() throws IOException {
305                    calledGetOutputStream = true;
306    
307                    return super.getOutputStream();
308            }
309    
310            @Override
311            public int getStatus() {
312                    return _metaData._status;
313            }
314    
315            @Override
316            public PrintWriter getWriter() throws IOException {
317                    calledGetWriter = true;
318    
319                    return super.getWriter();
320            }
321    
322            @Override
323            public boolean isCommitted() {
324                    ServletResponse servletResponse = getResponse();
325    
326                    return _committed || servletResponse.isCommitted();
327            }
328    
329            @Override
330            public void reset() {
331                    if (isCommitted()) {
332                            throw new IllegalStateException("Reset after commit");
333                    }
334    
335                    // No need to reset _error, _errorMessage and _location, because setting
336                    // them requires commit.
337    
338                    _metaData._charsetName = null;
339                    _metaData._contentLength = -1;
340                    _metaData._contentType = null;
341                    _metaData._headers.clear();
342                    _metaData._locale = null;
343                    _metaData._status = SC_OK;
344                    _metaData._statusMessage = null;
345    
346                    // calledGetOutputStream and calledGetWriter should be cleared by
347                    // resetBuffer() in subclass.
348    
349                    resetBuffer();
350    
351                    super.reset();
352            }
353    
354            @Override
355            public void resetBuffer() {
356                    if (isCommitted()) {
357                            throw new IllegalStateException("Reset buffer after commit");
358                    }
359    
360                    resetBuffer(false);
361            }
362    
363            @Override
364            public void sendError(int status) throws IOException {
365                    if (isCommitted()) {
366                            throw new IllegalStateException("Send error after commit");
367                    }
368    
369                    _metaData._error = true;
370                    _metaData._status = status;
371    
372                    resetBuffer();
373    
374                    _committed = true;
375    
376                    super.sendError(status);
377            }
378    
379            @Override
380            public void sendError(int status, String errorMessage) throws IOException {
381                    if (isCommitted()) {
382                            throw new IllegalStateException("Send error after commit");
383                    }
384    
385                    _metaData._error = true;
386                    _metaData._errorMessage = errorMessage;
387                    _metaData._status = status;
388    
389                    resetBuffer();
390    
391                    _committed = true;
392    
393                    super.sendError(status, errorMessage);
394            }
395    
396            @Override
397            public void sendRedirect(String location) throws IOException {
398                    if (isCommitted()) {
399                            throw new IllegalStateException("Send redirect after commit");
400                    }
401    
402                    resetBuffer(true);
403    
404                    setStatus(SC_FOUND);
405    
406                    _metaData._location = location;
407    
408                    _committed = true;
409    
410                    super.sendRedirect(location);
411            }
412    
413            @Override
414            public void setBufferSize(int bufferSize) {
415                    if (isCommitted()) {
416                            throw new IllegalStateException("Set buffer size after commit");
417                    }
418    
419                    _metaData._bufferSize = bufferSize;
420    
421                    super.setBufferSize(bufferSize);
422            }
423    
424            @Override
425            public void setCharacterEncoding(String charsetName) {
426                    if (isCommitted()) {
427                            return;
428                    }
429    
430                    if (calledGetWriter) {
431                            return;
432                    }
433    
434                    if (charsetName == null) {
435                            return;
436                    }
437    
438                    _metaData._charsetName = charsetName;
439    
440                    super.setCharacterEncoding(charsetName);
441            }
442    
443            @Override
444            public void setContentLength(int contentLength) {
445                    if (isCommitted()) {
446                            return;
447                    }
448    
449                    _metaData._contentLength = contentLength;
450    
451                    super.setContentLength(contentLength);
452            }
453    
454            @Override
455            public void setContentType(String contentType) {
456                    if (isCommitted()) {
457                            return;
458                    }
459    
460                    if (contentType == null) {
461                            return;
462                    }
463    
464                    int index = contentType.indexOf(CharPool.SEMICOLON);
465    
466                    if (index != -1) {
467                            String firstPart = contentType.substring(0, index);
468    
469                            _metaData._contentType = firstPart.trim();
470    
471                            index = contentType.indexOf("charset=");
472    
473                            if (index != -1) {
474                                    String charsetName = contentType.substring(index + 8);
475    
476                                    charsetName = charsetName.trim();
477    
478                                    setCharacterEncoding(charsetName);
479                            }
480                            else {
481                                    _metaData._charsetName = null;
482                            }
483                    }
484                    else {
485                            _metaData._contentType = contentType;
486    
487                            _metaData._charsetName = null;
488                    }
489    
490                    super.setContentType(contentType);
491            }
492    
493            @Override
494            public void setDateHeader(String name, long value) {
495                    Set<Header> values = new HashSet<>();
496    
497                    _metaData._headers.put(name, values);
498    
499                    Header header = new Header(value);
500    
501                    values.add(header);
502    
503                    super.setDateHeader(name, value);
504            }
505    
506            @Override
507            public void setHeader(String name, String value) {
508                    if (name.equals(HttpHeaders.CONTENT_TYPE)) {
509                            setContentType(value);
510    
511                            return;
512                    }
513    
514                    Set<Header> values = new HashSet<>();
515    
516                    _metaData._headers.put(name, values);
517    
518                    Header header = new Header(value);
519    
520                    values.add(header);
521    
522                    super.setHeader(name, value);
523            }
524    
525            @Override
526            public void setIntHeader(String name, int value) {
527                    Set<Header> values = new HashSet<>();
528    
529                    _metaData._headers.put(name, values);
530    
531                    Header header = new Header(value);
532    
533                    values.add(header);
534    
535                    super.setIntHeader(name, value);
536            }
537    
538            @Override
539            public void setLocale(Locale locale) {
540                    if (isCommitted()) {
541                            return;
542                    }
543    
544                    _metaData._locale = locale;
545    
546                    super.setLocale(locale);
547            }
548    
549            @Override
550            public void setStatus(int status) {
551                    if (isCommitted()) {
552                            return;
553                    }
554    
555                    _metaData._status = status;
556    
557                    super.setStatus(status);
558            }
559    
560            @Override
561            @SuppressWarnings("deprecation")
562            public void setStatus(int status, String statusMessage) {
563                    if (isCommitted()) {
564                            return;
565                    }
566    
567                    _metaData._status = status;
568                    _metaData._statusMessage = statusMessage;
569    
570                    super.setStatus(status, statusMessage);
571            }
572    
573            @Override
574            public String toString() {
575                    StringBundler sb = new StringBundler(23);
576    
577                    sb.append("{bufferSize=");
578                    sb.append(_metaData._bufferSize);
579                    sb.append(", charsetName=");
580                    sb.append(_metaData._charsetName);
581                    sb.append(", committed=");
582                    sb.append(_committed);
583                    sb.append(", contentLength=");
584                    sb.append(_metaData._contentLength);
585                    sb.append(", contentType=");
586                    sb.append(_metaData._contentType);
587                    sb.append(", error=");
588                    sb.append(_metaData._error);
589                    sb.append(", errorMessage=");
590                    sb.append(_metaData._errorMessage);
591                    sb.append(", headers=");
592                    sb.append(_metaData._headers);
593                    sb.append(", location=");
594                    sb.append(_metaData._location);
595                    sb.append(", locale=");
596                    sb.append(_metaData._locale);
597                    sb.append(", status=");
598                    sb.append(_metaData._status);
599                    sb.append("}");
600    
601                    return sb.toString();
602            }
603    
604            public static class MetaData implements Serializable {
605    
606                    private int _bufferSize;
607                    private String _charsetName;
608                    private int _contentLength = -1;
609                    private String _contentType;
610                    private boolean _error;
611                    private String _errorMessage;
612                    private final Map<String, Set<Header>> _headers = new HashMap<>();
613                    private Locale _locale;
614                    private String _location;
615                    private int _status = SC_OK;
616                    private String _statusMessage;
617    
618            }
619    
620            protected static void resetThrough(HttpServletResponse response) {
621                    if (response instanceof MetaInfoCacheServletResponse) {
622                            MetaInfoCacheServletResponse metaInfoCacheServletResponse =
623                                    (MetaInfoCacheServletResponse)response;
624    
625                            resetThrough(
626                                    (HttpServletResponse)
627                                            metaInfoCacheServletResponse.getResponse());
628                    }
629                    else {
630                            response.reset();
631                    }
632            }
633    
634            /**
635             * Stub method for subclass to provide buffer resetting logic.
636             *
637             * @param nullOutReferences whether to reset flags. It is not directly used
638             *        by this class. Subclasses with an actual buffer may behave
639             *        differently depending on the value of this parameter.
640             */
641            protected void resetBuffer(boolean nullOutReferences) {
642                    super.resetBuffer();
643            }
644    
645            protected boolean calledGetOutputStream;
646            protected boolean calledGetWriter;
647    
648            private boolean _committed;
649            private final MetaData _metaData = new MetaData();
650    
651    }