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.Iterator; 029import java.util.List; 030import java.util.Properties; 031import java.util.concurrent.ConcurrentHashMap; 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 ConcurrentHashMap<String, PageLock> m_pageLocks = new ConcurrentHashMap<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 fireEvent(WikiPageEvent.PAGE_LOCK, page.getName()); // prior to or after actual lock? 329 lock = m_pageLocks.get(page.getName()); 330 331 if (lock == null) { 332 // 333 // Lock is available, so make a lock. 334 // 335 Date d = new Date(); 336 lock = new PageLock(page, user, d, new Date(d.getTime() + m_expiryTime * 60 * 1000L)); 337 m_pageLocks.put(page.getName(), lock); 338 log.debug("Locked page " + page.getName() + " for " + user); 339 } else { 340 log.debug("Page " + page.getName() + " already locked by " + lock.getLocker()); 341 lock = null; // Nothing to return 342 } 343 344 return lock; 345 } 346 347 /** 348 * Marks a page free to be written again. If there has not been a lock, will fail quietly. 349 * 350 * @param lock A lock acquired in lockPage(). Safe to be null. 351 */ 352 public void unlockPage(PageLock lock) { 353 if (lock == null) { 354 return; 355 } 356 357 m_pageLocks.remove(lock.getPage()); 358 log.debug("Unlocked page " + lock.getPage()); 359 360 fireEvent(WikiPageEvent.PAGE_UNLOCK, lock.getPage()); 361 } 362 363 /** 364 * Returns the current lock owner of a page. If the page is not 365 * locked, will return null. 366 * 367 * @param page The page to check the lock for 368 * @return Current lock, or null, if there is no lock 369 */ 370 public PageLock getCurrentLock(WikiPage page) { 371 return m_pageLocks.get(page.getName()); 372 } 373 374 /** 375 * Returns a list of currently applicable locks. Note that by the time you get the list, 376 * the locks may have already expired, so use this only for informational purposes. 377 * 378 * @return List of PageLock objects, detailing the locks. If no locks exist, returns 379 * an empty list. 380 * @since 2.0.22. 381 */ 382 public List<PageLock> getActiveLocks() { 383 ArrayList<PageLock> result = new ArrayList<PageLock>(); 384 385 for (PageLock lock : m_pageLocks.values()) { 386 result.add(lock); 387 } 388 389 return result; 390 } 391 392 /** 393 * Finds a WikiPage object describing a particular page and version. 394 * 395 * @param pageName The name of the page 396 * @param version A version number 397 * @return A WikiPage object, or null, if the page does not exist 398 * @throws ProviderException If there is something wrong with the page 399 * name or the repository 400 */ 401 public WikiPage getPageInfo(String pageName, int version) throws ProviderException { 402 if (pageName == null || pageName.length() == 0) { 403 throw new ProviderException("Illegal page name '" + pageName + "'"); 404 } 405 406 WikiPage page = null; 407 408 try { 409 page = m_provider.getPageInfo(pageName, version); 410 } catch (RepositoryModifiedException e) { 411 // 412 // This only occurs with the latest version. 413 // 414 log.info("Repository has been modified externally while fetching info for " + pageName); 415 page = m_provider.getPageInfo(pageName, version); 416 if (page != null) { 417 m_engine.updateReferences(page); 418 } else { 419 m_engine.getReferenceManager().pageRemoved(new WikiPage(m_engine, pageName)); 420 } 421 } 422 423 // 424 // Should update the metadata. 425 // 426 /* 427 if( page != null && !page.hasMetadata() ) 428 { 429 WikiContext ctx = new WikiContext(m_engine,page); 430 m_engine.textToHTML( ctx, getPageText(pageName,version) ); 431 } 432 */ 433 return page; 434 } 435 436 /** 437 * Gets a version history of page. Each element in the returned 438 * List is a WikiPage. 439 * 440 * @param pageName The name of the page to fetch history for 441 * @return If the page does not exist, returns null, otherwise a List 442 * of WikiPages. 443 * @throws ProviderException If the repository fails. 444 */ 445 public List getVersionHistory(String pageName) throws ProviderException { 446 if (pageExists(pageName)) { 447 return m_provider.getVersionHistory(pageName); 448 } 449 450 return null; 451 } 452 453 /** 454 * Returns a human-readable description of the current provider. 455 * 456 * @return A human-readable description. 457 */ 458 public String getProviderDescription() { 459 return m_provider.getProviderInfo(); 460 } 461 462 /** 463 * Returns the total count of all pages in the repository. This 464 * method is equivalent of calling getAllPages().size(), but 465 * it swallows the ProviderException and returns -1 instead of 466 * any problems. 467 * 468 * @return The number of pages, or -1, if there is an error. 469 */ 470 public int getTotalPageCount() { 471 try { 472 return m_provider.getAllPages().size(); 473 } catch (ProviderException e) { 474 log.error("Unable to count pages: ", e); 475 return -1; 476 } 477 } 478 479 /** 480 * Returns true, if the page exists (any version). 481 * 482 * @param pageName Name of the page. 483 * @return A boolean value describing the existence of a page 484 * @throws ProviderException If the backend fails or the name is illegal. 485 */ 486 public boolean pageExists(String pageName) throws ProviderException { 487 if (pageName == null || pageName.length() == 0) { 488 throw new ProviderException("Illegal page name"); 489 } 490 491 return m_provider.pageExists(pageName); 492 } 493 494 /** 495 * Checks for existence of a specific page and version. 496 * 497 * @param pageName Name of the page 498 * @param version The version to check 499 * @return <code>true</code> if the page exists, <code>false</code> otherwise 500 * @throws ProviderException If backend fails or name is illegal 501 * @since 2.3.29 502 */ 503 public boolean pageExists(String pageName, int version) throws ProviderException { 504 if (pageName == null || pageName.length() == 0) { 505 throw new ProviderException("Illegal page name"); 506 } 507 508 if (version == WikiProvider.LATEST_VERSION) { 509 return pageExists(pageName); 510 } 511 512 return m_provider.pageExists(pageName, version); 513 } 514 515 /** 516 * Deletes only a specific version of a WikiPage. 517 * 518 * @param page The page to delete. 519 * @throws ProviderException if the page fails 520 */ 521 public void deleteVersion(WikiPage page) throws ProviderException { 522 m_provider.deleteVersion(page.getName(), page.getVersion()); 523 524 // FIXME: If this was the latest, reindex Lucene 525 // FIXME: Update RefMgr 526 } 527 528 /** 529 * Deletes an entire page, all versions, all traces. 530 * 531 * @param page The WikiPage to delete 532 * @throws ProviderException If the repository operation fails 533 */ 534 public void deletePage(WikiPage page) throws ProviderException { 535 fireEvent(WikiPageEvent.PAGE_DELETE_REQUEST, page.getName()); 536 m_provider.deletePage(page.getName()); 537 fireEvent(WikiPageEvent.PAGE_DELETED, page.getName()); 538 } 539 540 /** 541 * This is a simple reaper thread that runs roughly every minute 542 * or so (it's not really that important, as long as it runs), 543 * and removes all locks that have expired. 544 */ 545 private class LockReaper extends WikiBackgroundThread { 546 /** 547 * Create a LockReaper for a given engine. 548 * 549 * @param engine WikiEngine to own this thread. 550 */ 551 public LockReaper(WikiEngine engine) { 552 super(engine, 60); 553 setName("JSPWiki Lock Reaper"); 554 } 555 556 public void backgroundTask() throws Exception { 557 Collection<PageLock> entries = m_pageLocks.values(); 558 Date now = new Date(); 559 for (Iterator<PageLock> i = entries.iterator(); i.hasNext(); ) { 560 PageLock p = i.next(); 561 562 if (now.after(p.getExpiryTime())) { 563 i.remove(); 564 565 log.debug("Reaped lock: " + p.getPage() + 566 " by " + p.getLocker() + 567 ", acquired " + p.getAcquisitionTime() + 568 ", and expired " + p.getExpiryTime()); 569 } 570 } 571 } 572 } 573 574 // workflow task inner classes.................................................... 575 576 /** 577 * Inner class that handles the page pre-save actions. If the proposed page 578 * text is the same as the current version, the {@link #execute()} method 579 * returns {@link org.apache.wiki.workflow.Outcome#STEP_ABORT}. Any 580 * WikiExceptions thrown by page filters will be re-thrown, and the workflow 581 * will abort. 582 */ 583 public static class PreSaveWikiPageTask extends Task { 584 private static final long serialVersionUID = 6304715570092804615L; 585 private final WikiContext m_context; 586 private final String m_proposedText; 587 588 /** 589 * Creates the task. 590 * 591 * @param context The WikiContext 592 * @param proposedText The text that was just saved. 593 */ 594 public PreSaveWikiPageTask(WikiContext context, String proposedText) { 595 super(PRESAVE_TASK_MESSAGE_KEY); 596 m_context = context; 597 m_proposedText = proposedText; 598 } 599 600 /** 601 * {@inheritDoc} 602 */ 603 @Override 604 public Outcome execute() throws WikiException { 605 // Retrieve attributes 606 WikiEngine engine = m_context.getEngine(); 607 Workflow workflow = getWorkflow(); 608 609 // Get the wiki page 610 WikiPage page = m_context.getPage(); 611 612 // Figure out who the author was. Prefer the author 613 // set programmatically; otherwise get from the 614 // current logged in user 615 if (page.getAuthor() == null) { 616 Principal wup = m_context.getCurrentUser(); 617 618 if (wup != null) { 619 page.setAuthor(wup.getName()); 620 } 621 } 622 623 // Run the pre-save filters. If any exceptions, add error to list, abort, and redirect 624 String saveText; 625 FilterManager fm = engine.getFilterManager(); 626 saveText = fm.doPreSaveFiltering(m_context, m_proposedText); 627 628 // Stash the wiki context, old and new text as workflow attributes 629 workflow.setAttribute(PRESAVE_WIKI_CONTEXT, m_context); 630 workflow.setAttribute(FACT_PROPOSED_TEXT, saveText); 631 return Outcome.STEP_COMPLETE; 632 } 633 } 634 635 /** 636 * Inner class that handles the actual page save and post-save actions. Instances 637 * of this class are assumed to have been added to an approval workflow via 638 * {@link org.apache.wiki.workflow.WorkflowBuilder#buildApprovalWorkflow(Principal, String, Task, String, org.apache.wiki.workflow.Fact[], Task, String)}; 639 * they will not function correctly otherwise. 640 */ 641 public static class SaveWikiPageTask extends Task { 642 private static final long serialVersionUID = 3190559953484411420L; 643 644 /** 645 * Creates the Task. 646 */ 647 public SaveWikiPageTask() { 648 super(SAVE_TASK_MESSAGE_KEY); 649 } 650 651 /** 652 * {@inheritDoc} 653 */ 654 @Override 655 public Outcome execute() throws WikiException { 656 // Retrieve attributes 657 WikiContext context = (WikiContext) getWorkflow().getAttribute(PRESAVE_WIKI_CONTEXT); 658 String proposedText = (String) getWorkflow().getAttribute(FACT_PROPOSED_TEXT); 659 660 WikiEngine engine = context.getEngine(); 661 WikiPage page = context.getPage(); 662 663 // Let the rest of the engine handle actual saving. 664 engine.getPageManager().putPageText(page, proposedText); 665 666 // Refresh the context for post save filtering. 667 engine.getPage(page.getName()); 668 engine.textToHTML(context, proposedText); 669 FilterManager fm = engine.getFilterManager(); 670 fm.doPostSaveFiltering(context, proposedText); 671 672 return Outcome.STEP_COMPLETE; 673 } 674 } 675 676 // events processing ....................................................... 677 678 /** 679 * Fires a WikiPageEvent of the provided type and page name 680 * to all registered listeners. 681 * 682 * @param type the event type to be fired 683 * @param pagename the wiki page name as a String 684 * @see org.apache.wiki.event.WikiPageEvent 685 */ 686 protected final void fireEvent(int type, String pagename) { 687 if (WikiEventManager.isListening(this)) { 688 WikiEventManager.fireEvent(this, new WikiPageEvent(m_engine, type, pagename)); 689 } 690 } 691 692 /** 693 * {@inheritDoc} 694 */ 695 @Override 696 public Collection< WikiModuleInfo > modules() { 697 return null; 698 } 699 700 /** 701 * Returns null! 702 * {@inheritDoc} 703 */ 704 @Override 705 public WikiModuleInfo getModuleInfo(String moduleName) { 706 return null; 707 } 708 709 /** 710 * Listens for {@link org.apache.wiki.event.WikiSecurityEvent#PROFILE_NAME_CHANGED} 711 * events. If a user profile's name changes, each page ACL is inspected. If an entry contains 712 * a name that has changed, it is replaced with the new one. No events are emitted 713 * as a consequence of this method, because the page contents are still the same; it is 714 * only the representations of the names within the ACL that are changing. 715 * 716 * @param event The event 717 */ 718 public void actionPerformed(WikiEvent event) { 719 if (!(event instanceof WikiSecurityEvent)) { 720 return; 721 } 722 723 WikiSecurityEvent se = (WikiSecurityEvent) event; 724 if (se.getType() == WikiSecurityEvent.PROFILE_NAME_CHANGED) { 725 UserProfile[] profiles = (UserProfile[]) se.getTarget(); 726 Principal[] oldPrincipals = new Principal[] 727 {new WikiPrincipal(profiles[0].getLoginName()), 728 new WikiPrincipal(profiles[0].getFullname()), 729 new WikiPrincipal(profiles[0].getWikiName())}; 730 Principal newPrincipal = new WikiPrincipal(profiles[1].getFullname()); 731 732 // Examine each page ACL 733 try { 734 int pagesChanged = 0; 735 Collection pages = getAllPages(); 736 for (Iterator it = pages.iterator(); it.hasNext(); ) { 737 WikiPage page = (WikiPage) it.next(); 738 boolean aclChanged = changeAcl(page, oldPrincipals, newPrincipal); 739 if (aclChanged) { 740 // If the Acl needed changing, change it now 741 try { 742 m_engine.getAclManager().setPermissions(page, page.getAcl()); 743 } catch (WikiSecurityException e) { 744 log.error("Could not change page ACL for page " + page.getName() + ": " + e.getMessage(), e); 745 } 746 pagesChanged++; 747 } 748 } 749 log.info("Profile name change for '" + newPrincipal.toString() + 750 "' caused " + pagesChanged + " page ACLs to change also."); 751 } catch (ProviderException e) { 752 // Oooo! This is really bad... 753 log.error("Could not change user name in Page ACLs because of Provider error:" + e.getMessage(), e); 754 } 755 } 756 } 757 758 /** 759 * For a single wiki page, replaces all Acl entries matching a supplied array of Principals 760 * with a new Principal. 761 * 762 * @param page the wiki page whose Acl is to be modified 763 * @param oldPrincipals an array of Principals to replace; all AclEntry objects whose 764 * {@link AclEntry#getPrincipal()} method returns one of these Principals will be replaced 765 * @param newPrincipal the Principal that should receive the old Principals' permissions 766 * @return <code>true</code> if the Acl was actually changed; <code>false</code> otherwise 767 */ 768 protected boolean changeAcl(WikiPage page, Principal[] oldPrincipals, Principal newPrincipal) { 769 Acl acl = page.getAcl(); 770 boolean pageChanged = false; 771 if (acl != null) { 772 Enumeration<AclEntry> entries = acl.entries(); 773 Collection<AclEntry> entriesToAdd = new ArrayList<AclEntry>(); 774 Collection<AclEntry> entriesToRemove = new ArrayList<AclEntry>(); 775 while (entries.hasMoreElements()) { 776 AclEntry entry = entries.nextElement(); 777 if (ArrayUtils.contains(oldPrincipals, entry.getPrincipal())) { 778 // Create new entry 779 AclEntry newEntry = new AclEntryImpl(); 780 newEntry.setPrincipal(newPrincipal); 781 Enumeration<Permission> permissions = entry.permissions(); 782 while (permissions.hasMoreElements()) { 783 Permission permission = permissions.nextElement(); 784 newEntry.addPermission(permission); 785 } 786 pageChanged = true; 787 entriesToRemove.add(entry); 788 entriesToAdd.add(newEntry); 789 } 790 } 791 for (Iterator<AclEntry> ix = entriesToRemove.iterator(); ix.hasNext(); ) { 792 AclEntry entry = ix.next(); 793 acl.removeEntry(entry); 794 } 795 for (Iterator<AclEntry> ix = entriesToAdd.iterator(); ix.hasNext(); ) { 796 AclEntry entry = ix.next(); 797 acl.addEntry(entry); 798 } 799 } 800 return pageChanged; 801 } 802 803}