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.filters; 020 021import java.io.File; 022import java.io.FileInputStream; 023import java.io.IOException; 024import java.io.InputStream; 025import java.util.ArrayList; 026import java.util.Collection; 027import java.util.HashMap; 028import java.util.Iterator; 029import java.util.List; 030import java.util.Map; 031import java.util.Properties; 032 033import org.apache.commons.io.IOUtils; 034import org.apache.log4j.Logger; 035import org.apache.wiki.WikiContext; 036import org.apache.wiki.WikiEngine; 037import org.apache.wiki.api.engine.FilterManager; 038import org.apache.wiki.api.exceptions.FilterException; 039import org.apache.wiki.api.exceptions.WikiException; 040import org.apache.wiki.api.filters.PageFilter; 041import org.apache.wiki.event.WikiEventManager; 042import org.apache.wiki.event.WikiPageEvent; 043import org.apache.wiki.modules.ModuleManager; 044import org.apache.wiki.modules.WikiModuleInfo; 045import org.apache.wiki.util.ClassUtil; 046import org.apache.wiki.util.PriorityList; 047import org.apache.wiki.util.XmlUtil; 048import org.jdom2.Element; 049 050 051/** 052 * Manages the page filters. Page filters are components that can be executed 053 * at certain places: 054 * <ul> 055 * <li>Before the page is translated into HTML. 056 * <li>After the page has been translated into HTML. 057 * <li>Before the page is saved. 058 * <li>After the page has been saved. 059 * </ul> 060 * 061 * Using page filters allows you to modify the page data on-the-fly, and do things like 062 * adding your own custom WikiMarkup. 063 * 064 * <p> 065 * The initial page filter configuration is kept in a file called "filters.xml". The 066 * format is really very simple: 067 * <pre> 068 * <?xml version="1.0"?> 069 * 070 * <pagefilters> 071 * 072 * <filter> 073 * <class>org.apache.wiki.filters.ProfanityFilter</class> 074 * </filter> 075 * 076 * <filter> 077 * <class>org.apache.wiki.filters.TestFilter</class> 078 * 079 * <param> 080 * <name>foobar</name> 081 * <value>Zippadippadai</value> 082 * </param> 083 * 084 * <param> 085 * <name>blatblaa</name> 086 * <value>5</value> 087 * </param> 088 * 089 * </filter> 090 * </pagefilters> 091 * </pre> 092 * 093 * The <filter> -sections define the filters. For more information, please see 094 * the PageFilterConfiguration page in the JSPWiki distribution. 095 */ 096public class DefaultFilterManager extends ModuleManager implements FilterManager { 097 098 private PriorityList< PageFilter > m_pageFilters = new PriorityList< PageFilter >(); 099 100 private Map< String, PageFilterInfo > m_filterClassMap = new HashMap< String, PageFilterInfo >(); 101 102 private static final Logger log = Logger.getLogger(DefaultFilterManager.class); 103 104 /** 105 * Constructs a new FilterManager object. 106 * 107 * @param engine The WikiEngine which owns the FilterManager 108 * @param props Properties to initialize the FilterManager with 109 * @throws WikiException If something goes wrong. 110 */ 111 public DefaultFilterManager( WikiEngine engine, Properties props ) 112 throws WikiException 113 { 114 super( engine ); 115 initialize( props ); 116 } 117 118 /** 119 * Adds a page filter to the queue. The priority defines in which 120 * order the page filters are run, the highest priority filters go 121 * in the queue first. 122 * <p> 123 * In case two filters have the same priority, their execution order 124 * is the insertion order. 125 * 126 * @since 2.1.44. 127 * @param f PageFilter to add 128 * @param priority The priority in which position to add it in. 129 * @throws IllegalArgumentException If the PageFilter is null or invalid. 130 */ 131 public void addPageFilter( PageFilter f, int priority ) throws IllegalArgumentException 132 { 133 if( f == null ) 134 { 135 throw new IllegalArgumentException("Attempt to provide a null filter - this should never happen. Please check your configuration (or if you're a developer, check your own code.)"); 136 } 137 138 m_pageFilters.add( f, priority ); 139 } 140 141 private void initPageFilter( String className, Properties props ) 142 { 143 try 144 { 145 PageFilterInfo info = m_filterClassMap.get( className ); 146 147 if( info != null && !checkCompatibility(info) ) 148 { 149 String msg = "Filter '"+info.getName()+"' not compatible with this version of JSPWiki"; 150 log.warn(msg); 151 return; 152 } 153 154 int priority = 0; // FIXME: Currently fixed. 155 156 Class< ? > cl = ClassUtil.findClass( "org.apache.wiki.filters", className ); 157 158 PageFilter filter = (PageFilter)cl.newInstance(); 159 160 filter.initialize( m_engine, props ); 161 162 addPageFilter( filter, priority ); 163 log.info("Added page filter "+cl.getName()+" with priority "+priority); 164 } 165 catch( ClassNotFoundException e ) 166 { 167 log.error("Unable to find the filter class: "+className); 168 } 169 catch( InstantiationException e ) 170 { 171 log.error("Cannot create filter class: "+className); 172 } 173 catch( IllegalAccessException e ) 174 { 175 log.error("You are not allowed to access class: "+className); 176 } 177 catch( ClassCastException e ) 178 { 179 log.error("Suggested class is not a PageFilter: "+className); 180 } 181 catch( FilterException e ) 182 { 183 log.error("Filter "+className+" failed to initialize itself.", e); 184 } 185 } 186 187 188 /** 189 * Initializes the filters from an XML file. 190 * 191 * @param props The list of properties. Typically jspwiki.properties 192 * @throws WikiException If something goes wrong. 193 */ 194 protected void initialize( Properties props ) throws WikiException { 195 InputStream xmlStream = null; 196 String xmlFile = props.getProperty( PROP_FILTERXML ); 197 198 try { 199 registerFilters(); 200 201 if( m_engine.getServletContext() != null ) { 202 log.debug( "Attempting to locate " + DEFAULT_XMLFILE + " from servlet context." ); 203 if( xmlFile == null ) { 204 xmlStream = m_engine.getServletContext().getResourceAsStream( DEFAULT_XMLFILE ); 205 } else { 206 xmlStream = m_engine.getServletContext().getResourceAsStream( xmlFile ); 207 } 208 } 209 210 if( xmlStream == null ) { 211 // just a fallback element to the old behaviour prior to 2.5.8 212 log.debug( "Attempting to locate filters.xml from class path." ); 213 214 if( xmlFile == null ) { 215 xmlStream = getClass().getResourceAsStream( "/filters.xml" ); 216 } else { 217 xmlStream = getClass().getResourceAsStream( xmlFile ); 218 } 219 } 220 221 if( (xmlStream == null) && (xmlFile != null) ) { 222 log.debug("Attempting to load property file "+xmlFile); 223 xmlStream = new FileInputStream( new File(xmlFile) ); 224 } 225 226 if( xmlStream == null ) { 227 log.info( "Cannot find property file for filters (this is okay, expected to find it as: '" + 228 ( xmlFile == null ? DEFAULT_XMLFILE : xmlFile ) + 229 "')" ); 230 return; 231 } 232 233 parseConfigFile( xmlStream ); 234 } catch( IOException e ) { 235 log.error("Unable to read property file", e); 236 } finally { 237 IOUtils.closeQuietly( xmlStream ); 238 } 239 } 240 241 /** 242 * Parses the XML filters configuration file. 243 * 244 * @param xmlStream stream to parse 245 */ 246 private void parseConfigFile( InputStream xmlStream ) { 247 List< Element > pageFilters = XmlUtil.parse( xmlStream, "/pagefilters/filter" ); 248 for( Iterator< Element > i = pageFilters.iterator(); i.hasNext(); ) { 249 Element f = i.next(); 250 String filterClass = f.getChildText( "class" ); 251 Properties props = new Properties(); 252 253 List< Element > params = f.getChildren( "param" ); 254 for( Iterator< Element > par = params.iterator(); par.hasNext(); ) { 255 Element p = par.next(); 256 props.setProperty( p.getChildText( "name" ), p.getChildText( "value" ) ); 257 } 258 259 initPageFilter( filterClass, props ); 260 } 261 } 262 263 264 /** 265 * Does the filtering before a translation. 266 * 267 * @param context The WikiContext 268 * @param pageData WikiMarkup data to be passed through the preTranslate chain. 269 * @throws FilterException If any of the filters throws a FilterException 270 * @return The modified WikiMarkup 271 * 272 * @see PageFilter#preTranslate(WikiContext, String) 273 */ 274 public String doPreTranslateFiltering( WikiContext context, String pageData ) 275 throws FilterException 276 { 277 fireEvent( WikiPageEvent.PRE_TRANSLATE_BEGIN, context ); 278 279 for( PageFilter f : m_pageFilters ) 280 { 281 pageData = f.preTranslate( context, pageData ); 282 } 283 284 fireEvent( WikiPageEvent.PRE_TRANSLATE_END, context ); 285 286 return pageData; 287 } 288 289 /** 290 * Does the filtering after HTML translation. 291 * 292 * @param context The WikiContext 293 * @param htmlData HTML data to be passed through the postTranslate 294 * @throws FilterException If any of the filters throws a FilterException 295 * @return The modified HTML 296 * @see PageFilter#postTranslate(WikiContext, String) 297 */ 298 public String doPostTranslateFiltering( WikiContext context, String htmlData ) 299 throws FilterException 300 { 301 fireEvent( WikiPageEvent.POST_TRANSLATE_BEGIN, context ); 302 303 for( PageFilter f : m_pageFilters ) 304 { 305 htmlData = f.postTranslate( context, htmlData ); 306 } 307 308 fireEvent( WikiPageEvent.POST_TRANSLATE_END, context ); 309 310 return htmlData; 311 } 312 313 /** 314 * Does the filtering before a save to the page repository. 315 * 316 * @param context The WikiContext 317 * @param pageData WikiMarkup data to be passed through the preSave chain. 318 * @throws FilterException If any of the filters throws a FilterException 319 * @return The modified WikiMarkup 320 * @see PageFilter#preSave(WikiContext, String) 321 */ 322 public String doPreSaveFiltering( WikiContext context, String pageData ) 323 throws FilterException 324 { 325 fireEvent( WikiPageEvent.PRE_SAVE_BEGIN, context ); 326 327 for( PageFilter f : m_pageFilters ) 328 { 329 pageData = f.preSave( context, pageData ); 330 } 331 332 fireEvent( WikiPageEvent.PRE_SAVE_END, context ); 333 334 return pageData; 335 } 336 337 /** 338 * Does the page filtering after the page has been saved. 339 * 340 * @param context The WikiContext 341 * @param pageData WikiMarkup data to be passed through the postSave chain. 342 * @throws FilterException If any of the filters throws a FilterException 343 * 344 * @see PageFilter#postSave(WikiContext, String) 345 */ 346 public void doPostSaveFiltering( WikiContext context, String pageData ) 347 throws FilterException 348 { 349 fireEvent( WikiPageEvent.POST_SAVE_BEGIN, context ); 350 351 for( PageFilter f : m_pageFilters ) 352 { 353 // log.info("POSTSAVE: "+f.toString() ); 354 f.postSave( context, pageData ); 355 } 356 357 fireEvent( WikiPageEvent.POST_SAVE_END, context ); 358 } 359 360 /** 361 * Returns the list of filters currently installed. Note that this is not 362 * a copy, but the actual list. So be careful with it. 363 * 364 * @return A List of PageFilter objects 365 */ 366 public List< PageFilter > getFilterList() 367 { 368 return m_pageFilters; 369 } 370 371 /** 372 * 373 * Notifies PageFilters to clean up their ressources. 374 * 375 */ 376 public void destroy() 377 { 378 for( PageFilter f : m_pageFilters ) 379 { 380 f.destroy( m_engine ); 381 } 382 } 383 384 // events processing ....................................................... 385 386 /** 387 * Fires a WikiPageEvent of the provided type and WikiContext. 388 * Invalid WikiPageEvent types are ignored. 389 * 390 * @see org.apache.wiki.event.WikiPageEvent 391 * @param type the WikiPageEvent type to be fired. 392 * @param context the WikiContext of the event. 393 */ 394 public void fireEvent( int type, WikiContext context ) 395 { 396 if ( WikiEventManager.isListening(this) && WikiPageEvent.isValidType(type) ) 397 { 398 WikiEventManager.fireEvent(this, 399 new WikiPageEvent(m_engine,type,context.getPage().getName()) ); 400 } 401 } 402 403 /** 404 * {@inheritDoc} 405 */ 406 @Override 407 public Collection modules() 408 { 409 ArrayList< PageFilter > modules = new ArrayList< PageFilter >(); 410 411 modules.addAll( m_pageFilters ); 412 413 return modules; 414 } 415 416 417 /** 418 * {@inheritDoc} 419 */ 420 @Override 421 public PageFilterInfo getModuleInfo(String moduleName) { 422 return m_filterClassMap.get(moduleName); 423 } 424 425 private void registerFilters() { 426 log.info( "Registering filters" ); 427 List< Element > filters = XmlUtil.parse( PLUGIN_RESOURCE_LOCATION, "/modules/filter" ); 428 429 // 430 // Register all filters which have created a resource containing its properties. 431 // 432 // Get all resources of all plugins. 433 // 434 for( Iterator< Element > i = filters.iterator(); i.hasNext(); ) { 435 Element pluginEl = i.next(); 436 String className = pluginEl.getAttributeValue( "class" ); 437 PageFilterInfo filterInfo = PageFilterInfo.newInstance( className, pluginEl ); 438 if( filterInfo != null ) { 439 registerFilter( filterInfo ); 440 } 441 } 442 } 443 444 private void registerFilter(PageFilterInfo pluginInfo) { 445 m_filterClassMap.put( pluginInfo.getName(), pluginInfo ); 446 } 447 448 /** 449 * Stores information about the filters. 450 * 451 * @since 2.6.1 452 */ 453 private static final class PageFilterInfo extends WikiModuleInfo 454 { 455 private PageFilterInfo( String name ) 456 { 457 super(name); 458 } 459 460 protected static PageFilterInfo newInstance(String className, Element pluginEl) 461 { 462 if( className == null || className.length() == 0 ) return null; 463 PageFilterInfo info = new PageFilterInfo( className ); 464 465 info.initializeFromXML( pluginEl ); 466 return info; 467 } 468 } 469}