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