package com.firefly.server.http;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletResponse;
import com.firefly.mvc.web.servlet.SystemHtmlPage;
import com.firefly.net.Session;
import com.firefly.server.exception.HttpServerException;
import com.firefly.server.io.ChunkedOutputStream;
import com.firefly.server.io.HttpServerOutpuStream;
import com.firefly.server.io.NetBufferedOutputStream;
import com.firefly.server.io.StaticFileOutputStream;
import com.firefly.utils.VerifyUtils;
import com.firefly.utils.log.Log;
import com.firefly.utils.log.LogFactory;
import com.firefly.utils.time.SafeSimpleDateFormat;
public class HttpServletResponseImpl implements HttpServletResponse {
private static Log log = LogFactory.getInstance().getLog("firefly-system");
public static SafeSimpleDateFormat GMT_FORMAT;
private boolean committed;
private HttpServletRequestImpl request;
private int status, bufferSize;
private String characterEncoding, shortMessage, contentLanguage;
private Locale locale;
private Map<String, String> headMap = new HashMap<String, String>();
private List<Cookie> cookies = new LinkedList<Cookie>();
private boolean usingWriter, usingOutputStream, usingFileOutputStream;
private HttpServerOutpuStream out;
private StaticFileOutputStream fileOut;
private PrintWriter writer;
private NetBufferedOutputStream bufferedOutput;
boolean system;
String systemResponseContent;
static {
SimpleDateFormat sdf = new SimpleDateFormat(
"EEE, d MMM yyyy HH:mm:ss z", Locale.ENGLISH);
sdf.setTimeZone(TimeZone.getTimeZone("GMT"));
GMT_FORMAT = new SafeSimpleDateFormat(sdf);
}
public HttpServletResponseImpl(Session session,
HttpServletRequestImpl request, String characterEncoding,
int bufferSize) {
this.request = request;
this.characterEncoding = characterEncoding;
this.bufferSize = bufferSize;
locale = HttpServletRequestImpl.DEFAULT_LOCALE;
setStatus(200);
setHeader("Server", "firefly-server/1.0");
}
private void createOutput() {
if (bufferedOutput == null) {
setHeader("Date", GMT_FORMAT.format(new Date()));
setHeader("Connection", request.isKeepAlive() ? "keep-alive"
: "close");
bufferedOutput = new NetBufferedOutputStream(request.session,
bufferSize, request.isKeepAlive());
out = request.isChunked() ? new ChunkedOutputStream(bufferSize,
bufferedOutput, request, this) : new HttpServerOutpuStream(
bufferSize, bufferedOutput, request, this);
fileOut = new StaticFileOutputStream(bufferSize, bufferedOutput,
request, this);
writer = new PrintWriter(out);
}
}
public byte[] getHeadData() {
StringBuilder sb = new StringBuilder();
sb.append(request.getProtocol()).append(' ').append(status).append(' ')
.append(shortMessage).append("\r\n");
for (String name : headMap.keySet())
sb.append(name).append(": ").append(headMap.get(name))
.append("\r\n");
if (contentLanguage != null)
sb.append("Content-Language: ").append(contentLanguage)
.append("\r\n");
for (Cookie cookie : cookies) {
sb.append("Set-Cookie: ").append(cookie.getName()).append('=')
.append(cookie.getValue());
if (VerifyUtils.isNotEmpty(cookie.getComment()))
sb.append(";Comment=").append(cookie.getComment());
if (VerifyUtils.isNotEmpty(cookie.getDomain()))
sb.append(";Domain=").append(cookie.getDomain());
if (cookie.getMaxAge() >= 0)
sb.append(";Max-Age=").append(cookie.getMaxAge());
String path = VerifyUtils.isEmpty(cookie.getPath()) ? "/" : cookie
.getPath();
sb.append(";Path=").append(path);
if (cookie.getSecure())
sb.append(";Secure");
sb.append(";Version=").append(cookie.getVersion()).append("\r\n");
}
sb.append("\r\n");
String head = sb.toString();
// System.out.println(head);
return stringToByte(head);
}
public byte[] getChunkedSize(int length) {
return stringToByte(Integer.toHexString(length) + "\r\n");
}
public byte[] stringToByte(String str) {
byte[] ret = null;
try {
ret = str.getBytes(characterEncoding);
} catch (UnsupportedEncodingException e) {
log.error("string to bytes", e);
}
return ret;
}
@Override
public String getCharacterEncoding() {
return characterEncoding;
}
@Override
public String getContentType() {
return headMap.get("Content-Type");
}
public StaticFileOutputStream getStaticFileOutputStream()
throws IOException {
if (usingWriter)
throw new HttpServerException(
"getWriter has already been called for this response");
if (usingOutputStream)
throw new HttpServerException(
"getOutputStream has already been called for this response");
createOutput();
usingFileOutputStream = true;
return fileOut;
}
@Override
public ServletOutputStream getOutputStream() throws IOException {
if (usingWriter)
throw new HttpServerException(
"getWriter has already been called for this response");
if (usingFileOutputStream)
throw new HttpServerException(
"getStaticFileOutputStream has already been called for this response");
createOutput();
usingOutputStream = true;
return out;
}
@Override
public PrintWriter getWriter() throws IOException {
if (usingOutputStream)
throw new HttpServerException(
"getOutputStream has already been called for this response");
if (usingFileOutputStream)
throw new HttpServerException(
"getStaticFileOutputStream has already been called for this response");
createOutput();
usingWriter = true;
return writer;
}
@Override
public void setCharacterEncoding(String charset) {
characterEncoding = charset;
}
@Override
public void setContentLength(int len) {
headMap.put("Content-Length", String.valueOf(len));
}
@Override
public void setContentType(String type) {
headMap.put("Content-Type", type);
}
@Override
public void setBufferSize(int size) {
bufferSize = size;
}
@Override
public int getBufferSize() {
return bufferSize;
}
@Override
public void flushBuffer() throws IOException {
out.flush();
}
@Override
public void resetBuffer() {
out.resetBuffer();
}
@Override
public boolean isCommitted() {
return committed;
}
public void setCommitted(boolean committed) {
this.committed = committed;
}
@Override
public void reset() {
usingWriter = false;
usingOutputStream = false;
committed = false;
bufferedOutput = null;
out = null;
writer = null;
}
@Override
public void setLocale(Locale locale) {
if (locale == null) {
return;
}
this.locale = locale;
// Set the contentLanguage for header output
contentLanguage = locale.getLanguage();
if ((contentLanguage != null) && (contentLanguage.length() > 0)) {
String country = locale.getCountry();
StringBuilder value = new StringBuilder(contentLanguage);
if (country != null && country.length() > 0) {
value.append('-');
value.append(country);
}
contentLanguage = value.toString();
}
}
@Override
public Locale getLocale() {
return locale;
}
@Override
public void addCookie(Cookie cookie) {
if (cookie == null)
throw new HttpServerException("cookie is null");
if (VerifyUtils.isNotEmpty(cookie.getName())
&& VerifyUtils.isNotEmpty(cookie.getValue())) {
cookies.add(cookie);
} else {
throw new HttpServerException(
"cookie name or value or domain is null");
}
}
@Override
public boolean containsHeader(String name) {
return headMap.containsKey(name);
}
@Override
public String encodeURL(String url) {
if (VerifyUtils.isEmpty(url))
return null;
if (url.contains(";" + request.config.getSessionIdName() + "="))
return url;
String absoluteURL = toAbsolute(url);
if (request.isRequestedSessionIdFromCookie()
|| request.isRequestedSessionIdFromURL())
return toEncoded(absoluteURL, request.getRequestedSessionId(),
request.config.getSessionIdName());
return null;
}
@Override
public String encodeRedirectURL(String url) {
return encodeURL(url);
}
@Override
public String encodeUrl(String url) {
return encodeURL(url);
}
@Override
public String encodeRedirectUrl(String url) {
return encodeRedirectURL(url);
}
@Override
public void sendError(int sc, String msg) throws IOException {
setStatus(sc, msg);
systemResponseContent = shortMessage;
outSystemData();
}
@Override
public void sendError(int sc) throws IOException {
setStatus(sc);
systemResponseContent = shortMessage;
outSystemData();
}
public void outSystemData() throws IOException {
if (isCommitted())
throw new IllegalStateException("response is committed");
createOutput();
if (status >= 400) {
try {
boolean hasContent = VerifyUtils
.isNotEmpty(systemResponseContent);
byte[] b = null;
if (hasContent) {
b = SystemHtmlPage.systemPageTemplate(status,
systemResponseContent).getBytes(characterEncoding);
setHeader("Content-Length", String.valueOf(b.length));
} else {
setHeader("Content-Length", "0");
}
bufferedOutput.write(getHeadData());
if (hasContent)
bufferedOutput.write(b);
} finally {
bufferedOutput.close();
}
setCommitted(true);
}
}
public void scheduleSendContinue(int sc) {
setStatus(sc);
system = true;
}
public void scheduleSendError(int sc, String content) {
setStatus(sc);
systemResponseContent = content;
system = true;
}
@Override
public void sendRedirect(String location) throws IOException {
String absolute = toAbsolute(location);
setStatus(SC_FOUND);
setHeader("Location", absolute);
setHeader("Content-Length", "0");
outHeadData();
}
public void outHeadData() throws IOException {
if (isCommitted())
throw new IllegalStateException("response is committed");
createOutput();
try {
bufferedOutput.write(getHeadData());
} finally {
bufferedOutput.close();
}
setCommitted(true);
}
public String toAbsolute(String location) {
if (location.startsWith("http"))
return location;
StringBuilder sb = new StringBuilder();
sb.append(request.getScheme()).append("://")
.append(request.getServerName()).append(":")
.append(request.getServerPort());
if (location.charAt(0) == '/') {
sb.append(location);
} else {
String URI = request.getRequestURI();
int last = 0;
for (int i = URI.length() - 1; i >= 0; i--) {
if (URI.charAt(i) == '/') {
last = i + 1;
break;
}
}
sb.append(URI.substring(0, last)).append(location);
}
return sb.toString();
}
@Override
public void setDateHeader(String name, long date) {
setHeader(name, GMT_FORMAT.format(new Date(date)));
}
@Override
public void addDateHeader(String name, long date) {
addHeader(name, GMT_FORMAT.format(new Date(date)));
}
@Override
public void setHeader(String name, String value) {
headMap.put(name, value);
}
@Override
public void addHeader(String name, String value) {
String v = headMap.get(name);
if (v != null) {
v += "," + value;
setHeader(name, v);
} else
setHeader(name, value);
}
@Override
public void setIntHeader(String name, int value) {
setHeader(name, String.valueOf(value));
}
@Override
public void addIntHeader(String name, int value) {
addHeader(name, String.valueOf(value));
}
@Override
public void setStatus(int status) {
this.status = status;
this.shortMessage = Constants.STATUS_CODE.get(status);
}
@Override
public void setStatus(int status, String shortMessage) {
this.status = status;
this.shortMessage = shortMessage;
}
public int getStatus() {
return status;
}
public static String toEncoded(String url, String sessionId,
String sessionIdName) {
if (url == null || sessionId == null)
return url;
String path = url;
String query = "";
String anchor = "";
int question = url.indexOf('?');
if (question >= 0) {
path = url.substring(0, question);
query = url.substring(question);
}
int pound = path.indexOf('#');
if (pound >= 0) {
anchor = path.substring(pound);
path = path.substring(0, pound);
}
StringBuilder sb = new StringBuilder(path);
if (sb.length() > 0) { // jsessionid can't be first.
sb.append(";");
sb.append(sessionIdName);
sb.append("=");
sb.append(sessionId);
}
sb.append(anchor);
sb.append(query);
return sb.toString();
}
}