001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2006 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.render;
021:
022: import java.io.IOException;
023: import java.io.StringReader;
024: import java.lang.reflect.Constructor;
025: import java.util.Collection;
026: import java.util.Iterator;
027: import java.util.Properties;
028:
029: import org.apache.log4j.Logger;
030:
031: import com.ecyrd.jspwiki.TextUtil;
032: import com.ecyrd.jspwiki.WikiContext;
033: import com.ecyrd.jspwiki.WikiEngine;
034: import com.ecyrd.jspwiki.WikiException;
035: import com.ecyrd.jspwiki.event.WikiEvent;
036: import com.ecyrd.jspwiki.event.WikiEventListener;
037: import com.ecyrd.jspwiki.event.WikiEventUtils;
038: import com.ecyrd.jspwiki.event.WikiPageEvent;
039: import com.ecyrd.jspwiki.modules.InternalModule;
040: import com.ecyrd.jspwiki.parser.JSPWikiMarkupParser;
041: import com.ecyrd.jspwiki.parser.MarkupParser;
042: import com.ecyrd.jspwiki.parser.WikiDocument;
043: import com.ecyrd.jspwiki.providers.CachingProvider;
044: import com.opensymphony.oscache.base.Cache;
045: import com.opensymphony.oscache.base.NeedsRefreshException;
046:
047: /**
048: * This class provides a facade towards the differing rendering routines. You should
049: * use the routines in this manager instead of the ones in WikiEngine, if you don't
050: * want the different side effects to occur - such as WikiFilters.
051: * <p>
052: * This class also manages a rendering cache, i.e. documents are stored between calls.
053: * You may control the size of the cache by using the "jspwiki.renderingManager.cacheSize"
054: * parameter in jspwiki.properties. The property value is the number of items that
055: * are stored in the cache. By default, the value of this parameter is taken from
056: * the "jspwiki.cachingProvider.cacheSize" parameter (i.e. the rendering cache is
057: * the same size as the page cache), but you may control them separately.
058: * <p>
059: * You can turn caching completely off by stating a cacheSize of zero.
060: *
061: * @author jalkanen
062: * @since 2.4
063: */
064: public class RenderingManager implements WikiEventListener,
065: InternalModule {
066: private static Logger log = Logger
067: .getLogger(RenderingManager.class);
068:
069: private int m_cacheExpiryPeriod = 24 * 60 * 60; // This can be relatively long
070:
071: private WikiEngine m_engine;
072:
073: public static final String PROP_CACHESIZE = "jspwiki.renderingManager.capacity";
074: private static final int DEFAULT_CACHESIZE = 1000;
075: private static final String VERSION_DELIMITER = "::";
076: private static final String OSCACHE_ALGORITHM = "com.opensymphony.oscache.base.algorithm.LRUCache";
077: private static final String PROP_RENDERER = "jspwiki.renderingManager.renderer";
078: public static final String DEFAULT_RENDERER = XHTMLRenderer.class
079: .getName();
080:
081: /**
082: * Stores the WikiDocuments that have been cached.
083: */
084: private Cache m_documentCache;
085:
086: /**
087: *
088: */
089: private Constructor m_rendererConstructor;
090:
091: public static final String WYSIWYG_EDITOR_MODE = "WYSIWYG_EDITOR_MODE";
092:
093: public static final String VAR_EXECUTE_PLUGINS = "_PluginContent.execute";
094:
095: /**
096: * Initializes the RenderingManager.
097: * Checks for cache size settings, initializes the document cache.
098: * Looks for alternative WikiRenderers, initializes one, or the default
099: * XHTMLRenderer, for use.
100: *
101: * @param engine A WikiEngine instance.
102: * @param properties A list of properties to get parameters from.
103: */
104: public void initialize(WikiEngine engine, Properties properties)
105: throws WikiException {
106: m_engine = engine;
107: int cacheSize = TextUtil.getIntegerProperty(properties,
108: PROP_CACHESIZE, -1);
109:
110: if (cacheSize == -1) {
111: cacheSize = TextUtil.getIntegerProperty(properties,
112: CachingProvider.PROP_CACHECAPACITY,
113: DEFAULT_CACHESIZE);
114: }
115:
116: if (cacheSize > 0) {
117: m_documentCache = new Cache(true, false, false, false,
118: OSCACHE_ALGORITHM, cacheSize);
119: } else {
120: log.info("RenderingManager caching is disabled.");
121: }
122:
123: String renderImplName = properties.getProperty(PROP_RENDERER);
124: if (renderImplName == null) {
125: renderImplName = DEFAULT_RENDERER;
126: }
127: Class[] rendererParams = { WikiContext.class,
128: WikiDocument.class };
129: try {
130: Class c = Class.forName(renderImplName);
131: m_rendererConstructor = c.getConstructor(rendererParams);
132: } catch (ClassNotFoundException e) {
133: log.error("Unable to find WikiRenderer implementation "
134: + renderImplName);
135: } catch (SecurityException e) {
136: log
137: .error("Unable to access the WikiRenderer(WikiContext,WikiDocument) constructor for "
138: + renderImplName);
139: } catch (NoSuchMethodException e) {
140: log
141: .error("Unable to locate the WikiRenderer(WikiContext,WikiDocument) constructor for "
142: + renderImplName);
143: }
144: if (m_rendererConstructor == null) {
145: throw new WikiException("Failed to get WikiRenderer '"
146: + renderImplName + "'.");
147: }
148: log.info("Rendering content with " + renderImplName + ".");
149:
150: WikiEventUtils.addWikiEventListener(m_engine,
151: WikiPageEvent.POST_SAVE_BEGIN, this );
152: }
153:
154: /**
155: * Returns the default Parser for this context.
156: *
157: * @param context the wiki context
158: * @param pagedata the page data
159: * @return A MarkupParser instance.
160: */
161: public MarkupParser getParser(WikiContext context, String pagedata) {
162: MarkupParser parser = new JSPWikiMarkupParser(context,
163: new StringReader(pagedata));
164:
165: return parser;
166: }
167:
168: /**
169: * Returns a cached document, if one is found.
170: *
171: * @param context the wiki context
172: * @param pagedata the page data
173: * @return the rendered wiki document
174: * @throws IOException
175: */
176: // FIXME: The cache management policy is not very good: deleted/changed pages
177: // should be detected better.
178: protected WikiDocument getRenderedDocument(WikiContext context,
179: String pagedata) throws IOException {
180: String pageid = context.getRealPage().getName()
181: + VERSION_DELIMITER
182: + context.getRealPage().getVersion();
183:
184: boolean wasUpdated = false;
185:
186: if (m_documentCache != null) {
187: try {
188: WikiDocument doc = (WikiDocument) m_documentCache
189: .getFromCache(pageid, m_cacheExpiryPeriod);
190:
191: wasUpdated = true;
192:
193: //
194: // This check is needed in case the different filters have actually
195: // changed the page data.
196: // FIXME: Figure out a faster method
197: if (pagedata.equals(doc.getPageData())) {
198: if (log.isDebugEnabled())
199: log.debug("Using cached HTML for page "
200: + pageid);
201: return doc;
202: }
203: } catch (NeedsRefreshException e) {
204: if (log.isDebugEnabled())
205: log.debug("Re-rendering and storing " + pageid);
206: }
207: }
208:
209: //
210: // Refresh the data content
211: //
212: try {
213: MarkupParser parser = getParser(context, pagedata);
214: WikiDocument doc = parser.parse();
215: doc.setPageData(pagedata);
216: if (m_documentCache != null) {
217: m_documentCache.putInCache(pageid, doc);
218: wasUpdated = true;
219: }
220: return doc;
221: } catch (IOException ex) {
222: log.error("Unable to parse", ex);
223: } finally {
224: if (m_documentCache != null && !wasUpdated)
225: m_documentCache.cancelUpdate(pageid);
226: }
227:
228: return null;
229: }
230:
231: /**
232: * Simply renders a WikiDocument to a String. This version does not get the document
233: * from the cache - in fact, it does not cache the document at all. This is
234: * very useful, if you have something that you want to render outside the caching
235: * routines. Because the cache is based on full pages, and the cache keys are
236: * based on names, use this routine if you're rendering anything for yourself.
237: *
238: * @param context The WikiContext to render in
239: * @param doc A proper WikiDocument
240: * @return Rendered HTML.
241: * @throws IOException If the WikiDocument is poorly formed.
242: */
243: public String getHTML(WikiContext context, WikiDocument doc)
244: throws IOException {
245: WikiRenderer rend = getRenderer(context, doc);
246:
247: return rend.getString();
248: }
249:
250: /**
251: * Returns a WikiRenderer instance, initialized with the given
252: * context and doc. The object is an XHTMLRenderer, unless overridden
253: * in jspwiki.properties with PROP_RENDERER.
254: */
255: public WikiRenderer getRenderer(WikiContext context,
256: WikiDocument doc) {
257: Object[] params = { context, doc };
258: WikiRenderer rval = null;
259:
260: try {
261: rval = (WikiRenderer) m_rendererConstructor
262: .newInstance(params);
263: } catch (Exception e) {
264: log.error("Unable to create WikiRenderer", e);
265: }
266: return rval;
267: }
268:
269: /**
270: * Convinience method for rendering, using the default parser and renderer. Note that
271: * you can't use this method to do any arbitrary rendering, as the pagedata MUST
272: * be the data from the that the WikiContext refers to - this method caches the HTML
273: * internally, and will return the cached version. If the pagedata is different
274: * from what was cached, will re-render and store the pagedata into the internal cache.
275: *
276: * @param context the wiki context
277: * @param pagedata the page data
278: * @return XHTML data.
279: */
280: public String getHTML(WikiContext context, String pagedata) {
281: try {
282: WikiDocument doc = getRenderedDocument(context, pagedata);
283:
284: return getHTML(context, doc);
285: } catch (IOException e) {
286: log.error("Unable to parse", e);
287: }
288:
289: return null;
290: }
291:
292: /**
293: * Flushes the document cache in response to a POST_SAVE_BEGIN event.
294: *
295: * @see com.ecyrd.jspwiki.event.WikiEventListener#actionPerformed(com.ecyrd.jspwiki.event.WikiEvent)
296: */
297: // @SuppressWarnings("deprecation")
298: public void actionPerformed(WikiEvent event) {
299: if ((event instanceof WikiPageEvent)
300: && (event.getType() == WikiPageEvent.POST_SAVE_BEGIN)) {
301: if (m_documentCache != null) {
302: String pageName = ((WikiPageEvent) event).getPageName();
303: m_documentCache.flushPattern(pageName);
304: Collection referringPages = m_engine
305: .getReferenceManager().findReferrers(pageName);
306:
307: //
308: // Flush also those pages that refer to this page (if an nonexistant page
309: // appears; we need to flush the HTML that refers to the now-existant page
310: //
311: if (referringPages != null) {
312: Iterator i = referringPages.iterator();
313: while (i.hasNext()) {
314: String page = (String) i.next();
315: if (log.isDebugEnabled())
316: log.debug("Flushing " + page);
317: m_documentCache.flushPattern(page);
318: }
319: }
320: }
321: }
322: }
323:
324: }
|