001: /*
002: JSPWiki - a JSP-based WikiWiki clone.
003:
004: Copyright (C) 2001-2002 Janne Jalkanen (Janne.Jalkanen@iki.fi)
005:
006: This program is free software; you can redistribute it and/or modify
007: it under the terms of the GNU Lesser General Public License as published by
008: the Free Software Foundation; either version 2.1 of the License, or
009: (at your option) any later version.
010:
011: This program is distributed in the hope that it will be useful,
012: but WITHOUT ANY WARRANTY; without even the implied warranty of
013: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
014: GNU Lesser General Public License for more details.
015:
016: You should have received a copy of the GNU Lesser General Public License
017: along with this program; if not, write to the Free Software
018: Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
019: */
020: package com.ecyrd.jspwiki;
021:
022: import java.io.IOException;
023: import java.util.Properties;
024: import java.util.Collection;
025: import java.util.HashMap;
026: import java.util.Date;
027: import java.util.Iterator;
028: import java.util.List;
029: import java.util.ArrayList;
030: import java.util.Map;
031: import java.io.FileInputStream;
032:
033: import org.apache.log4j.Logger;
034:
035: import com.ecyrd.jspwiki.providers.WikiPageProvider;
036: import com.ecyrd.jspwiki.providers.ProviderException;
037: import com.ecyrd.jspwiki.providers.RepositoryModifiedException;
038:
039: import com.ecyrd.jspwiki.util.ClassUtil;
040:
041: /**
042: * Manages the WikiPages. This class functions as an unified interface towards
043: * the page providers. It handles initialization and management of the providers,
044: * and provides utility methods for accessing the contents.
045: *
046: * @author Janne Jalkanen
047: * @since 2.0
048: */
049: // FIXME: This class currently only functions just as an extra layer over providers,
050: // complicating things. We need to move more provider-specific functionality
051: // from WikiEngine (which is too big now) into this class.
052: public class PageManager {
053: public static final String PROP_PAGEPROVIDER = "jspwiki.pageProvider";
054: public static final String PROP_USECACHE = "jspwiki.usePageCache";
055: public static final String PROP_LOCKEXPIRY = "jspwiki.lockExpiryTime";
056:
057: static Logger log = Logger.getLogger(PageManager.class);
058:
059: private WikiPageProvider m_provider;
060:
061: private HashMap m_pageLocks = new HashMap();
062:
063: private WikiEngine m_engine;
064:
065: /**
066: * The expiry time. Default is 60 minutes.
067: */
068: private int m_expiryTime = 60;
069:
070: /**
071: * Creates a new PageManager.
072: * @throws WikiException If anything goes wrong, you get this.
073: */
074: public PageManager(WikiEngine engine, Properties props)
075: throws WikiException {
076: String classname;
077:
078: m_engine = engine;
079:
080: boolean useCache = "true".equals(props
081: .getProperty(PROP_USECACHE));
082:
083: m_expiryTime = TextUtil.parseIntParameter(props
084: .getProperty(PROP_LOCKEXPIRY), m_expiryTime);
085:
086: //
087: // If user wants to use a cache, then we'll use the CachingProvider.
088: //
089: if (useCache) {
090: classname = "com.ecyrd.jspwiki.providers.CachingProvider";
091: } else {
092: classname = props.getProperty(PROP_PAGEPROVIDER);
093: }
094:
095: try {
096: Class providerclass = ClassUtil.findClass(
097: "com.ecyrd.jspwiki.providers", classname);
098:
099: m_provider = (WikiPageProvider) providerclass.newInstance();
100:
101: log.debug("Initializing page provider class " + m_provider);
102: m_provider.initialize(m_engine, props);
103: } catch (ClassNotFoundException e) {
104: log
105: .error("Unable to locate provider class "
106: + classname, e);
107: throw new WikiException("no provider class");
108: } catch (InstantiationException e) {
109: log
110: .error("Unable to create provider class "
111: + classname, e);
112: throw new WikiException("faulty provider class");
113: } catch (IllegalAccessException e) {
114: log.error("Illegal access to provider class " + classname,
115: e);
116: throw new WikiException("illegal provider class");
117: } catch (NoRequiredPropertyException e) {
118: log.error(
119: "Provider did not found a property it was looking for: "
120: + e.getMessage(), e);
121: throw e; // Same exception works.
122: } catch (IOException e) {
123: log.error(
124: "An I/O exception occurred while trying to create a new page provider: "
125: + classname, e);
126: throw new WikiException("Unable to start page provider: "
127: + e.getMessage());
128: }
129:
130: //
131: // Start the lock reaper.
132: //
133: new LockReaper().start();
134: }
135:
136: /**
137: * Returns the page provider currently in use.
138: */
139: public WikiPageProvider getProvider() {
140: return m_provider;
141: }
142:
143: public Collection getAllPages() throws ProviderException {
144: return m_provider.getAllPages();
145: }
146:
147: /**
148: * Fetches the page text from the repository. This method also does some sanity checks,
149: * like checking for the pageName validity, etc. Also, if the page repository has been
150: * modified externally, it is smart enough to handle such occurrences.
151: */
152: public String getPageText(String pageName, int version)
153: throws ProviderException {
154: if (pageName == null || pageName.length() == 0) {
155: throw new ProviderException("Illegal page name");
156: }
157:
158: String text = null;
159:
160: try {
161: text = m_provider.getPageText(pageName, version);
162: } catch (RepositoryModifiedException e) {
163: //
164: // This only occurs with the latest version.
165: //
166: log
167: .info("Repository has been modified externally while fetching page "
168: + pageName);
169:
170: //
171: // Empty the references and yay, it shall be recalculated
172: //
173: //WikiPage p = new WikiPage( pageName );
174: WikiPage p = m_provider.getPageInfo(pageName, version);
175:
176: m_engine.updateReferences(p);
177:
178: if (p != null) {
179: m_engine.getSearchManager().reindexPage(p);
180: text = m_provider.getPageText(pageName, version);
181: } else {
182: m_engine.getSearchManager().pageRemoved(
183: new WikiPage(pageName));
184: }
185: }
186:
187: return text;
188: }
189:
190: public void putPageText(WikiPage page, String content)
191: throws ProviderException {
192: if (page == null || page.getName() == null
193: || page.getName().length() == 0) {
194: throw new ProviderException("Illegal page name");
195: }
196:
197: m_provider.putPageText(page, content);
198:
199: m_engine.getSearchManager().reindexPage(page);
200: }
201:
202: /**
203: * Locks page for editing. Note, however, that the PageManager
204: * will in no way prevent you from actually editing this page;
205: * the lock is just for information.
206: *
207: * @return null, if page could not be locked.
208: */
209: public PageLock lockPage(WikiPage page, String user) {
210: PageLock lock = null;
211:
212: synchronized (m_pageLocks) {
213: lock = (PageLock) m_pageLocks.get(page.getName());
214:
215: if (lock == null) {
216: //
217: // Lock is available, so make a lock.
218: //
219: Date d = new Date();
220: lock = new PageLock(page, user, d, new Date(d.getTime()
221: + m_expiryTime * 60 * 1000L));
222:
223: m_pageLocks.put(page.getName(), lock);
224:
225: log.debug("Locked page " + page.getName() + " for "
226: + user);
227: } else {
228: log.debug("Page " + page.getName()
229: + " already locked by " + lock.getLocker());
230: lock = null; // Nothing to return
231: }
232: }
233:
234: return lock;
235: }
236:
237: /**
238: * Marks a page free to be written again. If there has not been a lock,
239: * will fail quietly.
240: *
241: * @param lock A lock acquired in lockPage(). Safe to be null.
242: */
243: public void unlockPage(PageLock lock) {
244: if (lock == null)
245: return;
246:
247: synchronized (m_pageLocks) {
248: PageLock old = (PageLock) m_pageLocks.remove(lock.getPage()
249: .getName());
250:
251: log.debug("Unlocked page " + lock.getPage().getName());
252: }
253: }
254:
255: /**
256: * Returns the current lock owner of a page. If the page is not
257: * locked, will return null.
258: *
259: * @return Current lock.
260: */
261: public PageLock getCurrentLock(WikiPage page) {
262: PageLock lock = null;
263:
264: synchronized (m_pageLocks) {
265: lock = (PageLock) m_pageLocks.get(page.getName());
266: }
267:
268: return lock;
269: }
270:
271: /**
272: * Returns a list of currently applicable locks. Note that by the time you get the list,
273: * the locks may have already expired, so use this only for informational purposes.
274: *
275: * @return List of PageLock objects, detailing the locks. If no locks exist, returns
276: * an empty list.
277: * @since 2.0.22.
278: */
279: public List getActiveLocks() {
280: ArrayList result = new ArrayList();
281:
282: synchronized (m_pageLocks) {
283: for (Iterator i = m_pageLocks.values().iterator(); i
284: .hasNext();) {
285: result.add(i.next());
286: }
287: }
288:
289: return result;
290: }
291:
292: public WikiPage getPageInfo(String pageName, int version)
293: throws ProviderException {
294: if (pageName == null || pageName.length() == 0) {
295: throw new ProviderException("Illegal page name");
296: }
297:
298: WikiPage page = null;
299:
300: try {
301: page = m_provider.getPageInfo(pageName, version);
302: } catch (RepositoryModifiedException e) {
303: //
304: // This only occurs with the latest version.
305: //
306: log
307: .info("Repository has been modified externally while fetching info for "
308: + pageName);
309:
310: WikiPage p = new WikiPage(pageName);
311:
312: m_engine.updateReferences(p);
313:
314: page = m_provider.getPageInfo(pageName, version);
315: }
316:
317: return page;
318: }
319:
320: /**
321: * Gets a version history of page. Each element in the returned
322: * List is a WikiPage.
323: * <P>
324: * @return If the page does not exist, returns null, otherwise a List
325: * of WikiPages.
326: */
327: public List getVersionHistory(String pageName)
328: throws ProviderException {
329: if (pageExists(pageName)) {
330: return m_provider.getVersionHistory(pageName);
331: }
332:
333: return null;
334: }
335:
336: public String getProviderDescription() {
337: return m_provider.getProviderInfo();
338: }
339:
340: public int getTotalPageCount() {
341: try {
342: return m_provider.getAllPages().size();
343: } catch (ProviderException e) {
344: log.error("Unable to count pages: ", e);
345: return -1;
346: }
347: }
348:
349: public boolean pageExists(String pageName) throws ProviderException {
350: if (pageName == null || pageName.length() == 0) {
351: throw new ProviderException("Illegal page name");
352: }
353:
354: return m_provider.pageExists(pageName);
355: }
356:
357: /**
358: * Deletes only a specific version of a WikiPage.
359: */
360: public void deleteVersion(WikiPage page) throws ProviderException {
361: m_provider.deleteVersion(page.getName(), page.getVersion());
362:
363: // FIXME: If this was the latest, reindex Lucene
364: // FIXME: Update RefMgr
365: }
366:
367: /**
368: * Deletes an entire page, all versions, all traces.
369: */
370: public void deletePage(WikiPage page) throws ProviderException {
371: m_provider.deletePage(page.getName());
372:
373: m_engine.getSearchManager().pageRemoved(page);
374:
375: m_engine.getReferenceManager().pageRemoved(page);
376: }
377:
378: public void importPages(String path, Map swaptags)
379: throws IOException {
380: Properties importProps = new Properties();
381: importProps.load(new FileInputStream(path));
382: String classname = importProps
383: .getProperty("jspwiki.pageProvider");
384: WikiPageProvider importProvider = null;
385: try {
386: Class providerclass = ClassUtil.findClass(
387: "com.ecyrd.jspwiki.providers", classname);
388: importProvider = (WikiPageProvider) providerclass
389: .newInstance();
390: } catch (Exception e) {
391: log.error(
392: "Unable to locate/instantiate import provider class "
393: + classname, e);
394: return;
395: }
396: try {
397: importProvider.initialize(m_engine, importProps);
398: Collection pages = importProvider.getAllPages();
399: for (Iterator i = pages.iterator(); i.hasNext();) {
400: WikiPage latest = (WikiPage) i.next();
401: int version = 1;
402: boolean done = false;
403: while (!done) {
404: WikiPage page = importProvider.getPageInfo(latest
405: .getName(), version);
406: if (page == null) {
407: done = true;
408: } else {
409: String text = importProvider.getPageText(page
410: .getName(), version);
411: if (swaptags != null) {
412: for (Iterator it = swaptags.entrySet()
413: .iterator(); it.hasNext();) {
414: java.util.Map.Entry e = (java.util.Map.Entry) it
415: .next();
416: String tag = (String) e.getKey();
417: String swap = (String) e.getValue();
418: text = text.replaceAll(tag, swap);
419: }
420:
421: }
422: WikiPage newpage = (WikiPage) page.clone();
423: newpage.setName(WikiEngine
424: .makeAbsolutePageName(page.getName()));
425: putPageText(newpage, text);
426: if (page.getVersion() >= latest.getVersion())
427: done = true;
428: version++;
429: }
430: }
431: }
432:
433: } catch (ProviderException e) {
434: throw new IOException(e.getMessage());
435: } catch (NoRequiredPropertyException e) {
436: throw new IOException(e.getMessage());
437: }
438: }
439:
440: /**
441: * This is a simple reaper thread that runs roughly every minute
442: * or so (it's not really that important, as long as it runs),
443: * and removes all locks that have expired.
444: */
445: private class LockReaper extends Thread {
446: public void run() {
447: while (true) {
448: try {
449: Thread.sleep(60 * 1000L);
450:
451: synchronized (m_pageLocks) {
452: Collection entries = m_pageLocks.values();
453:
454: Date now = new Date();
455:
456: for (Iterator i = entries.iterator(); i
457: .hasNext();) {
458: PageLock p = (PageLock) i.next();
459:
460: if (now.after(p.getExpiryTime())) {
461: i.remove();
462:
463: log.debug("Reaped lock: "
464: + p.getPage().getName()
465: + " by " + p.getLocker()
466: + ", acquired "
467: + p.getAcquisitionTime()
468: + ", and expired "
469: + p.getExpiryTime());
470: }
471: }
472: }
473: } catch (Throwable t) {
474: }
475: }
476: }
477: }
478: }
|