📄 referencemanager.java
字号:
/* JSPWiki - a JSP-based WikiWiki clone. 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 com.ecyrd.jspwiki;import java.io.*;import java.security.MessageDigest;import java.security.NoSuchAlgorithmException;import java.util.*;import org.apache.commons.lang.time.StopWatch;import org.apache.log4j.Logger;import com.ecyrd.jspwiki.attachment.Attachment;import com.ecyrd.jspwiki.event.WikiEvent;import com.ecyrd.jspwiki.event.WikiEventListener;import com.ecyrd.jspwiki.event.WikiEventUtils;import com.ecyrd.jspwiki.event.WikiPageEvent;import com.ecyrd.jspwiki.filters.BasicPageFilter;import com.ecyrd.jspwiki.modules.InternalModule;import com.ecyrd.jspwiki.providers.ProviderException;import com.ecyrd.jspwiki.providers.WikiPageProvider;/* BUGS - if a wikilink is added to a page, then removed, RefMan still thinks that the page refers to the wikilink page. Hm. - if a page is deleted, gets very confused. - Serialization causes page attributes to be missing, when InitializablePlugins are not executed properly. Thus, serialization should really also mark whether a page is serializable or not... *//* A word about synchronizing: I expect this object to be accessed in three situations: - when a WikiEngine is created and it scans its wikipages - when the WE saves a page - when a JSP page accesses one of the WE's ReferenceManagers to display a list of (un)referenced pages. So, access to this class is fairly rare, and usually triggered by user interaction. OTOH, the methods in this class use their storage objects intensively (and, sorry to say, in an unoptimized manner =). My deduction: using unsynchronized HashMaps etc and syncing methods or code blocks is preferrable to using slow, synced storage objects. We don't have iterative code here, so I'm going to use synced methods for now. Please contact me if you notice problems with ReferenceManager, and especially with synchronization, or if you have suggestions about syncing. ebu@memecry.net*//** * Keeps track of wikipage references: * <UL> * <LI>What pages a given page refers to * <LI>What pages refer to a given page * </UL> * * This is a quick'n'dirty approach without any finesse in storage and * searching algorithms; we trust java.util.*. * <P> * This class contains two HashMaps, m_refersTo and m_referredBy. The * first is indexed by WikiPage names and contains a Collection of all * WikiPages the page refers to. (Multiple references are not counted, * naturally.) The second is indexed by WikiPage names and contains * a Set of all pages that refer to the indexing page. (Notice - * the keys of both Maps should be kept in sync.) * <P> * When a page is added or edited, its references are parsed, a Collection * is received, and we crudely replace anything previous with this new * Collection. We then check each referenced page name and make sure they * know they are referred to by the new page. * <P> * Based on this information, we can perform non-optimal searches for * e.g. unreferenced pages, top ten lists, etc. * <P> * The owning class must take responsibility of filling in any pre-existing * information, probably by loading each and every WikiPage and calling this * class to update the references when created. * * @author ebu@memecry.net * @since 1.6.1 */// FIXME: The way that we save attributes is now a major booboo, and must be// replace forthwith. However, this is a workaround for the great deal// of problems that occur here...public class ReferenceManager extends BasicPageFilter implements InternalModule, WikiEventListener{ /** Maps page wikiname to a Collection of pages it refers to. The Collection * must contain Strings. The Collection may contain names of non-existing * pages. */ private Map<String,Collection<String>> m_refersTo; private Map<String,Collection<String>> m_unmutableRefersTo; /** Maps page wikiname to a Set of referring pages. The Set must * contain Strings. Non-existing pages (a reference exists, but not a file * for the page contents) may have an empty Set in m_referredBy. */ private Map<String,Set<String>> m_referredBy; private Map<String,Set<String>> m_unmutableReferredBy; /** The WikiEngine that owns this object. */ private WikiEngine m_engine; private boolean m_matchEnglishPlurals = false; private static Logger log = Logger.getLogger(ReferenceManager.class); private static final String SERIALIZATION_FILE = "refmgr.ser"; private static final String SERIALIZATION_DIR = "refmgr-attr"; /** We use this also a generic serialization id */ private static final long serialVersionUID = 4L; /** * Builds a new ReferenceManager. * * @param engine The WikiEngine to which this is managing references to. */ public ReferenceManager( WikiEngine engine ) { m_refersTo = new HashMap<String,Collection<String>>(); m_referredBy = new HashMap<String,Set<String>>(); m_engine = engine; m_matchEnglishPlurals = TextUtil.getBooleanProperty( engine.getWikiProperties(), WikiEngine.PROP_MATCHPLURALS, m_matchEnglishPlurals ); // // Create two maps that contain unmutable versions of the two basic maps. // m_unmutableReferredBy = Collections.unmodifiableMap( m_referredBy ); m_unmutableRefersTo = Collections.unmodifiableMap( m_refersTo ); } /** * Does a full reference update. Does not sync; assumes that you do it afterwards. */ @SuppressWarnings("unchecked") private void updatePageReferences( WikiPage page ) throws ProviderException { String content = m_engine.getPageManager().getPageText( page.getName(), WikiPageProvider.LATEST_VERSION ); TreeSet<String> res = new TreeSet<String>(); Collection<String> links = m_engine.scanWikiLinks( page, content ); res.addAll( links ); Collection attachments = m_engine.getAttachmentManager().listAttachments( page ); for( Iterator atti = attachments.iterator(); atti.hasNext(); ) { res.add( ((Attachment)(atti.next())).getName() ); } internalUpdateReferences( page.getName(), res ); } /** * Initializes the entire reference manager with the initial set of pages * from the collection. * * @param pages A collection of all pages you want to be included in the reference * count. * @since 2.2 * @throws ProviderException If reading of pages fail. */ public void initialize( Collection pages ) throws ProviderException { log.debug( "Initializing new ReferenceManager with "+pages.size()+" initial pages." ); StopWatch sw = new StopWatch(); sw.start(); log.info( "Starting cross reference scan of WikiPages" ); // // First, try to serialize old data from disk. If that fails, // we'll go and update the entire reference lists (which'll take // time) // try { // // Unserialize things. The loop below cannot be combined with // the other loop below, simply because engine.getPage() has // side effects such as loading initializing the user databases, // which in turn want all of the pages to be read already... // // Yes, this is a kludge. We know. Will be fixed. // long saved = unserializeFromDisk(); for( Iterator it = pages.iterator(); it.hasNext(); ) { WikiPage page = (WikiPage) it.next(); unserializeAttrsFromDisk( page ); } // // Now we must check if any of the pages have been changed // while we were in the electronic la-la-land, and update // the references for them. // Iterator it = pages.iterator(); while( it.hasNext() ) { WikiPage page = (WikiPage) it.next(); if( page instanceof Attachment ) { // Skip attachments } else { // Refresh with the latest copy page = m_engine.getPage( page.getName() ); if( page.getLastModified() == null ) { log.fatal( "Provider returns null lastModified. Please submit a bug report." ); } else if( page.getLastModified().getTime() > saved ) { updatePageReferences( page ); } } } } catch( Exception e ) { log.info("Unable to unserialize old refmgr information, rebuilding database: "+e.getMessage()); buildKeyLists( pages ); // Scan the existing pages from disk and update references in the manager. Iterator it = pages.iterator(); while( it.hasNext() ) { WikiPage page = (WikiPage)it.next(); if( page instanceof Attachment ) { // We cannot build a reference list from the contents // of attachments, so we skip them. } else { updatePageReferences( page ); serializeAttrsToDisk( page ); } } serializeToDisk(); } sw.stop(); log.info( "Cross reference scan done in "+sw ); WikiEventUtils.addWikiEventListener(m_engine.getPageManager(), WikiPageEvent.PAGE_DELETED, this); } /** * Reads the serialized data from the disk back to memory. * Returns the date when the data was last written on disk */ @SuppressWarnings("unchecked") private synchronized long unserializeFromDisk() throws IOException, ClassNotFoundException { ObjectInputStream in = null; long saved = 0L; try { StopWatch sw = new StopWatch(); sw.start(); File f = new File( m_engine.getWorkDir(), SERIALIZATION_FILE ); in = new ObjectInputStream( new BufferedInputStream(new FileInputStream(f)) ); long ver = in.readLong(); if( ver != serialVersionUID ) { throw new IOException("File format has changed; I need to recalculate references."); } saved = in.readLong(); m_refersTo = (Map) in.readObject(); m_referredBy = (Map) in.readObject(); in.close(); m_unmutableReferredBy = Collections.unmodifiableMap( m_referredBy ); m_unmutableRefersTo = Collections.unmodifiableMap( m_refersTo ); sw.stop(); log.debug("Read serialized data successfully in "+sw); } finally { if( in != null ) in.close(); } return saved; } /** * Serializes hashmaps to disk. The format is private, don't touch it. */ private synchronized void serializeToDisk() { ObjectOutputStream out = null; try { StopWatch sw = new StopWatch(); sw.start(); File f = new File( m_engine.getWorkDir(), SERIALIZATION_FILE ); out = new ObjectOutputStream( new BufferedOutputStream(new FileOutputStream(f)) ); out.writeLong( serialVersionUID ); out.writeLong( System.currentTimeMillis() ); // Timestamp out.writeObject( m_refersTo ); out.writeObject( m_referredBy ); out.close(); sw.stop(); log.debug("serialization done - took "+sw); } catch( IOException e ) { log.error("Unable to serialize!"); try { if( out != null ) out.close(); } catch( IOException ex ) {} } } private String getHashFileName( String pageName )
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -