/* * RHQ Management Platform * Copyright (C) 2005-2013 Red Hat, Inc. * All rights reserved. * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation version 2 of the License. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA */ package org.rhq.enterprise.rest; import java.io.BufferedReader; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.util.Enumeration; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import javax.servlet.Filter; import javax.servlet.FilterChain; import javax.servlet.FilterConfig; import javax.servlet.ServletException; import javax.servlet.ServletInputStream; import javax.servlet.ServletOutputStream; import javax.servlet.ServletRequest; import javax.servlet.ServletResponse; import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequestWrapper; import javax.servlet.http.HttpServletResponse; import javax.servlet.http.HttpServletResponseWrapper; /** * A filter to wrap json answers as jsonp * For this to happen, the user has to pass ?<filter.jsonp.callback>=<name> in the url like * <pre>http://localhost:7080/rest/metric/data/10001/raw.json?jsonp=foo</pre> * The <filter.jsonp.callback> is defined in web.xml and defaults to 'jsonp'. * @author Heiko W. Rupp */ public class JsonPFilter implements Filter { private static final String APPLICATION_JSON = "application/json"; private static final String VND_RHQ_WRAPPED_JSON = "application/vnd.rhq.wrapped+json"; private static final String ACCEPT = "accept"; private String callbackName; public void destroy() { } public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws ServletException, IOException { if (!(request instanceof HttpServletRequest)) { throw new ServletException("This filter can only process HttpServletRequest requests"); } HttpServletRequest httpRequest = (HttpServletRequest) request; HttpServletResponse httpResponse = (HttpServletResponse) response; if (hasCallback(httpRequest)) { String callback = getCallback(httpRequest); // We need to wrap request and response, as we need to do some re-writing on both // We want to get json data inside, so change the accept header JsonPRequestWrapper requestWrapper = new JsonPRequestWrapper(httpRequest); if (requestsJsonWrapping(httpRequest)) { requestWrapper.setHeader(ACCEPT, VND_RHQ_WRAPPED_JSON); } else { requestWrapper.setHeader(ACCEPT, APPLICATION_JSON); } requestWrapper.setContentType(APPLICATION_JSON); JsonPResponseWrapper responseWrapper = new JsonPResponseWrapper(httpResponse); chain.doFilter(requestWrapper, responseWrapper); response.setContentType("application/javascript; charset=utf-8"); ServletOutputStream outputStream = response.getOutputStream(); outputStream.write((callback + "(").getBytes()); responseWrapper.getByteArrayOutputStream().writeTo(outputStream); outputStream.write(");".getBytes()); outputStream.flush(); } else { chain.doFilter(request, response); } } /** * Check if the incoming request requests jsonw wrapping and jsonp-wrapping * @param httpRequest * @return */ private boolean requestsJsonWrapping(HttpServletRequest httpRequest) { String mimeType = httpRequest.getHeader(ACCEPT); if (mimeType.equals(VND_RHQ_WRAPPED_JSON)) { return true; } String localPart = httpRequest.getContextPath(); if (localPart.endsWith(".jsonw")) { return true; } return false; } public void init(FilterConfig config) throws ServletException { callbackName = config.getInitParameter("filter.jsonp.callback"); } private boolean hasCallback(ServletRequest request) { String cb = request.getParameter(callbackName); return (cb != null && !cb.isEmpty()); } private String getCallback(HttpServletRequest request) { return request.getParameter(callbackName); } private static class JsonPResponseWrapper extends HttpServletResponseWrapper { ByteArrayOutputStream baos = new ByteArrayOutputStream(); private JsonPResponseWrapper(HttpServletResponse response) { super(response); } ByteArrayOutputStream getByteArrayOutputStream() { return baos; } @Override public ServletOutputStream getOutputStream() throws IOException { return new ServletOutputStream() { @Override public void write(int b) throws IOException { baos.write(b); } }; } @Override public PrintWriter getWriter() throws IOException { return new PrintWriter(baos); } } private static class JsonPRequestWrapper extends HttpServletRequestWrapper { int contentLength; BufferedReader reader; ByteArrayInputStream bais; Map<String, String> headers = new HashMap<String, String>(); public JsonPRequestWrapper(HttpServletRequest request) { super(request); copyHeaders(request); } private void copyHeaders(HttpServletRequest request) { Enumeration headers = request.getHeaderNames(); while (headers.hasMoreElements()) { String key = (String) headers.nextElement(); if (key.equalsIgnoreCase("Accept-Encoding")) { // Filter Content codings like compression, as we would end up // with compressed inner data and uncompressed wrapper continue; } String value = request.getHeader(key); this.headers.put(key, value); } } public void setHeader(String key, String value) { headers.put(key, value); } @Override public BufferedReader getReader() throws IOException { reader = new BufferedReader(new InputStreamReader(bais)); return reader; } @Override public ServletInputStream getInputStream() throws IOException { return new ServletInputStream() { @Override public int read() throws IOException { return bais.read(); } }; } private String contentType; public void setContentType(String contentType) { this.contentType = contentType; headers.put("content-type", contentType); } @Override public String getContentType() { return contentType; } @Override public int getContentLength() { return contentLength; } @Override public String getHeader(String name) { String val = headers.get(name); if (val != null) { return val; } return super.getHeader(name); } @Override public Enumeration getHeaders(final String name) { final String val = headers.get(name); return new Enumeration() { boolean first = true; @Override public boolean hasMoreElements() { return first; } @Override public Object nextElement() { if (first) { first = false; return val; } else return null; } }; } @Override public Enumeration getHeaderNames() { final Iterator it = headers.keySet().iterator(); return new Enumeration() { public boolean hasMoreElements() { return it.hasNext(); } public Object nextElement() { return it.hasNext() ? it.next() : null; } }; } @Override public int getIntHeader(String name) { String val = headers.get(name); if (val == null) { return 0; // TODO ?? } else { return Integer.parseInt(val); } } } }