/**
* Copyright (c) 2000-present Liferay, Inc. All rights reserved.
*
* This library is free software; you can redistribute it and/or modify it under
* the terms of the GNU Lesser General Public License as published by the Free
* Software Foundation; either version 2.1 of the License, or (at your option)
* any later version.
*
* This library is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
* FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
* details.
*/
package com.liferay.portal.kernel.servlet;
import com.liferay.portal.kernel.util.CharPool;
import com.liferay.portal.kernel.util.StringBundler;
import com.liferay.portal.kernel.util.StringPool;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpServletResponseWrapper;
/**
* @author Shuyang Zhou
*/
public class MetaInfoCacheServletResponse extends HttpServletResponseWrapper {
@SuppressWarnings("deprecation")
public static void finishResponse(
MetaData metaInfoDataBag, HttpServletResponse response)
throws IOException {
if (response.isCommitted()) {
return;
}
resetThrough(response);
for (Map.Entry<String, Set<Header>> entry :
metaInfoDataBag._headers.entrySet()) {
String key = entry.getKey();
boolean first = true;
for (Header header : entry.getValue()) {
if (first) {
header.setToResponse(key, response);
first = false;
}
else {
header.addToResponse(key, response);
}
}
}
if (metaInfoDataBag._location != null) {
response.sendRedirect(metaInfoDataBag._location);
}
else if (metaInfoDataBag._error) {
response.sendError(
metaInfoDataBag._status, metaInfoDataBag._errorMessage);
}
else {
if (metaInfoDataBag._charsetName != null) {
response.setCharacterEncoding(metaInfoDataBag._charsetName);
}
if (metaInfoDataBag._contentLength != -1) {
response.setContentLength(metaInfoDataBag._contentLength);
}
if (metaInfoDataBag._contentType != null) {
response.setContentType(metaInfoDataBag._contentType);
}
if (metaInfoDataBag._locale != null) {
response.setLocale(metaInfoDataBag._locale);
}
if (metaInfoDataBag._status != SC_OK) {
response.setStatus(
metaInfoDataBag._status, metaInfoDataBag._statusMessage);
}
}
}
public MetaInfoCacheServletResponse(HttpServletResponse response) {
super(response);
}
@Override
public void addCookie(Cookie cookie) {
// The correct header name should be "Set-Cookie". Otherwise, the method
// containsHeader will not able to detect cookies with the correct
// header name.
Set<Header> values = _metaData._headers.get(HttpHeaders.SET_COOKIE);
if (values == null) {
values = new HashSet<>();
_metaData._headers.put(HttpHeaders.SET_COOKIE, values);
}
Header header = new Header(cookie);
values.add(header);
super.addCookie(cookie);
}
@Override
public void addDateHeader(String name, long value) {
Set<Header> values = _metaData._headers.get(name);
if (values == null) {
values = new HashSet<>();
_metaData._headers.put(name, values);
}
Header header = new Header(value);
values.add(header);
super.addDateHeader(name, value);
}
@Override
public void addHeader(String name, String value) {
if (name.equals(HttpHeaders.CONTENT_TYPE)) {
setContentType(value);
return;
}
Set<Header> values = _metaData._headers.get(name);
if (values == null) {
values = new HashSet<>();
_metaData._headers.put(name, values);
}
Header header = new Header(value);
values.add(header);
super.addHeader(name, value);
}
@Override
public void addIntHeader(String name, int value) {
Set<Header> values = _metaData._headers.get(name);
if (values == null) {
values = new HashSet<>();
_metaData._headers.put(name, values);
}
Header header = new Header(value);
values.add(header);
super.addIntHeader(name, value);
}
@Override
public boolean containsHeader(String name) {
return _metaData._headers.containsKey(name);
}
/**
* @deprecated As of 7.0.0, replaced by {@link #finishResponse(boolean)}}
*/
@Deprecated
public void finishResponse() throws IOException {
finishResponse(false);
}
public void finishResponse(boolean reapplyMetaData) throws IOException {
HttpServletResponse response = (HttpServletResponse)getResponse();
if (reapplyMetaData) {
finishResponse(_metaData, response);
}
_committed = true;
}
@Override
@SuppressWarnings("unused")
public void flushBuffer() throws IOException {
_committed = true;
}
@Override
public int getBufferSize() {
return _metaData._bufferSize;
}
@Override
public String getCharacterEncoding() {
// We are supposed to default to ISO-8859-1 based on the Servlet
// specification. However, most application servers honors the system
// property "file.encoding". Using the system default character gives us
// better application server compatibility.
if (_metaData._charsetName == null) {
return StringPool.DEFAULT_CHARSET_NAME;
}
return _metaData._charsetName;
}
@Override
public String getContentType() {
String contentType = _metaData._contentType;
if ((contentType != null) && (_metaData._charsetName != null)) {
contentType = contentType.concat("; charset=").concat(
_metaData._charsetName);
}
return contentType;
}
/**
* When the header for this given name is "Cookie", the return value cannot
* be used for the "Set-Cookie" header. The string representation for
* "Cookie" is application server specific. The only safe way to add the
* header is to call {@link HttpServletResponse#addCookie(Cookie)}.
*/
@Override
public String getHeader(String name) {
Set<Header> values = _metaData._headers.get(name);
if (values == null) {
return null;
}
Header header = values.iterator().next();
return header.toString();
}
@Override
public Collection<String> getHeaderNames() {
return _metaData._headers.keySet();
}
public Map<String, Set<Header>> getHeaders() {
return _metaData._headers;
}
/**
* When the header for this given name is "Cookie", the return value cannot
* be used for the "Set-Cookie" header. The string representation for
* "Cookie" is application server specific. The only safe way to add the
* header is to call {@link HttpServletResponse#addCookie(Cookie)}.
*/
@Override
public Collection<String> getHeaders(String name) {
Set<Header> values = _metaData._headers.get(name);
if (values == null) {
return Collections.emptyList();
}
List<String> stringValues = new ArrayList<>();
for (Header header : values) {
stringValues.add(header.toString());
}
return stringValues;
}
@Override
public Locale getLocale() {
return _metaData._locale;
}
public MetaData getMetaData() {
return _metaData;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
calledGetOutputStream = true;
return super.getOutputStream();
}
@Override
public int getStatus() {
return _metaData._status;
}
@Override
public PrintWriter getWriter() throws IOException {
calledGetWriter = true;
return super.getWriter();
}
@Override
public boolean isCommitted() {
ServletResponse servletResponse = getResponse();
if (_committed || servletResponse.isCommitted()) {
return true;
}
return false;
}
@Override
public void reset() {
if (isCommitted()) {
throw new IllegalStateException("Reset after commit");
}
// No need to reset _error, _errorMessage and _location, because setting
// them requires commit.
_metaData._charsetName = null;
_metaData._contentLength = -1;
_metaData._contentType = null;
_metaData._headers.clear();
_metaData._locale = null;
_metaData._status = SC_OK;
_metaData._statusMessage = null;
// calledGetOutputStream and calledGetWriter should be cleared by
// resetBuffer() in subclass.
resetBuffer();
super.reset();
}
@Override
public void resetBuffer() {
if (isCommitted()) {
throw new IllegalStateException("Reset buffer after commit");
}
resetBuffer(false);
}
@Override
public void sendError(int status) throws IOException {
if (isCommitted()) {
throw new IllegalStateException("Send error after commit");
}
_metaData._error = true;
_metaData._status = status;
resetBuffer();
_committed = true;
super.sendError(status);
}
@Override
public void sendError(int status, String errorMessage) throws IOException {
if (isCommitted()) {
throw new IllegalStateException("Send error after commit");
}
_metaData._error = true;
_metaData._errorMessage = errorMessage;
_metaData._status = status;
resetBuffer();
_committed = true;
super.sendError(status, errorMessage);
}
@Override
public void sendRedirect(String location) throws IOException {
if (isCommitted()) {
throw new IllegalStateException("Send redirect after commit");
}
resetBuffer(true);
setStatus(SC_FOUND);
_metaData._location = location;
_committed = true;
super.sendRedirect(location);
}
@Override
public void setBufferSize(int bufferSize) {
if (isCommitted()) {
throw new IllegalStateException("Set buffer size after commit");
}
_metaData._bufferSize = bufferSize;
super.setBufferSize(bufferSize);
}
@Override
public void setCharacterEncoding(String charsetName) {
if (isCommitted()) {
return;
}
_setCharacterEncoding(charsetName);
}
@Override
public void setContentLength(int contentLength) {
if (isCommitted()) {
return;
}
_metaData._contentLength = contentLength;
super.setContentLength(contentLength);
}
@Override
public void setContentType(String contentType) {
if (isCommitted()) {
return;
}
if (contentType == null) {
return;
}
int index = contentType.indexOf(CharPool.SEMICOLON);
if (index != -1) {
String firstPart = contentType.substring(0, index);
_metaData._contentType = firstPart.trim();
index = contentType.indexOf("charset=");
if (index != -1) {
String charsetName = contentType.substring(index + 8);
charsetName = charsetName.trim();
_setCharacterEncoding(charsetName);
}
else {
_metaData._charsetName = null;
}
}
else {
_metaData._contentType = contentType;
_metaData._charsetName = null;
}
super.setContentType(contentType);
}
@Override
public void setDateHeader(String name, long value) {
Set<Header> values = new HashSet<>();
_metaData._headers.put(name, values);
Header header = new Header(value);
values.add(header);
super.setDateHeader(name, value);
}
@Override
public void setHeader(String name, String value) {
if (name.equals(HttpHeaders.CONTENT_TYPE)) {
setContentType(value);
return;
}
Set<Header> values = new HashSet<>();
_metaData._headers.put(name, values);
Header header = new Header(value);
values.add(header);
super.setHeader(name, value);
}
@Override
public void setIntHeader(String name, int value) {
Set<Header> values = new HashSet<>();
_metaData._headers.put(name, values);
Header header = new Header(value);
values.add(header);
super.setIntHeader(name, value);
}
@Override
public void setLocale(Locale locale) {
if (isCommitted()) {
return;
}
_metaData._locale = locale;
super.setLocale(locale);
}
@Override
public void setStatus(int status) {
if (isCommitted()) {
return;
}
_metaData._status = status;
super.setStatus(status);
}
@Override
@SuppressWarnings("deprecation")
public void setStatus(int status, String statusMessage) {
if (isCommitted()) {
return;
}
_metaData._status = status;
_metaData._statusMessage = statusMessage;
super.setStatus(status, statusMessage);
}
@Override
public String toString() {
StringBundler sb = new StringBundler(23);
sb.append("{bufferSize=");
sb.append(_metaData._bufferSize);
sb.append(", charsetName=");
sb.append(_metaData._charsetName);
sb.append(", committed=");
sb.append(_committed);
sb.append(", contentLength=");
sb.append(_metaData._contentLength);
sb.append(", contentType=");
sb.append(_metaData._contentType);
sb.append(", error=");
sb.append(_metaData._error);
sb.append(", errorMessage=");
sb.append(_metaData._errorMessage);
sb.append(", headers=");
sb.append(_metaData._headers);
sb.append(", location=");
sb.append(_metaData._location);
sb.append(", locale=");
sb.append(_metaData._locale);
sb.append(", status=");
sb.append(_metaData._status);
sb.append("}");
return sb.toString();
}
public static class MetaData implements Serializable {
private int _bufferSize;
private String _charsetName;
private int _contentLength = -1;
private String _contentType;
private boolean _error;
private String _errorMessage;
private final Map<String, Set<Header>> _headers = new HashMap<>();
private Locale _locale;
private String _location;
private int _status = SC_OK;
private String _statusMessage;
}
protected static void resetThrough(HttpServletResponse response) {
if (response instanceof MetaInfoCacheServletResponse) {
MetaInfoCacheServletResponse metaInfoCacheServletResponse =
(MetaInfoCacheServletResponse)response;
resetThrough(
(HttpServletResponse)
metaInfoCacheServletResponse.getResponse());
}
else {
response.reset();
}
}
/**
* Stub method for subclass to provide buffer resetting logic.
*
* @param nullOutReferences whether to reset flags. It is not directly used
* by this class. Subclasses with an actual buffer may behave
* differently depending on the value of this parameter.
*/
protected void resetBuffer(boolean nullOutReferences) {
super.resetBuffer();
}
protected boolean calledGetOutputStream;
protected boolean calledGetWriter;
private void _setCharacterEncoding(String charsetName) {
if (calledGetWriter) {
return;
}
if (charsetName == null) {
return;
}
_metaData._charsetName = charsetName;
super.setCharacterEncoding(charsetName);
}
private boolean _committed;
private final MetaData _metaData = new MetaData();
}