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