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