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; 020 021import java.io.IOException; 022import java.security.Permission; 023import java.security.Principal; 024import java.util.ArrayList; 025import java.util.Collection; 026import java.util.Date; 027import java.util.Enumeration; 028import java.util.HashMap; 029import java.util.Iterator; 030import java.util.List; 031import java.util.Properties; 032 033import org.apache.commons.lang.ArrayUtils; 034import org.apache.log4j.Logger; 035import org.apache.wiki.api.engine.FilterManager; 036import org.apache.wiki.api.exceptions.NoRequiredPropertyException; 037import org.apache.wiki.api.exceptions.ProviderException; 038import org.apache.wiki.api.exceptions.WikiException; 039import org.apache.wiki.auth.WikiPrincipal; 040import org.apache.wiki.auth.WikiSecurityException; 041import org.apache.wiki.auth.acl.Acl; 042import org.apache.wiki.auth.acl.AclEntry; 043import org.apache.wiki.auth.acl.AclEntryImpl; 044import org.apache.wiki.auth.user.UserProfile; 045import org.apache.wiki.event.WikiEvent; 046import org.apache.wiki.event.WikiEventListener; 047import org.apache.wiki.event.WikiEventManager; 048import org.apache.wiki.event.WikiPageEvent; 049import org.apache.wiki.event.WikiSecurityEvent; 050import org.apache.wiki.modules.ModuleManager; 051import org.apache.wiki.modules.WikiModuleInfo; 052import org.apache.wiki.providers.RepositoryModifiedException; 053import org.apache.wiki.providers.WikiPageProvider; 054import org.apache.wiki.util.ClassUtil; 055import org.apache.wiki.util.TextUtil; 056import org.apache.wiki.workflow.Outcome; 057import org.apache.wiki.workflow.Task; 058import org.apache.wiki.workflow.Workflow; 059 060 061/** 062 * Manages the WikiPages. This class functions as an unified interface towards 063 * the page providers. It handles initialization and management of the providers, 064 * and provides utility methods for accessing the contents. 065 * <p/> 066 * Saving a page is a two-stage Task; first the pre-save operations and then the 067 * actual save. See the descriptions of the tasks for further information. 068 * 069 * @since 2.0 070 */ 071// FIXME: This class currently only functions just as an extra layer over providers, 072// complicating things. We need to move more provider-specific functionality 073// from WikiEngine (which is too big now) into this class. 074public class PageManager extends ModuleManager implements WikiEventListener { 075 076 private static final long serialVersionUID = 1L; 077 078 /** 079 * The property value for setting the current page provider. Value is {@value}. 080 */ 081 public static final String PROP_PAGEPROVIDER = "jspwiki.pageProvider"; 082 083 /** 084 * The property value for setting the cache on/off. Value is {@value}. 085 */ 086 public static final String PROP_USECACHE = "jspwiki.usePageCache"; 087 088 /** 089 * The property value for setting the amount of time before the page locks expire. Value is {@value}. 090 */ 091 public static final String PROP_LOCKEXPIRY = "jspwiki.lockExpiryTime"; 092 093 /** 094 * The message key for storing the text for the presave task. Value is <tt>{@value}</tt> 095 */ 096 public static final String PRESAVE_TASK_MESSAGE_KEY = "task.preSaveWikiPage"; 097 098 /** 099 * The workflow attribute which stores the wikiContext. 100 */ 101 public static final String PRESAVE_WIKI_CONTEXT = "wikiContext"; 102 103 /** 104 * The name of the key from jspwiki.properties which defines who shall approve 105 * the workflow of storing a wikipage. Value is <tt>{@value}</tt> 106 */ 107 public static final String SAVE_APPROVER = "workflow.saveWikiPage"; 108 109 /** 110 * The message key for storing the Decision text for saving a page. Value is {@value}. 111 */ 112 public static final String SAVE_DECISION_MESSAGE_KEY = "decision.saveWikiPage"; 113 114 /** 115 * The message key for rejecting the decision to save the page. Value is {@value}. 116 */ 117 public static final String SAVE_REJECT_MESSAGE_KEY = "notification.saveWikiPage.reject"; 118 119 /** 120 * The message key of the text to finally approve a page save. Value is {@value}. 121 */ 122 public static final String SAVE_TASK_MESSAGE_KEY = "task.saveWikiPage"; 123 124 /** 125 * Fact name for storing the page name. Value is {@value}. 126 */ 127 public static final String FACT_PAGE_NAME = "fact.pageName"; 128 129 /** 130 * Fact name for storing a diff text. Value is {@value}. 131 */ 132 public static final String FACT_DIFF_TEXT = "fact.diffText"; 133 134 /** 135 * Fact name for storing the current text. Value is {@value}. 136 */ 137 public static final String FACT_CURRENT_TEXT = "fact.currentText"; 138 139 /** 140 * Fact name for storing the proposed (edited) text. Value is {@value}. 141 */ 142 public static final String FACT_PROPOSED_TEXT = "fact.proposedText"; 143 144 /** 145 * Fact name for storing whether the user is authenticated or not. Value is {@value}. 146 */ 147 public static final String FACT_IS_AUTHENTICATED = "fact.isAuthenticated"; 148 149 static Logger log = Logger.getLogger(PageManager.class); 150 151 private WikiPageProvider m_provider; 152 153 protected HashMap<String, PageLock> m_pageLocks = new HashMap<String, PageLock>(); 154 155 private WikiEngine m_engine; 156 157 private int m_expiryTime = 60; 158 159 private LockReaper m_reaper = null; 160 161 /** 162 * Creates a new PageManager. 163 * 164 * @param engine WikiEngine instance 165 * @param props Properties to use for initialization 166 * @throws WikiException If anything goes wrong, you get this. 167 */ 168 public PageManager(WikiEngine engine, Properties props) throws WikiException { 169 super(engine); 170 String classname; 171 m_engine = engine; 172 boolean useCache = "true".equals(props.getProperty(PROP_USECACHE)); 173 174 m_expiryTime = TextUtil.parseIntParameter(props.getProperty(PROP_LOCKEXPIRY), 60); 175 176 // 177 // If user wants to use a cache, then we'll use the CachingProvider. 178 // 179 if (useCache) { 180 classname = "org.apache.wiki.providers.CachingProvider"; 181 } else { 182 classname = TextUtil.getRequiredProperty(props, PROP_PAGEPROVIDER); 183 } 184 185 try { 186 log.debug("Page provider class: '" + classname + "'"); 187 Class<?> providerclass = ClassUtil.findClass("org.apache.wiki.providers", classname); 188 m_provider = (WikiPageProvider) providerclass.newInstance(); 189 190 log.debug("Initializing page provider class " + m_provider); 191 m_provider.initialize(m_engine, props); 192 } catch (ClassNotFoundException e) { 193 log.error("Unable to locate provider class '" + classname + "' (" + e.getMessage() + ")", e); 194 throw new WikiException("No provider class. (" + e.getMessage() + ")", e); 195 } catch (InstantiationException e) { 196 log.error("Unable to create provider class '" + classname + "' (" + e.getMessage() + ")", e); 197 throw new WikiException("Faulty provider class. (" + e.getMessage() + ")", e); 198 } catch (IllegalAccessException e) { 199 log.error("Illegal access to provider class '" + classname + "' (" + e.getMessage() + ")", e); 200 throw new WikiException("Illegal provider class. (" + e.getMessage() + ")", e); 201 } catch (NoRequiredPropertyException e) { 202 log.error("Provider did not found a property it was looking for: " + e.getMessage(), e); 203 throw e; // Same exception works. 204 } catch (IOException e) { 205 log.error("An I/O exception occurred while trying to create a new page provider: " + classname, e); 206 throw new WikiException("Unable to start page provider: " + e.getMessage(), e); 207 } 208 209 } 210 211 /** 212 * Returns the page provider currently in use. 213 * 214 * @return A WikiPageProvider instance. 215 */ 216 public WikiPageProvider getProvider() { 217 return m_provider; 218 } 219 220 /** 221 * Returns all pages in some random order. If you need just the page names, 222 * please see {@link ReferenceManager#findCreated()}, which is probably a lot 223 * faster. This method may cause repository access. 224 * 225 * @return A Collection of WikiPage objects. 226 * @throws ProviderException If the backend has problems. 227 */ 228 public Collection getAllPages() throws ProviderException { 229 return m_provider.getAllPages(); 230 } 231 232 /** 233 * Fetches the page text from the repository. This method also does some sanity checks, 234 * like checking for the pageName validity, etc. Also, if the page repository has been 235 * modified externally, it is smart enough to handle such occurrences. 236 * 237 * @param pageName The name of the page to fetch. 238 * @param version The version to find 239 * @return The page content as a raw string 240 * @throws ProviderException If the backend has issues. 241 */ 242 public String getPageText(String pageName, int version) throws ProviderException { 243 if (pageName == null || pageName.length() == 0) { 244 throw new ProviderException("Illegal page name"); 245 } 246 String text = null; 247 248 try { 249 text = m_provider.getPageText(pageName, version); 250 } catch (RepositoryModifiedException e) { 251 // 252 // This only occurs with the latest version. 253 // 254 log.info("Repository has been modified externally while fetching page " + pageName); 255 256 // 257 // Empty the references and yay, it shall be recalculated 258 // 259 //WikiPage p = new WikiPage( pageName ); 260 WikiPage p = m_provider.getPageInfo(pageName, version); 261 262 m_engine.updateReferences(p); 263 264 if (p != null) { 265 m_engine.getSearchManager().reindexPage(p); 266 text = m_provider.getPageText(pageName, version); 267 } else { 268 // 269 // Make sure that it no longer exists in internal data structures either. 270 // 271 WikiPage dummy = new WikiPage(m_engine, pageName); 272 m_engine.getSearchManager().pageRemoved(dummy); 273 m_engine.getReferenceManager().pageRemoved(dummy); 274 } 275 } 276 277 return text; 278 } 279 280 /** 281 * Returns the WikiEngine to which this PageManager belongs to. 282 * 283 * @return The WikiEngine object. 284 */ 285 public WikiEngine getEngine() { 286 return m_engine; 287 } 288 289 /** 290 * Puts the page text into the repository. Note that this method does NOT update 291 * JSPWiki internal data structures, and therefore you should always use WikiEngine.saveText() 292 * 293 * @param page Page to save 294 * @param content Wikimarkup to save 295 * @throws ProviderException If something goes wrong in the saving phase 296 */ 297 public void putPageText(WikiPage page, String content) throws ProviderException { 298 if (page == null || page.getName() == null || page.getName().length() == 0) { 299 throw new ProviderException("Illegal page name"); 300 } 301 302 m_provider.putPageText(page, content); 303 } 304 305 /** 306 * Locks page for editing. Note, however, that the PageManager 307 * will in no way prevent you from actually editing this page; 308 * the lock is just for information. 309 * 310 * @param page WikiPage to lock 311 * @param user Username to use for locking 312 * @return null, if page could not be locked. 313 */ 314 public PageLock lockPage(WikiPage page, String user) { 315 PageLock lock = null; 316 317 if (m_reaper == null) { 318 // 319 // Start the lock reaper lazily. We don't want to start it in 320 // the constructor, because starting threads in constructors 321 // is a bad idea when it comes to inheritance. Besides, 322 // laziness is a virtue. 323 // 324 m_reaper = new LockReaper(m_engine); 325 m_reaper.start(); 326 } 327 328 synchronized (m_pageLocks) { 329 fireEvent(WikiPageEvent.PAGE_LOCK, page.getName()); // prior to or after actual lock? 330 lock = m_pageLocks.get(page.getName()); 331 332 if (lock == null) { 333 // 334 // Lock is available, so make a lock. 335 // 336 Date d = new Date(); 337 lock = new PageLock(page, user, d, new Date(d.getTime() + m_expiryTime * 60 * 1000L)); 338 m_pageLocks.put(page.getName(), lock); 339 log.debug("Locked page " + page.getName() + " for " + user); 340 } else { 341 log.debug("Page " + page.getName() + " already locked by " + lock.getLocker()); 342 lock = null; // Nothing to return 343 } 344 } 345 346 return lock; 347 } 348 349 /** 350 * Marks a page free to be written again. If there has not been a lock, will fail quietly. 351 * 352 * @param lock A lock acquired in lockPage(). Safe to be null. 353 */ 354 public void unlockPage(PageLock lock) { 355 if (lock == null) { 356 return; 357 } 358 359 synchronized (m_pageLocks) { 360 m_pageLocks.remove(lock.getPage()); 361 log.debug("Unlocked page " + lock.getPage()); 362 } 363 364 fireEvent(WikiPageEvent.PAGE_UNLOCK, lock.getPage()); 365 } 366 367 /** 368 * Returns the current lock owner of a page. If the page is not 369 * locked, will return null. 370 * 371 * @param page The page to check the lock for 372 * @return Current lock, or null, if there is no lock 373 */ 374 public PageLock getCurrentLock(WikiPage page) { 375 PageLock lock = null; 376 377 synchronized (m_pageLocks) { 378 lock = m_pageLocks.get(page.getName()); 379 } 380 381 return lock; 382 } 383 384 /** 385 * Returns a list of currently applicable locks. Note that by the time you get the list, 386 * the locks may have already expired, so use this only for informational purposes. 387 * 388 * @return List of PageLock objects, detailing the locks. If no locks exist, returns 389 * an empty list. 390 * @since 2.0.22. 391 */ 392 public List<PageLock> getActiveLocks() { 393 ArrayList<PageLock> result = new ArrayList<PageLock>(); 394 395 synchronized (m_pageLocks) { 396 for (PageLock lock : m_pageLocks.values()) { 397 result.add(lock); 398 } 399 } 400 401 return result; 402 } 403 404 /** 405 * Finds a WikiPage object describing a particular page and version. 406 * 407 * @param pageName The name of the page 408 * @param version A version number 409 * @return A WikiPage object, or null, if the page does not exist 410 * @throws ProviderException If there is something wrong with the page 411 * name or the repository 412 */ 413 public WikiPage getPageInfo(String pageName, int version) throws ProviderException { 414 if (pageName == null || pageName.length() == 0) { 415 throw new ProviderException("Illegal page name '" + pageName + "'"); 416 } 417 418 WikiPage page = null; 419 420 try { 421 page = m_provider.getPageInfo(pageName, version); 422 } catch (RepositoryModifiedException e) { 423 // 424 // This only occurs with the latest version. 425 // 426 log.info("Repository has been modified externally while fetching info for " + pageName); 427 page = m_provider.getPageInfo(pageName, version); 428 if (page != null) { 429 m_engine.updateReferences(page); 430 } else { 431 m_engine.getReferenceManager().pageRemoved(new WikiPage(m_engine, pageName)); 432 } 433 } 434 435 // 436 // Should update the metadata. 437 // 438 /* 439 if( page != null && !page.hasMetadata() ) 440 { 441 WikiContext ctx = new WikiContext(m_engine,page); 442 m_engine.textToHTML( ctx, getPageText(pageName,version) ); 443 } 444 */ 445 return page; 446 } 447 448 /** 449 * Gets a version history of page. Each element in the returned 450 * List is a WikiPage. 451 * 452 * @param pageName The name of the page to fetch history for 453 * @return If the page does not exist, returns null, otherwise a List 454 * of WikiPages. 455 * @throws ProviderException If the repository fails. 456 */ 457 public List getVersionHistory(String pageName) throws ProviderException { 458 if (pageExists(pageName)) { 459 return m_provider.getVersionHistory(pageName); 460 } 461 462 return null; 463 } 464 465 /** 466 * Returns a human-readable description of the current provider. 467 * 468 * @return A human-readable description. 469 */ 470 public String getProviderDescription() { 471 return m_provider.getProviderInfo(); 472 } 473 474 /** 475 * Returns the total count of all pages in the repository. This 476 * method is equivalent of calling getAllPages().size(), but 477 * it swallows the ProviderException and returns -1 instead of 478 * any problems. 479 * 480 * @return The number of pages, or -1, if there is an error. 481 */ 482 public int getTotalPageCount() { 483 try { 484 return m_provider.getAllPages().size(); 485 } catch (ProviderException e) { 486 log.error("Unable to count pages: ", e); 487 return -1; 488 } 489 } 490 491 /** 492 * Returns true, if the page exists (any version). 493 * 494 * @param pageName Name of the page. 495 * @return A boolean value describing the existence of a page 496 * @throws ProviderException If the backend fails or the name is illegal. 497 */ 498 public boolean pageExists(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 * Checks for existence of a specific page and version. 508 * 509 * @param pageName Name of the page 510 * @param version The version to check 511 * @return <code>true</code> if the page exists, <code>false</code> otherwise 512 * @throws ProviderException If backend fails or name is illegal 513 * @since 2.3.29 514 */ 515 public boolean pageExists(String pageName, int version) throws ProviderException { 516 if (pageName == null || pageName.length() == 0) { 517 throw new ProviderException("Illegal page name"); 518 } 519 520 if (version == WikiProvider.LATEST_VERSION) { 521 return pageExists(pageName); 522 } 523 524 return m_provider.pageExists(pageName, version); 525 } 526 527 /** 528 * Deletes only a specific version of a WikiPage. 529 * 530 * @param page The page to delete. 531 * @throws ProviderException if the page fails 532 */ 533 public void deleteVersion(WikiPage page) throws ProviderException { 534 m_provider.deleteVersion(page.getName(), page.getVersion()); 535 536 // FIXME: If this was the latest, reindex Lucene 537 // FIXME: Update RefMgr 538 } 539 540 /** 541 * Deletes an entire page, all versions, all traces. 542 * 543 * @param page The WikiPage to delete 544 * @throws ProviderException If the repository operation fails 545 */ 546 public void deletePage(WikiPage page) throws ProviderException { 547 fireEvent(WikiPageEvent.PAGE_DELETE_REQUEST, page.getName()); 548 m_provider.deletePage(page.getName()); 549 fireEvent(WikiPageEvent.PAGE_DELETED, page.getName()); 550 } 551 552 /** 553 * This is a simple reaper thread that runs roughly every minute 554 * or so (it's not really that important, as long as it runs), 555 * and removes all locks that have expired. 556 */ 557 private class LockReaper extends WikiBackgroundThread { 558 /** 559 * Create a LockReaper for a given engine. 560 * 561 * @param engine WikiEngine to own this thread. 562 */ 563 public LockReaper(WikiEngine engine) { 564 super(engine, 60); 565 setName("JSPWiki Lock Reaper"); 566 } 567 568 public void backgroundTask() throws Exception { 569 synchronized (m_pageLocks) { 570 Collection<PageLock> entries = m_pageLocks.values(); 571 Date now = new Date(); 572 for (Iterator<PageLock> i = entries.iterator(); i.hasNext(); ) { 573 PageLock p = i.next(); 574 575 if (now.after(p.getExpiryTime())) { 576 i.remove(); 577 578 log.debug("Reaped lock: " + p.getPage() + 579 " by " + p.getLocker() + 580 ", acquired " + p.getAcquisitionTime() + 581 ", and expired " + p.getExpiryTime()); 582 } 583 } 584 } 585 } 586 } 587 588 // workflow task inner classes.................................................... 589 590 /** 591 * Inner class that handles the page pre-save actions. If the proposed page 592 * text is the same as the current version, the {@link #execute()} method 593 * returns {@link org.apache.wiki.workflow.Outcome#STEP_ABORT}. Any 594 * WikiExceptions thrown by page filters will be re-thrown, and the workflow 595 * will abort. 596 */ 597 public static class PreSaveWikiPageTask extends Task { 598 private static final long serialVersionUID = 6304715570092804615L; 599 private final WikiContext m_context; 600 private final String m_proposedText; 601 602 /** 603 * Creates the task. 604 * 605 * @param context The WikiContext 606 * @param proposedText The text that was just saved. 607 */ 608 public PreSaveWikiPageTask(WikiContext context, String proposedText) { 609 super(PRESAVE_TASK_MESSAGE_KEY); 610 m_context = context; 611 m_proposedText = proposedText; 612 } 613 614 /** 615 * {@inheritDoc} 616 */ 617 @Override 618 public Outcome execute() throws WikiException { 619 // Retrieve attributes 620 WikiEngine engine = m_context.getEngine(); 621 Workflow workflow = getWorkflow(); 622 623 // Get the wiki page 624 WikiPage page = m_context.getPage(); 625 626 // Figure out who the author was. Prefer the author 627 // set programmatically; otherwise get from the 628 // current logged in user 629 if (page.getAuthor() == null) { 630 Principal wup = m_context.getCurrentUser(); 631 632 if (wup != null) { 633 page.setAuthor(wup.getName()); 634 } 635 } 636 637 // Run the pre-save filters. If any exceptions, add error to list, abort, and redirect 638 String saveText; 639 FilterManager fm = engine.getFilterManager(); 640 saveText = fm.doPreSaveFiltering(m_context, m_proposedText); 641 642 // Stash the wiki context, old and new text as workflow attributes 643 workflow.setAttribute(PRESAVE_WIKI_CONTEXT, m_context); 644 workflow.setAttribute(FACT_PROPOSED_TEXT, saveText); 645 return Outcome.STEP_COMPLETE; 646 } 647 } 648 649 /** 650 * Inner class that handles the actual page save and post-save actions. Instances 651 * of this class are assumed to have been added to an approval workflow via 652 * {@link org.apache.wiki.workflow.WorkflowBuilder#buildApprovalWorkflow(Principal, String, Task, String, org.apache.wiki.workflow.Fact[], Task, String)}; 653 * they will not function correctly otherwise. 654 */ 655 public static class SaveWikiPageTask extends Task { 656 private static final long serialVersionUID = 3190559953484411420L; 657 658 /** 659 * Creates the Task. 660 */ 661 public SaveWikiPageTask() { 662 super(SAVE_TASK_MESSAGE_KEY); 663 } 664 665 /** 666 * {@inheritDoc} 667 */ 668 @Override 669 public Outcome execute() throws WikiException { 670 // Retrieve attributes 671 WikiContext context = (WikiContext) getWorkflow().getAttribute(PRESAVE_WIKI_CONTEXT); 672 String proposedText = (String) getWorkflow().getAttribute(FACT_PROPOSED_TEXT); 673 674 WikiEngine engine = context.getEngine(); 675 WikiPage page = context.getPage(); 676 677 // Let the rest of the engine handle actual saving. 678 engine.getPageManager().putPageText(page, proposedText); 679 680 // Refresh the context for post save filtering. 681 engine.getPage(page.getName()); 682 engine.textToHTML(context, proposedText); 683 FilterManager fm = engine.getFilterManager(); 684 fm.doPostSaveFiltering(context, proposedText); 685 686 return Outcome.STEP_COMPLETE; 687 } 688 } 689 690 // events processing ....................................................... 691 692 /** 693 * Fires a WikiPageEvent of the provided type and page name 694 * to all registered listeners. 695 * 696 * @param type the event type to be fired 697 * @param pagename the wiki page name as a String 698 * @see org.apache.wiki.event.WikiPageEvent 699 */ 700 protected final void fireEvent(int type, String pagename) { 701 if (WikiEventManager.isListening(this)) { 702 WikiEventManager.fireEvent(this, new WikiPageEvent(m_engine, type, pagename)); 703 } 704 } 705 706 /** 707 * {@inheritDoc} 708 */ 709 @Override 710 public Collection modules() { 711 return null; 712 } 713 714 /** 715 * Returns null! 716 * {@inheritDoc} 717 */ 718 @Override 719 public WikiModuleInfo getModuleInfo(String moduleName) { 720 return null; 721 } 722 723 724 /** 725 * Listens for {@link org.apache.wiki.event.WikiSecurityEvent#PROFILE_NAME_CHANGED} 726 * events. If a user profile's name changes, each page ACL is inspected. If an entry contains 727 * a name that has changed, it is replaced with the new one. No events are emitted 728 * as a consequence of this method, because the page contents are still the same; it is 729 * only the representations of the names within the ACL that are changing. 730 * 731 * @param event The event 732 */ 733 public void actionPerformed(WikiEvent event) { 734 if (!(event instanceof WikiSecurityEvent)) { 735 return; 736 } 737 738 WikiSecurityEvent se = (WikiSecurityEvent) event; 739 if (se.getType() == WikiSecurityEvent.PROFILE_NAME_CHANGED) { 740 UserProfile[] profiles = (UserProfile[]) se.getTarget(); 741 Principal[] oldPrincipals = new Principal[] 742 {new WikiPrincipal(profiles[0].getLoginName()), 743 new WikiPrincipal(profiles[0].getFullname()), 744 new WikiPrincipal(profiles[0].getWikiName())}; 745 Principal newPrincipal = new WikiPrincipal(profiles[1].getFullname()); 746 747 // Examine each page ACL 748 try { 749 int pagesChanged = 0; 750 Collection pages = getAllPages(); 751 for (Iterator it = pages.iterator(); it.hasNext(); ) { 752 WikiPage page = (WikiPage) it.next(); 753 boolean aclChanged = changeAcl(page, oldPrincipals, newPrincipal); 754 if (aclChanged) { 755 // If the Acl needed changing, change it now 756 try { 757 m_engine.getAclManager().setPermissions(page, page.getAcl()); 758 } catch (WikiSecurityException e) { 759 log.error("Could not change page ACL for page " + page.getName() + ": " + e.getMessage(), e); 760 } 761 pagesChanged++; 762 } 763 } 764 log.info("Profile name change for '" + newPrincipal.toString() + 765 "' caused " + pagesChanged + " page ACLs to change also."); 766 } catch (ProviderException e) { 767 // Oooo! This is really bad... 768 log.error("Could not change user name in Page ACLs because of Provider error:" + e.getMessage(), e); 769 } 770 } 771 } 772 773 /** 774 * For a single wiki page, replaces all Acl entries matching a supplied array of Principals 775 * with a new Principal. 776 * 777 * @param page the wiki page whose Acl is to be modified 778 * @param oldPrincipals an array of Principals to replace; all AclEntry objects whose 779 * {@link AclEntry#getPrincipal()} method returns one of these Principals will be replaced 780 * @param newPrincipal the Principal that should receive the old Principals' permissions 781 * @return <code>true</code> if the Acl was actually changed; <code>false</code> otherwise 782 */ 783 protected boolean changeAcl(WikiPage page, Principal[] oldPrincipals, Principal newPrincipal) { 784 Acl acl = page.getAcl(); 785 boolean pageChanged = false; 786 if (acl != null) { 787 Enumeration<AclEntry> entries = acl.entries(); 788 Collection<AclEntry> entriesToAdd = new ArrayList<AclEntry>(); 789 Collection<AclEntry> entriesToRemove = new ArrayList<AclEntry>(); 790 while (entries.hasMoreElements()) { 791 AclEntry entry = entries.nextElement(); 792 if (ArrayUtils.contains(oldPrincipals, entry.getPrincipal())) { 793 // Create new entry 794 AclEntry newEntry = new AclEntryImpl(); 795 newEntry.setPrincipal(newPrincipal); 796 Enumeration<Permission> permissions = entry.permissions(); 797 while (permissions.hasMoreElements()) { 798 Permission permission = permissions.nextElement(); 799 newEntry.addPermission(permission); 800 } 801 pageChanged = true; 802 entriesToRemove.add(entry); 803 entriesToAdd.add(newEntry); 804 } 805 } 806 for (Iterator<AclEntry> ix = entriesToRemove.iterator(); ix.hasNext(); ) { 807 AclEntry entry = ix.next(); 808 acl.removeEntry(entry); 809 } 810 for (Iterator<AclEntry> ix = entriesToAdd.iterator(); ix.hasNext(); ) { 811 AclEntry entry = ix.next(); 812 acl.addEntry(entry); 813 } 814 } 815 return pageChanged; 816 } 817 818}