001/* 002 Licensed to the Apache Software Foundation (ASF) under one 003 or more contributor license agreements. See the NOTICE file 004 distributed with this work for additional information 005 regarding copyright ownership. The ASF licenses this file 006 to you under the Apache License, Version 2.0 (the 007 "License"); you may not use this file except in compliance 008 with the License. You may obtain a copy of the License at 009 010 http://www.apache.org/licenses/LICENSE-2.0 011 012 Unless required by applicable law or agreed to in writing, 013 software distributed under the License is distributed on an 014 "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 015 KIND, either express or implied. See the License for the 016 specific language governing permissions and limitations 017 under the License. 018 */ 019package org.apache.wiki.ui; 020 021import java.io.ByteArrayOutputStream; 022import java.io.IOException; 023import java.io.OutputStream; 024import java.io.OutputStreamWriter; 025import java.io.PrintWriter; 026import java.io.UnsupportedEncodingException; 027 028import javax.servlet.FilterChain; 029import javax.servlet.FilterConfig; 030import javax.servlet.ServletContext; 031import javax.servlet.ServletException; 032import javax.servlet.ServletOutputStream; 033import javax.servlet.ServletRequest; 034import javax.servlet.ServletResponse; 035import javax.servlet.http.HttpServletRequest; 036import javax.servlet.http.HttpServletResponse; 037import javax.servlet.http.HttpServletResponseWrapper; 038 039import org.apache.commons.lang.StringUtils; 040import org.apache.log4j.NDC; 041import org.apache.wiki.WatchDog; 042import org.apache.wiki.WikiContext; 043import org.apache.wiki.WikiEngine; 044import org.apache.wiki.event.WikiEventManager; 045import org.apache.wiki.event.WikiPageEvent; 046import org.apache.wiki.url.DefaultURLConstructor; 047import org.apache.wiki.util.TextUtil; 048import org.apache.wiki.util.UtilJ2eeCompat; 049 050/** 051 * This filter goes through the generated page response prior and 052 * places requested resources at the appropriate inclusion markers. 053 * This is done to let dynamic content (e.g. plugins, editors) 054 * include custom resources, even after the HTML head section is 055 * in fact built. This filter is typically the last filter to execute, 056 * and it <em>must</em> run after servlet or JSP code that performs 057 * redirections or sends error codes (such as access control methods). 058 * <p> 059 * Inclusion markers are placed by the IncludeResourcesTag; the 060 * defult content templates (see .../templates/default/commonheader.jsp) 061 * are configured to do this. As an example, a JavaScript resource marker 062 * is added like this: 063 * <pre> 064 * <wiki:IncludeResources type="script"/> 065 * </pre> 066 * Any code that requires special resources must register a resource 067 * request with the TemplateManager. For example: 068 * <pre> 069 * <wiki:RequestResource type="script" path="scripts/custom.js" /> 070 * </pre> 071 * or programmatically, 072 * <pre> 073 * TemplateManager.addResourceRequest( context, TemplateManager.RESOURCE_SCRIPT, "scripts/customresource.js" ); 074 * </pre> 075 * 076 * @see TemplateManager 077 * @see org.apache.wiki.tags.RequestResourceTag 078 */ 079public class WikiJSPFilter extends WikiServletFilter 080{ 081 private Boolean m_useOutputStream; 082 private String m_wiki_encoding; 083 084 /** {@inheritDoc} */ 085 public void init( FilterConfig config ) throws ServletException 086 { 087 super.init( config ); 088 m_wiki_encoding = m_engine.getWikiProperties().getProperty(WikiEngine.PROP_ENCODING); 089 ServletContext context = config.getServletContext(); 090 m_useOutputStream = UtilJ2eeCompat.useOutputStream( context.getServerInfo() ); 091 } 092 093 public void doFilter( ServletRequest request, ServletResponse response, FilterChain chain ) 094 throws ServletException, IOException 095 { 096 WatchDog w = m_engine.getCurrentWatchDog(); 097 try 098 { 099 NDC.push( m_engine.getApplicationName()+":"+((HttpServletRequest)request).getRequestURI() ); 100 101 w.enterState("Filtering for URL "+((HttpServletRequest)request).getRequestURI(), 90 ); 102 HttpServletResponseWrapper responseWrapper; 103 104 if( m_useOutputStream ) 105 { 106 log.debug( "Using ByteArrayResponseWrapper" ); 107 responseWrapper = new ByteArrayResponseWrapper( (HttpServletResponse)response, m_wiki_encoding ); 108 } 109 else 110 { 111 log.debug( "Using MyServletResponseWrapper" ); 112 responseWrapper = new MyServletResponseWrapper( (HttpServletResponse)response, m_wiki_encoding ); 113 } 114 115 // fire PAGE_REQUESTED event 116 String pagename = DefaultURLConstructor.parsePageFromURL( 117 (HttpServletRequest)request, response.getCharacterEncoding() ); 118 fireEvent( WikiPageEvent.PAGE_REQUESTED, pagename ); 119 120 super.doFilter( request, responseWrapper, chain ); 121 122 // The response is now complete. Lets replace the markers now. 123 124 // WikiContext is only available after doFilter! (That is after 125 // interpreting the jsp) 126 127 try 128 { 129 w.enterState( "Delivering response", 30 ); 130 WikiContext wikiContext = getWikiContext( request ); 131 String r = filter( wikiContext, responseWrapper ); 132 133 if (m_useOutputStream) 134 { 135 OutputStreamWriter out = new OutputStreamWriter(response.getOutputStream(), 136 response.getCharacterEncoding()); 137 out.write(r); 138 out.flush(); 139 out.close(); 140 } 141 else 142 { 143 response.getWriter().write(r); 144 } 145 146 // Clean up the UI messages and loggers 147 if( wikiContext != null ) 148 { 149 wikiContext.getWikiSession().clearMessages(); 150 } 151 152 // fire PAGE_DELIVERED event 153 fireEvent( WikiPageEvent.PAGE_DELIVERED, pagename ); 154 155 } 156 finally 157 { 158 w.exitState(); 159 } 160 } 161 finally 162 { 163 w.exitState(); 164 NDC.pop(); 165 NDC.remove(); 166 } 167 } 168 169 /** 170 * Goes through all types and writes the appropriate response. 171 * 172 * @param wikiContext The usual processing context 173 * @param response The source string 174 * @return The modified string with all the insertions in place. 175 */ 176 private String filter(WikiContext wikiContext, HttpServletResponse response ) 177 { 178 String string = response.toString(); 179 180 if( wikiContext != null ) 181 { 182 String[] resourceTypes = TemplateManager.getResourceTypes( wikiContext ); 183 184 for( int i = 0; i < resourceTypes.length; i++ ) 185 { 186 string = insertResources( wikiContext, string, resourceTypes[i] ); 187 } 188 189 // 190 // Add HTTP header Resource Requests 191 // 192 String[] headers = TemplateManager.getResourceRequests( wikiContext, 193 TemplateManager.RESOURCE_HTTPHEADER ); 194 195 for( int i = 0; i < headers.length; i++ ) 196 { 197 String key = headers[i]; 198 String value = ""; 199 int split = headers[i].indexOf(':'); 200 if( split > 0 && split < headers[i].length()-1 ) 201 { 202 key = headers[i].substring( 0, split ); 203 value = headers[i].substring( split+1 ); 204 } 205 206 response.addHeader( key.trim(), value.trim() ); 207 } 208 } 209 210 return string; 211 } 212 213 /** 214 * Inserts whatever resources 215 * were requested by any plugins or other components for this particular 216 * type. 217 * 218 * @param wikiContext The usual processing context 219 * @param string The source string 220 * @param type Type identifier for insertion 221 * @return The filtered string. 222 */ 223 private String insertResources(WikiContext wikiContext, String string, String type ) 224 { 225 if( wikiContext == null ) 226 { 227 return string; 228 } 229 230 String marker = TemplateManager.getMarker( wikiContext, type ); 231 int idx = string.indexOf( marker ); 232 233 if( idx == -1 ) 234 { 235 return string; 236 } 237 238 log.debug("...Inserting..."); 239 240 String[] resources = TemplateManager.getResourceRequests( wikiContext, type ); 241 242 StringBuilder concat = new StringBuilder( resources.length * 40 ); 243 244 for( int i = 0; i < resources.length; i++ ) 245 { 246 log.debug("...:::"+resources[i]); 247 concat.append( resources[i] ); 248 } 249 250 string = TextUtil.replaceString( string, 251 idx, 252 idx+marker.length(), 253 concat.toString() ); 254 255 return string; 256 } 257 258 /** 259 * Simple response wrapper that just allows us to gobble through the entire 260 * response before it's output. 261 */ 262 private static class MyServletResponseWrapper 263 extends HttpServletResponseWrapper 264 { 265 ByteArrayOutputStream m_output; 266 private MyServletOutputStream m_servletOut; 267 private PrintWriter m_writer; 268 269 public MyServletResponseWrapper( HttpServletResponse r, final String wiki_encoding ) 270 throws UnsupportedEncodingException { 271 super(r); 272 m_output = new ByteArrayOutputStream(); 273 m_servletOut = new MyServletOutputStream(m_output); 274 m_writer = new PrintWriter(new OutputStreamWriter(m_servletOut, wiki_encoding), true); 275 } 276 277 /** 278 * Returns a writer for output; this wraps the internal buffer 279 * into a PrintWriter. 280 */ 281 public PrintWriter getWriter() 282 { 283 return m_writer; 284 } 285 286 public ServletOutputStream getOutputStream() 287 { 288 return m_servletOut; 289 } 290 291 public void flushBuffer() throws IOException 292 { 293 m_writer.flush(); 294 super.flushBuffer(); 295 } 296 297 class MyServletOutputStream extends ServletOutputStream 298 { 299 ByteArrayOutputStream m_buffer; 300 301 public MyServletOutputStream(ByteArrayOutputStream byteArrayOutputStream) 302 { 303 super(); 304 m_buffer = byteArrayOutputStream; 305 } 306 307 @Override 308 public void write(int aInt) throws IOException 309 { 310 m_buffer.write( aInt ); 311 } 312 } 313 314 /** 315 * Returns whatever was written so far into the Writer. 316 */ 317 public String toString() 318 { 319 try 320 { 321 flushBuffer(); 322 } 323 catch( IOException e ) 324 { 325 log.error( MyServletResponseWrapper.class + " toString() flushBuffer() Failed", e ); 326 return StringUtils.EMPTY; 327 } 328 329 return m_output.toString(); 330 } 331 } 332 333 /** 334 * Response wrapper for application servers which do not work with CharArrayWriter 335 * Currently only OC4J 336 */ 337 private static class ByteArrayResponseWrapper 338 extends HttpServletResponseWrapper 339 { 340 private HttpServletResponse m_response; 341 342 private ByteArrayOutputStream m_output; 343 private MyServletOutputStream m_servletOut; 344 private PrintWriter m_writer; 345 346 /** 347 * How large the initial buffer should be. This should be tuned to achieve 348 * a balance in speed and memory consumption. 349 */ 350 private static final int INIT_BUFFER_SIZE = 4096; 351 352 public ByteArrayResponseWrapper( HttpServletResponse r, final String wiki_encoding ) 353 throws UnsupportedEncodingException { 354 super(r); 355 m_output = new ByteArrayOutputStream( INIT_BUFFER_SIZE ); 356 m_servletOut = new MyServletOutputStream(m_output); 357 m_writer = new PrintWriter(new OutputStreamWriter(m_servletOut, wiki_encoding), true); 358 m_response = r; 359 } 360 361 /** 362 * Returns a writer for output; this wraps the internal buffer 363 * into a PrintWriter. 364 */ 365 public PrintWriter getWriter() 366 { 367 return m_writer; 368 } 369 370 public ServletOutputStream getOutputStream() 371 { 372 return m_servletOut; 373 } 374 375 public void flushBuffer() throws IOException 376 { 377 m_writer.flush(); 378 super.flushBuffer(); 379 } 380 381 class MyServletOutputStream extends ServletOutputStream 382 { 383 private OutputStream m_stream; 384 385 public MyServletOutputStream( OutputStream aOutput ) 386 { 387 super(); 388 m_stream = aOutput; 389 } 390 391 @Override 392 public void write(int aInt) throws IOException 393 { 394 m_stream.write( aInt ); 395 } 396 } 397 398 /** 399 * Returns whatever was written so far into the Writer. 400 */ 401 public String toString() 402 { 403 try 404 { 405 flushBuffer(); 406 return m_output.toString( m_response.getCharacterEncoding() ); 407 } 408 catch( UnsupportedEncodingException e ) 409 { 410 log.error( ByteArrayResponseWrapper.class + " Unsupported Encoding", e ); 411 return StringUtils.EMPTY; 412 } 413 catch( IOException e ) 414 { 415 log.error( ByteArrayResponseWrapper.class + " toString() Flush Failed", e ); 416 return StringUtils.EMPTY; 417 } 418 } 419 } 420 421 // events processing ....................................................... 422 423 424 /** 425 * Fires a WikiPageEvent of the provided type and page name 426 * to all registered listeners of the current WikiEngine. 427 * 428 * @see org.apache.wiki.event.WikiPageEvent 429 * @param type the event type to be fired 430 * @param pagename the wiki page name as a String 431 */ 432 protected final void fireEvent( int type, String pagename ) 433 { 434 if ( WikiEventManager.isListening(m_engine) ) 435 { 436 WikiEventManager.fireEvent(m_engine,new WikiPageEvent(m_engine,type,pagename)); 437 } 438 } 439 440}