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