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