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     * &lt;wiki:IncludeResources type="script"/&gt;
066     * </pre>
067     * Any code that requires special resources must register a resource
068     * request with the TemplateManager. For example:
069     * <pre>
070     * &lt;wiki:RequestResource type="script" path="scripts/custom.js" /&gt;
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    }