001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.jsonpart; 029 030import org.opencms.gwt.shared.CmsGwtConstants; 031import org.opencms.json.JSONArray; 032import org.opencms.json.JSONObject; 033import org.opencms.main.CmsLog; 034 035import java.io.ByteArrayOutputStream; 036import java.io.IOException; 037import java.io.OutputStreamWriter; 038import java.io.PrintWriter; 039import java.util.Enumeration; 040import java.util.List; 041import java.util.Map; 042import java.util.Set; 043import java.util.Vector; 044 045import javax.servlet.Filter; 046import javax.servlet.FilterChain; 047import javax.servlet.FilterConfig; 048import javax.servlet.ServletException; 049import javax.servlet.ServletOutputStream; 050import javax.servlet.ServletRequest; 051import javax.servlet.ServletResponse; 052import javax.servlet.WriteListener; 053import javax.servlet.http.HttpServletRequest; 054import javax.servlet.http.HttpServletRequestWrapper; 055import javax.servlet.http.HttpServletResponse; 056import javax.servlet.http.HttpServletResponseWrapper; 057 058import org.apache.commons.logging.Log; 059 060import com.google.common.collect.Maps; 061import com.google.common.collect.Sets; 062 063/** 064 * This servlet filter post-processes the response output for requests with the parameter '__json=true'.<p> 065 * 066 * It converts the encoded JSON parts generated by the <cms:jsonpart> tag, converts them to JSON, writes them to the response, 067 * and throws everything else away. 068 */ 069public class CmsJsonPartFilter implements Filter { 070 071 /** 072 * Request wrapper used to disable direct edit functionality.<p> 073 */ 074 class RequestWrapper extends HttpServletRequestWrapper { 075 076 /** 077 * Creates a new instance.<p> 078 * 079 * @param request the wrapped request 080 */ 081 public RequestWrapper(HttpServletRequest request) { 082 super(request); 083 } 084 085 /** 086 * @see javax.servlet.ServletRequestWrapper#getParameter(java.lang.String) 087 */ 088 @Override 089 public String getParameter(String name) { 090 091 if (CmsGwtConstants.PARAM_DISABLE_DIRECT_EDIT.equals(name)) { 092 return Boolean.TRUE.toString(); 093 } else { 094 return super.getParameter(name); 095 } 096 } 097 098 /** 099 * @see javax.servlet.ServletRequestWrapper#getParameterMap() 100 */ 101 @Override 102 public Map<String, String[]> getParameterMap() { 103 104 Map<String, String[]> result = Maps.newHashMap(super.getParameterMap()); 105 result.put(CmsGwtConstants.PARAM_DISABLE_DIRECT_EDIT, new String[] {"true"}); 106 return result; 107 } 108 109 /** 110 * @see javax.servlet.ServletRequestWrapper#getParameterNames() 111 */ 112 @Override 113 public Enumeration<String> getParameterNames() { 114 115 Set<String> keys = Sets.newHashSet(); 116 keys.add(CmsGwtConstants.PARAM_DISABLE_DIRECT_EDIT); 117 return new Vector<String>(keys).elements(); 118 } 119 120 /** 121 * @see javax.servlet.ServletRequestWrapper#getParameterValues(java.lang.String) 122 */ 123 @Override 124 public String[] getParameterValues(String name) { 125 126 return super.getParameterValues(name); 127 } 128 } 129 130 /** 131 * A response wrapper used to capture output so we can post-process it.<p> 132 */ 133 class ResponseWrapper extends HttpServletResponseWrapper { 134 135 /** The stream used to collect the output bytes. */ 136 ByteArrayOutputStream m_byteStream = new java.io.ByteArrayOutputStream(); 137 138 /** A writer used to collect string-based output. */ 139 PrintWriter m_printWriter; 140 141 /** 142 * Creates a new wrapper instance for the given response.<p> 143 * 144 * @param response the original response 145 */ 146 public ResponseWrapper(HttpServletResponse response) { 147 super(response); 148 } 149 150 /** 151 * Gets the bytes written so far.<p> 152 * 153 * @return the bytes written 154 */ 155 public byte[] getBytes() { 156 157 if (m_printWriter != null) { 158 m_printWriter.flush(); 159 } 160 return m_byteStream.toByteArray(); 161 } 162 163 /** 164 * @see javax.servlet.ServletResponseWrapper#getOutputStream() 165 */ 166 @Override 167 public ServletOutputStream getOutputStream() { 168 169 return new ServletOutputStream() { 170 171 /** 172 * @see java.io.OutputStream#write(byte[]) 173 */ 174 @Override 175 public void write(byte[] b) throws IOException { 176 177 m_byteStream.write(b); 178 } 179 180 /** 181 * @see java.io.OutputStream#write(byte[], int, int) 182 */ 183 @Override 184 public void write(byte[] b, int off, int len) { 185 186 m_byteStream.write(b, off, len); 187 } 188 189 /** 190 * @see java.io.OutputStream#write(int) 191 */ 192 @Override 193 public void write(int b) { 194 195 m_byteStream.write(b); 196 } 197 198 /** 199 * @see javax.servlet.ServletOutputStream#isReady() 200 */ 201 @Override 202 public boolean isReady() { 203 204 return null != m_byteStream; 205 } 206 207 /** 208 * @see javax.servlet.ServletOutputStream#setWriteListener(javax.servlet.WriteListener) 209 */ 210 @Override 211 public void setWriteListener(WriteListener writeListener) { 212 } 213 }; 214 } 215 216 /** 217 * @see javax.servlet.ServletResponseWrapper#getWriter() 218 */ 219 @Override 220 public PrintWriter getWriter() throws IOException { 221 222 if (m_printWriter == null) { 223 m_printWriter = new PrintWriter( 224 new OutputStreamWriter(m_byteStream, getResponse().getCharacterEncoding())); 225 } 226 return m_printWriter; 227 } 228 229 /** 230 * This method does nothing, we want to ignore calls to setContentLength because we want to postprocess 231 * the output, resulting in a different length. 232 * 233 * @see javax.servlet.ServletResponseWrapper#setContentLength(int) 234 */ 235 @Override 236 public void setContentLength(int len) { 237 // ignore 238 } 239 } 240 241 /** The static log object for this class. */ 242 static final Log LOG = CmsLog.getLog(CmsJsonPartFilter.class); 243 244 /** JSON key for the list of part keys. */ 245 public static final String KEY_PARTS = "parts"; 246 247 /** Name of the parameter used to enable JSON rendering. */ 248 public static final String PARAM_JSON = "__json"; 249 250 /** ThreadLocal used to detect nested calls. */ 251 private ThreadLocal<Boolean> m_isNested = new ThreadLocal<Boolean>(); 252 253 /** 254 * Detects whether the filter needs to be used for the given request.<p> 255 * 256 * @param request the request 257 * @return true if the filter should be used for the request 258 */ 259 public static boolean isJsonRequest(ServletRequest request) { 260 261 HttpServletRequest sr = (HttpServletRequest)request; 262 return (sr.getQueryString() != null) && (sr.getQueryString().contains("__json=true")); 263 } 264 265 /** 266 * @see javax.servlet.Filter#destroy() 267 */ 268 public void destroy() { 269 // do nothing 270 } 271 272 /** 273 * @see javax.servlet.Filter#doFilter(javax.servlet.ServletRequest, javax.servlet.ServletResponse, javax.servlet.FilterChain) 274 */ 275 public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) 276 throws IOException, ServletException { 277 278 if (isJsonRequest(request)) { 279 if (m_isNested.get() == null) { 280 try { 281 m_isNested.set(Boolean.TRUE); 282 RequestWrapper reqWrapper = new RequestWrapper((HttpServletRequest)request); 283 ResponseWrapper resWrapper = new ResponseWrapper((HttpServletResponse)response); 284 chain.doFilter(reqWrapper, resWrapper); 285 byte[] data = resWrapper.getBytes(); 286 String content = new String(data, resWrapper.getCharacterEncoding()); 287 String transformedContent = transformContent(content); 288 byte[] transformedData = transformedContent.getBytes("UTF-8"); 289 response.setContentType("application/json; charset=UTF-8"); 290 response.setContentLength(transformedData.length); 291 response.getOutputStream().write(transformedData); 292 response.getOutputStream().flush(); 293 } finally { 294 m_isNested.set(null); 295 } 296 } 297 } else { 298 chain.doFilter(request, response); 299 } 300 } 301 302 /** 303 * @see javax.servlet.Filter#init(javax.servlet.FilterConfig) 304 */ 305 public void init(FilterConfig filterConfig) { 306 // do nothing 307 } 308 309 /** 310 * Transforms the response content as a string.<p> 311 * 312 * @param content the content 313 * @return the transformed content 314 */ 315 private String transformContent(String content) { 316 317 try { 318 List<CmsJsonPart> parts = CmsJsonPart.parseJsonParts(content); 319 JSONArray keys = new JSONArray(); 320 JSONObject output = new JSONObject(); 321 for (CmsJsonPart part : parts) { 322 if (output.has(part.getKey())) { 323 LOG.warn("Duplicate key for JSON parts: " + part.getKey()); 324 } 325 keys.put(part.getKey()); 326 output.put(part.getKey(), part.getValue()); 327 } 328 output.put(KEY_PARTS, keys); 329 return output.toString(); 330 } catch (Exception e) { 331 LOG.error(e.getLocalizedMessage(), e); 332 return content; 333 334 } 335 } 336}