001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2003 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki.filters;
021:
022: import java.io.InputStream;
023: import java.io.FileInputStream;
024: import java.io.IOException;
025: import java.io.File;
026: import java.net.URL;
027: import java.util.*;
028:
029: import org.apache.log4j.Logger;
030: import org.jdom.Document;
031: import org.jdom.Element;
032: import org.jdom.JDOMException;
033: import org.jdom.input.SAXBuilder;
034: import org.jdom.xpath.XPath;
035:
036: import com.ecyrd.jspwiki.WikiContext;
037: import com.ecyrd.jspwiki.WikiEngine;
038: import com.ecyrd.jspwiki.WikiException;
039: import com.ecyrd.jspwiki.event.WikiEventManager;
040: import com.ecyrd.jspwiki.event.WikiPageEvent;
041: import com.ecyrd.jspwiki.modules.ModuleManager;
042: import com.ecyrd.jspwiki.modules.WikiModuleInfo;
043: import com.ecyrd.jspwiki.plugin.PluginManager.WikiPluginInfo;
044:
045: import com.ecyrd.jspwiki.util.PriorityList;
046: import com.ecyrd.jspwiki.util.ClassUtil;
047:
048: /**
049: * Manages the page filters. Page filters are components that can be executed
050: * at certain places:
051: * <ul>
052: * <li>Before the page is translated into HTML.
053: * <li>After the page has been translated into HTML.
054: * <li>Before the page is saved.
055: * <li>After the page has been saved.
056: * </ul>
057: *
058: * Using page filters allows you to modify the page data on-the-fly, and do things like
059: * adding your own custom WikiMarkup.
060: *
061: * <p>
062: * The initial page filter configuration is kept in a file called "filters.xml". The
063: * format is really very simple:
064: * <pre>
065: * <?xml version="1.0"?>
066: *
067: * <pagefilters>
068: *
069: * <filter>
070: * <class>com.ecyrd.jspwiki.filters.ProfanityFilter</class>
071: * </filter>
072: *
073: * <filter>
074: * <class>com.ecyrd.jspwiki.filters.TestFilter</class>
075: *
076: * <param>
077: * <name>foobar</name>
078: * <value>Zippadippadai</value>
079: * </param>
080: *
081: * <param>
082: * <name>blatblaa</name>
083: * <value>5</value>
084: * </param>
085: *
086: * </filter>
087: * </pagefilters>
088: * </pre>
089: *
090: * The <filter> -sections define the filters. For more information, please see
091: * the PageFilterConfiguration page in the JSPWiki distribution.
092: *
093: * @author Janne Jalkanen
094: */
095: public final class FilterManager extends ModuleManager {
096: private PriorityList m_pageFilters = new PriorityList();
097:
098: private HashMap m_filterClassMap = new HashMap();
099:
100: private static final Logger log = Logger
101: .getLogger(WikiEngine.class);
102:
103: public static final String PROP_FILTERXML = "jspwiki.filterConfig";
104:
105: public static final String DEFAULT_XMLFILE = "/WEB-INF/filters.xml";
106:
107: /** JSPWiki system filters are all below this value. */
108: public static final int SYSTEM_FILTER_PRIORITY = -1000;
109:
110: /** The standard user level filtering. */
111: public static final int USER_FILTER_PRIORITY = 0;
112:
113: public FilterManager(WikiEngine engine, Properties props)
114: throws WikiException {
115: super (engine);
116: initialize(props);
117: }
118:
119: /**
120: * Adds a page filter to the queue. The priority defines in which
121: * order the page filters are run, the highest priority filters go
122: * in the queue first.
123: * <p>
124: * In case two filters have the same priority, their execution order
125: * is the insertion order.
126: *
127: * @since 2.1.44.
128: * @param f PageFilter to add
129: * @param priority The priority in which position to add it in.
130: * @throws IllegalArgumentException If the PageFilter is null or invalid.
131: */
132: public void addPageFilter(PageFilter f, int priority) {
133: if (f == null) {
134: throw new IllegalArgumentException(
135: "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: try {
143: PageFilterInfo info = (PageFilterInfo) m_filterClassMap
144: .get(className);
145:
146: if (info != null && !checkCompatibility(info)) {
147: String msg = "Filter '"
148: + info.getName()
149: + "' 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("com.ecyrd.jspwiki.filters",
157: className);
158:
159: PageFilter filter = (PageFilter) cl.newInstance();
160:
161: filter.initialize(m_engine, props);
162:
163: addPageFilter(filter, priority);
164: log.info("Added page filter " + cl.getName()
165: + " with priority " + priority);
166: } catch (ClassNotFoundException e) {
167: log.error("Unable to find the filter class: " + className);
168: } catch (InstantiationException e) {
169: log.error("Cannot create filter class: " + className);
170: } catch (IllegalAccessException e) {
171: log.error("You are not allowed to access class: "
172: + className);
173: } catch (ClassCastException e) {
174: log.error("Suggested class is not a PageFilter: "
175: + className);
176: } catch (FilterException e) {
177: log.error("Filter " + className
178: + " failed to initialize itself.", e);
179: }
180: }
181:
182: /**
183: * Initializes the filters from an XML file.
184: */
185: protected void initialize(Properties props) throws WikiException {
186: InputStream xmlStream = null;
187: String xmlFile = props.getProperty(PROP_FILTERXML);
188:
189: try {
190: registerFilters();
191:
192: if (xmlFile == null) {
193: if (m_engine.getServletContext() != null) {
194: log.debug("Attempting to locate " + DEFAULT_XMLFILE
195: + " from servlet context.");
196: xmlStream = m_engine.getServletContext()
197: .getResourceAsStream(DEFAULT_XMLFILE);
198: }
199:
200: if (xmlStream == null) {
201: //just a fallback element to the old behaviour prior to 2.5.8
202: log
203: .debug("Attempting to locate filters.xml from class path.");
204: xmlStream = getClass().getResourceAsStream(
205: "/filters.xml");
206: }
207: } else {
208: log
209: .debug("Attempting to load property file "
210: + xmlFile);
211: xmlStream = new FileInputStream(new File(xmlFile));
212: }
213:
214: if (xmlStream == null) {
215: log
216: .info("Cannot find property file for filters (this is okay, expected to find it as: '"
217: + (xmlFile == null ? DEFAULT_XMLFILE
218: : xmlFile) + "')");
219: return;
220: }
221:
222: parseConfigFile(xmlStream);
223: } catch (IOException e) {
224: log.error("Unable to read property file", e);
225: } catch (JDOMException e) {
226: log.error("Problem in the XML file", e);
227: }
228: }
229:
230: /**
231: * Parses the XML filters configuration file.
232: *
233: * @param xmlStream
234: * @throws JDOMException
235: * @throws IOException
236: */
237: private void parseConfigFile(InputStream xmlStream)
238: throws JDOMException, IOException {
239: Document doc = new SAXBuilder().build(xmlStream);
240:
241: XPath xpath = XPath.newInstance("/pagefilters/filter");
242:
243: List nodes = xpath.selectNodes(doc);
244:
245: for (Iterator i = nodes.iterator(); i.hasNext();) {
246: Element f = (Element) i.next();
247:
248: String filterClass = f.getChildText("class");
249:
250: Properties props = new Properties();
251:
252: List params = f.getChildren("param");
253:
254: for (Iterator par = params.iterator(); par.hasNext();) {
255: Element p = (Element) par.next();
256:
257: props.setProperty(p.getChildText("name"), p
258: .getChildText("value"));
259: }
260:
261: initPageFilter(filterClass, props);
262: }
263:
264: }
265:
266: /**
267: * Does the filtering before a translation.
268: */
269: public String doPreTranslateFiltering(WikiContext context,
270: String pageData) throws FilterException {
271: fireEvent(WikiPageEvent.PRE_TRANSLATE_BEGIN, context);
272:
273: for (Iterator i = m_pageFilters.iterator(); i.hasNext();) {
274: PageFilter f = (PageFilter) i.next();
275:
276: pageData = f.preTranslate(context, pageData);
277: }
278:
279: fireEvent(WikiPageEvent.PRE_TRANSLATE_END, context);
280:
281: return pageData;
282: }
283:
284: /**
285: * Does the filtering after HTML translation.
286: */
287: public String doPostTranslateFiltering(WikiContext context,
288: String pageData) throws FilterException {
289: fireEvent(WikiPageEvent.POST_TRANSLATE_BEGIN, context);
290:
291: for (Iterator i = m_pageFilters.iterator(); i.hasNext();) {
292: PageFilter f = (PageFilter) i.next();
293:
294: pageData = f.postTranslate(context, pageData);
295: }
296:
297: fireEvent(WikiPageEvent.POST_TRANSLATE_END, context);
298:
299: return pageData;
300: }
301:
302: /**
303: * Does the filtering before a save to the page repository.
304: */
305: public String doPreSaveFiltering(WikiContext context,
306: String pageData) throws FilterException {
307: fireEvent(WikiPageEvent.PRE_SAVE_BEGIN, context);
308:
309: for (Iterator i = m_pageFilters.iterator(); i.hasNext();) {
310: PageFilter f = (PageFilter) i.next();
311:
312: pageData = f.preSave(context, pageData);
313: }
314:
315: fireEvent(WikiPageEvent.PRE_SAVE_END, context);
316:
317: return pageData;
318: }
319:
320: /**
321: * Does the page filtering after the page has been saved.
322: */
323: public void doPostSaveFiltering(WikiContext context, String pageData)
324: throws FilterException {
325: fireEvent(WikiPageEvent.POST_SAVE_BEGIN, context);
326:
327: for (Iterator i = m_pageFilters.iterator(); i.hasNext();) {
328: PageFilter f = (PageFilter) i.next();
329:
330: // log.info("POSTSAVE: "+f.toString() );
331: f.postSave(context, pageData);
332: }
333:
334: fireEvent(WikiPageEvent.POST_SAVE_END, context);
335: }
336:
337: public List getFilterList() {
338: return m_pageFilters;
339: }
340:
341: /**
342: *
343: * Notifies PageFilters to clean up their ressources.
344: *
345: */
346: public void destroy() {
347: for (Iterator i = m_pageFilters.iterator(); i.hasNext();) {
348: PageFilter f = (PageFilter) i.next();
349:
350: f.destroy(m_engine);
351: }
352: }
353:
354: // events processing .......................................................
355:
356: /**
357: * Fires a WikiPageEvent of the provided type and WikiContext.
358: * Invalid WikiPageEvent types are ignored.
359: *
360: * @see com.ecyrd.jspwiki.event.WikiPageEvent
361: * @param type the WikiPageEvent type to be fired.
362: * @param context the WikiContext of the event.
363: */
364: public final void fireEvent(int type, WikiContext context) {
365: if (WikiEventManager.isListening(this )
366: && WikiPageEvent.isValidType(type)) {
367: WikiEventManager.fireEvent(this , new WikiPageEvent(
368: m_engine, type, context.getPage().getName()));
369: }
370: }
371:
372: public Collection modules() {
373: ArrayList modules = new ArrayList();
374:
375: modules.addAll(m_pageFilters);
376:
377: return modules;
378: }
379:
380: private void registerFilters() {
381: log.info("Registering filters");
382:
383: SAXBuilder builder = new SAXBuilder();
384:
385: try {
386: //
387: // Register all filters which have created a resource containing its properties.
388: //
389: // Get all resources of all plugins.
390: //
391:
392: Enumeration resources = getClass().getClassLoader()
393: .getResources(PLUGIN_RESOURCE_LOCATION);
394:
395: while (resources.hasMoreElements()) {
396: URL resource = (URL) resources.nextElement();
397:
398: try {
399: log.debug("Processing XML: " + resource);
400:
401: Document doc = builder.build(resource);
402:
403: List plugins = XPath.selectNodes(doc,
404: "/modules/filter");
405:
406: for (Iterator i = plugins.iterator(); i.hasNext();) {
407: Element pluginEl = (Element) i.next();
408:
409: String className = pluginEl
410: .getAttributeValue("class");
411:
412: PageFilterInfo pluginInfo = PageFilterInfo
413: .newInstance(className, pluginEl);
414:
415: if (pluginInfo != null) {
416: registerPlugin(pluginInfo);
417: }
418: }
419: } catch (java.io.IOException e) {
420: log.error("Couldn't load "
421: + PLUGIN_RESOURCE_LOCATION + " resources: "
422: + resource, e);
423: } catch (JDOMException e) {
424: log.error("Error parsing XML for filter: "
425: + PLUGIN_RESOURCE_LOCATION);
426: }
427: }
428: } catch (java.io.IOException e) {
429: log.error("Couldn't load all " + PLUGIN_RESOURCE_LOCATION
430: + " resources", e);
431: }
432: }
433:
434: private void registerPlugin(PageFilterInfo pluginInfo) {
435: m_filterClassMap.put(pluginInfo.getName(), pluginInfo);
436: }
437:
438: /**
439: * Stores information about the filters.
440: *
441: * @since 2.6.1
442: */
443: private static class PageFilterInfo extends WikiModuleInfo {
444: private PageFilterInfo(String name) {
445: super (name);
446: }
447:
448: protected static PageFilterInfo newInstance(String className,
449: Element pluginEl) {
450: if (className == null || className.length() == 0)
451: return null;
452: PageFilterInfo info = new PageFilterInfo(className);
453:
454: info.initializeFromXML(pluginEl);
455: return info;
456: }
457: }
458: }
|