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.pages;
020
021import org.apache.commons.lang3.ArrayUtils;
022import org.apache.log4j.Logger;
023import org.apache.wiki.WikiBackgroundThread;
024import org.apache.wiki.api.core.Acl;
025import org.apache.wiki.api.core.AclEntry;
026import org.apache.wiki.api.core.Attachment;
027import org.apache.wiki.api.core.Context;
028import org.apache.wiki.api.core.Engine;
029import org.apache.wiki.api.core.Page;
030import org.apache.wiki.api.exceptions.NoRequiredPropertyException;
031import org.apache.wiki.api.exceptions.ProviderException;
032import org.apache.wiki.api.exceptions.WikiException;
033import org.apache.wiki.api.providers.PageProvider;
034import org.apache.wiki.api.providers.WikiProvider;
035import org.apache.wiki.api.spi.Wiki;
036import org.apache.wiki.attachment.AttachmentManager;
037import org.apache.wiki.auth.WikiPrincipal;
038import org.apache.wiki.auth.WikiSecurityException;
039import org.apache.wiki.auth.acl.AclManager;
040import org.apache.wiki.auth.user.UserProfile;
041import org.apache.wiki.diff.DifferenceManager;
042import org.apache.wiki.event.WikiEvent;
043import org.apache.wiki.event.WikiEventManager;
044import org.apache.wiki.event.WikiPageEvent;
045import org.apache.wiki.event.WikiSecurityEvent;
046import org.apache.wiki.providers.RepositoryModifiedException;
047import org.apache.wiki.references.ReferenceManager;
048import org.apache.wiki.tasks.TasksManager;
049import org.apache.wiki.ui.CommandResolver;
050import org.apache.wiki.util.ClassUtil;
051import org.apache.wiki.util.TextUtil;
052import org.apache.wiki.workflow.Decision;
053import org.apache.wiki.workflow.DecisionRequiredException;
054import org.apache.wiki.workflow.Fact;
055import org.apache.wiki.workflow.Step;
056import org.apache.wiki.workflow.Workflow;
057import org.apache.wiki.workflow.WorkflowBuilder;
058import org.apache.wiki.workflow.WorkflowManager;
059
060import java.io.IOException;
061import java.security.Permission;
062import java.security.Principal;
063import java.util.ArrayList;
064import java.util.Collection;
065import java.util.Collections;
066import java.util.Date;
067import java.util.Enumeration;
068import java.util.Iterator;
069import java.util.List;
070import java.util.NoSuchElementException;
071import java.util.Properties;
072import java.util.Set;
073import java.util.TreeSet;
074import java.util.concurrent.ConcurrentHashMap;
075
076
077/**
078 * Manages the WikiPages. This class functions as an unified interface towards the page providers. It handles initialization
079 * and management of the providers, and provides utility methods for accessing the contents.
080 * <p/>
081 * Saving a page is a two-stage Task; first the pre-save operations and then the actual save. See the descriptions of the tasks
082 * for further information.
083 *
084 * @since 2.0
085 */
086public class DefaultPageManager implements PageManager {
087
088    private static final Logger LOG = Logger.getLogger( DefaultPageManager.class );
089
090    private PageProvider m_provider;
091
092    private Engine m_engine;
093
094    protected ConcurrentHashMap< String, PageLock > m_pageLocks = new ConcurrentHashMap<>();
095
096    private int m_expiryTime;
097
098    private LockReaper m_reaper = null;
099
100    private PageSorter pageSorter = new PageSorter();
101
102    /**
103     * Creates a new PageManager.
104     *
105     * @param engine Engine instance
106     * @param props  Properties to use for initialization
107     * @throws NoSuchElementException {@value #PROP_PAGEPROVIDER} property not found on Engine properties
108     * @throws WikiException If anything goes wrong, you get this.
109     */
110    public DefaultPageManager(final Engine engine, final Properties props) throws NoSuchElementException, WikiException {
111        m_engine = engine;
112        final String classname;
113        final boolean useCache = "true".equals( props.getProperty( PROP_USECACHE ) );
114        m_expiryTime = TextUtil.parseIntParameter( props.getProperty( PROP_LOCKEXPIRY ), 60 );
115
116        //  If user wants to use a cache, then we'll use the CachingProvider.
117        if( useCache ) {
118            classname = "org.apache.wiki.providers.CachingProvider";
119        } else {
120            classname = TextUtil.getRequiredProperty( props, PROP_PAGEPROVIDER );
121        }
122
123        pageSorter.initialize( props );
124
125        try {
126            LOG.debug("Page provider class: '" + classname + "'");
127            final Class<?> providerclass = ClassUtil.findClass("org.apache.wiki.providers", classname);
128            m_provider = ( PageProvider ) providerclass.newInstance();
129
130            LOG.debug("Initializing page provider class " + m_provider);
131            m_provider.initialize(m_engine, props);
132        } catch (final ClassNotFoundException e) {
133            LOG.error("Unable to locate provider class '" + classname + "' (" + e.getMessage() + ")", e);
134            throw new WikiException("No provider class. (" + e.getMessage() + ")", e);
135        } catch (final InstantiationException e) {
136            LOG.error("Unable to create provider class '" + classname + "' (" + e.getMessage() + ")", e);
137            throw new WikiException("Faulty provider class. (" + e.getMessage() + ")", e);
138        } catch (final IllegalAccessException e) {
139            LOG.error("Illegal access to provider class '" + classname + "' (" + e.getMessage() + ")", e);
140            throw new WikiException("Illegal provider class. (" + e.getMessage() + ")", e);
141        } catch (final NoRequiredPropertyException e) {
142            LOG.error("Provider did not found a property it was looking for: " + e.getMessage(), e);
143            throw e;  // Same exception works.
144        } catch (final IOException e) {
145            LOG.error("An I/O exception occurred while trying to create a new page provider: " + classname, e);
146            throw new WikiException("Unable to start page provider: " + e.getMessage(), e);
147        }
148
149    }
150
151    /**
152     * {@inheritDoc}
153     * @see org.apache.wiki.pages.PageManager#getProvider()
154     */
155    @Override
156    public PageProvider getProvider() {
157        return m_provider;
158    }
159
160    /**
161     * {@inheritDoc}
162     * @see org.apache.wiki.pages.PageManager#getAllPages()
163     */
164    @Override
165    public Collection< Page > getAllPages() throws ProviderException {
166        return m_provider.getAllPages();
167    }
168
169    /**
170     * {@inheritDoc}
171     * @see org.apache.wiki.pages.PageManager#getPageText(java.lang.String, int)
172     */
173    @Override
174    public String getPageText( final String pageName, final int version ) throws ProviderException {
175        if (pageName == null || pageName.length() == 0) {
176            throw new ProviderException( "Illegal page name" );
177        }
178        String text;
179
180        try {
181            text = m_provider.getPageText( pageName, version );
182        } catch ( final RepositoryModifiedException e ) {
183            //  This only occurs with the latest version.
184            LOG.info( "Repository has been modified externally while fetching page " + pageName );
185
186            //  Empty the references and yay, it shall be recalculated
187            final Page p = m_provider.getPageInfo( pageName, version );
188
189            m_engine.getManager( ReferenceManager.class ).updateReferences( p );
190            fireEvent( WikiPageEvent.PAGE_REINDEX, p.getName() );
191            text = m_provider.getPageText( pageName, version );
192        }
193
194        return text;
195    }
196
197    /**
198     * {@inheritDoc}
199     * @see org.apache.wiki.pages.PageManager#getPureText(String, int)
200     */
201    @Override
202    public String getPureText( final String page, final int version ) {
203        String result = null;
204        try {
205            result = getPageText( page, version );
206        } catch( final ProviderException e ) {
207            LOG.error( "ProviderException getPureText for page " + page + " [version " + version + "]", e );
208        } finally {
209            if( result == null ) {
210                result = "";
211            }
212        }
213        return result;
214    }
215
216    /**
217     * {@inheritDoc}
218     * @see org.apache.wiki.pages.PageManager#getText(String, int)
219     */
220    @Override
221    public String getText( final String page, final int version ) {
222        final String result = getPureText( page, version );
223        return TextUtil.replaceEntities( result );
224    }
225
226    @Override
227    public void saveText( final Context context, final String text ) throws WikiException {
228        // Check if page data actually changed; bail if not
229        final Page page = context.getPage();
230        final String oldText = getPureText( page );
231        final String proposedText = TextUtil.normalizePostData( text );
232        if ( oldText != null && oldText.equals( proposedText ) ) {
233            return;
234        }
235
236        // Check if creation of empty pages is allowed; bail if not
237        final boolean allowEmpty = TextUtil.getBooleanProperty( m_engine.getWikiProperties(),
238                                                                Engine.PROP_ALLOW_CREATION_OF_EMPTY_PAGES,
239                                                         false );
240        if ( !allowEmpty && !wikiPageExists( page ) && text.trim().equals( "" ) ) {
241            return;
242        }
243
244        // Create approval workflow for page save; add the diffed, proposed and old text versions as
245        // Facts for the approver (if approval is required). If submitter is authenticated, any reject
246        // messages will appear in his/her workflow inbox.
247        final WorkflowBuilder builder = WorkflowBuilder.getBuilder( m_engine );
248        final Principal submitter = context.getCurrentUser();
249        final Step prepTask = m_engine.getManager( TasksManager.class ).buildPreSaveWikiPageTask( proposedText );
250        final Step completionTask = m_engine.getManager( TasksManager.class ).buildSaveWikiPageTask();
251        final String diffText = m_engine.getManager( DifferenceManager.class ).makeDiff( context, oldText, proposedText );
252        final boolean isAuthenticated = context.getWikiSession().isAuthenticated();
253        final Fact[] facts = new Fact[ 5 ];
254        facts[ 0 ] = new Fact( WorkflowManager.WF_WP_SAVE_FACT_PAGE_NAME, page.getName() );
255        facts[ 1 ] = new Fact( WorkflowManager.WF_WP_SAVE_FACT_DIFF_TEXT, diffText );
256        facts[ 2 ] = new Fact( WorkflowManager.WF_WP_SAVE_FACT_PROPOSED_TEXT, proposedText );
257        facts[ 3 ] = new Fact( WorkflowManager.WF_WP_SAVE_FACT_CURRENT_TEXT, oldText);
258        facts[ 4 ] = new Fact( WorkflowManager.WF_WP_SAVE_FACT_IS_AUTHENTICATED, isAuthenticated );
259        final String rejectKey = isAuthenticated ? WorkflowManager.WF_WP_SAVE_REJECT_MESSAGE_KEY : null;
260        final Workflow workflow = builder.buildApprovalWorkflow( submitter,
261                                                                 WorkflowManager.WF_WP_SAVE_APPROVER,
262                                                                 prepTask,
263                                                                 WorkflowManager.WF_WP_SAVE_DECISION_MESSAGE_KEY,
264                                                                 facts,
265                                                                 completionTask,
266                                                                 rejectKey );
267        workflow.start( context );
268
269        // Let callers know if the page-save requires approval
270        if ( workflow.getCurrentStep() instanceof Decision ) {
271            throw new DecisionRequiredException( "The page contents must be approved before they become active." );
272        }
273    }
274
275    /**
276     * Returns the Engine to which this PageManager belongs to.
277     *
278     * @return The Engine object.
279     */
280    protected Engine getEngine() {
281        return m_engine;
282    }
283
284    /**
285     * {@inheritDoc}
286     * @see org.apache.wiki.pages.PageManager#putPageText(org.apache.wiki.api.core.Page, java.lang.String)
287     */
288    @Override
289    public void putPageText( final Page page, final String content ) throws ProviderException {
290        if (page == null || page.getName() == null || page.getName().length() == 0) {
291            throw new ProviderException("Illegal page name");
292        }
293
294        m_provider.putPageText(page, content);
295    }
296
297    /**
298     * {@inheritDoc}
299     * @see org.apache.wiki.pages.PageManager#lockPage(org.apache.wiki.api.core.Page, java.lang.String)
300     */
301    @Override
302    public PageLock lockPage( final Page page, final String user ) {
303        if( m_reaper == null ) {
304            //  Start the lock reaper lazily.  We don't want to start it in the constructor, because starting threads in constructors
305            //  is a bad idea when it comes to inheritance.  Besides, laziness is a virtue.
306            m_reaper = new LockReaper( m_engine );
307            m_reaper.start();
308        }
309
310        fireEvent( WikiPageEvent.PAGE_LOCK, page.getName() ); // prior to or after actual lock?
311        PageLock lock = m_pageLocks.get( page.getName() );
312
313        if( lock == null ) {
314            //
315            //  Lock is available, so make a lock.
316            //
317            final Date d = new Date();
318            lock = new PageLock( page, user, d, new Date( d.getTime() + m_expiryTime * 60 * 1000L ) );
319            m_pageLocks.put( page.getName(), lock );
320            LOG.debug( "Locked page " + page.getName() + " for " + user );
321        } else {
322            LOG.debug( "Page " + page.getName() + " already locked by " + lock.getLocker() );
323            lock = null; // Nothing to return
324        }
325
326        return lock;
327    }
328
329    /**
330     * {@inheritDoc}
331     * @see org.apache.wiki.pages.PageManager#unlockPage(org.apache.wiki.pages.PageLock)
332     */
333    @Override
334    public void unlockPage( final PageLock lock ) {
335        if (lock == null) {
336            return;
337        }
338
339        m_pageLocks.remove( lock.getPage() );
340        LOG.debug( "Unlocked page " + lock.getPage() );
341
342        fireEvent( WikiPageEvent.PAGE_UNLOCK, lock.getPage() );
343    }
344
345    /**
346     * {@inheritDoc}
347     * @see org.apache.wiki.pages.PageManager#getCurrentLock(org.apache.wiki.api.core.Page)
348     */
349    @Override
350    public PageLock getCurrentLock( final Page page ) {
351        return m_pageLocks.get( page.getName() );
352    }
353
354    /**
355     * {@inheritDoc}
356     * @see org.apache.wiki.pages.PageManager#getActiveLocks()
357     */
358    @Override
359    public List< PageLock > getActiveLocks() {
360        return  new ArrayList<>( m_pageLocks.values() );
361    }
362
363    /**
364     * {@inheritDoc}
365     * @see org.apache.wiki.pages.PageManager#getPage(java.lang.String)
366     */
367    @Override
368    public Page getPage( final String pagereq ) {
369        return getPage( pagereq, PageProvider.LATEST_VERSION );
370    }
371
372    /**
373     * {@inheritDoc}
374     * @see org.apache.wiki.pages.PageManager#getPage(java.lang.String, int)
375     */
376    @Override
377    public Page getPage( final String pagereq, final int version ) {
378        try {
379            Page p = getPageInfo( pagereq, version );
380            if( p == null ) {
381                p = m_engine.getManager( AttachmentManager.class ).getAttachmentInfo( null, pagereq );
382            }
383
384            return p;
385        } catch( final ProviderException e ) {
386            LOG.error( "Unable to fetch page info for " + pagereq + " [version " + version + "]", e );
387            return null;
388        }
389    }
390
391    /**
392     * {@inheritDoc}
393     * @see org.apache.wiki.pages.PageManager#getPageInfo(java.lang.String, int)
394     */
395    @Override
396    public Page getPageInfo( final String pageName, final int version) throws ProviderException {
397        if( pageName == null || pageName.length() == 0 ) {
398            throw new ProviderException( "Illegal page name '" + pageName + "'" );
399        }
400
401        Page page;
402
403        try {
404            page = m_provider.getPageInfo( pageName, version );
405        } catch( final RepositoryModifiedException e ) {
406            //  This only occurs with the latest version.
407            LOG.info( "Repository has been modified externally while fetching info for " + pageName );
408            page = m_provider.getPageInfo( pageName, version );
409            if( page != null ) {
410                m_engine.getManager( ReferenceManager.class ).updateReferences( page );
411            } else {
412                m_engine.getManager( ReferenceManager.class ).pageRemoved( Wiki.contents().page( m_engine, pageName ) );
413            }
414        }
415
416        return page;
417    }
418
419    /**
420     * {@inheritDoc}
421     * @see org.apache.wiki.pages.PageManager#getVersionHistory(java.lang.String)
422     */
423    @Override @SuppressWarnings( "unchecked" )
424    public < T extends Page > List< T > getVersionHistory( final String pageName ) {
425        List< T > c = null;
426
427        try {
428            if( pageExists( pageName ) ) {
429                c = ( List< T > )m_provider.getVersionHistory( pageName );
430            }
431
432            if( c == null ) {
433                c = ( List< T > )m_engine.getManager( AttachmentManager.class ).getVersionHistory( pageName );
434            }
435        } catch( final ProviderException e ) {
436            LOG.error( "ProviderException requesting version history for " + pageName, e );
437        }
438
439        return c;
440    }
441
442    /**
443     * {@inheritDoc}
444     * @see org.apache.wiki.pages.PageManager#getCurrentProvider()
445     */
446    @Override 
447    public String getCurrentProvider() {
448        return getProvider().getClass().getName();
449    }
450
451    /**
452     * {@inheritDoc}
453     *
454     * @see org.apache.wiki.pages.PageManager#getProviderDescription()
455     */
456    @Override 
457    public String getProviderDescription() {
458        return m_provider.getProviderInfo();
459    }
460
461    /**
462     * {@inheritDoc}
463     * @see org.apache.wiki.pages.PageManager#getTotalPageCount()
464     */
465    @Override
466    public int getTotalPageCount() {
467        try {
468            return m_provider.getAllPages().size();
469        } catch( final ProviderException e ) {
470            LOG.error( "Unable to count pages: ", e );
471            return -1;
472        }
473    }
474
475    /**
476     * {@inheritDoc}
477     * @see org.apache.wiki.pages.PageManager#getRecentChanges()
478     */
479    @Override
480    public Set< Page > getRecentChanges() {
481        try {
482            final TreeSet< Page > sortedPages = new TreeSet<>( new PageTimeComparator() );
483            sortedPages.addAll( getAllPages() );
484            sortedPages.addAll( m_engine.getManager( AttachmentManager.class ).getAllAttachments() );
485
486            return sortedPages;
487        } catch( final ProviderException e ) {
488            LOG.error( "Unable to fetch all pages: ", e );
489            return Collections.emptySet();
490        }
491    }
492
493    /**
494     * {@inheritDoc}
495     * @see org.apache.wiki.pages.PageManager#pageExists(java.lang.String)
496     */
497    @Override
498    public boolean pageExists( final String pageName ) throws ProviderException {
499        if (pageName == null || pageName.length() == 0) {
500            throw new ProviderException("Illegal page name");
501        }
502
503        return m_provider.pageExists(pageName);
504    }
505
506    /**
507     * {@inheritDoc}
508     * @see org.apache.wiki.pages.PageManager#pageExists(java.lang.String, int)
509     */
510    @Override
511    public boolean pageExists( final String pageName, final int version ) throws ProviderException {
512        if( pageName == null || pageName.length() == 0 ) {
513            throw new ProviderException( "Illegal page name" );
514        }
515
516        if( version == WikiProvider.LATEST_VERSION ) {
517            return pageExists( pageName );
518        }
519
520        return m_provider.pageExists( pageName, version );
521    }
522
523    /**
524     * {@inheritDoc}
525     * @see org.apache.wiki.pages.PageManager#wikiPageExists(java.lang.String)
526     */
527    @Override
528    public boolean wikiPageExists( final String page ) {
529        if( m_engine.getManager( CommandResolver.class ).getSpecialPageReference( page ) != null ) {
530            return true;
531        }
532
533        Attachment att = null;
534        try {
535            if( m_engine.getFinalPageName( page ) != null ) {
536                return true;
537            }
538
539            att = m_engine.getManager( AttachmentManager.class ).getAttachmentInfo( null, page );
540        } catch( final ProviderException e ) {
541            LOG.debug( "pageExists() failed to find attachments", e );
542        }
543
544        return att != null;
545    }
546
547    /**
548     * {@inheritDoc}
549     * @see org.apache.wiki.pages.PageManager#wikiPageExists(java.lang.String, int)
550     */
551    @Override
552    public boolean wikiPageExists( final String page, final int version ) throws ProviderException {
553        if( m_engine.getManager( CommandResolver.class ).getSpecialPageReference( page ) != null ) {
554            return true;
555        }
556
557        boolean isThere = false;
558        final String finalName = m_engine.getFinalPageName( page );
559        if( finalName != null ) {
560            isThere = pageExists( finalName, version );
561        }
562
563        if( !isThere ) {
564            //  Go check if such an attachment exists.
565            try {
566                isThere = m_engine.getManager( AttachmentManager.class ).getAttachmentInfo( null, page, version ) != null;
567            } catch( final ProviderException e ) {
568                LOG.debug( "wikiPageExists() failed to find attachments", e );
569            }
570        }
571
572        return isThere;
573    }
574
575    /**
576     * {@inheritDoc}
577     * @see org.apache.wiki.pages.PageManager#deleteVersion(org.apache.wiki.api.core.Page)
578     */
579    @Override
580    public void deleteVersion( final Page page ) throws ProviderException {
581        if( page instanceof Attachment ) {
582            m_engine.getManager( AttachmentManager.class ).deleteVersion( ( Attachment )page );
583        } else {
584            m_provider.deleteVersion( page.getName(), page.getVersion() );
585            // FIXME: If this was the latest, reindex Lucene, update RefMgr
586        }
587    }
588
589    /**
590     * {@inheritDoc}
591     * @see org.apache.wiki.pages.PageManager#deletePage(java.lang.String)
592     */
593    @Override
594    public void deletePage( final String pageName ) throws ProviderException {
595        final Page p = getPage( pageName );
596        if( p != null ) {
597            if( p instanceof Attachment ) {
598                m_engine.getManager( AttachmentManager.class ).deleteAttachment( ( Attachment )p );
599            } else {
600                final Collection< String > refTo = m_engine.getManager( ReferenceManager.class ).findRefersTo( pageName );
601                // May return null, if the page does not exist or has not been indexed yet.
602
603                if( m_engine.getManager( AttachmentManager.class ).hasAttachments( p ) ) {
604                    final List< Attachment > attachments = m_engine.getManager( AttachmentManager.class ).listAttachments( p );
605                    for( final Attachment attachment : attachments ) {
606                        if( refTo != null ) {
607                            refTo.remove( attachment.getName() );
608                        }
609
610                        m_engine.getManager( AttachmentManager.class ).deleteAttachment( attachment );
611                    }
612                }
613                deletePage( p );
614                fireEvent( WikiPageEvent.PAGE_DELETED, pageName );
615            }
616        }
617    }
618
619    /**
620     * {@inheritDoc}
621     * @see org.apache.wiki.pages.PageManager#deletePage(org.apache.wiki.api.core.Page)
622     */
623    @Override
624    public void deletePage( final Page page ) throws ProviderException {
625        fireEvent( WikiPageEvent.PAGE_DELETE_REQUEST, page.getName() );
626        m_provider.deletePage( page.getName() );
627        fireEvent( WikiPageEvent.PAGE_DELETED, page.getName() );
628    }
629
630    /**
631     * This is a simple reaper thread that runs roughly every minute
632     * or so (it's not really that important, as long as it runs),
633     * and removes all locks that have expired.
634     */
635    private class LockReaper extends WikiBackgroundThread {
636        /**
637         * Create a LockReaper for a given engine.
638         *
639         * @param engine Engine to own this thread.
640         */
641        public LockReaper( final Engine engine) {
642            super( engine, 60 );
643            setName( "JSPWiki Lock Reaper" );
644        }
645
646        @Override
647        public void backgroundTask() {
648            final Collection< PageLock > entries = m_pageLocks.values();
649            for( final Iterator<PageLock> i = entries.iterator(); i.hasNext(); ) {
650                final PageLock p = i.next();
651
652                if ( p.isExpired() ) {
653                    i.remove();
654
655                    LOG.debug( "Reaped lock: " + p.getPage() +
656                               " by " + p.getLocker() +
657                               ", acquired " + p.getAcquisitionTime() +
658                               ", and expired " + p.getExpiryTime() );
659                }
660            }
661        }
662    }
663
664    // events processing .......................................................
665
666    /**
667     * Fires a WikiPageEvent of the provided type and page name
668     * to all registered listeners.
669     *
670     * @param type     the event type to be fired
671     * @param pagename the wiki page name as a String
672     * @see org.apache.wiki.event.WikiPageEvent
673     */
674    protected final void fireEvent( final int type, final String pagename ) {
675        if( WikiEventManager.isListening( this ) ) {
676            WikiEventManager.fireEvent( this, new WikiPageEvent( m_engine, type, pagename ) );
677        }
678    }
679
680    /**
681     * Listens for {@link org.apache.wiki.event.WikiSecurityEvent#PROFILE_NAME_CHANGED}
682     * events. If a user profile's name changes, each page ACL is inspected. If an entry contains
683     * a name that has changed, it is replaced with the new one. No events are emitted
684     * as a consequence of this method, because the page contents are still the same; it is
685     * only the representations of the names within the ACL that are changing.
686     *
687     * @param event The event
688     */
689    @Override
690    public void actionPerformed( final WikiEvent event ) {
691        if( !( event instanceof WikiSecurityEvent ) ) {
692            return;
693        }
694
695        final WikiSecurityEvent se = ( WikiSecurityEvent ) event;
696        if( se.getType() == WikiSecurityEvent.PROFILE_NAME_CHANGED ) {
697            final UserProfile[] profiles = (UserProfile[]) se.getTarget();
698            final Principal[] oldPrincipals = new Principal[] { new WikiPrincipal( profiles[ 0 ].getLoginName() ),
699                                                                new WikiPrincipal( profiles[ 0 ].getFullname()),
700                                                                new WikiPrincipal( profiles[ 0 ].getWikiName() ) };
701            final Principal newPrincipal = new WikiPrincipal( profiles[ 1 ].getFullname() );
702
703            // Examine each page ACL
704            try {
705                int pagesChanged = 0;
706                final Collection< Page > pages = getAllPages();
707                for( final Page page : pages ) {
708                    final boolean aclChanged = changeAcl( page, oldPrincipals, newPrincipal );
709                    if( aclChanged ) {
710                        // If the Acl needed changing, change it now
711                        try {
712                            m_engine.getManager( AclManager.class ).setPermissions( page, page.getAcl() );
713                        } catch( final WikiSecurityException e ) {
714                            LOG.error("Could not change page ACL for page " + page.getName() + ": " + e.getMessage(), e);
715                        }
716                        pagesChanged++;
717                    }
718                }
719                LOG.info( "Profile name change for '" + newPrincipal.toString() + "' caused " + pagesChanged + " page ACLs to change also." );
720            } catch( final ProviderException e ) {
721                // Oooo! This is really bad...
722                LOG.error( "Could not change user name in Page ACLs because of Provider error:" + e.getMessage(), e );
723            }
724        }
725    }
726
727    /**
728     * For a single wiki page, replaces all Acl entries matching a supplied array of Principals with a new Principal.
729     *
730     * @param page the wiki page whose Acl is to be modified
731     * @param oldPrincipals an array of Principals to replace; all AclEntry objects whose {@link AclEntry#getPrincipal()} method returns
732     *                      one of these Principals will be replaced
733     * @param newPrincipal the Principal that should receive the old Principals' permissions
734     * @return <code>true</code> if the Acl was actually changed; <code>false</code> otherwise
735     */
736    protected boolean changeAcl( final Page page, final Principal[] oldPrincipals, final Principal newPrincipal ) {
737        final Acl acl = page.getAcl();
738        boolean pageChanged = false;
739        if( acl != null ) {
740            final Enumeration< AclEntry > entries = acl.aclEntries();
741            final Collection< AclEntry > entriesToAdd = new ArrayList<>();
742            final Collection< AclEntry > entriesToRemove = new ArrayList<>();
743            while( entries.hasMoreElements() ) {
744                final AclEntry entry = entries.nextElement();
745                if( ArrayUtils.contains( oldPrincipals, entry.getPrincipal() ) ) {
746                    // Create new entry
747                    final AclEntry newEntry = Wiki.acls().entry();
748                    newEntry.setPrincipal( newPrincipal );
749                    final Enumeration< Permission > permissions = entry.permissions();
750                    while( permissions.hasMoreElements() ) {
751                        final Permission permission = permissions.nextElement();
752                        newEntry.addPermission( permission );
753                    }
754                    pageChanged = true;
755                    entriesToRemove.add( entry );
756                    entriesToAdd.add( newEntry );
757                }
758            }
759            for( final AclEntry entry : entriesToRemove ) {
760                acl.removeEntry( entry );
761            }
762            for( final AclEntry entry : entriesToAdd ) {
763                acl.addEntry( entry );
764            }
765        }
766        return pageChanged;
767    }
768
769    /**
770     * {@inheritDoc}
771     * @see org.apache.wiki.pages.PageManager#getPageSorter()
772     */
773    @Override
774    public PageSorter getPageSorter() {
775        return pageSorter;
776    }
777
778}