    001/*
    002    Licensed to the Apache Software Foundation (ASF) under one
    003    or more contributor license agreements.  See the NOTICE file
    004    distributed with this work for additional information
    005    regarding copyright ownership.  The ASF licenses this file
    006    to you under the Apache License, Version 2.0 (the
    007    "License"); you may not use this file except in compliance
    008    with the License.  You may obtain a copy of the License at
    009
    010       http://www.apache.org/licenses/LICENSE-2.0
    011
    012    Unless required by applicable law or agreed to in writing,
    013    software distributed under the License is distributed on an
    014    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
    015    KIND, either express or implied.  See the License for the
    016    specific language governing permissions and limitations
    017    under the License.
    018 */
    019package org.apache.wiki;
    020
    021import org.apache.commons.lang3.StringUtils;
    022import org.apache.logging.log4j.LogManager;
    023import org.apache.logging.log4j.Logger;
    024import org.apache.wiki.api.Release;
    025import org.apache.wiki.api.core.Engine;
    026import org.apache.wiki.api.core.Page;
    027import org.apache.wiki.api.engine.Initializable;
    028import org.apache.wiki.api.exceptions.ProviderException;
    029import org.apache.wiki.api.exceptions.WikiException;
    030import org.apache.wiki.attachment.AttachmentManager;
    031import org.apache.wiki.auth.AuthenticationManager;
    032import org.apache.wiki.auth.AuthorizationManager;
    033import org.apache.wiki.auth.UserManager;
    034import org.apache.wiki.auth.acl.AclManager;
    035import org.apache.wiki.auth.authorize.GroupManager;
    036import org.apache.wiki.cache.CachingManager;
    037import org.apache.wiki.content.PageRenamer;
    038import org.apache.wiki.diff.DifferenceManager;
    039import org.apache.wiki.event.WikiEngineEvent;
    040import org.apache.wiki.event.WikiEventListener;
    041import org.apache.wiki.event.WikiEventManager;
    042import org.apache.wiki.event.WikiPageEvent;
    043import org.apache.wiki.filters.FilterManager;
    044import org.apache.wiki.i18n.InternationalizationManager;
    045import org.apache.wiki.pages.PageManager;
    046import org.apache.wiki.plugin.PluginManager;
    047import org.apache.wiki.references.ReferenceManager;
    048import org.apache.wiki.render.RenderingManager;
    049import org.apache.wiki.rss.RSSGenerator;
    050import org.apache.wiki.search.SearchManager;
    051import org.apache.wiki.tasks.TasksManager;
    052import org.apache.wiki.ui.CommandResolver;
    053import org.apache.wiki.ui.EditorManager;
    054import org.apache.wiki.ui.TemplateManager;
    055import org.apache.wiki.ui.admin.AdminBeanManager;
    056import org.apache.wiki.ui.progress.ProgressManager;
    057import org.apache.wiki.url.URLConstructor;
    058import org.apache.wiki.util.ClassUtil;
    059import org.apache.wiki.util.PropertyReader;
    060import org.apache.wiki.util.TextUtil;
    061import org.apache.wiki.variables.VariableManager;
    062import org.apache.wiki.workflow.WorkflowManager;
    063
    064import jakarta.servlet.ServletConfig;
    065import jakarta.servlet.ServletContext;
    066import java.io.File;
    067import java.io.UnsupportedEncodingException;
    068import java.net.MalformedURLException;
    069import java.net.URL;
    070import java.net.URLDecoder;
    071import java.net.URLEncoder;
    072import java.nio.charset.Charset;
    073import java.nio.charset.StandardCharsets;
    074import java.util.ArrayList;
    075import java.util.Collection;
    076import java.util.Date;
    077import java.util.Enumeration;
    078import java.util.List;
    079import java.util.Locale;
    080import java.util.Map;
    081import java.util.Properties;
    082import java.util.TimeZone;
    083import java.util.concurrent.ConcurrentHashMap;
    084import java.util.stream.Collectors;
    085import org.apache.wiki.auth.SecurityVerificationUtility;
    086import org.apache.wiki.api.core.Context;
    087import org.apache.wiki.security.AuditLogger;
    088
    089
    090/**
    091 *  Main implementation for {@link Engine}.
    092 *
    093 *  <P>
    094 *  Using this class:  Always get yourself an instance from JSP page by using the {@code WikiEngine.getInstance(..)} method.  Never create
    095 *  a new WikiEngine() from scratch, unless you're writing tests.
    096 *
    097 *  <p>
    098 *  {@inheritDoc}
    099 */
    100public class WikiEngine implements Engine {
    101
    102    private static final String ATTR_WIKIENGINE = "org.apache.wiki.WikiEngine";
    103    private static final Logger LOG = LogManager.getLogger( WikiEngine.class );
    104
    105    /** Stores properties. */
    106    private Properties m_properties;
    107
    108    /** Should the user info be saved with the page data as well? */
    109    private boolean m_saveUserInfo = true;
    110
    111    /** If true, uses UTF8 encoding for all data */
    112    private boolean m_useUTF8 = true;
    113
    114    /** Store the file path to the basic URL.  When we're not running as a servlet, it defaults to the user's current directory. */
    115    private String m_rootPath = System.getProperty( "user.dir" );
    116
    117    /** Store the ServletContext that we're in.  This may be null if WikiEngine is not running inside a servlet container (i.e. when testing). */
    118    private ServletContext   m_servletContext;
    119
    120    /** Stores the template path.  This is relative to "templates". */
    121    private String           m_templateDir;
    122
    123    /** The default front page name.  Defaults to "Main". */
    124    private String           m_frontPage;
    125
    126    /** The time when this engine was started. */
    127    private Date             m_startTime;
    128
    129    /** The location where the work directory is. */
    130    private String           m_workDir;
    131
    132    /** Each engine has their own application id. */
    133    private String           m_appid = "";
    134
    135    /** engine is up and running or not */
    136    private boolean          m_isConfigured;
    137
    138    /** Stores wikiengine attributes. */
    139    private final Map< String, Object > m_attributes = new ConcurrentHashMap<>();
    140
    141    /** Stores WikiEngine's associated managers. */
    142    protected final Map< Class< ? >, Object > managers = new ConcurrentHashMap<>();
    143
    144    /**
    145     *  Gets a WikiEngine related to this servlet.  Since this method is only called from JSP pages (and JspInit()) to be specific,
    146     *  we throw a RuntimeException if things don't work.
    147     *
    148     *  @param config The ServletConfig object for this servlet.
    149     *  @return A WikiEngine instance.
    150     *  @throws InternalWikiException in case something fails. This is a RuntimeException, so be prepared for it.
    151     */
    152    public static synchronized WikiEngine getInstance( final ServletConfig config ) throws InternalWikiException {
    153        return getInstance( config.getServletContext(), null );
    154    }
    155
    156    /**
    157     *  Gets a WikiEngine related to the servlet. Works like getInstance(ServletConfig), but does not force the Properties object.
    158     *  This method is just an optional way of initializing a WikiEngine for embedded JSPWiki applications; normally, you
    159     *  should use getInstance(ServletConfig).
    160     *
    161     *  @param config The ServletConfig of the webapp servlet/JSP calling this method.
    162     *  @param props  A set of properties, or null, if we are to load JSPWiki's default jspwiki.properties (this is the usual case).
    163     *
    164     *  @return One well-behaving WikiEngine instance.
    165     */
    166    public static synchronized WikiEngine getInstance( final ServletConfig config, final Properties props ) {
    167        return getInstance( config.getServletContext(), props );
    168    }
    169
    170    /**
    171     *  Gets a WikiEngine related to the servlet. Works just like getInstance( ServletConfig )
    172     *
    173     *  @param context The ServletContext of the webapp servlet/JSP calling this method.
    174     *  @param props  A set of properties, or null, if we are to load JSPWiki's default jspwiki.properties (this is the usual case).
    175     *  @return One fully functional, properly behaving WikiEngine.
    176     *  @throws InternalWikiException If the WikiEngine instantiation fails.
    177     */
    178    public static synchronized WikiEngine getInstance( final ServletContext context, Properties props ) throws InternalWikiException {
    179        WikiEngine engine = ( WikiEngine )context.getAttribute( ATTR_WIKIENGINE );
    180        if( engine == null ) {
    181            final String appid = Integer.toString( context.hashCode() );
    182            context.log( " Assigning new engine to " + appid );
    183            try {
    184                if( props == null ) {
    185                    props = PropertyReader.loadWebAppProps( context );
    186                }
    187
    188                engine = new WikiEngine( context, appid );
    189                try {
    190                    //  Note: May be null, if JSPWiki has been deployed in a WAR file.
    191                    engine.start( props );
    192                    LOG.info( "Root path for this Wiki is: '{}'", engine.getRootPath() );
    193                } catch( final Exception e ) {
    194                    final String msg = Release.APPNAME + ": Unable to load and setup properties from jspwiki.properties. " + e.getMessage();
    195                    context.log( msg );
    196                    LOG.error( msg, e );
    197                    throw new WikiException( msg, e );
    198                }
    199                context.setAttribute( ATTR_WIKIENGINE, engine );
    200            } catch( final Exception e ) {
    201                context.log( "ERROR: Failed to create a Wiki engine: " + e.getMessage() );
    202                LOG.error( "ERROR: Failed to create a Wiki engine, stacktrace follows ", e );
    203                throw new InternalWikiException( "No wiki engine, check logs.", e );
    204            }
    205        }
    206        return engine;
    207    }
    208
    209    /**
    210     *  Instantiate the WikiEngine using a given set of properties. Use this constructor for testing purposes only.
    211     *
    212     *  @param properties A set of properties to use to initialize this WikiEngine.
    213     *  @throws WikiException If the initialization fails.
    214     */
    215    public WikiEngine( final Properties properties ) throws WikiException {
    216        start( properties );
    217    }
    218
    219    /**
    220     *  Instantiate using this method when you're running as a servlet and WikiEngine will figure out where to look for the property file.
    221     *  Do not use this method - use WikiEngine.getInstance() instead.
    222     *
    223     *  @param context A ServletContext.
    224     *  @param appid   An Application ID.  This application is a unique random string which is used to recognize this WikiEngine.
    225     *  @throws WikiException If the WikiEngine construction fails.
    226     */
    227    protected WikiEngine( final ServletContext context, final String appid ) throws WikiException {
    228        m_servletContext = context;
    229        m_appid          = appid;
    230
    231        // Stash the WikiEngine in the servlet context
    232        if ( context != null ) {
    233            context.setAttribute( ATTR_WIKIENGINE,  this );
    234            m_rootPath = context.getRealPath( "/" );
    235        }
    236    }
    237
    238    /**
    239     *  Does all the real initialization.
    240     */
    241    @Override
    242    public void initialize( final Properties props ) throws WikiException {
    243        m_startTime  = new Date();
    244
    245        m_properties = props;
    246
    247        LOG.info( "*******************************************" );
    248        LOG.info( "{} {} starting. Whee!", Release.APPNAME, Release.getVersionString() );
    249        LOG.debug( "Java version: {}", System.getProperty( "java.runtime.version" ) );
    250        LOG.debug( "Java vendor: {}", System.getProperty( "java.vm.vendor" ) );
    251        LOG.debug( "OS: {} {} {}", System.getProperty( "os.name" ), System.getProperty( "os.version" ), System.getProperty( "os.arch" ) );
    252        LOG.debug( "Default server locale: {}", Locale.getDefault() );
    253        LOG.debug( "Default server timezone: {}", TimeZone.getDefault().getDisplayName( true, TimeZone.LONG ) );
    254
    255        if( m_servletContext != null ) {
    256            LOG.info( "Servlet container: {}", m_servletContext.getServerInfo() );
    257            if( m_servletContext.getMajorVersion() < 3 || ( m_servletContext.getMajorVersion() == 3 && m_servletContext.getMinorVersion() < 1 ) ) {
    258                throw new InternalWikiException( "JSPWiki requires a container which supports at least version 3.1 of Servlet specification" );
    259            }
    260        }
    261        AuditLogger.initialize(this);
    262        fireEvent( WikiEngineEvent.INITIALIZING ); // begin initialization
    263
    264        LOG.debug( "Configuring WikiEngine..." );
    265
    266        createAndFindWorkingDirectory( props );
    267
    268        m_useUTF8        = StandardCharsets.UTF_8.name().equals( TextUtil.getStringProperty( props, PROP_ENCODING, StandardCharsets.ISO_8859_1.name() ) );
    269        m_saveUserInfo   = TextUtil.getBooleanProperty( props, PROP_STOREUSERNAME, m_saveUserInfo );
    270        m_frontPage      = TextUtil.getStringProperty( props, PROP_FRONTPAGE, "Main" );
    271        m_templateDir    = TextUtil.getStringProperty( props, PROP_TEMPLATEDIR, "default" );
    272        enforceValidTemplateDirectory();
    273
    274        //
    275        //  Initialize the important modules.  Any exception thrown by the managers means that we will not start up.
    276        //
    277        try {
    278            final String aclClassName = m_properties.getProperty( PROP_ACL_MANAGER_IMPL, ClassUtil.getMappedClass( AclManager.class.getName() ).getName() );
    279            final String urlConstructorClassName = TextUtil.getStringProperty( props, PROP_URLCONSTRUCTOR, "DefaultURLConstructor" );
    280            final Class< URLConstructor > urlclass = ClassUtil.findClass( "org.apache.wiki.url", urlConstructorClassName );
    281
    282            initComponent( CommandResolver.class, this, props );
    283            initComponent( urlclass.getName(), URLConstructor.class );
    284            initComponent( CachingManager.class, this, props );
    285            initComponent( PageManager.class, this, props );
    286            initComponent( PluginManager.class, this, props );
    287            initComponent( DifferenceManager.class, this, props );
    288            initComponent( AttachmentManager.class, this, props );
    289            initComponent( VariableManager.class, props );
    290            initComponent( SearchManager.class, this, props );
    291            initComponent( AuthenticationManager.class );
    292            initComponent( AuthorizationManager.class );
    293            initComponent( UserManager.class );
    294            initComponent( GroupManager.class );
    295            initComponent( EditorManager.class, this );
    296            initComponent( ProgressManager.class, this );
    297            initComponent( aclClassName, AclManager.class );
    298            initComponent( WorkflowManager.class );
    299            initComponent( TasksManager.class );
    300            initComponent( InternationalizationManager.class, this );
    301            initComponent( TemplateManager.class, this, props );
    302            initComponent( FilterManager.class, this, props );
    303            initComponent( AdminBeanManager.class, this );
    304            initComponent( PageRenamer.class, this, props );
    305
    306            // RenderingManager depends on FilterManager events.
    307            initComponent( RenderingManager.class );
    308
    309            //  ReferenceManager has the side effect of loading all pages. Therefore, after this point, all page attributes are available.
    310            //  initReferenceManager is indirectly using m_filterManager, so it has to be called after it was initialized.
    311            initReferenceManager();
    312
    313            //  Hook the different manager routines into the system.
    314            getManager( FilterManager.class ).addPageFilter( getManager( ReferenceManager.class ), -1001 );
    315            getManager( FilterManager.class ).addPageFilter( getManager( SearchManager.class ), -1002 );
    316        } catch( final RuntimeException e ) {
    317            // RuntimeExceptions may occur here, even if they shouldn't.
    318            LOG.fatal( "Failed to start managers.", e );
    319            throw new WikiException( "Failed to start managers: " + e.getMessage(), e );
    320        } catch( final ClassNotFoundException e ) {
    321            LOG.fatal( "JSPWiki could not start, URLConstructor was not found: {}", e.getMessage(), e );
    322            throw new WikiException( e.getMessage(), e );
    323        } catch( final InstantiationException e ) {
    324            LOG.fatal( "JSPWiki could not start, URLConstructor could not be instantiated: {}", e.getMessage(), e );
    325            throw new WikiException( e.getMessage(), e );
    326        } catch( final IllegalAccessException e ) {
    327            LOG.fatal( "JSPWiki could not start, URLConstructor cannot be accessed: {}", e.getMessage(), e );
    328            throw new WikiException( e.getMessage(), e );
    329        } catch( final Exception e ) {
    330            // Final catch-all for everything
    331            LOG.fatal( "JSPWiki could not start, due to an unknown exception when starting.", e );
    332            throw new WikiException( "Failed to start. Caused by: " + e.getMessage() + "; please check log files for better information.", e );
    333        }
    334
    335        //  Initialize the good-to-have-but-not-fatal modules.
    336        try {
    337            if( TextUtil.getBooleanProperty( props, RSSGenerator.PROP_GENERATE_RSS,false ) ) {
    338                initComponent( RSSGenerator.class, this, props );
    339            }
    340        } catch( final Exception e ) {
    341            LOG.error( "Unable to start RSS generator - JSPWiki will still work, but there will be no RSS feed.", e );
    342        }
    343
    344        final Map< String, String > extraComponents = ClassUtil.getExtraClassMappings();
    345        initExtraComponents( extraComponents );
    346
    347
    348        ProductUpdateChecker.initialize(props);
    349
    350        fireEvent( WikiEngineEvent.INITIALIZED ); // initialization complete
    351
    352        LOG.info( "WikiEngine configured." );
    353
    354        new SecurityVerificationUtility().verify(this);
    355        m_isConfigured = true;
    356    }
    357
    358    void createAndFindWorkingDirectory( final Properties props ) throws WikiException {
    359        m_workDir = TextUtil.getStringProperty( props, PROP_WORKDIR, null );
    360
    361        final File f = new File( m_workDir );
    362        try {
    363            f.mkdirs();
    364        } catch( final SecurityException e ) {
    365            LOG.fatal( "Unable to find or create the working directory: {}", m_workDir, e );
    366            throw new WikiException( "Unable to find or create the working dir: " + m_workDir, e );
    367        }
    368
    369        //  A bunch of sanity checks
    370        checkWorkingDirectory( !f.exists(), "Work directory does not exist: " + m_workDir );
    371        checkWorkingDirectory( !f.canRead(), "No permission to read work directory: " + m_workDir );
    372        checkWorkingDirectory( !f.canWrite(), "No permission to write to work directory: " + m_workDir );
    373        checkWorkingDirectory( !f.isDirectory(), "jspwiki.workDir does not point to a directory: " + m_workDir );
    374
    375        LOG.info( "JSPWiki working directory is '{}'", m_workDir );
    376    }
    377
    378    void checkWorkingDirectory( final boolean condition, final String errMsg ) throws WikiException {
    379        if( condition ) {
    380            throw new WikiException( errMsg );
    381        }
    382    }
    383
    384    void initExtraComponents( final Map< String, String > extraComponents ) {
    385        for( final Map.Entry< String, String > extraComponent : extraComponents.entrySet() ) {
    386            try {
    387                LOG.info( "Registering on WikiEngine {} as {}", extraComponent.getKey(), extraComponent.getValue() );
    388                initComponent( extraComponent.getKey(), Class.forName( extraComponent.getValue() ) );
    389            } catch( final Exception e ) {
    390                LOG.error( "Unable to start {}", extraComponent.getKey(), e );
    391            }
    392        }
    393    }
    394
    395    < T > void initComponent( final Class< T > componentClass, final Object... initArgs ) throws Exception {
    396        initComponent( componentClass.getName(), componentClass, initArgs );
    397    }
    398
    399    < T > void initComponent( final String componentInitClass, final Class< T > componentClass, final Object... initArgs ) throws Exception {
    400        final T component;
    401        if( initArgs == null || initArgs.length == 0 ) {
    402            component = ClassUtil.getMappedObject( componentInitClass );
    403        } else {
    404            component = ClassUtil.getMappedObject( componentInitClass, initArgs );
    405        }
    406        managers.put( componentClass, component );
    407        if( Initializable.class.isAssignableFrom( component.getClass() ) ) {
    408            ( ( Initializable )component ).initialize( this, m_properties );
    409        }
    410    }
    411
    412    /** {@inheritDoc} */
    413    @Override
    414    @SuppressWarnings( "unchecked" )
    415    public < T > T getManager( final Class< T > manager ) {
    416        return ( T )managers.entrySet().stream()
    417                                       .filter( e -> manager.isAssignableFrom( e.getKey() ) )
    418                                       .map( Map.Entry::getValue )
    419                                       .findFirst().orElse( null );
    420    }
    421
    422    /** {@inheritDoc} */
    423    @Override
    424    @SuppressWarnings( "unchecked" )
    425    public < T > List< T > getManagers( final Class< T > manager ) {
    426        return ( List< T > )managers.entrySet().stream()
    427                                               .filter( e -> manager.isAssignableFrom( e.getKey() ) )
    428                                               .map( Map.Entry::getValue )
    429                                               .collect( Collectors.toList() );
    430    }
    431
    432    /** {@inheritDoc} */
    433    @Override
    434    public boolean isConfigured() {
    435        return m_isConfigured;
    436    }
    437
    438    /**
    439     * Checks if the template directory specified in the wiki's properties actually exists. If it doesn't, then {@code m_templateDir} is
    440     * set to {@link #DEFAULT_TEMPLATE_NAME}.
    441     * <p>
    442     * This checks the existence of the <tt>ViewTemplate.jsp</tt> file, which exists in every template using {@code m_servletContext.getRealPath("/")}.
    443     * <p>
    444     * {@code m_servletContext.getRealPath("/")} can return {@code null} on certain servers/conditions (f.ex, packed wars), an extra check
    445     * against {@code m_servletContext.getResource} is made.
    446     */
    447    void enforceValidTemplateDirectory() {
    448        if( m_servletContext != null ) {
    449            final String viewTemplate = "templates" + File.separator + getTemplateDir() + File.separator + "ViewTemplate.jsp";
    450            boolean exists = new File( m_servletContext.getRealPath( "/" ) + viewTemplate ).exists();
    451            if( !exists ) {
    452                try {
    453                    final URL url = m_servletContext.getResource( viewTemplate );
    454                    exists = url != null && StringUtils.isNotEmpty( url.getFile() );
    455                } catch( final MalformedURLException e ) {
    456                    LOG.warn( "template not found with viewTemplate {}", viewTemplate );
    457                }
    458            }
    459            if( !exists ) {
    460                LOG.warn( "{} template not found, updating WikiEngine's default template to {}", getTemplateDir(), DEFAULT_TEMPLATE_NAME );
    461                m_templateDir = DEFAULT_TEMPLATE_NAME;
    462            }
    463        }
    464    }
    465
    466    /**
    467     *  Initializes the reference manager. Scans all existing WikiPages for
    468     *  internal links and adds them to the ReferenceManager object.
    469     *
    470     *  @throws WikiException If the reference manager initialization fails.
    471     */
    472    public void initReferenceManager() throws WikiException {
    473        try {
    474            // Build a new manager with default key lists.
    475            if( getManager( ReferenceManager.class ) == null ) {
    476                final ArrayList< Page > pages = new ArrayList<>();
    477                pages.addAll( getManager( PageManager.class ).getAllPages() );
    478                pages.addAll( getManager( AttachmentManager.class ).getAllAttachments() );
    479                final String refMgrClassName = m_properties.getProperty( PROP_REF_MANAGER_IMPL, ClassUtil.getMappedClass( ReferenceManager.class.getName() ).getName() );
    480
    481                initComponent( refMgrClassName, ReferenceManager.class, this );
    482
    483                getManager( ReferenceManager.class ).initialize( pages );
    484            }
    485
    486        } catch( final ProviderException e ) {
    487            LOG.fatal( "PageProvider is unable to list pages: ", e );
    488        } catch( final Exception e ) {
    489            throw new WikiException( "Could not instantiate ReferenceManager: " + e.getMessage(), e );
    490        }
    491    }
    492
    493    /** {@inheritDoc} */
    494    @Override
    495    public Properties getWikiProperties() {
    496        return m_properties;
    497    }
    498
    499    /** {@inheritDoc} */
    500    @Override
    501    public String getWorkDir() {
    502        return m_workDir;
    503    }
    504
    505    /** {@inheritDoc} */
    506    @Override
    507    public String getTemplateDir() {
    508        return m_templateDir;
    509    }
    510
    511    /** {@inheritDoc} */
    512    @Override
    513    public Date getStartTime() {
    514        return ( Date )m_startTime.clone();
    515    }
    516
    517    /** {@inheritDoc} */
    518    @Override
    519    public String getBaseURL() {
    520        return m_servletContext.getContextPath();
    521    }
    522
    523    /** {@inheritDoc} */
    524    @Override
    525    public String getGlobalRSSURL() {
    526        final RSSGenerator rssGenerator = getManager( RSSGenerator.class );
    527        if( rssGenerator != null && rssGenerator.isEnabled() ) {
    528            return getBaseURL() + "/" + rssGenerator.getRssFile();
    529        }
    530
    531        return null;
    532    }
    533
    534    /** {@inheritDoc} */
    535    @Override
    536    public String getInterWikiURL( final String wikiName ) {
    537        return TextUtil.getStringProperty( m_properties,PROP_INTERWIKIREF + wikiName,null );
    538    }
    539
    540    /** {@inheritDoc} */
    541    @Override
    542    public String getURL( final String context, String pageName, final String params ) {
    543        if( pageName == null ) {
    544            pageName = getFrontPage();
    545        }
    546        final URLConstructor urlConstructor = getManager( URLConstructor.class );
    547        return urlConstructor.makeURL( context, pageName, params );
    548    }
    549
    550    /** {@inheritDoc} */
    551    @Override
    552    public String getFrontPage() {
    553        return m_frontPage;
    554    }
    555
    556    /** {@inheritDoc} */
    557    @Override
    558    public ServletContext getServletContext() {
    559        return m_servletContext;
    560    }
    561
    562    /** {@inheritDoc} */
    563    @Override
    564    public Collection< String > getAllInterWikiLinks() {
    565        final ArrayList< String > list = new ArrayList<>();
    566        for( final Enumeration< ? > i = m_properties.propertyNames(); i.hasMoreElements(); ) {
    567            final String prop = ( String )i.nextElement();
    568            if( prop.startsWith( PROP_INTERWIKIREF ) ) {
    569                list.add( prop.substring( prop.lastIndexOf( "." ) + 1 ) );
    570            }
    571        }
    572
    573        return list;
    574    }
    575
    576    /** {@inheritDoc} */
    577    @Override
    578    public Collection< String > getAllInlinedImagePatterns() {
    579        final ArrayList< String > ptrnlist = new ArrayList<>();
    580        for( final Enumeration< ? > e = m_properties.propertyNames(); e.hasMoreElements(); ) {
    581            final String name = ( String )e.nextElement();
    582            if( name.startsWith( PROP_INLINEIMAGEPTRN ) ) {
    583                ptrnlist.add( TextUtil.getStringProperty( m_properties, name, null ) );
    584            }
    585        }
    586
    587        if( ptrnlist.isEmpty() ) {
    588            ptrnlist.add( DEFAULT_INLINEPATTERN );
    589        }
    590
    591        return ptrnlist;
    592    }
    593
    594    /** {@inheritDoc} */
    595    @Override
    596    public String getSpecialPageReference( final String original ) {
    597        return getManager( CommandResolver.class ).getSpecialPageReference( original );
    598    }
    599
    600    /** {@inheritDoc} */
    601    @Override
    602    public String getApplicationName() {
    603        final String appName = TextUtil.getStringProperty( m_properties, PROP_APPNAME, Release.APPNAME );
    604        return TextUtil.cleanString( appName, TextUtil.PUNCTUATION_CHARS_ALLOWED );
    605    }
    606
    607    /** {@inheritDoc} */
    608    @Override
    609    public String getFinalPageName( final String page ) throws ProviderException {
    610        return getManager( CommandResolver.class ).getFinalPageName( page );
    611    }
    612
    613    /** {@inheritDoc} */
    614    @Override
    615    public String encodeName( final String pagename ) {
    616        try {
    617            return URLEncoder.encode( pagename, m_useUTF8 ? StandardCharsets.UTF_8.name() : StandardCharsets.ISO_8859_1.name() );
    618        } catch( final UnsupportedEncodingException e ) {
    619            throw new InternalWikiException( "ISO-8859-1 not a supported encoding!?!  Your platform is borked." , e);
    620        }
    621    }
    622
    623    /** {@inheritDoc} */
    624    @Override
    625    public String decodeName( final String pagerequest ) {
    626        try {
    627            return URLDecoder.decode( pagerequest, m_useUTF8 ? StandardCharsets.UTF_8.name() : StandardCharsets.ISO_8859_1.name() );
    628        } catch( final UnsupportedEncodingException e ) {
    629            throw new InternalWikiException("ISO-8859-1 not a supported encoding!?!  Your platform is borked.", e);
    630        }
    631    }
    632
    633    /** {@inheritDoc} */
    634    @Override
    635    public Charset getContentEncoding() {
    636        if( m_useUTF8 ) {
    637            return StandardCharsets.UTF_8;
    638        }
    639        return StandardCharsets.ISO_8859_1;
    640    }
    641
    642    /**
    643     * {@inheritDoc}
    644     * <p>It is called by {@link WikiServlet#destroy()}. When this method is called, it fires a "shutdown" WikiEngineEvent to
    645     * all registered listeners.
    646     */
    647    @Override
    648    public void shutdown() {
    649        fireEvent( WikiEngineEvent.SHUTDOWN );
    650        getManager( CachingManager.class ).shutdown();
    651        getManager( FilterManager.class ).destroy();
    652        if (ProductUpdateChecker.getInstance() != null) {
    653            ProductUpdateChecker.getInstance().shutdown();
    654        }
    655        WikiEventManager.shutdown();
    656    }
    657
    658    /**
    659     *  Returns the current TemplateManager.
    660     *
    661     *  @return A TemplateManager instance.
    662     * @deprecated use {@code getManager( TemplateManager.class )} instead.
    663     */
    664    @Deprecated
    665    public TemplateManager getTemplateManager() {
    666        return getManager( TemplateManager.class );
    667    }
    668
    669    /**
    670     * Returns the {@link org.apache.wiki.workflow.WorkflowManager} associated with this WikiEngine. If the WikiEngine has not been
    671     * initialized, this method will return <code>null</code>.
    672     *
    673     * @return the task queue
    674     * @deprecated use {@code getManager( WorkflowManager.class )} instead.
    675     */
    676    @Deprecated
    677    public WorkflowManager getWorkflowManager() {
    678        return getManager( WorkflowManager.class );
    679    }
    680
    681    /**
    682     * Returns this object's ReferenceManager.
    683     *
    684     * @return The current ReferenceManager instance.
    685     * @since 1.6.1
    686     * @deprecated use {@code getManager( ReferenceManager.class )} instead.
    687     */
    688    @Deprecated
    689    public ReferenceManager getReferenceManager() {
    690        return getManager( ReferenceManager.class );
    691    }
    692
    693    /**
    694     * Returns the current rendering manager for this wiki application.
    695     *
    696     * @since 2.3.27
    697     * @return A RenderingManager object.
    698     * @deprecated use {@code getManager( RenderingManager.class )} instead.
    699     */
    700    @Deprecated
    701    public RenderingManager getRenderingManager() {
    702        return getManager( RenderingManager.class );
    703    }
    704
    705    /**
    706     * Returns the current plugin manager.
    707     *
    708     * @since 1.6.1
    709     * @return The current PluginManager instance
    710     * @deprecated use {@code getManager( PluginManager.class )} instead.
    711     */
    712    @Deprecated
    713    public PluginManager getPluginManager() {
    714        return getManager( PluginManager.class );
    715    }
    716
    717    /**
    718     *  Returns the current variable manager.
    719     *
    720     *  @return The current VariableManager.
    721     * @deprecated use {@code getManager( VariableManager.class )} instead.
    722     */
    723    @Deprecated
    724    public VariableManager getVariableManager()  {
    725        return getManager( VariableManager.class );
    726    }
    727
    728    /**
    729     * Returns the current PageManager which is responsible for storing and managing WikiPages.
    730     *
    731     * @return The current PageManager instance.
    732     * @deprecated use {@code getManager( PageManager.class )} instead.
    733     */
    734    @Deprecated
    735    public PageManager getPageManager() {
    736        return getManager( PageManager.class );
    737    }
    738
    739    /**
    740     * Returns the CommandResolver for this wiki engine.
    741     *
    742     * @return the resolver
    743     * @deprecated use {@code getManager( CommandResolver.class )} instead.
    744     */
    745    @Deprecated
    746    public CommandResolver getCommandResolver() {
    747        return getManager( CommandResolver.class );
    748    }
    749
    750    /**
    751     * Returns the current AttachmentManager, which is responsible for storing and managing attachments.
    752     *
    753     * @since 1.9.31.
    754     * @return The current AttachmentManager instance
    755     * @deprecated use {@code getManager( AttachmentManager.class )} instead.
    756     */
    757    @Deprecated
    758    public AttachmentManager getAttachmentManager() {
    759        return getManager( AttachmentManager.class );
    760    }
    761
    762    /**
    763     * Returns the currently used authorization manager.
    764     *
    765     * @return The current AuthorizationManager instance.
    766     * @deprecated use {@code getManager( AuthorizationManager.class )} instead.
    767     */
    768    @Deprecated
    769    public AuthorizationManager getAuthorizationManager()  {
    770        return getManager( AuthorizationManager.class );
    771    }
    772
    773    /**
    774     * Returns the currently used authentication manager.
    775     *
    776     * @return The current AuthenticationManager instance.
    777     * @deprecated use {@code getManager( AuthenticationManager.class )} instead.
    778     */
    779    @Deprecated
    780    public AuthenticationManager getAuthenticationManager() {
    781        return getManager( AuthenticationManager.class );
    782    }
    783
    784    /**
    785     * Returns the manager responsible for the filters.
    786     *
    787     * @since 2.1.88
    788     * @return The current FilterManager instance.
    789     * @deprecated use {@code getManager( FilterManager.class )} instead.
    790     */
    791    @Deprecated
    792    public FilterManager getFilterManager() {
    793        return getManager( FilterManager.class );
    794    }
    795
    796    /**
    797     * Returns the manager responsible for searching the Wiki.
    798     *
    799     * @since 2.2.21
    800     * @return The current SearchManager instance.
    801     * @deprecated use {@code getManager( SearchManager.class )} instead.
    802     */
    803    @Deprecated
    804    public SearchManager getSearchManager() {
    805        return getManager( SearchManager.class );
    806    }
    807
    808    /**
    809     * Returns the progress manager we're using
    810     *
    811     * @return A ProgressManager.
    812     * @since 2.6
    813     * @deprecated use {@code getManager( ProgressManager.class )} instead.
    814     */
    815    @Deprecated
    816    public ProgressManager getProgressManager() {
    817        return getManager( ProgressManager.class );
    818    }
    819
    820    /** {@inheritDoc} */
    821    @Override
    822    public String getRootPath() {
    823        return m_rootPath;
    824    }
    825
    826    /**
    827     * @since 2.2.6
    828     * @return the URL constructor.
    829     * @deprecated use {@code getManager( URLConstructor.class )} instead.
    830     */
    831    @Deprecated
    832    public URLConstructor getURLConstructor() {
    833        return getManager( URLConstructor.class );
    834    }
    835
    836    /**
    837     * Returns the RSSGenerator. If the property <code>jspwiki.rss.generate</code> has not been set to <code>true</code>, this method
    838     * will return <code>null</code>, <em>and callers should check for this value.</em>
    839     *
    840     * @since 2.1.165
    841     * @return the RSS generator
    842     * @deprecated use {@code getManager( RSSGenerator.class )} instead.
    843     */
    844    @Deprecated
    845    public RSSGenerator getRSSGenerator() {
    846        return getManager( RSSGenerator.class );
    847    }
    848
    849    /**
    850     *  Returns the PageRenamer employed by this WikiEngine.
    851     *
    852     *  @since 2.5.141
    853     *  @return The current PageRenamer instance.
    854     * @deprecated use {@code getManager( PageRenamer.class )} instead.
    855     */
    856    @Deprecated
    857    public PageRenamer getPageRenamer() {
    858        return getManager( PageRenamer.class );
    859    }
    860
    861    /**
    862     *  Returns the UserManager employed by this WikiEngine.
    863     *
    864     *  @since 2.3
    865     *  @return The current UserManager instance.
    866     * @deprecated use {@code getManager( UserManager.class )} instead.
    867     */
    868    @Deprecated
    869    public UserManager getUserManager() {
    870        return getManager( UserManager.class );
    871    }
    872
    873    /**
    874     *  Returns the TasksManager employed by this WikiEngine.
    875     *
    876     *  @return The current TasksManager instance.
    877     * @deprecated use {@code getManager( TaskManager.class )} instead.
    878     */
    879    @Deprecated
    880    public TasksManager getTasksManager() {
    881        return getManager( TasksManager.class );
    882    }
    883
    884    /**
    885     *  Returns the GroupManager employed by this WikiEngine.
    886     *
    887     *  @since 2.3
    888     *  @return The current GroupManager instance.
    889     * @deprecated use {@code getManager( GroupManager.class )} instead.
    890     */
    891    @Deprecated
    892    public GroupManager getGroupManager() {
    893        return getManager( GroupManager.class );
    894    }
    895
    896    /**
    897     *  Returns the current {@link AdminBeanManager}.
    898     *
    899     *  @return The current {@link AdminBeanManager}.
    900     *  @since  2.6
    901     * @deprecated use {@code getManager( AdminBeanManager.class )} instead.
    902     */
    903    @Deprecated
    904    public AdminBeanManager getAdminBeanManager() {
    905        return getManager( AdminBeanManager.class );
    906    }
    907
    908    /**
    909     *  Returns the AclManager employed by this WikiEngine. The AclManager is lazily initialized.
    910     *  <p>
    911     *  The AclManager implementing class may be set by the System property {@link #PROP_ACL_MANAGER_IMPL}.
    912     *  </p>
    913     *
    914     * @since 2.3
    915     * @return The current AclManager.
    916     * @deprecated use {@code getManager( AclManager.class )} instead.
    917     */
    918    @Deprecated
    919    public AclManager getAclManager()  {
    920        return getManager( AclManager.class );
    921    }
    922
    923    /**
    924     *  Returns the DifferenceManager so that texts can be compared.
    925     *
    926     *  @return the difference manager.
    927     * @deprecated use {@code getManager( DifferenceManager.class )} instead.
    928     */
    929    @Deprecated
    930    public DifferenceManager getDifferenceManager() {
    931        return getManager( DifferenceManager.class );
    932    }
    933
    934    /**
    935     *  Returns the current EditorManager instance.
    936     *
    937     *  @return The current EditorManager.
    938     * @deprecated use {@code getManager( EditorManager.class )} instead.
    939     */
    940    @Deprecated
    941    public EditorManager getEditorManager() {
    942        return getManager( EditorManager.class );
    943    }
    944
    945    /**
    946     *  Returns the current i18n manager.
    947     *
    948     *  @return The current Intertan... Interante... Internatatializ... Whatever.
    949     * @deprecated use {@code getManager( InternationalizationManager.class )} instead.
    950     */
    951    @Deprecated
    952    public InternationalizationManager getInternationalizationManager() {
    953        return getManager( InternationalizationManager.class );
    954    }
    955
    956    /** {@inheritDoc} */
    957    @Override
    958    public final synchronized void addWikiEventListener( final WikiEventListener listener ) {
    959        WikiEventManager.addWikiEventListener( this, listener );
    960    }
    961
    962    /** {@inheritDoc} */
    963    @Override
    964    public final synchronized void removeWikiEventListener( final WikiEventListener listener ) {
    965        WikiEventManager.removeWikiEventListener( this, listener );
    966    }
    967
    968    /**
    969     * Fires a WikiEngineEvent to all registered listeners.
    970     *
    971     * @param type  the event type
    972     */
    973    protected final void fireEvent( final int type ) {
    974        if( WikiEventManager.isListening(this ) ) {
    975            WikiEventManager.fireEvent( this, new WikiEngineEvent(this, type ) );
    976        }
    977    }
    978
    979    /**
    980     * Fires a WikiPageEvent to all registered listeners.
    981     *
    982     * @param type  the event type
    983     */
    984    protected final void firePageEvent( final int type, final String pageName ) {
    985        if( WikiEventManager.isListening(this ) ) {
    986            WikiEventManager.fireEvent(this,new WikiPageEvent(this, type, pageName ));
    987        }
    988    }
    989
    990    /** {@inheritDoc} */
    991    @Override
    992    public void setAttribute( final String key, final Object value ) {
    993        m_attributes.put( key, value );
    994    }
    995
    996    /** {@inheritDoc} */
    997    @Override
    998    @SuppressWarnings( "unchecked" )
    999    public < T > T getAttribute( final String key ) {
    1000        return ( T )m_attributes.get( key );
    1001    }
    1002
    1003    /** {@inheritDoc} */
    1004    @Override
    1005    @SuppressWarnings( "unchecked" )
    1006    public < T > T removeAttribute( final String key ) {
    1007        return ( T )m_attributes.remove( key );
    1008    }
    1009
    1010}