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