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