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