    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}