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