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