| /* |
| Licensed to the Apache Software Foundation (ASF) under one |
| or more contributor license agreements. See the NOTICE file |
| distributed with this work for additional information |
| regarding copyright ownership. The ASF licenses this file |
| to you under the Apache License, Version 2.0 (the |
| "License"); you may not use this file except in compliance |
| with the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, |
| software distributed under the License is distributed on an |
| "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY |
| KIND, either express or implied. See the License for the |
| specific language governing permissions and limitations |
| under the License. |
| */ |
| package org.apache.wiki.pages; |
| |
| import org.apache.commons.lang3.ArrayUtils; |
| import org.apache.log4j.Logger; |
| import org.apache.wiki.WikiBackgroundThread; |
| import org.apache.wiki.WikiEngine; |
| import org.apache.wiki.WikiPage; |
| import org.apache.wiki.WikiProvider; |
| import org.apache.wiki.api.exceptions.NoRequiredPropertyException; |
| import org.apache.wiki.api.exceptions.ProviderException; |
| import org.apache.wiki.api.exceptions.WikiException; |
| import org.apache.wiki.attachment.Attachment; |
| import org.apache.wiki.auth.WikiPrincipal; |
| import org.apache.wiki.auth.WikiSecurityException; |
| import org.apache.wiki.auth.acl.Acl; |
| import org.apache.wiki.auth.acl.AclEntry; |
| import org.apache.wiki.auth.acl.AclEntryImpl; |
| import org.apache.wiki.auth.user.UserProfile; |
| import org.apache.wiki.event.WikiEvent; |
| import org.apache.wiki.event.WikiEventManager; |
| import org.apache.wiki.event.WikiPageEvent; |
| import org.apache.wiki.event.WikiSecurityEvent; |
| import org.apache.wiki.modules.ModuleManager; |
| import org.apache.wiki.modules.WikiModuleInfo; |
| import org.apache.wiki.providers.RepositoryModifiedException; |
| import org.apache.wiki.providers.WikiPageProvider; |
| import org.apache.wiki.util.ClassUtil; |
| import org.apache.wiki.util.TextUtil; |
| |
| import java.io.IOException; |
| import java.security.Permission; |
| import java.security.Principal; |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Date; |
| import java.util.Enumeration; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.NoSuchElementException; |
| import java.util.Properties; |
| import java.util.concurrent.ConcurrentHashMap; |
| |
| |
| /** |
| * Manages the WikiPages. This class functions as an unified interface towards |
| * the page providers. It handles initialization and management of the providers, |
| * and provides utility methods for accessing the contents. |
| * <p/> |
| * Saving a page is a two-stage Task; first the pre-save operations and then the |
| * actual save. See the descriptions of the tasks for further information. |
| * |
| * @since 2.0 |
| */ |
| // FIXME: This class currently only functions just as an extra layer over providers, |
| // complicating things. We need to move more provider-specific functionality |
| // from WikiEngine (which is too big now) into this class. |
| public class DefaultPageManager extends ModuleManager implements PageManager { |
| |
| private static final Logger LOG = Logger.getLogger(DefaultPageManager.class); |
| |
| private WikiPageProvider m_provider; |
| |
| protected ConcurrentHashMap<String, PageLock> m_pageLocks = new ConcurrentHashMap<>(); |
| |
| //private WikiEngine m_engine; //inherited protected field from the ModuleManager |
| |
| private int m_expiryTime = 60; |
| |
| private LockReaper m_reaper = null; |
| |
| private PageSorter pageSorter = new PageSorter(); |
| |
| /** |
| * Creates a new PageManager. |
| * |
| * @param engine WikiEngine instance |
| * @param props Properties to use for initialization |
| * @throws NoSuchElementException {@value #PROP_PAGEPROVIDER} property not found on WikiEngine properties |
| * @throws WikiException If anything goes wrong, you get this. |
| */ |
| public DefaultPageManager(final WikiEngine engine, final Properties props) throws NoSuchElementException, WikiException { |
| super(engine); |
| final String classname; |
| m_engine = engine; |
| final boolean useCache = "true".equals(props.getProperty(PROP_USECACHE)); |
| |
| m_expiryTime = TextUtil.parseIntParameter(props.getProperty(PROP_LOCKEXPIRY), 60); |
| |
| // |
| // If user wants to use a cache, then we'll use the CachingProvider. |
| // |
| if (useCache) { |
| classname = "org.apache.wiki.providers.CachingProvider"; |
| } else { |
| classname = TextUtil.getRequiredProperty(props, PROP_PAGEPROVIDER); |
| } |
| |
| pageSorter.initialize( props ); |
| |
| try { |
| LOG.debug("Page provider class: '" + classname + "'"); |
| final Class<?> providerclass = ClassUtil.findClass("org.apache.wiki.providers", classname); |
| m_provider = (WikiPageProvider) providerclass.newInstance(); |
| |
| LOG.debug("Initializing page provider class " + m_provider); |
| m_provider.initialize(m_engine, props); |
| } catch (final ClassNotFoundException e) { |
| LOG.error("Unable to locate provider class '" + classname + "' (" + e.getMessage() + ")", e); |
| throw new WikiException("No provider class. (" + e.getMessage() + ")", e); |
| } catch (final InstantiationException e) { |
| LOG.error("Unable to create provider class '" + classname + "' (" + e.getMessage() + ")", e); |
| throw new WikiException("Faulty provider class. (" + e.getMessage() + ")", e); |
| } catch (final IllegalAccessException e) { |
| LOG.error("Illegal access to provider class '" + classname + "' (" + e.getMessage() + ")", e); |
| throw new WikiException("Illegal provider class. (" + e.getMessage() + ")", e); |
| } catch (final NoRequiredPropertyException e) { |
| LOG.error("Provider did not found a property it was looking for: " + e.getMessage(), e); |
| throw e; // Same exception works. |
| } catch (final IOException e) { |
| LOG.error("An I/O exception occurred while trying to create a new page provider: " + classname, e); |
| throw new WikiException("Unable to start page provider: " + e.getMessage(), e); |
| } |
| |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#getProvider() |
| */ |
| @Override |
| public WikiPageProvider getProvider() { |
| return m_provider; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#getAllPages() |
| */ |
| @Override |
| public Collection< WikiPage > getAllPages() throws ProviderException { |
| return m_provider.getAllPages(); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#getPageText(java.lang.String, int) |
| */ |
| @Override |
| public String getPageText( final String pageName, final int version) throws ProviderException { |
| if (pageName == null || pageName.length() == 0) { |
| throw new ProviderException("Illegal page name"); |
| } |
| String text; |
| |
| try { |
| text = m_provider.getPageText(pageName, version); |
| } catch ( final RepositoryModifiedException e ) { |
| // This only occurs with the latest version. |
| LOG.info( "Repository has been modified externally while fetching page " + pageName ); |
| |
| // Empty the references and yay, it shall be recalculated |
| final WikiPage p = m_provider.getPageInfo( pageName, version ); |
| |
| m_engine.getReferenceManager().updateReferences( p ); |
| m_engine.getSearchManager().reindexPage( p ); |
| text = m_provider.getPageText( pageName, version ); |
| } |
| |
| return text; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#getEngine() |
| */ |
| @Override |
| public WikiEngine getEngine() { |
| return m_engine; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#putPageText(org.apache.wiki.WikiPage, java.lang.String) |
| */ |
| @Override |
| public void putPageText(WikiPage page, String content) throws ProviderException { |
| if (page == null || page.getName() == null || page.getName().length() == 0) { |
| throw new ProviderException("Illegal page name"); |
| } |
| |
| m_provider.putPageText(page, content); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#lockPage(org.apache.wiki.WikiPage, java.lang.String) |
| */ |
| @Override |
| public PageLock lockPage(WikiPage page, String user) { |
| if (m_reaper == null) { |
| // |
| // Start the lock reaper lazily. We don't want to start it in |
| // the constructor, because starting threads in constructors |
| // is a bad idea when it comes to inheritance. Besides, |
| // laziness is a virtue. |
| // |
| m_reaper = new LockReaper(m_engine); |
| m_reaper.start(); |
| } |
| |
| fireEvent(WikiPageEvent.PAGE_LOCK, page.getName()); // prior to or after actual lock? |
| PageLock lock = m_pageLocks.get(page.getName()); |
| |
| if (lock == null) { |
| // |
| // Lock is available, so make a lock. |
| // |
| Date d = new Date(); |
| lock = new PageLock(page, user, d, new Date(d.getTime() + m_expiryTime * 60 * 1000L)); |
| m_pageLocks.put(page.getName(), lock); |
| LOG.debug("Locked page " + page.getName() + " for " + user); |
| } else { |
| LOG.debug("Page " + page.getName() + " already locked by " + lock.getLocker()); |
| lock = null; // Nothing to return |
| } |
| |
| return lock; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#unlockPage(org.apache.wiki.pages.PageLock) |
| */ |
| @Override |
| public void unlockPage(PageLock lock) { |
| if (lock == null) { |
| return; |
| } |
| |
| m_pageLocks.remove(lock.getPage()); |
| LOG.debug("Unlocked page " + lock.getPage()); |
| |
| fireEvent(WikiPageEvent.PAGE_UNLOCK, lock.getPage()); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#getCurrentLock(org.apache.wiki.WikiPage) |
| */ |
| @Override |
| public PageLock getCurrentLock(WikiPage page) { |
| return m_pageLocks.get(page.getName()); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#getActiveLocks() |
| */ |
| @Override |
| public List<PageLock> getActiveLocks() { |
| ArrayList<PageLock> result = new ArrayList<>(); |
| |
| for (PageLock lock : m_pageLocks.values()) { |
| result.add(lock); |
| } |
| |
| return result; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#getPageInfo(java.lang.String, int) |
| */ |
| @Override |
| public WikiPage getPageInfo( final String pageName, final int version) throws ProviderException { |
| if (pageName == null || pageName.length() == 0) { |
| throw new ProviderException("Illegal page name '" + pageName + "'"); |
| } |
| |
| WikiPage page; |
| |
| try { |
| page = m_provider.getPageInfo(pageName, version); |
| } catch ( final RepositoryModifiedException e) { |
| // This only occurs with the latest version. |
| LOG.info("Repository has been modified externally while fetching info for " + pageName); |
| page = m_provider.getPageInfo(pageName, version); |
| if (page != null) { |
| m_engine.getReferenceManager().updateReferences(page); |
| } else { |
| m_engine.getReferenceManager().pageRemoved(new WikiPage(m_engine, pageName)); |
| } |
| } |
| |
| return page; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#getVersionHistory(java.lang.String) |
| */ |
| @Override |
| public List< WikiPage > getVersionHistory(String pageName) throws ProviderException { |
| if (pageExists(pageName)) { |
| return m_provider.getVersionHistory(pageName); |
| } |
| |
| return null; |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.pages.PageManager#getCurrentProvider() |
| */ |
| public String getCurrentProvider() { |
| return getProvider().getClass().getName(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.pages.PageManager#getProviderDescription() |
| */ |
| @Override |
| public String getProviderDescription() { |
| return m_provider.getProviderInfo(); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.pages.PageManager#getTotalPageCount() |
| */ |
| @Override |
| public int getTotalPageCount() { |
| try { |
| return m_provider.getAllPages().size(); |
| } catch (ProviderException e) { |
| LOG.error("Unable to count pages: ", e); |
| return -1; |
| } |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#pageExists(java.lang.String) |
| */ |
| @Override |
| public boolean pageExists(String pageName) throws ProviderException { |
| if (pageName == null || pageName.length() == 0) { |
| throw new ProviderException("Illegal page name"); |
| } |
| |
| return m_provider.pageExists(pageName); |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#pageExists(java.lang.String, int) |
| */ |
| @Override |
| public boolean pageExists(String pageName, int version) throws ProviderException { |
| if (pageName == null || pageName.length() == 0) { |
| throw new ProviderException("Illegal page name"); |
| } |
| |
| if (version == WikiProvider.LATEST_VERSION) { |
| return pageExists(pageName); |
| } |
| |
| return m_provider.pageExists(pageName, version); |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.pages.PageManager#deleteVersion(org.apache.wiki.WikiPage) |
| */ |
| @Override |
| public void deleteVersion( final WikiPage page ) throws ProviderException { |
| if( page instanceof Attachment ) { |
| m_engine.getAttachmentManager().deleteVersion( (Attachment) page ); |
| } else { |
| m_provider.deleteVersion(page.getName(), page.getVersion()); |
| // FIXME: If this was the latest, reindex Lucene |
| // FIXME: Update RefMgr |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.pages.PageManager#deletePage(java.lang.String) |
| */ |
| public void deletePage( final String pageName ) throws ProviderException { |
| final WikiPage p = m_engine.getPage( pageName ); |
| if( p != null ) { |
| if( p instanceof Attachment ) { |
| m_engine.getAttachmentManager().deleteAttachment( ( Attachment )p ); |
| } else { |
| final Collection< String > refTo = m_engine.getReferenceManager().findRefersTo( pageName ); |
| // May return null, if the page does not exist or has not been indexed yet. |
| |
| if( m_engine.getAttachmentManager().hasAttachments( p ) ) { |
| final List< Attachment > attachments = m_engine.getAttachmentManager().listAttachments( p ); |
| for( final Attachment attachment : attachments ) { |
| if( refTo != null ) { |
| refTo.remove( attachment.getName() ); |
| } |
| |
| m_engine.getAttachmentManager().deleteAttachment( attachment ); |
| } |
| } |
| deletePage( p ); |
| fireEvent( WikiPageEvent.PAGE_DELETED, pageName ); |
| } |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| * @see org.apache.wiki.pages.PageManager#deletePage(org.apache.wiki.WikiPage) |
| */ |
| @Override |
| public void deletePage(WikiPage page) throws ProviderException { |
| fireEvent(WikiPageEvent.PAGE_DELETE_REQUEST, page.getName()); |
| m_provider.deletePage(page.getName()); |
| fireEvent(WikiPageEvent.PAGE_DELETED, page.getName()); |
| } |
| |
| /** |
| * This is a simple reaper thread that runs roughly every minute |
| * or so (it's not really that important, as long as it runs), |
| * and removes all locks that have expired. |
| */ |
| private class LockReaper extends WikiBackgroundThread { |
| /** |
| * Create a LockReaper for a given engine. |
| * |
| * @param engine WikiEngine to own this thread. |
| */ |
| public LockReaper(WikiEngine engine) { |
| super(engine, 60); |
| setName("JSPWiki Lock Reaper"); |
| } |
| |
| @Override |
| public void backgroundTask() throws Exception { |
| Collection<PageLock> entries = m_pageLocks.values(); |
| for (Iterator<PageLock> i = entries.iterator(); i.hasNext(); ) { |
| PageLock p = i.next(); |
| |
| if ( p.isExpired() ) { |
| i.remove(); |
| |
| LOG.debug("Reaped lock: " + p.getPage() + |
| " by " + p.getLocker() + |
| ", acquired " + p.getAcquisitionTime() + |
| ", and expired " + p.getExpiryTime()); |
| } |
| } |
| } |
| } |
| |
| // events processing ....................................................... |
| |
| /** |
| * Fires a WikiPageEvent of the provided type and page name |
| * to all registered listeners. |
| * |
| * @param type the event type to be fired |
| * @param pagename the wiki page name as a String |
| * @see org.apache.wiki.event.WikiPageEvent |
| */ |
| protected final void fireEvent(int type, String pagename) { |
| if (WikiEventManager.isListening(this)) { |
| WikiEventManager.fireEvent(this, new WikiPageEvent(m_engine, type, pagename)); |
| } |
| } |
| |
| /** |
| * {@inheritDoc} |
| */ |
| @Override |
| public Collection< WikiModuleInfo > modules() { |
| return new ArrayList<>(); |
| } |
| |
| /** |
| * Returns null! |
| * {@inheritDoc} |
| */ |
| @Override |
| public WikiModuleInfo getModuleInfo(String moduleName) { |
| return null; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#actionPerformed(org.apache.wiki.event.WikiEvent) |
| */ |
| @Override |
| public void actionPerformed(WikiEvent event) { |
| if (!(event instanceof WikiSecurityEvent)) { |
| return; |
| } |
| |
| WikiSecurityEvent se = (WikiSecurityEvent) event; |
| if (se.getType() == WikiSecurityEvent.PROFILE_NAME_CHANGED) { |
| UserProfile[] profiles = (UserProfile[]) se.getTarget(); |
| Principal[] oldPrincipals = new Principal[] |
| {new WikiPrincipal(profiles[0].getLoginName()), |
| new WikiPrincipal(profiles[0].getFullname()), |
| new WikiPrincipal(profiles[0].getWikiName())}; |
| Principal newPrincipal = new WikiPrincipal(profiles[1].getFullname()); |
| |
| // Examine each page ACL |
| try { |
| int pagesChanged = 0; |
| Collection< WikiPage > pages = getAllPages(); |
| for (Iterator< WikiPage > it = pages.iterator(); it.hasNext(); ) { |
| WikiPage page = it.next(); |
| boolean aclChanged = changeAcl(page, oldPrincipals, newPrincipal); |
| if (aclChanged) { |
| // If the Acl needed changing, change it now |
| try { |
| m_engine.getAclManager().setPermissions(page, page.getAcl()); |
| } catch (WikiSecurityException e) { |
| LOG.error("Could not change page ACL for page " + page.getName() + ": " + e.getMessage(), e); |
| } |
| pagesChanged++; |
| } |
| } |
| LOG.info("Profile name change for '" + newPrincipal.toString() + |
| "' caused " + pagesChanged + " page ACLs to change also."); |
| } catch (ProviderException e) { |
| // Oooo! This is really bad... |
| LOG.error("Could not change user name in Page ACLs because of Provider error:" + e.getMessage(), e); |
| } |
| } |
| } |
| |
| /** |
| * For a single wiki page, replaces all Acl entries matching a supplied array of Principals |
| * with a new Principal. |
| * |
| * @param page the wiki page whose Acl is to be modified |
| * @param oldPrincipals an array of Principals to replace; all AclEntry objects whose |
| * {@link AclEntry#getPrincipal()} method returns one of these Principals will be replaced |
| * @param newPrincipal the Principal that should receive the old Principals' permissions |
| * @return <code>true</code> if the Acl was actually changed; <code>false</code> otherwise |
| */ |
| protected boolean changeAcl(WikiPage page, Principal[] oldPrincipals, Principal newPrincipal) { |
| Acl acl = page.getAcl(); |
| boolean pageChanged = false; |
| if (acl != null) { |
| Enumeration<AclEntry> entries = acl.entries(); |
| Collection<AclEntry> entriesToAdd = new ArrayList<>(); |
| Collection<AclEntry> entriesToRemove = new ArrayList<>(); |
| while (entries.hasMoreElements()) { |
| AclEntry entry = entries.nextElement(); |
| if (ArrayUtils.contains(oldPrincipals, entry.getPrincipal())) { |
| // Create new entry |
| AclEntry newEntry = new AclEntryImpl(); |
| newEntry.setPrincipal(newPrincipal); |
| Enumeration<Permission> permissions = entry.permissions(); |
| while (permissions.hasMoreElements()) { |
| Permission permission = permissions.nextElement(); |
| newEntry.addPermission(permission); |
| } |
| pageChanged = true; |
| entriesToRemove.add(entry); |
| entriesToAdd.add(newEntry); |
| } |
| } |
| for (Iterator<AclEntry> ix = entriesToRemove.iterator(); ix.hasNext(); ) { |
| AclEntry entry = ix.next(); |
| acl.removeEntry(entry); |
| } |
| for (Iterator<AclEntry> ix = entriesToAdd.iterator(); ix.hasNext(); ) { |
| AclEntry entry = ix.next(); |
| acl.addEntry(entry); |
| } |
| } |
| return pageChanged; |
| } |
| |
| /* (non-Javadoc) |
| * @see org.apache.wiki.pages.PageManager#getPageSorter() |
| */ |
| @Override |
| public PageSorter getPageSorter() { |
| return pageSorter; |
| } |
| |
| } |