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;
020
021import java.io.File;
022import java.io.IOException;
023import java.io.UnsupportedEncodingException;
024import java.net.URLDecoder;
025import java.net.URLEncoder;
026import java.security.Principal;
027import java.util.ArrayList;
028import java.util.Collection;
029import java.util.Collections;
030import java.util.Date;
031import java.util.Enumeration;
032import java.util.HashMap;
033import java.util.Iterator;
034import java.util.List;
035import java.util.Locale;
036import java.util.Map;
037import java.util.Properties;
038import java.util.TimeZone;
039import java.util.TreeSet;
040
041import javax.servlet.ServletConfig;
042import javax.servlet.ServletContext;
043import javax.servlet.ServletRequest;
044import javax.servlet.http.HttpServletRequest;
045
046import org.apache.commons.lang.time.StopWatch;
047import org.apache.log4j.Logger;
048import org.apache.log4j.PropertyConfigurator;
049import org.apache.wiki.api.engine.AdminBeanManager;
050import org.apache.wiki.api.engine.FilterManager;
051import org.apache.wiki.api.engine.PluginManager;
052import org.apache.wiki.api.exceptions.FilterException;
053import org.apache.wiki.api.exceptions.NoSuchVariableException;
054import org.apache.wiki.api.exceptions.ProviderException;
055import org.apache.wiki.api.exceptions.WikiException;
056import org.apache.wiki.attachment.Attachment;
057import org.apache.wiki.attachment.AttachmentManager;
058import org.apache.wiki.auth.AuthenticationManager;
059import org.apache.wiki.auth.AuthorizationManager;
060import org.apache.wiki.auth.UserManager;
061import org.apache.wiki.auth.acl.AclManager;
062import org.apache.wiki.auth.acl.DefaultAclManager;
063import org.apache.wiki.auth.authorize.GroupManager;
064import org.apache.wiki.content.PageRenamer;
065import org.apache.wiki.diff.DifferenceManager;
066import org.apache.wiki.event.WikiEngineEvent;
067import org.apache.wiki.event.WikiEventListener;
068import org.apache.wiki.event.WikiEventManager;
069import org.apache.wiki.event.WikiPageEvent;
070import org.apache.wiki.event.WikiPageRenameEvent;
071import org.apache.wiki.i18n.InternationalizationManager;
072import org.apache.wiki.parser.MarkupParser;
073import org.apache.wiki.parser.WikiDocument;
074import org.apache.wiki.providers.WikiPageProvider;
075import org.apache.wiki.render.RenderingManager;
076import org.apache.wiki.rss.RSSGenerator;
077import org.apache.wiki.rss.RSSThread;
078import org.apache.wiki.search.SearchManager;
079import org.apache.wiki.ui.Command;
080import org.apache.wiki.ui.CommandResolver;
081import org.apache.wiki.ui.EditorManager;
082import org.apache.wiki.ui.TemplateManager;
083import org.apache.wiki.ui.progress.ProgressManager;
084import org.apache.wiki.url.URLConstructor;
085import org.apache.wiki.util.ClassUtil;
086import org.apache.wiki.util.PropertyReader;
087import org.apache.wiki.util.TextUtil;
088import org.apache.wiki.util.comparators.PageTimeComparator;
089import org.apache.wiki.workflow.Decision;
090import org.apache.wiki.workflow.DecisionRequiredException;
091import org.apache.wiki.workflow.Fact;
092import org.apache.wiki.workflow.Task;
093import org.apache.wiki.workflow.Workflow;
094import org.apache.wiki.workflow.WorkflowBuilder;
095import org.apache.wiki.workflow.WorkflowManager;
096
097
098/**
099 *  Provides Wiki services to the JSP page.
100 *
101 *  <P>
102 *  This is the main interface through which everything should go.
103 *
104 *  <P>
105 *  Using this class:  Always get yourself an instance from JSP page
106 *  by using the WikiEngine.getInstance() method.  Never create a new
107 *  WikiEngine() from scratch, unless you're writing tests.
108 *  <p>
109 *  There's basically only a single WikiEngine for each web application, and
110 *  you should always get it using the WikiEngine.getInstance() method.
111 */
112public class WikiEngine
113{
114    private static final String ATTR_WIKIENGINE = "org.apache.wiki.WikiEngine";
115
116    private static final Logger log = Logger.getLogger(WikiEngine.class);
117
118    /** True, if log4j has been configured. */
119    // FIXME: If you run multiple applications, the first application
120    // to run defines where the log goes.  Not what we want.
121    private static boolean   c_configured = false;
122
123    /** Stores properties. */
124    private Properties       m_properties;
125    
126    /** The default inlining pattern.  Currently "*.png" */
127    public static final String DEFAULT_INLINEPATTERN = "*.png";
128
129    /** Property for application name */
130    public static final String PROP_APPNAME = "jspwiki.applicationName";
131    
132    /** This property defines the inline image pattern.  It's current value is {@value} */
133    public static final String PROP_INLINEIMAGEPTRN  = "jspwiki.translatorReader.inlinePattern";
134
135    /** Property start for any interwiki reference. */
136    public static final String PROP_INTERWIKIREF = "jspwiki.interWikiRef.";
137
138    /** If true, then the user name will be stored with the page data.*/
139    public static final String PROP_STOREUSERNAME= "jspwiki.storeUserName";
140
141    /** Define the used encoding.  Currently supported are ISO-8859-1 and UTF-8 */
142    public static final String PROP_ENCODING     = "jspwiki.encoding";
143
144    /** The name for the base URL to use in all references. */
145    public static final String PROP_BASEURL      = "jspwiki.baseURL";
146
147    /** The name for the property which allows you to set the current reference
148     *  style.  The value is {@value}.
149     */
150    public static final String PROP_REFSTYLE     = "jspwiki.referenceStyle";
151
152    /** Property name for the "spaces in titles" -hack. */
153    public static final String PROP_BEAUTIFYTITLE = "jspwiki.breakTitleWithSpaces";
154
155    /** Property name for where the jspwiki work directory should be.
156        If not specified, reverts to ${java.tmpdir}. */
157    public static final String PROP_WORKDIR      = "jspwiki.workDir";
158
159    /** The name of the cookie that gets stored to the user browser. */
160    public static final String PREFS_COOKIE_NAME = "JSPWikiUserProfile";
161
162    /** Property name for the "match english plurals" -hack. */
163    public static final String PROP_MATCHPLURALS     = "jspwiki.translatorReader.matchEnglishPlurals";
164
165    /** Property name for the template that is used. */
166    public static final String PROP_TEMPLATEDIR  = "jspwiki.templateDir";
167
168    /** Property name for the default front page. */
169    public static final String PROP_FRONTPAGE    = "jspwiki.frontPage";
170
171    /** Property name for setting the url generator instance */
172
173    public static final String PROP_URLCONSTRUCTOR = "jspwiki.urlConstructor";
174
175    /** If this property is set to false, all filters are disabled when translating. */
176    public static final String PROP_RUNFILTERS   = "jspwiki.runFilters";
177
178    /** Compares pages by name */
179    private PageSorter     m_pageSorter = null;
180    
181    /** Does the work in renaming pages. */
182    private PageRenamer    m_pageRenamer = null;
183
184    /** The name of the property containing the ACLManager implementing class.
185     *  The value is {@value}. */
186    public static final String PROP_ACL_MANAGER_IMPL = "jspwiki.aclManager";
187    
188    /** If this property is set to false, we don't allow the creation of empty pages */
189    public static final String PROP_ALLOW_CREATION_OF_EMPTY_PAGES = "jspwiki.allowCreationOfEmptyPages";
190
191    /** Should the user info be saved with the page data as well? */
192    private boolean          m_saveUserInfo = true;
193
194    /** If true, uses UTF8 encoding for all data */
195    private boolean          m_useUTF8      = true;
196
197    /** Stores the base URL. */
198    private String           m_baseURL;
199
200    /** Store the file path to the basic URL.  When we're not running as
201        a servlet, it defaults to the user's current directory. */
202    private String           m_rootPath = System.getProperty("user.dir");
203
204    /** Stores references between wikipages. */
205    private ReferenceManager m_referenceManager = null;
206
207    /** Stores the Plugin manager */
208    private PluginManager    m_pluginManager;
209
210    /** Stores the Variable manager */
211    private VariableManager  m_variableManager;
212
213    /** Stores the Attachment manager */
214    private AttachmentManager m_attachmentManager = null;
215
216    /** Stores the Page manager */
217    private PageManager      m_pageManager = null;
218
219    /** Stores the authorization manager */
220    private AuthorizationManager m_authorizationManager = null;
221
222    /** Stores the authentication manager.*/
223    private AuthenticationManager      m_authenticationManager = null;
224
225    /** Stores the ACL manager. */
226    private AclManager       m_aclManager = null;
227
228    /** Resolves wiki actions, JSPs and special pages. */
229    private CommandResolver m_commandResolver = null;
230
231    private TemplateManager  m_templateManager = null;
232
233    /** Does all our diffs for us. */
234    private DifferenceManager m_differenceManager;
235
236    /** Handlers page filters. */
237    private FilterManager    m_filterManager;
238
239    /** Stores the Search manager */
240    private SearchManager    m_searchManager = null;
241
242    /** Facade for managing users */
243    private UserManager      m_userManager = null;
244
245    /** Facade for managing users */
246    private GroupManager     m_groupManager = null;
247
248    private RenderingManager m_renderingManager;
249
250    private EditorManager    m_editorManager;
251
252    private InternationalizationManager m_internationalizationManager;
253
254    private ProgressManager  m_progressManager;
255
256    /** Constructs URLs */
257    private URLConstructor   m_urlConstructor;
258
259    /** Generates RSS feed when requested. */
260    private RSSGenerator     m_rssGenerator;
261
262    /** The RSS file to generate. */
263    private String           m_rssFile;
264
265    /** Store the ServletContext that we're in.  This may be null if WikiEngine
266        is not running inside a servlet container (i.e. when testing). */
267    private ServletContext   m_servletContext = null;
268
269    /** If true, all titles will be cleaned. */
270    private boolean          m_beautifyTitle = false;
271
272    /** Stores the template path.  This is relative to "templates". */
273    private String           m_templateDir;
274
275    /** The default front page name.  Defaults to "Main". */
276    private String           m_frontPage;
277
278    /** The time when this engine was started. */
279    private Date             m_startTime;
280
281    /** The location where the work directory is. */
282    private String           m_workDir;
283
284    /** Each engine has their own application id. */
285    private String           m_appid = "";
286
287    private boolean          m_isConfigured = false; // Flag.
288
289
290    /** Each engine has its own workflow manager. */
291    private WorkflowManager m_workflowMgr = null;
292
293    private AdminBeanManager m_adminBeanManager;
294
295    /** Stores wikiengine attributes. */
296    private Map<String,Object> m_attributes = Collections.synchronizedMap(new HashMap<String,Object>());
297
298    /**
299     *  Gets a WikiEngine related to this servlet.  Since this method
300     *  is only called from JSP pages (and JspInit()) to be specific,
301     *  we throw a RuntimeException if things don't work.
302     *
303     *  @param config The ServletConfig object for this servlet.
304     *
305     *  @return A WikiEngine instance.
306     *  @throws InternalWikiException in case something fails.  This
307     *          is a RuntimeException, so be prepared for it.
308     */
309
310    // FIXME: It seems that this does not work too well, jspInit()
311    // does not react to RuntimeExceptions, or something...
312
313    public static synchronized WikiEngine getInstance( ServletConfig config )
314        throws InternalWikiException
315    {
316        return getInstance( config.getServletContext(), null );
317    }
318
319    /**
320     *  Gets a WikiEngine related to the servlet. Works like getInstance(ServletConfig),
321     *  but does not force the Properties object. This method is just an optional way
322     *  of initializing a WikiEngine for embedded JSPWiki applications; normally, you
323     *  should use getInstance(ServletConfig).
324     *
325     *  @param config The ServletConfig of the webapp servlet/JSP calling this method.
326     *  @param props  A set of properties, or null, if we are to load JSPWiki's default
327     *                jspwiki.properties (this is the usual case).
328     *
329     *  @return One well-behaving WikiEngine instance.
330     */
331    public static synchronized WikiEngine getInstance( ServletConfig config,
332                                                       Properties props )
333    {
334        return getInstance( config.getServletContext(), props );
335    }
336
337    /**
338     *  Gets a WikiEngine related to the servlet. Works just like getInstance( ServletConfig )
339     *
340     *  @param context The ServletContext of the webapp servlet/JSP calling this method.
341     *  @param props  A set of properties, or null, if we are to load JSPWiki's default
342     *                jspwiki.properties (this is the usual case).
343     *
344     *  @return One fully functional, properly behaving WikiEngine.
345     *  @throws InternalWikiException If the WikiEngine instantiation fails.
346     */
347
348    // FIXME: Potential make-things-easier thingy here: no need to fetch the wikiengine anymore
349    //        Wiki.jsp.jspInit() [really old code]; it's probably even faster to fetch it
350    //        using this method every time than go to pageContext.getAttribute().
351
352    public static synchronized WikiEngine getInstance( ServletContext context,
353                                                       Properties props )
354        throws InternalWikiException
355    {
356        WikiEngine engine = (WikiEngine) context.getAttribute( ATTR_WIKIENGINE );
357
358        if( engine == null )
359        {
360            String appid = Integer.toString(context.hashCode()); //FIXME: Kludge, use real type.
361
362            context.log(" Assigning new engine to "+appid);
363            try
364            {
365                if( props == null )
366                {
367                    props = PropertyReader.loadWebAppProps( context );
368                }
369
370                engine = new WikiEngine( context, appid, props );
371                context.setAttribute( ATTR_WIKIENGINE, engine );
372            }
373            catch( Exception e )
374            {
375                context.log( "ERROR: Failed to create a Wiki engine: "+e.getMessage() );
376                log.error( "ERROR: Failed to create a Wiki engine, stacktrace follows " , e);
377                throw new InternalWikiException( "No wiki engine, check logs." );
378            }
379
380        }
381
382        return engine;
383    }
384
385
386    /**
387     *  Instantiate the WikiEngine using a given set of properties.
388     *  Use this constructor for testing purposes only.
389     *
390     *  @param properties A set of properties to use to initialize this WikiEngine.
391     *  @throws WikiException If the initialization fails.
392     */
393    public WikiEngine( Properties properties )
394        throws WikiException
395    {
396        initialize( properties );
397    }
398
399    /**
400     *  Instantiate using this method when you're running as a servlet and
401     *  WikiEngine will figure out where to look for the property
402     *  file.
403     *  Do not use this method - use WikiEngine.getInstance() instead.
404     *
405     *  @param context A ServletContext.
406     *  @param appid   An Application ID.  This application is an unique random string which
407     *                 is used to recognize this WikiEngine.
408     *  @param props   The WikiEngine configuration.
409     *  @throws WikiException If the WikiEngine construction fails.
410     */
411    protected WikiEngine( ServletContext context, String appid, Properties props )
412        throws WikiException
413    {
414        super();
415        m_servletContext = context;
416        m_appid          = appid;
417
418        // Stash the WikiEngine in the servlet context
419        if ( context != null )
420        {
421            context.setAttribute( ATTR_WIKIENGINE,  this );
422            m_rootPath = context.getRealPath("/");
423        }
424        
425        try
426        {
427            //
428            //  Note: May be null, if JSPWiki has been deployed in a WAR file.
429            //
430            initialize( props );
431            log.info("Root path for this Wiki is: '"+m_rootPath+"'");
432        }
433        catch( Exception e )
434        {
435            String msg = Release.APPNAME+": Unable to load and setup properties from jspwiki.properties. "+e.getMessage();
436            if ( context != null )
437            {
438                context.log( msg );
439            }
440            throw new WikiException( msg, e );
441        }
442    }
443
444    /**
445     *  Does all the real initialization.
446     */
447    private void initialize( Properties props )
448        throws WikiException
449    {
450        m_startTime  = new Date();
451        m_properties = props;
452
453        //
454        //  Initialize log4j.  However, make sure that we don't initialize it multiple times.
455        //  By default we load the log4j config statements from jspwiki.properties, unless
456        //  the property jspwiki.use.external.logconfig=true, in that case we let log4j figure out the
457        //  logging configuration.
458        //
459        if( !c_configured )
460        {
461            String useExternalLogConfig = TextUtil.getStringProperty(props,"jspwiki.use.external.logconfig","false");
462            if( useExternalLogConfig == null || useExternalLogConfig.equals("false"))
463            {
464                PropertyConfigurator.configure( props );
465            }
466            c_configured = true;
467        }
468
469        log.info("*******************************************");
470        log.info(Release.APPNAME+" "+Release.getVersionString()+" starting. Whee!");
471
472        fireEvent( WikiEngineEvent.INITIALIZING ); // begin initialization
473
474        log.debug("Java version: "+System.getProperty("java.runtime.version"));
475        log.debug("Java vendor: "+System.getProperty("java.vm.vendor"));
476        log.debug("OS: "+System.getProperty("os.name")+" "+System.getProperty("os.version")+" "+System.getProperty("os.arch"));
477        log.debug("Default server locale: "+Locale.getDefault());
478        log.debug("Default server timezone: "+TimeZone.getDefault().getDisplayName(true, TimeZone.LONG));
479
480        if( m_servletContext != null )
481        {
482            log.info("Servlet container: "+m_servletContext.getServerInfo() );
483            if( m_servletContext.getMajorVersion() < 2 ||
484                (m_servletContext.getMajorVersion() == 2 && m_servletContext.getMinorVersion() < 4) )
485            {
486                throw new InternalWikiException("I require a container which supports at least version 2.4 of Servlet specification");
487            }
488        }
489
490        log.debug("Configuring WikiEngine...");
491
492        //  Initializes the CommandResolver
493        m_commandResolver  = new CommandResolver( this, props );
494
495        //
496        //  Create and find the default working directory.
497        //
498        m_workDir        = TextUtil.getStringProperty( props, PROP_WORKDIR, null );
499
500        if( m_workDir == null )
501        {
502            m_workDir = System.getProperty("java.io.tmpdir", ".");
503            m_workDir += File.separator+Release.APPNAME+"-"+m_appid;
504        }
505
506        try
507        {
508            File f = new File( m_workDir );
509            f.mkdirs();
510
511            //
512            //  A bunch of sanity checks
513            //
514            if( !f.exists() ) throw new WikiException("Work directory does not exist: "+m_workDir);
515            if( !f.canRead() ) throw new WikiException("No permission to read work directory: "+m_workDir);
516            if( !f.canWrite() ) throw new WikiException("No permission to write to work directory: "+m_workDir);
517            if( !f.isDirectory() ) throw new WikiException("jspwiki.workDir does not point to a directory: "+m_workDir);
518        }
519        catch( SecurityException e )
520        {
521            log.fatal( "Unable to find or create the working directory: "+m_workDir, e );
522            throw new IllegalArgumentException( "Unable to find or create the working dir: " + m_workDir, e );
523        }
524
525        log.info("JSPWiki working directory is '"+m_workDir+"'");
526
527        m_saveUserInfo   = TextUtil.getBooleanProperty( props,
528                                                        PROP_STOREUSERNAME,
529                                                        m_saveUserInfo );
530
531        m_useUTF8        = "UTF-8".equals( TextUtil.getStringProperty( props, PROP_ENCODING, "ISO-8859-1" ) );
532        m_baseURL = TextUtil.getStringProperty(props, PROP_BASEURL, "");
533        if (!m_baseURL.endsWith("/"))
534        {
535            m_baseURL = m_baseURL + "/";
536        }
537
538        m_beautifyTitle  = TextUtil.getBooleanProperty( props,
539                                                        PROP_BEAUTIFYTITLE,
540                                                        m_beautifyTitle );
541
542        m_templateDir    = TextUtil.getStringProperty( props, PROP_TEMPLATEDIR, "default" );
543        m_frontPage      = TextUtil.getStringProperty( props, PROP_FRONTPAGE,   "Main" );
544        
545        // Initialize the page name comparator now as it may be used while
546        // initializing other modules
547        initPageSorter( props );
548
549        //
550        //  Initialize the important modules.  Any exception thrown by the
551        //  managers means that we will not start up.
552        //
553
554        // FIXME: This part of the code is getting unwieldy.  We must think
555        //        of a better way to do the startup-sequence.
556        try
557        {
558            Class< ? > urlclass = ClassUtil.findClass( "org.apache.wiki.url",
559                    TextUtil.getStringProperty( props, PROP_URLCONSTRUCTOR, "DefaultURLConstructor" ) );
560            m_urlConstructor = (URLConstructor) urlclass.newInstance();
561            m_urlConstructor.initialize( this, props );
562            
563            m_pageManager       = (PageManager)ClassUtil.getMappedObject(PageManager.class.getName(), this, props );
564            m_pluginManager     = (PluginManager)ClassUtil.getMappedObject(PluginManager.class.getName(), this, props );
565            m_differenceManager = (DifferenceManager)ClassUtil.getMappedObject(DifferenceManager.class.getName(), this, props );
566            m_attachmentManager = (AttachmentManager)ClassUtil.getMappedObject(AttachmentManager.class.getName(), this, props );
567            m_variableManager   = (VariableManager)ClassUtil.getMappedObject(VariableManager.class.getName(), props );
568            // m_filterManager     = (FilterManager)ClassUtil.getMappedObject(FilterManager.class.getName(), this, props );
569            m_renderingManager  = (RenderingManager) ClassUtil.getMappedObject(RenderingManager.class.getName());
570
571            m_searchManager     = (SearchManager)ClassUtil.getMappedObject(SearchManager.class.getName(), this, props );
572
573            m_authenticationManager = (AuthenticationManager) ClassUtil.getMappedObject(AuthenticationManager.class.getName());
574            m_authorizationManager  = (AuthorizationManager) ClassUtil.getMappedObject( AuthorizationManager.class.getName());
575            m_userManager           = (UserManager) ClassUtil.getMappedObject(UserManager.class.getName());
576            m_groupManager          = (GroupManager) ClassUtil.getMappedObject(GroupManager.class.getName());
577
578            m_editorManager     = (EditorManager)ClassUtil.getMappedObject(EditorManager.class.getName(), this );
579            m_editorManager.initialize( props );
580
581            m_progressManager   = new ProgressManager();
582
583            // Initialize the authentication, authorization, user and acl managers
584
585            m_authenticationManager.initialize( this, props );
586            m_authorizationManager.initialize( this, props );
587            m_userManager.initialize( this, props );
588            m_groupManager.initialize( this, props );
589            m_aclManager = getAclManager();
590
591            // Start the Workflow manager
592            m_workflowMgr = (WorkflowManager)ClassUtil.getMappedObject(WorkflowManager.class.getName());
593            m_workflowMgr.initialize(this, props);
594
595            m_internationalizationManager = (InternationalizationManager)
596                ClassUtil.getMappedObject(InternationalizationManager.class.getName(),this);
597
598            m_templateManager   = (TemplateManager)
599                ClassUtil.getMappedObject(TemplateManager.class.getName(), this, props );
600
601            m_adminBeanManager = (AdminBeanManager)
602                ClassUtil.getMappedObject(AdminBeanManager.class.getName(),this);
603
604            // Since we want to use a page filters initilize() method
605            // as a engine startup listener where we can initialize global event listeners,
606            // it must be called lastly, so that all object references in the engine
607            // are availabe to the initialize() method
608            m_filterManager     = (FilterManager)
609                ClassUtil.getMappedObject(FilterManager.class.getName(), this, props );
610
611            // RenderingManager depends on FilterManager events.
612
613            m_renderingManager.initialize( this, props );
614
615            //
616            //  ReferenceManager has the side effect of loading all
617            //  pages.  Therefore after this point, all page attributes
618            //  are available.
619            //
620            //  initReferenceManager is indirectly using m_filterManager, therefore
621            //  it has to be called after it was initialized.
622            //
623            initReferenceManager();
624
625            //
626            //  Hook the different manager routines into the system.
627            //
628            m_filterManager.addPageFilter(m_referenceManager, -1001 );
629            m_filterManager.addPageFilter(m_searchManager, -1002 );
630        }
631
632        catch( RuntimeException e )
633        {
634            // RuntimeExceptions may occur here, even if they shouldn't.
635            log.fatal( "Failed to start managers.", e );
636            throw new WikiException( "Failed to start managers: " + e.getMessage(), e );
637        }
638        catch (ClassNotFoundException e)
639        {
640            log.fatal( "JSPWiki could not start, URLConstructor was not found: " + e.getMessage(), e );
641            throw new WikiException(e.getMessage(), e );
642        }
643        catch (InstantiationException e)
644        {
645            log.fatal( "JSPWiki could not start, URLConstructor could not be instantiated: " + e.getMessage(), e );
646            throw new WikiException(e.getMessage(), e );
647        }
648        catch (IllegalAccessException e)
649        {
650            log.fatal( "JSPWiki could not start, URLConstructor cannot be accessed: " + e.getMessage(), e );
651            throw new WikiException(e.getMessage(), e );
652        }
653        catch( Exception e )
654        {
655            // Final catch-all for everything
656            log.fatal( "JSPWiki could not start, due to an unknown exception when starting.",e );
657            throw new WikiException( "Failed to start. Caused by: " + e.getMessage() + 
658                                     "; please check log files for better information.", e );
659        }
660        
661        //
662        //  Initialize the good-to-have-but-not-fatal modules.
663        //
664        try
665        {
666            if( TextUtil.getBooleanProperty( props,
667                                             RSSGenerator.PROP_GENERATE_RSS,
668                                             false ) )
669            {
670                m_rssGenerator = (RSSGenerator)ClassUtil.getMappedObject(RSSGenerator.class.getName(), this, props );
671            }
672
673            m_pageRenamer = (PageRenamer)ClassUtil.getMappedObject(PageRenamer.class.getName(), this, props );
674        }
675        catch( Exception e )
676        {
677            log.error( "Unable to start RSS generator - JSPWiki will still work, "+
678                       "but there will be no RSS feed.", e );
679        }
680
681        // Start the RSS generator & generator thread
682        if( m_rssGenerator != null )
683        {
684            m_rssFile = TextUtil.getStringProperty( props,
685                    RSSGenerator.PROP_RSSFILE, "rss.rdf" );
686            File rssFile=null;
687            if (m_rssFile.startsWith(File.separator))
688            {
689                // honor absolute pathnames:
690                rssFile = new File(m_rssFile );
691            }
692            else
693            {
694                // relative path names are anchored from the webapp root path:
695                rssFile = new File( getRootPath(), m_rssFile );
696            }
697            int rssInterval = TextUtil.getIntegerProperty( props,
698                    RSSGenerator.PROP_INTERVAL, 3600 );
699            RSSThread rssThread = new RSSThread( this, rssFile, rssInterval );
700            rssThread.start();
701        }
702
703        fireEvent( WikiEngineEvent.INITIALIZED ); // initialization complete
704
705        log.info("WikiEngine configured.");
706        m_isConfigured = true;
707    }
708
709    /**
710     *  Initializes the reference manager. Scans all existing WikiPages for
711     *  internal links and adds them to the ReferenceManager object.
712     *
713     *  @throws WikiException If the reference manager initialization fails.
714     */
715    @SuppressWarnings("unchecked")
716    public void initReferenceManager() throws WikiException
717    {
718        try
719        {
720            ArrayList<WikiPage> pages = new ArrayList<WikiPage>();
721            pages.addAll( m_pageManager.getAllPages() );
722            pages.addAll( m_attachmentManager.getAllAttachments() );
723
724            // Build a new manager with default key lists.
725            if( m_referenceManager == null )
726            {
727                m_referenceManager =
728                    (ReferenceManager) ClassUtil.getMappedObject(ReferenceManager.class.getName(), this );
729                m_referenceManager.initialize( pages );
730            }
731
732        }
733        catch( ProviderException e )
734        {
735            log.fatal("PageProvider is unable to list pages: ", e);
736        }
737    }
738
739    /**
740     *  Returns the set of properties that the WikiEngine was initialized
741     *  with.  Note that this method returns a direct reference, so it's possible
742     *  to manipulate the properties.  However, this is not advised unless you
743     *  really know what you're doing.
744     *
745     *  @return The wiki properties
746     */
747
748    public Properties getWikiProperties()
749    {
750        return m_properties;
751    }
752
753    /**
754     *  Returns the JSPWiki working directory set with "jspwiki.workDir".
755     *
756     *  @since 2.1.100
757     *  @return The working directory.
758     */
759    public String getWorkDir()
760    {
761        return m_workDir;
762    }
763
764    /**
765     *  Returns the current template directory.
766     *
767     *  @since 1.9.20
768     *  @return The template directory as initialized by the engine.
769     */
770    public String getTemplateDir()
771    {
772        return m_templateDir;
773    }
774
775    /**
776     *  Returns the current TemplateManager.
777     *
778     *  @return A TemplateManager instance.
779     */
780    public TemplateManager getTemplateManager()
781    {
782        return m_templateManager;
783    }
784
785    /**
786     *  Returns the base URL, telling where this Wiki actually lives.
787     *
788     *  @since 1.6.1
789     *  @return The Base URL.
790     */
791
792    public String getBaseURL()
793    {
794        return m_baseURL;
795    }
796
797    /**
798     *  Returns the moment when this engine was started.
799     *
800     *  @since 2.0.15.
801     *  @return The start time of this wiki.
802     */
803
804    public Date getStartTime()
805    {
806        return (Date)m_startTime.clone();
807    }
808
809    /**
810     * <p>
811     * Returns the basic absolute URL to a page, without any modifications. You
812     * may add any parameters to this.
813     * </p>
814     * <p>
815     * Since 2.3.90 it is safe to call this method with <code>null</code>
816     * pageName, in which case it will default to the front page.
817     * </p>
818     * @since 2.0.3
819     * @param pageName The name of the page.  May be null, in which case defaults to the front page.
820     * @return An absolute URL to the page.
821     */
822    public String getViewURL( String pageName )
823    {
824        if( pageName == null )
825        {
826            pageName = getFrontPage();
827        }
828        return getURLConstructor().makeURL( WikiContext.VIEW, pageName, true, null );
829    }
830
831    /**
832     *  Returns the basic URL to an editor.  Please use WikiContext.getURL() or
833     *  WikiEngine.getURL() instead.
834     *
835     *  @see #getURL(String, String, String, boolean)
836     *  @see WikiContext#getURL(String, String)
837     *  @deprecated
838     *  
839     *  @param pageName The name of the page.
840     *  @return An URI.
841     *
842     *  @since 2.0.3
843     */
844    public String getEditURL( String pageName )
845    {
846        return m_urlConstructor.makeURL( WikiContext.EDIT, pageName, false, null );
847    }
848
849    /**
850     *  Returns the basic attachment URL.Please use WikiContext.getURL() or
851     *  WikiEngine.getURL() instead.
852     *
853     *  @see #getURL(String, String, String, boolean)
854     *  @see WikiContext#getURL(String, String)
855     *  @since 2.0.42.
856     *  @param attName Attachment name
857     *  @deprecated
858     *  @return An URI.
859     */
860    public String getAttachmentURL( String attName )
861    {
862        return m_urlConstructor.makeURL( WikiContext.ATTACH, attName, false, null );
863    }
864
865    /**
866     *  Returns an URL if a WikiContext is not available.
867     *
868     *  @param context The WikiContext (VIEW, EDIT, etc...)
869     *  @param pageName Name of the page, as usual
870     *  @param params List of parameters. May be null, if no parameters.
871     *  @param absolute If true, will generate an absolute URL regardless of properties setting.
872     *  @return An URL (absolute or relative).
873     */
874    public String getURL( String context, String pageName, String params, boolean absolute )
875    {
876        if( pageName == null ) pageName = getFrontPage();
877        return m_urlConstructor.makeURL( context, pageName, absolute, params );
878    }
879
880    /**
881     *  Returns the default front page, if no page is used.
882     *
883     *  @return The front page name.
884     */
885
886    public String getFrontPage()
887    {
888        return m_frontPage;
889    }
890
891    /**
892     *  Returns the ServletContext that this particular WikiEngine was
893     *  initialized with.  <B>It may return null</B>, if the WikiEngine is not
894     *  running inside a servlet container!
895     *
896     *  @since 1.7.10
897     *  @return ServletContext of the WikiEngine, or null.
898     */
899
900    public ServletContext getServletContext()
901    {
902        return m_servletContext;
903    }
904
905    /**
906     *  This is a safe version of the Servlet.Request.getParameter() routine.
907     *  Unfortunately, the default version always assumes that the incoming
908     *  character set is ISO-8859-1, even though it was something else.
909     *  This means that we need to make a new string using the correct
910     *  encoding.
911     *  <P>
912     *  For more information, see:
913     *     <A HREF="http://www.jguru.com/faq/view.jsp?EID=137049">JGuru FAQ</A>.
914     *  <P>
915     *  Incidentally, this is almost the same as encodeName(), below.
916     *  I am not yet entirely sure if it's safe to merge the code.
917     *
918     *  @param request The servlet request
919     *  @param name    The parameter name to get.
920     *  @return The parameter value or null
921     *  @since 1.5.3
922     *  @deprecated JSPWiki now requires servlet API 2.3, which has a better
923     *              way of dealing with this stuff.  This will be removed in
924     *              the near future.
925     */
926
927    public String safeGetParameter( ServletRequest request, String name )
928    {
929        try
930        {
931            String res = request.getParameter( name );
932            if( res != null )
933            {
934                res = new String(res.getBytes("ISO-8859-1"),
935                                 getContentEncoding() );
936            }
937
938            return res;
939        }
940        catch( UnsupportedEncodingException e )
941        {
942            log.fatal( "Unsupported encoding", e );
943            return "";
944        }
945
946    }
947
948    /**
949     *  Returns the query string (the portion after the question mark).
950     *
951     *  @param request The HTTP request to parse.
952     *  @return The query string.  If the query string is null,
953     *          returns an empty string.
954     *
955     *  @since 2.1.3
956     */
957    public String safeGetQueryString( HttpServletRequest request )
958    {
959        if (request == null)
960        {
961            return "";
962        }
963
964        try
965        {
966            String res = request.getQueryString();
967            if( res != null )
968            {
969                res = new String(res.getBytes("ISO-8859-1"),
970                                 getContentEncoding() );
971
972                //
973                // Ensure that the 'page=xyz' attribute is removed
974                // FIXME: Is it really the mandate of this routine to
975                //        do that?
976                //
977                int pos1 = res.indexOf("page=");
978                if (pos1 >= 0)
979                {
980                    String tmpRes = res.substring(0, pos1);
981                    int pos2 = res.indexOf("&",pos1) + 1;
982                    if ( (pos2 > 0) && (pos2 < res.length()) )
983                    {
984                        tmpRes = tmpRes + res.substring(pos2);
985                    }
986                    res = tmpRes;
987                }
988            }
989
990            return res;
991        }
992        catch( UnsupportedEncodingException e )
993        {
994            log.fatal( "Unsupported encoding", e );
995            return "";
996        }
997    }
998
999    /**
1000     *  Returns an URL to some other Wiki that we know.
1001     *
1002     *  @param  wikiName The name of the other wiki.
1003     *  @return null, if no such reference was found.
1004     */
1005    public String getInterWikiURL( String wikiName )
1006    {
1007        return TextUtil.getStringProperty(m_properties,PROP_INTERWIKIREF+wikiName,null);
1008    }
1009
1010    /**
1011     *  Returns a collection of all supported InterWiki links.
1012     *
1013     *  @return A Collection of Strings.
1014     */
1015    public Collection< String > getAllInterWikiLinks()
1016    {
1017        ArrayList< String > list = new ArrayList< String >();
1018
1019        for( Enumeration< ? > i = m_properties.propertyNames(); i.hasMoreElements(); )
1020        {
1021            String prop = ( String )i.nextElement();
1022
1023            if( prop.startsWith( PROP_INTERWIKIREF ) )
1024            {
1025                list.add( prop.substring( prop.lastIndexOf( "." ) + 1 ) );
1026            }
1027        }
1028
1029        return list;
1030    }
1031
1032    /**
1033     *  Returns a collection of all image types that get inlined.
1034     *
1035     *  @return A Collection of Strings with a regexp pattern.
1036     */
1037    public Collection< String > getAllInlinedImagePatterns()
1038    {
1039        Properties props    = getWikiProperties();
1040        ArrayList<String>  ptrnlist = new ArrayList<String>();
1041
1042        for( Enumeration< ? > e = props.propertyNames(); e.hasMoreElements(); )
1043        {
1044            String name = ( String )e.nextElement();
1045
1046            if( name.startsWith( PROP_INLINEIMAGEPTRN ) )
1047            {
1048                String ptrn = TextUtil.getStringProperty( props, name, null );
1049
1050                ptrnlist.add( ptrn );
1051            }
1052        }
1053
1054        if( ptrnlist.size() == 0 )
1055        {
1056            ptrnlist.add( DEFAULT_INLINEPATTERN );
1057        }
1058
1059        return ptrnlist;
1060    }
1061
1062    /**
1063     *  <p>If the page is a special page, then returns a direct URL
1064     *  to that page.  Otherwise returns <code>null</code>.
1065     *  This method delegates requests to
1066     *  {@link org.apache.wiki.ui.CommandResolver#getSpecialPageReference(String)}.
1067     *  </p>
1068     *  <p>
1069     *  Special pages are defined in jspwiki.properties using the jspwiki.specialPage
1070     *  setting.  They're typically used to give Wiki page names to e.g. custom JSP
1071     *  pages.
1072     *  </p>
1073     *
1074     *  @param original The page to check
1075     *  @return A reference to the page, or null, if there's no special page.
1076     */
1077    public String getSpecialPageReference( String original )
1078    {
1079        return m_commandResolver.getSpecialPageReference( original );
1080    }
1081
1082    /**
1083     *  Returns the name of the application.
1084     *
1085     *  @return A string describing the name of this application.
1086     */
1087
1088    // FIXME: Should use servlet context as a default instead of a constant.
1089    public String getApplicationName()
1090    {
1091        String appName = TextUtil.getStringProperty(m_properties,PROP_APPNAME,Release.APPNAME);
1092
1093        return MarkupParser.cleanLink( appName );
1094    }
1095
1096    /**
1097     *  Beautifies the title of the page by appending spaces in suitable
1098     *  places, if the user has so decreed in the properties when constructing
1099     *  this WikiEngine.  However, attachment names are only beautified by
1100     *  the name.
1101     *
1102     *  @param title The title to beautify
1103     *  @return A beautified title (or, if beautification is off,
1104     *          returns the title without modification)
1105     *  @since 1.7.11
1106     */
1107    public String beautifyTitle( String title )
1108    {
1109        if( m_beautifyTitle )
1110        {
1111            try
1112            {
1113                Attachment att = m_attachmentManager.getAttachmentInfo(title);
1114
1115                if(att == null)
1116                {
1117                    return TextUtil.beautifyString( title );
1118                }
1119
1120                String parent = TextUtil.beautifyString( att.getParentName() );
1121
1122                return parent + "/" + att.getFileName();
1123            }
1124            catch( ProviderException e )
1125            {
1126                return title;
1127            }
1128        }
1129
1130        return title;
1131    }
1132
1133    /**
1134     *  Beautifies the title of the page by appending non-breaking spaces
1135     *  in suitable places.  This is really suitable only for HTML output,
1136     *  as it uses the &amp;nbsp; -character.
1137     *
1138     *  @param title The title to beautify
1139     *  @return A beautified title.
1140     *  @since 2.1.127
1141     */
1142    public String beautifyTitleNoBreak( String title )
1143    {
1144        if( m_beautifyTitle )
1145        {
1146            return TextUtil.beautifyString( title, "&nbsp;" );
1147        }
1148
1149        return title;
1150    }
1151
1152    /**
1153     *  Returns true, if the requested page (or an alias) exists.  Will consider
1154     *  any version as existing.  Will also consider attachments.
1155     *
1156     *  @param page WikiName of the page.
1157     *  @return true, if page (or attachment) exists.
1158     */
1159    public boolean pageExists( String page )
1160    {
1161        Attachment att = null;
1162
1163        try
1164        {
1165            if( m_commandResolver.getSpecialPageReference(page) != null ) return true;
1166
1167            if( getFinalPageName( page ) != null )
1168            {
1169                return true;
1170            }
1171
1172            att = getAttachmentManager().getAttachmentInfo( (WikiContext)null, page );
1173        }
1174        catch( ProviderException e )
1175        {
1176            log.debug("pageExists() failed to find attachments",e);
1177        }
1178
1179        return att != null;
1180    }
1181
1182    /**
1183     *  Returns true, if the requested page (or an alias) exists with the
1184     *  requested version.
1185     *
1186     *  @param page Page name
1187     *  @param version Page version
1188     *  @return True, if page (or alias, or attachment) exists
1189     *  @throws ProviderException If the provider fails.
1190     */
1191    public boolean pageExists( String page, int version )
1192        throws ProviderException
1193    {
1194        if( m_commandResolver.getSpecialPageReference(page) != null ) return true;
1195
1196        String finalName = getFinalPageName( page );
1197
1198        boolean isThere = false;
1199
1200        if( finalName != null )
1201        {
1202            //
1203            //  Go and check if this particular version of this page
1204            //  exists.
1205            //
1206            isThere = m_pageManager.pageExists( finalName, version );
1207        }
1208
1209        if( isThere == false )
1210        {
1211            //
1212            //  Go check if such an attachment exists.
1213            //
1214            try
1215            {
1216                isThere = getAttachmentManager().getAttachmentInfo( (WikiContext)null, page, version ) != null;
1217            }
1218            catch( ProviderException e )
1219            {
1220                log.debug("pageExists() failed to find attachments",e);
1221            }
1222        }
1223
1224        return isThere;
1225    }
1226
1227    /**
1228     *  Returns true, if the requested page (or an alias) exists, with the
1229     *  specified version in the WikiPage.
1230     *
1231     *  @param page A WikiPage object describing the name and version.
1232     *  @return true, if the page (or alias, or attachment) exists.
1233     *  @throws ProviderException If something goes badly wrong.
1234     *  @since 2.0
1235     */
1236    public boolean pageExists( WikiPage page )
1237        throws ProviderException
1238    {
1239        if( page != null )
1240        {
1241            return pageExists( page.getName(), page.getVersion() );
1242        }
1243        return false;
1244    }
1245
1246    /**
1247     *  Returns the correct page name, or null, if no such
1248     *  page can be found.  Aliases are considered. This
1249     *  method simply delegates to
1250     *  {@link org.apache.wiki.ui.CommandResolver#getFinalPageName(String)}.
1251     *  @since 2.0
1252     *  @param page Page name.
1253     *  @return The rewritten page name, or null, if the page does not exist.
1254     *  @throws ProviderException If something goes wrong in the backend.
1255     */
1256    public String getFinalPageName( String page )
1257        throws ProviderException
1258    {
1259        return m_commandResolver.getFinalPageName( page );
1260    }
1261
1262    /**
1263     *  Turns a WikiName into something that can be
1264     *  called through using an URL.
1265     *
1266     *  @since 1.4.1
1267     *  @param pagename A name.  Can be actually any string.
1268     *  @return A properly encoded name.
1269     *  @see #decodeName(String)
1270     */
1271    public String encodeName( String pagename )
1272    {
1273        try
1274        {
1275            return URLEncoder.encode( pagename, m_useUTF8 ? "UTF-8" : "ISO-8859-1" );
1276        }
1277        catch( UnsupportedEncodingException e )
1278        {
1279            throw new InternalWikiException( "ISO-8859-1 not a supported encoding!?!  Your platform is borked." );
1280        }
1281    }
1282
1283    /**
1284     *  Decodes a URL-encoded request back to regular life.  This properly heeds
1285     *  the encoding as defined in the settings file.
1286     *
1287     *  @param pagerequest The URL-encoded string to decode
1288     *  @return A decoded string.
1289     *  @see #encodeName(String)
1290     */
1291    public String decodeName( String pagerequest )
1292    {
1293        try
1294        {
1295            return URLDecoder.decode( pagerequest, m_useUTF8 ? "UTF-8" : "ISO-8859-1" );
1296        }
1297        catch( UnsupportedEncodingException e )
1298        {
1299            throw new InternalWikiException("ISO-8859-1 not a supported encoding!?!  Your platform is borked.");
1300        }
1301    }
1302
1303    /**
1304     *  Returns the IANA name of the character set encoding we're
1305     *  supposed to be using right now.
1306     *
1307     *  @since 1.5.3
1308     *  @return The content encoding (either UTF-8 or ISO-8859-1).
1309     */
1310    public String getContentEncoding()
1311    {
1312        if( m_useUTF8 )
1313            return "UTF-8";
1314
1315        return "ISO-8859-1";
1316    }
1317
1318    /**
1319     * Returns the {@link org.apache.wiki.workflow.WorkflowManager} associated with this
1320     * WikiEngine. If the WIkiEngine has not been initialized, this method will return
1321     * <code>null</code>.
1322     * @return the task queue
1323     */
1324    public WorkflowManager getWorkflowManager()
1325    {
1326        return m_workflowMgr;
1327    }
1328
1329    /**
1330     *  Returns the un-HTMLized text of the latest version of a page.
1331     *  This method also replaces the &lt; and &amp; -characters with
1332     *  their respective HTML entities, thus making it suitable
1333     *  for inclusion on an HTML page.  If you want to have the
1334     *  page text without any conversions, use getPureText().
1335     *
1336     *  @param page WikiName of the page to fetch.
1337     *  @return WikiText.
1338     */
1339    public String getText( String page )
1340    {
1341        return getText( page, WikiPageProvider.LATEST_VERSION );
1342    }
1343
1344    /**
1345     *  Returns the un-HTMLized text of the given version of a page.
1346     *  This method also replaces the &lt; and &amp; -characters with
1347     *  their respective HTML entities, thus making it suitable
1348     *  for inclusion on an HTML page.  If you want to have the
1349     *  page text without any conversions, use getPureText().
1350     *
1351     *
1352     * @param page WikiName of the page to fetch
1353     * @param version  Version of the page to fetch
1354     * @return WikiText.
1355     */
1356    public String getText( String page, int version )
1357    {
1358        String result = getPureText( page, version );
1359
1360        //
1361        //  Replace ampersand first, or else all quotes and stuff
1362        //  get replaced as well with &quot; etc.
1363        //
1364        /*
1365        result = TextUtil.replaceString( result, "&", "&amp;" );
1366        */
1367
1368        result = TextUtil.replaceEntities( result );
1369
1370        return result;
1371    }
1372
1373    /**
1374     *  Returns the un-HTMLized text of the given version of a page in
1375     *  the given context.  USE THIS METHOD if you don't know what
1376     *  doing.
1377     *  <p>
1378     *  This method also replaces the &lt; and &amp; -characters with
1379     *  their respective HTML entities, thus making it suitable
1380     *  for inclusion on an HTML page.  If you want to have the
1381     *  page text without any conversions, use getPureText().
1382     *
1383     *  @since 1.9.15.
1384     *  @param context The WikiContext
1385     *  @param page    A page reference (not an attachment)
1386     *  @return The page content as HTMLized String.
1387     *  @see   #getPureText(WikiPage)
1388     */
1389    public String getText( WikiContext context, WikiPage page )
1390    {
1391        return getText( page.getName(), page.getVersion() );
1392    }
1393
1394
1395    /**
1396     *  Returns the pure text of a page, no conversions.  Use this
1397     *  if you are writing something that depends on the parsing
1398     *  of the page.  Note that you should always check for page
1399     *  existence through pageExists() before attempting to fetch
1400     *  the page contents.
1401     *
1402     *  @param page    The name of the page to fetch.
1403     *  @param version If WikiPageProvider.LATEST_VERSION, then uses the
1404     *  latest version.
1405     *  @return The page contents.  If the page does not exist,
1406     *          returns an empty string.
1407     */
1408    // FIXME: Should throw an exception on unknown page/version?
1409    public String getPureText( String page, int version )
1410    {
1411        String result = null;
1412
1413        try
1414        {
1415            result = m_pageManager.getPageText( page, version );
1416        }
1417        catch( ProviderException e )
1418        {
1419            // FIXME
1420        }
1421        finally
1422        {
1423            if( result == null )
1424                result = "";
1425        }
1426
1427        return result;
1428    }
1429
1430    /**
1431     *  Returns the pure text of a page, no conversions.  Use this
1432     *  if you are writing something that depends on the parsing
1433     *  the page. Note that you should always check for page
1434     *  existence through pageExists() before attempting to fetch
1435     *  the page contents.
1436     *
1437     *  @param page A handle to the WikiPage
1438     *  @return String of WikiText.
1439     *  @since 2.1.13.
1440     */
1441    public String getPureText( WikiPage page )
1442    {
1443        return getPureText( page.getName(), page.getVersion() );
1444    }
1445
1446    /**
1447     *  Returns the converted HTML of the page using a different
1448     *  context than the default context.
1449     *
1450     *  @param  context A WikiContext in which you wish to render this page in.
1451     *  @param  page WikiPage reference.
1452     *  @return HTML-rendered version of the page.
1453     */
1454
1455    public String getHTML( WikiContext context, WikiPage page )
1456    {
1457        String pagedata = null;
1458
1459        pagedata = getPureText( page.getName(), page.getVersion() );
1460
1461        String res = textToHTML( context, pagedata );
1462
1463        return res;
1464    }
1465
1466    /**
1467     *  Returns the converted HTML of the page.
1468     *
1469     *  @param page WikiName of the page to convert.
1470     *  @return HTML-rendered version of the page.
1471     */
1472    public String getHTML( String page )
1473    {
1474        return getHTML( page, WikiPageProvider.LATEST_VERSION );
1475    }
1476
1477    /**
1478     *  Returns the converted HTML of the page's specific version.
1479     *  The version must be a positive integer, otherwise the current
1480     *  version is returned.
1481     *
1482     *  @param pagename WikiName of the page to convert.
1483     *  @param version Version number to fetch
1484     *  @return HTML-rendered page text.
1485     */
1486    public String getHTML( String pagename, int version )
1487    {
1488        WikiPage page = getPage( pagename, version );
1489
1490        WikiContext context = new WikiContext( this,
1491                                               page );
1492        context.setRequestContext( WikiContext.NONE );
1493
1494        String res = getHTML( context, page );
1495
1496        return res;
1497    }
1498
1499    /**
1500     *  Converts raw page data to HTML.
1501     *
1502     *  @param pagedata Raw page data to convert to HTML
1503     *  @param context  The WikiContext in which the page is to be rendered
1504     *  @return Rendered page text
1505     */
1506    public String textToHTML( WikiContext context, String pagedata )
1507    {
1508        String result = "";
1509
1510        boolean runFilters = "true".equals(m_variableManager.getValue(context,PROP_RUNFILTERS,"true"));
1511
1512        StopWatch sw = new StopWatch();
1513        sw.start();
1514        try
1515        {
1516            if( runFilters )
1517                pagedata = m_filterManager.doPreTranslateFiltering( context, pagedata );
1518
1519            result = m_renderingManager.getHTML( context, pagedata );
1520
1521            if( runFilters )
1522                result = m_filterManager.doPostTranslateFiltering( context, result );
1523        }
1524        catch( FilterException e )
1525        {
1526            // FIXME: Don't yet know what to do
1527        }
1528        sw.stop();
1529        if( log.isDebugEnabled() )
1530            log.debug("Page "+context.getRealPage().getName()+" rendered, took "+sw );
1531
1532        return result;
1533    }
1534
1535    /**
1536     * Protected method that signals that the WikiEngine will be
1537     * shut down by the servlet container. It is called by
1538     * {@link WikiServlet#destroy()}. When this method is called,
1539     * it fires a "shutdown" WikiEngineEvent to all registered
1540     * listeners.
1541     */
1542    protected void shutdown()
1543    {
1544        fireEvent( WikiEngineEvent.SHUTDOWN );
1545        m_filterManager.destroy();
1546    }
1547
1548    /**
1549     *  Reads a WikiPageful of data from a String and returns all links
1550     *  internal to this Wiki in a Collection.
1551     *
1552     *  @param page The WikiPage to scan
1553     *  @param pagedata The page contents
1554     *  @return a Collection of Strings
1555     */
1556    public Collection< String > scanWikiLinks( WikiPage page, String pagedata ) {
1557        LinkCollector localCollector = new LinkCollector();
1558
1559        textToHTML( new WikiContext( this, page ),
1560                    pagedata,
1561                    localCollector,
1562                    null,
1563                    localCollector,
1564                    false,
1565                    true );
1566
1567        return localCollector.getLinks();
1568    }
1569
1570    /**
1571     *  Just convert WikiText to HTML.
1572     *
1573     *  @param context The WikiContext in which to do the conversion
1574     *  @param pagedata The data to render
1575     *  @param localLinkHook Is called whenever a wiki link is found
1576     *  @param extLinkHook   Is called whenever an external link is found
1577     *
1578     *  @return HTML-rendered page text.
1579     */
1580
1581    public String textToHTML( WikiContext context,
1582                              String pagedata,
1583                              StringTransmutator localLinkHook,
1584                              StringTransmutator extLinkHook )
1585    {
1586        return textToHTML( context, pagedata, localLinkHook, extLinkHook, null, true, false );
1587    }
1588
1589    /**
1590     *  Just convert WikiText to HTML.
1591     *
1592     *  @param context The WikiContext in which to do the conversion
1593     *  @param pagedata The data to render
1594     *  @param localLinkHook Is called whenever a wiki link is found
1595     *  @param extLinkHook   Is called whenever an external link is found
1596     *  @param attLinkHook   Is called whenever an attachment link is found
1597     *  @return HTML-rendered page text.
1598     */
1599
1600    public String textToHTML( WikiContext context,
1601                              String pagedata,
1602                              StringTransmutator localLinkHook,
1603                              StringTransmutator extLinkHook,
1604                              StringTransmutator attLinkHook )
1605    {
1606        return textToHTML( context, pagedata, localLinkHook, extLinkHook, attLinkHook, true, false );
1607    }
1608
1609    /**
1610     *  Helper method for doing the HTML translation.
1611     *
1612     *  @param context The WikiContext in which to do the conversion
1613     *  @param pagedata The data to render
1614     *  @param localLinkHook Is called whenever a wiki link is found
1615     *  @param extLinkHook   Is called whenever an external link is found
1616     *  @param parseAccessRules Parse the access rules if we encounter them
1617     *  @param justParse Just parses the pagedata, does not actually render.  In this case,
1618     *                   this methods an empty string.
1619     *  @return HTML-rendered page text.
1620
1621     */
1622    private String textToHTML( WikiContext context,
1623                               String pagedata,
1624                               StringTransmutator localLinkHook,
1625                               StringTransmutator extLinkHook,
1626                               StringTransmutator attLinkHook,
1627                               boolean            parseAccessRules,
1628                               boolean            justParse )
1629    {
1630        String result = "";
1631
1632        if( pagedata == null )
1633        {
1634            log.error("NULL pagedata to textToHTML()");
1635            return null;
1636        }
1637
1638        boolean runFilters = "true".equals(m_variableManager.getValue(context,PROP_RUNFILTERS,"true"));
1639
1640        try
1641        {
1642            StopWatch sw = new StopWatch();
1643            sw.start();
1644
1645            if( runFilters && m_filterManager != null )
1646                pagedata = m_filterManager.doPreTranslateFiltering( context, pagedata );
1647
1648            MarkupParser mp = m_renderingManager.getParser( context, pagedata );
1649            mp.addLocalLinkHook( localLinkHook );
1650            mp.addExternalLinkHook( extLinkHook );
1651            mp.addAttachmentLinkHook( attLinkHook );
1652
1653            if( !parseAccessRules ) mp.disableAccessRules();
1654
1655            WikiDocument doc = mp.parse();
1656
1657            //
1658            //  In some cases it's better just to parse, not to render
1659            //
1660            if( !justParse )
1661            {
1662                result = m_renderingManager.getHTML( context, doc );
1663
1664                if( runFilters && m_filterManager != null )
1665                    result = m_filterManager.doPostTranslateFiltering( context, result );
1666            }
1667
1668            sw.stop();
1669
1670            if( log.isDebugEnabled() )
1671                log.debug("Page "+context.getRealPage().getName()+" rendered, took "+sw );
1672        }
1673        catch( IOException e )
1674        {
1675            log.error( "Failed to scan page data: ", e );
1676        }
1677        catch( FilterException e )
1678        {
1679            log.error( "page filter threw exception: ", e );
1680            // FIXME: Don't yet know what to do
1681        }
1682
1683        return result;
1684    }
1685
1686    /**
1687     *  Updates all references for the given page.
1688     *
1689     *  @param page wiki page for which references should be updated
1690     */
1691    public void updateReferences( WikiPage page )
1692    {
1693        String pageData = getPureText( page.getName(), WikiProvider.LATEST_VERSION );
1694
1695        m_referenceManager.updateReferences( page.getName(),
1696                                             scanWikiLinks( page, pageData ) );
1697    }
1698
1699
1700    /**
1701     *  Writes the WikiText of a page into the
1702     *  page repository. If the <code>jspwiki.properties</code> file contains
1703     *  the property <code>jspwiki.approver.workflow.saveWikiPage</code> and
1704     *  its value resolves to a valid user, {@link org.apache.wiki.auth.authorize.Group}
1705     *  or {@link org.apache.wiki.auth.authorize.Role}, this method will
1706     *  place a {@link org.apache.wiki.workflow.Decision} in the approver's
1707     *  workflow inbox and throw a {@link org.apache.wiki.workflow.DecisionRequiredException}.
1708     *  If the submitting user is authenticated and the page save is rejected,
1709     *  a notification will be placed in the user's decision queue.
1710     *
1711     *  @since 2.1.28
1712     *  @param context The current WikiContext
1713     *  @param text    The Wiki markup for the page.
1714     *  @throws WikiException if the save operation encounters an error during the
1715     *  save operation. If the page-save operation requires approval, the exception will
1716     *  be of type {@link org.apache.wiki.workflow.DecisionRequiredException}. Individual
1717     *  PageFilters, such as the {@link org.apache.wiki.filters.SpamFilter} may also
1718     *  throw a {@link org.apache.wiki.api.exceptions.RedirectException}.
1719     */
1720    public void saveText( WikiContext context, String text )
1721        throws WikiException
1722    {
1723        // Check if page data actually changed; bail if not
1724        WikiPage page = context.getPage();
1725        String oldText = getPureText( page );
1726        String proposedText = TextUtil.normalizePostData( text );
1727        if ( oldText != null && oldText.equals( proposedText ) )
1728        {
1729            return;
1730        }
1731
1732        // Check if creation of empty pages is allowed; bail if not
1733        boolean allowEmpty = TextUtil.getBooleanProperty( m_properties, 
1734                                                          PROP_ALLOW_CREATION_OF_EMPTY_PAGES, 
1735                                                          false );
1736        if ( !allowEmpty && !pageExists( page ) && text.trim().equals( "" ) )  
1737        {
1738            return;
1739        }
1740        
1741        // Create approval workflow for page save; add the diffed, proposed
1742        // and old text versions as Facts for the approver (if approval is required)
1743        // If submitter is authenticated, any reject messages will appear in his/her workflow inbox.
1744        WorkflowBuilder builder = WorkflowBuilder.getBuilder( this );
1745        Principal submitter = context.getCurrentUser();
1746        Task prepTask = new PageManager.PreSaveWikiPageTask( context, proposedText );
1747        Task completionTask = new PageManager.SaveWikiPageTask();
1748        String diffText = m_differenceManager.makeDiff( context, oldText, proposedText );
1749        boolean isAuthenticated = context.getWikiSession().isAuthenticated();
1750        Fact[] facts = new Fact[5];
1751        facts[0] = new Fact( PageManager.FACT_PAGE_NAME, page.getName() );
1752        facts[1] = new Fact( PageManager.FACT_DIFF_TEXT, diffText );
1753        facts[2] = new Fact( PageManager.FACT_PROPOSED_TEXT, proposedText );
1754        facts[3] = new Fact( PageManager.FACT_CURRENT_TEXT, oldText);
1755        facts[4] = new Fact( PageManager.FACT_IS_AUTHENTICATED, Boolean.valueOf( isAuthenticated ) );
1756        String rejectKey = isAuthenticated ? PageManager.SAVE_REJECT_MESSAGE_KEY : null;
1757        Workflow workflow = builder.buildApprovalWorkflow( submitter,
1758                                                           PageManager.SAVE_APPROVER,
1759                                                           prepTask,
1760                                                           PageManager.SAVE_DECISION_MESSAGE_KEY,
1761                                                           facts,
1762                                                           completionTask,
1763                                                           rejectKey );
1764        m_workflowMgr.start(workflow);
1765
1766        // Let callers know if the page-save requires approval
1767        if ( workflow.getCurrentStep() instanceof Decision )
1768        {
1769            throw new DecisionRequiredException( "The page contents must be approved before they become active." );
1770        }
1771    }
1772
1773    /**
1774     *  Returns the number of pages in this Wiki
1775     *  @return The total number of pages.
1776     */
1777    public int getPageCount()
1778    {
1779        return m_pageManager.getTotalPageCount();
1780    }
1781
1782    /**
1783     *  Returns the provider name.
1784     *  @return The full class name of the current page provider.
1785     */
1786
1787    public String getCurrentProvider()
1788    {
1789        return m_pageManager.getProvider().getClass().getName();
1790    }
1791
1792    /**
1793     *  Return information about current provider.  This method just calls
1794     *  the corresponding PageManager method, which in turn calls the
1795     *  provider method.
1796     *
1797     *  @return A textual description of the current provider.
1798     *  @since 1.6.4
1799     */
1800    public String getCurrentProviderInfo()
1801    {
1802        return m_pageManager.getProviderDescription();
1803    }
1804
1805    /**
1806     *  Returns a Collection of WikiPages, sorted in time
1807     *  order of last change (i.e. first object is the most
1808     *  recently changed).  This method also includes attachments.
1809     *
1810     *  @return Collection of WikiPage objects.  In reality, the returned
1811     *          collection is a Set, but due to API compatibility reasons,
1812     *          we're not changing the signature soon...
1813     */
1814
1815    // FIXME: Should really get a Date object and do proper comparisons.
1816    //        This is terribly wasteful.
1817    @SuppressWarnings("unchecked")
1818    public Collection getRecentChanges()
1819    {
1820        try
1821        {
1822            Collection<WikiPage>   pages = m_pageManager.getAllPages();
1823            Collection<Attachment>  atts = m_attachmentManager.getAllAttachments();
1824
1825            TreeSet<WikiPage> sortedPages = new TreeSet<WikiPage>( new PageTimeComparator() );
1826
1827            sortedPages.addAll( pages );
1828            sortedPages.addAll( atts );
1829
1830            return sortedPages;
1831        }
1832        catch( ProviderException e )
1833        {
1834            log.error( "Unable to fetch all pages: ",e);
1835            return null;
1836        }
1837    }
1838
1839    /**
1840     *  Parses an incoming search request, then
1841     *  does a search.
1842     *  <P>
1843     *  The query is dependent on the actual chosen search provider - each one of them has
1844     *  a language of its own.
1845     *
1846     *  @param query The query string
1847     *  @param wikiContext the context within which to run the search
1848     *  @return A Collection of SearchResult objects.
1849     *  @throws ProviderException If the searching failed
1850     *  @throws IOException       If the searching failed
1851     */
1852
1853    //
1854    // FIXME: Should also have attributes attached.
1855    //
1856    public Collection findPages( String query, WikiContext wikiContext )
1857        throws ProviderException, IOException
1858    {
1859        Collection results = m_searchManager.findPages( query, wikiContext );
1860
1861        return results;
1862    }
1863
1864    /**
1865     *  Finds the corresponding WikiPage object based on the page name.  It always finds
1866     *  the latest version of a page.
1867     *
1868     *  @param pagereq The name of the page to look for.
1869     *  @return A WikiPage object, or null, if the page by the name could not be found.
1870     */
1871
1872    public WikiPage getPage( String pagereq )
1873    {
1874        return getPage( pagereq, WikiProvider.LATEST_VERSION );
1875    }
1876
1877    /**
1878     *  Finds the corresponding WikiPage object base on the page name and version.
1879     *
1880     *  @param pagereq The name of the page to look for.
1881     *  @param version The version number to look for.  May be WikiProvider.LATEST_VERSION,
1882     *  in which case it will look for the latest version (and this method then becomes
1883     *  the equivalent of getPage(String).
1884     *
1885     *  @return A WikiPage object, or null, if the page could not be found; or if there
1886     *  is no such version of the page.
1887     *  @since 1.6.7.
1888     */
1889
1890    public WikiPage getPage( String pagereq, int version )
1891    {
1892        try
1893        {
1894            WikiPage p = m_pageManager.getPageInfo( pagereq, version );
1895
1896            if( p == null )
1897            {
1898                p = m_attachmentManager.getAttachmentInfo( (WikiContext)null, pagereq );
1899            }
1900
1901            return p;
1902        }
1903        catch( ProviderException e )
1904        {
1905            log.error( "Unable to fetch page info",e);
1906            return null;
1907        }
1908    }
1909
1910
1911    /**
1912     *  Returns a Collection of WikiPages containing the
1913     *  version history of a page.
1914     *
1915     *  @param page Name of the page to look for
1916     *  @return an ordered List of WikiPages, each corresponding to a different
1917     *          revision of the page.
1918     */
1919
1920    public List getVersionHistory( String page )
1921    {
1922        List c = null;
1923
1924        try
1925        {
1926            c = m_pageManager.getVersionHistory( page );
1927
1928            if( c == null )
1929            {
1930                c = m_attachmentManager.getVersionHistory( page );
1931            }
1932        }
1933        catch( ProviderException e )
1934        {
1935            log.error( "FIXME", e );
1936        }
1937
1938        return c;
1939    }
1940
1941    /**
1942     *  Returns a diff of two versions of a page.
1943     *  <p>
1944     *  Note that the API was changed in 2.6 to provide a WikiContext object!
1945     *
1946     *  @param context The WikiContext of the page you wish to get a diff from
1947     *  @param version1 Version number of the old page.  If
1948     *         WikiPageProvider.LATEST_VERSION (-1), then uses current page.
1949     *  @param version2 Version number of the new page.  If
1950     *         WikiPageProvider.LATEST_VERSION (-1), then uses current page.
1951     *
1952     *  @return A HTML-ized difference between two pages.  If there is no difference,
1953     *          returns an empty string.
1954     */
1955    public String getDiff( WikiContext context, int version1, int version2 )
1956    {
1957        String page = context.getPage().getName();
1958        String page1 = getPureText( page, version1 );
1959        String page2 = getPureText( page, version2 );
1960
1961        // Kludge to make diffs for new pages to work this way.
1962
1963        if( version1 == WikiPageProvider.LATEST_VERSION )
1964        {
1965            page1 = "";
1966        }
1967
1968        String diff  = m_differenceManager.makeDiff( context, page1, page2 );
1969
1970        return diff;
1971    }
1972
1973    /**
1974     *  Returns this object's ReferenceManager.
1975     *  @return The current ReferenceManager instance.
1976     *
1977     *  @since 1.6.1
1978     */
1979    public ReferenceManager getReferenceManager()
1980    {
1981        return m_referenceManager;
1982    }
1983
1984    /**
1985     *  Returns the current rendering manager for this wiki application.
1986     *
1987     *  @since 2.3.27
1988     * @return A RenderingManager object.
1989     */
1990    public RenderingManager getRenderingManager()
1991    {
1992        return m_renderingManager;
1993    }
1994
1995    /**
1996     *  Returns the current plugin manager.
1997     *  
1998     *  In 2.10 the PluginManager will be returned instead of the generic
1999     *  
2000     *  @since 1.6.1
2001     *  @return The current PluginManager instance
2002     */
2003    @SuppressWarnings("unchecked")
2004    public < T extends PluginManager > T getPluginManager()
2005    {
2006        return (T)m_pluginManager;
2007    }
2008
2009    /**
2010     *  Returns the current variable manager.
2011     *  @return The current VariableManager.
2012     */
2013
2014    public VariableManager getVariableManager()
2015    {
2016        return m_variableManager;
2017    }
2018
2019    /**
2020     *  Shortcut to getVariableManager().getValue(). However, this method does not
2021     *  throw a NoSuchVariableException, but returns null in case the variable does
2022     *  not exist.
2023     *
2024     *  @param context WikiContext to look the variable in
2025     *  @param name Name of the variable to look for
2026     *  @return Variable value, or null, if there is no such variable.
2027     *  @since 2.2
2028     */
2029    public String getVariable( WikiContext context, String name )
2030    {
2031        try
2032        {
2033            return m_variableManager.getValue( context, name );
2034        }
2035        catch( NoSuchVariableException e )
2036        {
2037            return null;
2038        }
2039    }
2040
2041    /**
2042     *  Returns the current PageManager which is responsible for storing
2043     *  and managing WikiPages.
2044     *
2045     *  @return The current PageManager instance.
2046     */
2047    public PageManager getPageManager()
2048    {
2049        return m_pageManager;
2050    }
2051
2052    /**
2053     * Returns the CommandResolver for this wiki engine.
2054     * @return the resolver
2055     */
2056    public CommandResolver getCommandResolver()
2057    {
2058        return m_commandResolver;
2059    }
2060
2061    /**
2062     *  Returns the current AttachmentManager, which is responsible for
2063     *  storing and managing attachments.
2064     *
2065     *  @since 1.9.31.
2066     *  @return The current AttachmentManager instance
2067     */
2068    public AttachmentManager getAttachmentManager()
2069    {
2070        return m_attachmentManager;
2071    }
2072
2073    /**
2074     *  Returns the currently used authorization manager.
2075     *
2076     *  @return The current AuthorizationManager instance
2077     */
2078    public AuthorizationManager getAuthorizationManager()
2079    {
2080        return m_authorizationManager;
2081    }
2082
2083    /**
2084     *  Returns the currently used authentication manager.
2085     *
2086     *  @return The current AuthenticationManager instance.
2087     */
2088    public AuthenticationManager getAuthenticationManager()
2089    {
2090        return m_authenticationManager;
2091    }
2092
2093    /**
2094     *  Returns the manager responsible for the filters.
2095     *  @since 2.1.88
2096     *  @return The current FilterManager instance
2097     */
2098    @SuppressWarnings("unchecked")
2099    public < T extends FilterManager > T getFilterManager()
2100    {
2101        return (T)m_filterManager;
2102    }
2103
2104    /**
2105     *  Returns the manager responsible for searching the Wiki.
2106     *  @since 2.2.21
2107     *  @return The current SearchManager instance
2108     */
2109    public SearchManager getSearchManager()
2110    {
2111        return m_searchManager;
2112    }
2113
2114    /**
2115     *  Returns the progress manager we're using
2116     *  @return A ProgressManager
2117     *  @since 2.6
2118     */
2119    public ProgressManager getProgressManager()
2120    {
2121        return m_progressManager;
2122    }
2123
2124    /**
2125     *  Figure out to which page we are really going to.  Considers
2126     *  special page names from the jspwiki.properties, and possible aliases.
2127     *  This method delgates requests to
2128     *  {@link org.apache.wiki.WikiContext#getRedirectURL()}.
2129     *  @param context The Wiki Context in which the request is being made.
2130     *  @return A complete URL to the new page to redirect to
2131     *  @since 2.2
2132     */
2133
2134    public String getRedirectURL( WikiContext context )
2135    {
2136        return context.getRedirectURL();
2137    }
2138
2139    /**
2140     *  Shortcut to create a WikiContext from a supplied HTTP request,
2141     *  using a default wiki context.
2142     *  @param request the HTTP request
2143     *  @param requestContext the default context to use
2144     *  @return a new WikiContext object.
2145     *
2146     *  @see org.apache.wiki.ui.CommandResolver
2147     *  @see org.apache.wiki.ui.Command
2148     *  @since 2.1.15.
2149     */
2150    // FIXME: We need to have a version which takes a fixed page
2151    //        name as well, or check it elsewhere.
2152    public WikiContext createContext( HttpServletRequest request,
2153                                      String requestContext )
2154    {
2155        if( !m_isConfigured )
2156        {
2157            throw new InternalWikiException("WikiEngine has not been properly started.  It is likely that the configuration is faulty.  Please check all logs for the possible reason.");
2158        }
2159
2160        // Build the wiki context
2161        Command command = m_commandResolver.findCommand( request, requestContext );
2162        return new WikiContext( this, request, command );
2163    }
2164
2165    /**
2166     *  Deletes a page or an attachment completely, including all versions.  If the page
2167     *  does not exist, does nothing.
2168     *
2169     * @param pageName The name of the page.
2170     * @throws ProviderException If something goes wrong.
2171     */
2172    public void deletePage( String pageName )
2173        throws ProviderException
2174    {
2175        WikiPage p = getPage( pageName );
2176
2177        if( p != null )
2178        {
2179            if( p instanceof Attachment )
2180            {
2181                m_attachmentManager.deleteAttachment( (Attachment) p );
2182            }
2183            else
2184            {
2185                if (m_attachmentManager.hasAttachments( p ))
2186                {
2187                    Collection attachments = m_attachmentManager.listAttachments( p );
2188                    for( Iterator atti = attachments.iterator(); atti.hasNext(); )
2189                    {
2190                        m_attachmentManager.deleteAttachment( (Attachment)(atti.next()) );
2191                    }
2192                }
2193                m_pageManager.deletePage( p );
2194                firePageEvent( WikiPageEvent.PAGE_DELETED, pageName );
2195            }
2196        }
2197    }
2198
2199    /**
2200     *  Deletes a specific version of a page or an attachment.
2201     *
2202     *  @param page The page object.
2203     *  @throws ProviderException If something goes wrong.
2204     */
2205    public void deleteVersion( WikiPage page )
2206        throws ProviderException
2207    {
2208        if( page instanceof Attachment )
2209        {
2210            m_attachmentManager.deleteVersion( (Attachment) page );
2211        }
2212        else
2213        {
2214            m_pageManager.deleteVersion( page );
2215        }
2216    }
2217
2218    /**
2219     *  Returns the URL of the global RSS file.  May be null, if the
2220     *  RSS file generation is not operational.
2221     *  @since 1.7.10
2222     *  @return The global RSS url
2223     */
2224    public String getGlobalRSSURL()
2225    {
2226        if( m_rssGenerator != null && m_rssGenerator.isEnabled() )
2227        {
2228            return getBaseURL()+m_rssFile;
2229        }
2230
2231        return null;
2232    }
2233
2234    /**
2235     *  Returns the root path.  The root path is where the WikiEngine is
2236     *  located in the file system.
2237     *
2238     *  @since 2.2
2239     *  @return A path to where the Wiki is installed in the local filesystem.
2240     */
2241    public String getRootPath()
2242    {
2243        return m_rootPath;
2244    }
2245
2246    /**
2247     * @since 2.2.6
2248     * @return the URL constructor
2249     */
2250    public URLConstructor getURLConstructor()
2251    {
2252        return m_urlConstructor;
2253    }
2254
2255    /**
2256     * Returns the RSSGenerator. If the property <code>jspwiki.rss.generate</code>
2257     * has not been set to <code>true</code>, this method will return <code>null</code>,
2258     * <em>and callers should check for this value.</em>
2259     * @since 2.1.165
2260     * @return the RSS generator
2261     */
2262    public RSSGenerator getRSSGenerator()
2263    {
2264        return m_rssGenerator;
2265    }
2266
2267    /**
2268     * Renames, or moves, a wiki page. Can also alter referring wiki
2269     * links to point to the renamed page.
2270     *
2271     * @param context           The context during which this rename takes
2272     *                          place.
2273     * @param renameFrom        Name of the source page.
2274     * @param renameTo          Name of the destination page.
2275     * @param changeReferrers   If true, then changes any referring links
2276     *                          to point to the renamed page.
2277     *
2278     * @return The name of the page that the source was renamed to.
2279     *
2280     * @throws WikiException    In the case of an error, such as the destination
2281     *                          page already existing.
2282     */
2283    public String renamePage( WikiContext context,
2284                              String renameFrom,
2285                              String renameTo,
2286                              boolean changeReferrers)
2287        throws WikiException
2288    {
2289        String newPageName = m_pageRenamer.renamePage(context, renameFrom, renameTo, changeReferrers);
2290        firePageRenameEvent(renameFrom, newPageName);
2291        return newPageName;
2292    }
2293
2294    /**
2295     *  Returns the PageRenamer employed by this WikiEngine.
2296     *  @since 2.5.141
2297     *  @return The current PageRenamer instance.
2298     */
2299    public PageRenamer getPageRenamer()
2300    {
2301        return m_pageRenamer;
2302    }
2303
2304    /**
2305     *  Returns the UserManager employed by this WikiEngine.
2306     *  @since 2.3
2307     *  @return The current UserManager instance.
2308     */
2309    public UserManager getUserManager()
2310    {
2311        return m_userManager;
2312    }
2313
2314    /**
2315     *  Returns the GroupManager employed by this WikiEngine.
2316     *  @since 2.3
2317     *  @return The current GroupManager instance
2318     */
2319    public GroupManager getGroupManager()
2320    {
2321        return m_groupManager;
2322    }
2323
2324    /**
2325     *  Returns the current {@link AdminBeanManager}.
2326     *
2327     *  @return The current {@link AdminBeanManager}.
2328     *  @since  2.6
2329     */
2330    public AdminBeanManager getAdminBeanManager() {
2331        return m_adminBeanManager;
2332    }
2333
2334    /**
2335     *  Returns the AclManager employed by this WikiEngine.
2336     *  The AclManager is lazily initialized.
2337     *  <p>
2338     *  The AclManager implementing class may be set by the
2339     *  System property {@link #PROP_ACL_MANAGER_IMPL}.
2340     *  </p>
2341     *
2342     * @since 2.3
2343     * @return The current AclManager.
2344     */
2345    public AclManager getAclManager()
2346    {
2347        if( m_aclManager == null )
2348        {
2349            try
2350            {
2351                String s = m_properties.getProperty( PROP_ACL_MANAGER_IMPL,
2352                                                     DefaultAclManager.class.getName() );
2353                m_aclManager = (AclManager)ClassUtil.getMappedObject(s); // TODO: I am not sure whether this is the right call
2354                m_aclManager.initialize( this, m_properties );
2355            }
2356            catch ( WikiException we )
2357            {
2358                log.fatal( "unable to instantiate class for AclManager: " + we.getMessage() );
2359                throw new InternalWikiException("Cannot instantiate AclManager, please check logs.");
2360            }
2361        }
2362        return m_aclManager;
2363    }
2364
2365    /**
2366     *  Returns the DifferenceManager so that texts can be compared.
2367     *  @return the difference manager
2368     */
2369    public DifferenceManager getDifferenceManager()
2370    {
2371        return m_differenceManager;
2372    }
2373
2374    /**
2375     *  Returns the current EditorManager instance.
2376     *
2377     *  @return The current EditorManager.
2378     */
2379    public EditorManager getEditorManager()
2380    {
2381        return m_editorManager;
2382    }
2383
2384    /**
2385     *  Returns the current i18n manager.
2386     *
2387     *  @return The current Intertan... Interante... Internatatializ... Whatever.
2388     */
2389    public InternationalizationManager getInternationalizationManager()
2390    {
2391        return m_internationalizationManager;
2392    }
2393
2394    /**
2395     * Registers a WikiEventListener with this instance.
2396     * @param listener the event listener
2397     */
2398    public final synchronized void addWikiEventListener( WikiEventListener listener )
2399    {
2400        WikiEventManager.addWikiEventListener( this, listener );
2401    }
2402
2403    /**
2404     * Un-registers a WikiEventListener with this instance.
2405     * @param listener the event listener
2406     */
2407    public final synchronized void removeWikiEventListener( WikiEventListener listener )
2408    {
2409        WikiEventManager.removeWikiEventListener( this, listener );
2410    }
2411
2412    /**
2413     * Fires a WikiEngineEvent to all registered listeners.
2414     * @param type  the event type
2415     */
2416    protected final void fireEvent( int type )
2417    {
2418        if ( WikiEventManager.isListening(this) )
2419        {
2420            WikiEventManager.fireEvent(this,new WikiEngineEvent(this,type));
2421        }
2422    }
2423
2424    /**
2425     * Fires a WikiPageEvent to all registered listeners.
2426     * @param type  the event type
2427     */
2428    protected final void firePageEvent( int type, String pageName )
2429    {
2430        if ( WikiEventManager.isListening(this) )
2431        {
2432            WikiEventManager.fireEvent(this,new WikiPageEvent(this,type,pageName));
2433        }
2434    }
2435
2436    /**
2437     * Fires a WikiPageRenameEvent to all registered listeners.
2438     * @param oldName the former page name
2439     * @param newName the new page name
2440     */
2441    protected final void firePageRenameEvent(String oldName, String newName )
2442    {
2443        if ( WikiEventManager.isListening(this) )
2444        {
2445            WikiEventManager.fireEvent(this,new WikiPageRenameEvent(this,oldName,newName));
2446        }
2447    }
2448
2449    /**
2450     * Adds an attribute to the engine for the duration of this engine.  The
2451     * value is not persisted.
2452     *
2453     * @since 2.4.91
2454     * @param key the attribute name
2455     * @param value the value
2456     */
2457    public void setAttribute( String key, Object value )
2458    {
2459        m_attributes.put( key, value );
2460    }
2461
2462    /**
2463     *  Gets an attribute from the engine.
2464     *
2465     *  @param key the attribute name
2466     *  @return the value
2467     */
2468    public Object getAttribute( String key )
2469    {
2470        return m_attributes.get( key );
2471    }
2472
2473    /**
2474     *  Removes an attribute.
2475     *
2476     *  @param key The key of the attribute to remove.
2477     *  @return The previous attribute, if it existed.
2478     */
2479    public Object removeAttribute( String key )
2480    {
2481        return m_attributes.remove( key );
2482    }
2483
2484    /**
2485     *  Returns a WatchDog for current thread.
2486     *
2487     *  @return The current thread WatchDog.
2488     *  @since 2.4.92
2489     */
2490    public WatchDog getCurrentWatchDog()
2491    {
2492        return WatchDog.getCurrentWatchDog(this);
2493    }
2494    
2495    /**
2496     * Initialize the page name comparator.
2497     */
2498    private void initPageSorter( Properties props )
2499    {
2500        m_pageSorter = new PageSorter();
2501        m_pageSorter.initialize( props );
2502    }
2503    
2504    /**
2505     * Get this engine's page name comparator.
2506     * 
2507     * @return the PageSorter used to sort pages by name in this engine
2508     */
2509    public PageSorter getPageSorter()
2510    {
2511        return m_pageSorter;
2512    }
2513}