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 }