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                    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                    if (isCommitted()) {
362                            throw new IllegalStateException("Send error after commit");
363                    }
364    
365                    _metaData._error = true;
366                    _metaData._status = status;
367    
368                    resetBuffer();
369    
370                    _committed = true;
371    
372                    super.sendError(status);
373            }
374    
375            @Override
376            public void sendError(int status, String errorMessage) throws IOException {
377                    if (isCommitted()) {
378                            throw new IllegalStateException("Send error after commit");
379                    }
380    
381                    _metaData._error = true;
382                    _metaData._errorMessage = errorMessage;
383                    _metaData._status = status;
384    
385                    resetBuffer();
386    
387                    _committed = true;
388    
389                    super.sendError(status, errorMessage);
390            }
391    
392            @Override
393            public void sendRedirect(String location) throws IOException {
394                    if (isCommitted()) {
395                            throw new IllegalStateException("Send redirect after commit");
396                    }
397    
398                    resetBuffer(true);
399    
400                    setStatus(SC_FOUND);
401    
402                    _metaData._location = location;
403    
404                    _committed = true;
405    
406                    super.sendRedirect(location);
407            }
408    
409            @Override
410            public void setBufferSize(int bufferSize) {
411                    if (isCommitted()) {
412                            throw new IllegalStateException("Set buffer size after commit");
413                    }
414    
415                    _metaData._bufferSize = bufferSize;
416    
417                    super.setBufferSize(bufferSize);
418            }
419    
420            @Override
421            public void setCharacterEncoding(String charsetName) {
422                    if (isCommitted()) {
423                            return;
424                    }
425    
426                    if (calledGetWriter) {
427                            return;
428                    }
429    
430                    if (charsetName == null) {
431                            return;
432                    }
433    
434                    _metaData._charsetName = charsetName;
435    
436                    super.setCharacterEncoding(charsetName);
437            }
438    
439            @Override
440            public void setContentLength(int contentLength) {
441                    if (isCommitted()) {
442                            return;
443                    }
444    
445                    _metaData._contentLength = contentLength;
446    
447                    super.setContentLength(contentLength);
448            }
449    
450            @Override
451            public void setContentType(String contentType) {
452                    if (isCommitted()) {
453                            return;
454                    }
455    
456                    if (contentType == null) {
457                            return;
458                    }
459    
460                    int index = contentType.indexOf(CharPool.SEMICOLON);
461    
462                    if (index != -1) {
463                            String firstPart = contentType.substring(0, index);
464    
465                            _metaData._contentType = firstPart.trim();
466    
467                            index = contentType.indexOf("charset=");
468    
469                            if (index != -1) {
470                                    String charsetName = contentType.substring(index + 8);
471    
472                                    charsetName = charsetName.trim();
473    
474                                    setCharacterEncoding(charsetName);
475                            }
476                            else {
477                                    _metaData._charsetName = null;
478                            }
479                    }
480                    else {
481                            _metaData._contentType = contentType;
482    
483                            _metaData._charsetName = null;
484                    }
485    
486                    super.setContentType(contentType);
487            }
488    
489            @Override
490            public void setDateHeader(String name, long value) {
491                    Set<Header> values = new HashSet<Header>();
492    
493                    _metaData._headers.put(name, values);
494    
495                    Header header = new Header(value);
496    
497                    values.add(header);
498    
499                    super.setDateHeader(name, value);
500            }
501    
502            @Override
503            public void setHeader(String name, String value) {
504                    if (name.equals(HttpHeaders.CONTENT_TYPE)) {
505                            setContentType(value);
506    
507                            return;
508                    }
509    
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.setHeader(name, value);
519            }
520    
521            @Override
522            public void setIntHeader(String name, int value) {
523                    Set<Header> values = new HashSet<Header>();
524    
525                    _metaData._headers.put(name, values);
526    
527                    Header header = new Header(value);
528    
529                    values.add(header);
530    
531                    super.setIntHeader(name, value);
532            }
533    
534            @Override
535            public void setLocale(Locale locale) {
536                    if (isCommitted()) {
537                            return;
538                    }
539    
540                    _metaData._locale = locale;
541    
542                    super.setLocale(locale);
543            }
544    
545            @Override
546            public void setStatus(int status) {
547                    if (isCommitted()) {
548                            return;
549                    }
550    
551                    _metaData._status = status;
552    
553                    super.setStatus(status);
554            }
555    
556            @Override
557            @SuppressWarnings("deprecation")
558            public void setStatus(int status, String statusMessage) {
559                    if (isCommitted()) {
560                            return;
561                    }
562    
563                    _metaData._status = status;
564                    _metaData._statusMessage = statusMessage;
565    
566                    super.setStatus(status, statusMessage);
567            }
568    
569            @Override
570            public String toString() {
571                    StringBundler sb = new StringBundler(23);
572    
573                    sb.append("{bufferSize=");
574                    sb.append(_metaData._bufferSize);
575                    sb.append(", charsetName=");
576                    sb.append(_metaData._charsetName);
577                    sb.append(", committed=");
578                    sb.append(_committed);
579                    sb.append(", contentLength=");
580                    sb.append(_metaData._contentLength);
581                    sb.append(", contentType=");
582                    sb.append(_metaData._contentType);
583                    sb.append(", error=");
584                    sb.append(_metaData._error);
585                    sb.append(", errorMessage=");
586                    sb.append(_metaData._errorMessage);
587                    sb.append(", headers=");
588                    sb.append(_metaData._headers);
589                    sb.append(", location=");
590                    sb.append(_metaData._location);
591                    sb.append(", locale=");
592                    sb.append(_metaData._locale);
593                    sb.append(", status=");
594                    sb.append(_metaData._status);
595                    sb.append("}");
596    
597                    return sb.toString();
598            }
599    
600            public static class MetaData implements Serializable {
601    
602                    private int _bufferSize;
603                    private String _charsetName;
604                    private int _contentLength = -1;
605                    private String _contentType;
606                    private boolean _error;
607                    private String _errorMessage;
608                    private final Map<String, Set<Header>> _headers =
609                            new HashMap<String, Set<Header>>();
610                    private Locale _locale;
611                    private String _location;
612                    private int _status = SC_OK;
613                    private String _statusMessage;
614    
615            }
616    
617            /**
618             * Stub method for subclass to provide buffer resetting logic.
619             *
620             * @param nullOutReferences whether to reset flags. It is not directly used
621             *        by this class. Subclasses with an actual buffer may behave
622             *        differently depending on the value of this parameter.
623             */
624            protected void resetBuffer(boolean nullOutReferences) {
625                    super.resetBuffer();
626            }
627    
628            protected boolean calledGetOutputStream;
629            protected boolean calledGetWriter;
630    
631            private boolean _committed;
632            private final MetaData _metaData = new MetaData();
633    
634    }