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