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