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 }