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