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