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