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