/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You under the Apache License, Version 2.0 * (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * * getCharsetFromContentType() */ package utils; import play.mvc.Http; import play.mvc.Http.Response; import javax.servlet.*; import javax.servlet.http.*; import java.io.*; import java.nio.charset.Charset; import java.util.*; public class PlayServletResponse implements HttpServletResponse { private final PipedInputStream inputStream; private Response response; private String characterEncoding; private int status = 0; private PrintWriter pw; private ChunkedOutputStream outputStream; private final Object statusLock; private boolean committed; /** * Waits until the HTTP status code of this response is given, then returns * the code. * * @return the HTTP status code * @throws InterruptedException */ public int waitAndGetStatus() throws InterruptedException { Object statusLock = getStatusLock(); synchronized (statusLock) { statusLock.wait(); return getStatus(); } } public Object getStatusLock() { return statusLock; } class ChunkedOutputStream extends ServletOutputStream { private byte[] buffer = new byte[1024 * 1024]; private int offset = 0; private OutputStream target; public ChunkedOutputStream(OutputStream target) { this.target = target; } @Override public void write(int b) throws IOException { if (offset >= buffer.length - 1) { flush(); } buffer[offset++] = (byte) b; } @Override public void write(byte[] b) throws IOException { synchronized (statusLock) { // Make sure HTTP status and header is specified. statusLock.notifyAll(); committed = true; } target.write(b); } @Override public void flush() throws IOException { synchronized (statusLock) { // Make sure HTTP status and header is specified. statusLock.notifyAll(); } byte[] b = Arrays.copyOf(buffer, offset); target.write(b); offset = 0; } @Override public void close() throws IOException { offset = 0; target.close(); super.close(); } public void setWriteListener(WriteListener writeListener) { throw new UnsupportedOperationException(); } public boolean isReady() { throw new UnsupportedOperationException(); } } public PlayServletResponse(Response response) throws IOException { this.response = response; this.statusLock = new Object(); this.inputStream = new PipedInputStream(); this.outputStream = new ChunkedOutputStream(new PipedOutputStream(this.inputStream)); this.pw = new PrintWriter( new BufferedWriter(new OutputStreamWriter(this.outputStream, Config.getCharset())), false); } @Override public void flushBuffer() throws IOException { getWriter().flush(); } @Override public int getBufferSize() { throw new UnsupportedOperationException(); } @Override public String getCharacterEncoding() { if (characterEncoding != null) { return characterEncoding; } return getCharsetFromContentType(getContentType()); } @Override public String getContentType() { return getHeader(Http.HeaderNames.CONTENT_TYPE); } @Override public Locale getLocale() { throw new UnsupportedOperationException(); } public PipedInputStream getInputStream() { return this.inputStream; } @Override public ServletOutputStream getOutputStream() throws IOException { return outputStream; } @Override public PrintWriter getWriter() throws IOException { return pw; } @Override public boolean isCommitted() { return committed; } @Override public void reset() { throw new UnsupportedOperationException(); } @Override public void resetBuffer() { try { getWriter().flush(); } catch (IOException e) { e.printStackTrace(); //To change body of catch statement use File | Settings | File Templates. } // FIXME: pipedOutput does not have reset method. Is it required? // pipedOutput.reset(); } @Override public void setBufferSize(int arg0) { throw new UnsupportedOperationException(); } @Override public void setCharacterEncoding(String characterEncoding) { this.characterEncoding = characterEncoding; } @Override public void setContentLength(int length) { this.response.setHeader(Http.HeaderNames.CONTENT_LENGTH, Integer.toString(length)); } public void setContentLengthLong(long length) { this.response.setHeader(Http.HeaderNames.CONTENT_LENGTH, Long.toString(length)); } @Override public void setContentType(String type) { this.response.setContentType(type); } @Override public void setLocale(Locale locale) { throw new UnsupportedOperationException(); } @Override public void addCookie(Cookie cookie) { throw new UnsupportedOperationException(); } @Override public void addDateHeader(String name, long date) { addHeader(name, FastHttpDateFormat.formatDate(date, null)); } @Override public void addHeader(String name, String value) { String head = this.response.getHeaders().get(name); String newValue; if(head == null || head.trim().isEmpty()) { newValue = value; } else { newValue = head + "," + value; } this.response.setHeader(name, newValue); } @Override public void addIntHeader(String name, int value) { addHeader(name, value +""); } @Override public boolean containsHeader(String name) { return this.response.getHeaders().containsKey(name); } @Override public String encodeRedirectURL(String url) { throw new UnsupportedOperationException(); } /** * @deprecated */ @Deprecated @Override public String encodeRedirectUrl(String url) { return encodeRedirectURL(url); } @Override public String encodeURL(String arg0) { throw new UnsupportedOperationException(); } /** * @deprecated */ @Deprecated @Override public String encodeUrl(String arg0) { throw new UnsupportedOperationException(); } @Override public String getHeader(String headerName) { for(String h: response.getHeaders().keySet()) { if(headerName.toLowerCase().equals(h.toLowerCase())) { return response.getHeaders().get(h); } } return null; } @Override public Collection<String> getHeaderNames() { return this.response.getHeaders().keySet(); } @Override public Collection<String> getHeaders(String name) { return Arrays.asList(this.response.getHeaders().get(name).split(",")); } @Override public int getStatus() { return this.status; } @Override public void sendError(int statusCode) throws IOException { // FIXME response should be returned at this time. setStatus(statusCode); } @Override public void sendError(int statusCode, String msg) throws IOException { // FIXME response should be returned at this time. setStatus(statusCode); resetBuffer(); if (msg != null) { play.Logger.error(msg); getWriter().write(msg); response.setHeader(Http.HeaderNames.CONTENT_TYPE, "text/plain"); } else { response.getHeaders().remove(Http.HeaderNames.CONTENT_TYPE); } } @Override public void sendRedirect(String location) throws IOException { // FIXME response should be returned at this time. response.setHeader(Http.HeaderNames.LOCATION, location); setStatus(Http.Status.FOUND); } @Override public void setDateHeader(String name, long date) { this.response.setHeader(name, FastHttpDateFormat.formatDate(date, null)); } @Override public void setHeader(String name, String value) { if (name == null || name.length() == 0 || value == null) { return; } if (isCommitted()) { return; } response.setHeader(name, value); } @Override public void setIntHeader(String name, int value) { response.setHeader(name, Integer.toString(value)); } @Override public void setStatus(int status) { this.status = status; } /** * @deprecated */ @Deprecated @Override public void setStatus(int status, String msg) { throw new UnsupportedOperationException(); } /** * Parse the character encoding from the specified content type header. * If the content type is null, or there is no explicit character encoding, * <code>null</code> is returned. * * @param contentType a content type header */ private static String getCharsetFromContentType(String contentType) { if (contentType == null) { return (null); } int start = contentType.indexOf("charset="); if (start < 0) { return (null); } String encoding = contentType.substring(start + 8); int end = encoding.indexOf(';'); if (end >= 0) { encoding = encoding.substring(0, end); } encoding = encoding.trim(); if ((encoding.length() > 2) && (encoding.startsWith("\"")) && (encoding.endsWith("\""))) { encoding = encoding.substring(1, encoding.length() - 1); } return (encoding.trim()); } public String getVirtualServerName() { throw new UnsupportedOperationException(); } }