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}