Java tutorial
/** * Copyright (C) 2000 - 2013 Silverpeas * * This program is free software: you can redistribute it and/or modify it under the terms of the * GNU Affero General Public License as published by the Free Software Foundation, either version 3 * of the License, or (at your option) any later version. * * As a special exception to the terms and conditions of version 3.0 of the GPL, you may * redistribute this Program in connection with Free/Libre Open Source Software ("FLOSS") * applications as described in Silverpeas's FLOSS exception. You should have recieved a copy of the * text describing the FLOSS exception, and it is also available here: * "http://www.silverpeas.org/docs/core/legal/floss_exception.html" * * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see <http://www.gnu.org/licenses/>. */ package com.ecyrd.jspwiki.providers; import com.ecyrd.jspwiki.*; import com.silverpeas.util.ForeignPK; import com.silverpeas.wiki.control.WikiException; import com.silverpeas.wiki.control.WikiMultiInstanceManager; import com.silverpeas.wiki.control.WikiPageDAO; import com.silverpeas.wiki.control.model.PageDetail; import com.stratelia.silverpeas.silvertrace.SilverTrace; import com.stratelia.webactiv.util.FileRepositoryManager; import org.apache.commons.io.IOUtils; import org.silverpeas.attachment.AttachmentServiceFactory; import org.silverpeas.attachment.model.SimpleDocument; import java.io.BufferedInputStream; import java.io.BufferedOutputStream; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Properties; import java.util.TreeSet; /** * @author Ludovic Bertin */ public class WikiVersioningFileProvider extends AbstractFileProvider implements VersioningProvider { public static final String PAGEDIR = "OLD"; public static final String PROPERTYFILE = "page.properties"; public static final String ID = "ID"; private CachedProperties m_cachedProperties; private WikiPageDAO pageDAO = new WikiPageDAO(); @Override public void initialize(WikiEngine engine, Properties properties) throws NoRequiredPropertyException, IOException { super.initialize(engine, properties); } /** * Returns the directory where the old versions of the pages are being kept. */ private File findOldPageDir(String page) { if (page == null) { throw new InternalWikiException("Page may NOT be null in the provider!"); } File oldpages = new File(getPageDirectory(), PAGEDIR); return new File(oldpages, mangleName(page)); } /** * Goes through the repository and decides which version is the newest one in that directory. * * @return Latest version number in the repository, or -1, if there is no page in the repository. */ private int findLatestVersion(String page) { int version = -1; try { Properties props = getPageProperties(page); for (Iterator i = props.keySet().iterator(); i.hasNext();) { String key = (String) i.next(); if (key.endsWith(".author")) { int cutpoint = key.indexOf('.'); if (cutpoint > 0) { String pageNum = key.substring(0, cutpoint); try { int res = Integer.parseInt(pageNum); if (res > version) { version = res; } } catch (NumberFormatException e) { } // It's okay to skip these. } } } } catch (IOException e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.findLatestVersion()", "wiki.EX_FIND_PAGE", e); } return version; } /** * Reads page properties from the file system. */ private Properties getPageProperties(String page) throws IOException { File propertyFile = new File(findOldPageDir(page), PROPERTYFILE); if (propertyFile.exists()) { long lastModified = propertyFile.lastModified(); // // The profiler showed that when calling the history of a page the // propertyfile // was read just as much times as there were versions of that file. // The loading // of a propertyfile is a cpu-intensive jobs. So now hold on to the // last propertyfile // read because the next method will with a high probability ask for // the same propertyfile. // The time it took to show a historypage with 267 versions dropped // with 300%. // CachedProperties cp = m_cachedProperties; if (cp != null && cp.m_page.equals(page) && cp.m_lastModified == lastModified) { return cp.m_props; } InputStream in = null; try { in = new BufferedInputStream(new FileInputStream(propertyFile)); Properties props = new Properties(); props.load(in); cp = new CachedProperties(); cp.m_page = page; cp.m_lastModified = lastModified; cp.m_props = props; m_cachedProperties = cp; // Atomic return props; } finally { if (in != null) { in.close(); } } } return new Properties(); // Returns an empty object } /** * Writes the page properties back to the file system. Note that it WILL overwrite any previous * properties. */ private void putPageProperties(String page, Properties properties) throws IOException { File propertyFile = new File(findOldPageDir(page), PROPERTYFILE); OutputStream out = null; try { out = new FileOutputStream(propertyFile); properties.store(out, " JSPWiki page properties for " + page + ". DO NOT MODIFY!"); } finally { if (out != null) { out.close(); } } } /** * Figures out the real version number of the page and also checks for its existence. * * @throws NoSuchVersionException if there is no such version. */ private int realVersion(String page, int requestedVersion) throws NoSuchVersionException, ProviderException { // // Quickly check for the most common case. // if (requestedVersion == WikiProvider.LATEST_VERSION) { return -1; } int latest = findLatestVersion(page); if (requestedVersion == latest || (requestedVersion == 1 && latest == -1)) { return -1; } else if (requestedVersion <= 0 || requestedVersion > latest) { throw new NoSuchVersionException("Requested version " + requestedVersion + ", but latest is " + latest); } return requestedVersion; } public synchronized String getPageText(String page, int version) throws ProviderException { File dir = findOldPageDir(page); version = realVersion(page, version); if (version == -1) { // We can let the FileSystemProvider take care // of these requests. return super.getPageText(page, WikiPageProvider.LATEST_VERSION); } File pageFile = new File(dir, "" + version + FILE_EXT); if (!pageFile.exists()) { throw new NoSuchVersionException("Version " + version + "does not exist."); } return readFile(pageFile); } // FIXME: Should this really be here? private String readFile(File pagedata) throws ProviderException { String result = null; InputStream in = null; if (pagedata.exists()) { if (pagedata.canRead()) { try { in = new FileInputStream(pagedata); result = FileUtil.readContents(in, m_encoding); } catch (IOException e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.readFile()", "wiki.EX_FAIL_READ", e); throw new ProviderException("I/O error: " + e.getMessage()); } finally { try { if (in != null) { in.close(); } } catch (Exception e) { SilverTrace.fatal("wiki", "WikiVersioningFileProvider.readFile()", "wiki.EX_CLOSING_FAIL", e); } } } else { SilverTrace.warn("wiki", "WikiVersioningFileProvider.readFile()", "wiki.EX_FAIL_READ", "Failed to read page from '" + pagedata.getAbsolutePath() + "', possibly a permissions problem"); throw new ProviderException("I cannot read the requested page."); } } else { // This is okay. // FIXME: is it? SilverTrace.info("wiki", "WikiVersioningFileProvider.readFile()", "root.INFO", "New page"); } return result; } // FIXME: This method has no rollback whatsoever. /* * This is how the page directory should look like: version pagedir olddir none empty empty 1 * Main.txt (1) empty 2 Main.txt (2) 1.txt 3 Main.txt (3) 1.txt, 2.txt */ public synchronized void putPageText(WikiPage page, String text) throws ProviderException { int pageId = -1; // // This is a bit complicated. We'll first need to // copy the old file to be the newest file. // File pageDir = findOldPageDir(page.getName()); try { if (!pageDir.exists()) { pageId = pageDAO .createPage(new PageDetail(-1, page.getName(), WikiMultiInstanceManager.getComponentId())); pageDir.mkdirs(); } else { PageDetail pageDetail = pageDAO.getPage(page.getName(), WikiMultiInstanceManager.getComponentId()); pageId = pageDetail.getId(); } } catch (WikiException e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.putPageText()", "root.EX_PUT_PAGE_FAILED", e); throw new ProviderException("Could not save page text: " + e.getMessage()); } int latest = findLatestVersion(page.getName()); try { // // Copy old data to safety, if one exists. // File oldFile = findPage(page.getName()); // Figure out which version should the old page be? // Numbers should always start at 1. // "most recent" = -1 ==> 1 // "first" = 1 ==> 2 int versionNumber = (latest > 0) ? latest : 1; if (oldFile != null && oldFile.exists()) { InputStream in = null; OutputStream out = null; try { in = new BufferedInputStream(new FileInputStream(oldFile)); File pageFile = new File(pageDir, Integer.toString(versionNumber) + FILE_EXT); out = new BufferedOutputStream(new FileOutputStream(pageFile)); FileUtil.copyContents(in, out); // // We need also to set the date, since we rely on this. // pageFile.setLastModified(oldFile.lastModified()); // // Kludge to make the property code to work properly. // versionNumber++; } finally { if (out != null) { out.close(); } if (in != null) { in.close(); } } } // // Let superclass handler writing data to a new version. // super.putPageText(page, text); // // Finally, write page version data. // // FIXME: No rollback available. Properties props = getPageProperties(page.getName()); props.setProperty(versionNumber + ".author", (page.getAuthor() != null) ? page.getAuthor() : "unknown"); props.setProperty(versionNumber + ".id", String.valueOf(pageId)); String changeNote = (String) page.getAttribute(WikiPage.CHANGENOTE); if (changeNote != null) { props.setProperty(versionNumber + ".changenote", changeNote); } putPageProperties(page.getName(), props); } catch (IOException e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.putPageText()", "root.EX_PUT_PAGE_FAILED", e); throw new ProviderException("Could not save page text: " + e.getMessage()); } } public WikiPage getPageInfo(String page, int version) throws ProviderException { int latest = findLatestVersion(page); int realVersion; WikiPage p = null; if (version == WikiPageProvider.LATEST_VERSION || version == latest || (version == 1 && latest == -1)) { // // Yes, we need to talk to the top level directory // to get this version. // // I am listening to Press Play On Tape's guitar version of // the good old C64 "Wizardry" -tune at this moment. // Oh, the memories... // realVersion = (latest >= 0) ? latest : 1; p = super.getPageInfo(page, WikiPageProvider.LATEST_VERSION); if (p != null) { p.setVersion(realVersion); } } else { // // The file is not the most recent, so we'll need to // find it from the deep trenches of the "OLD" directory // structure. // realVersion = version; File dir = findOldPageDir(page); if (!dir.exists() || !dir.isDirectory()) { return null; } File file = new File(dir, version + FILE_EXT); if (file.exists()) { p = new WikiPage(m_engine, page); p.setLastModified(new Date(file.lastModified())); p.setVersion(version); } } // // Get author and other metadata information // (Modification date has already been set.) // if (p != null) { try { Properties props = getPageProperties(page); String author = props.getProperty(realVersion + ".author"); if (author != null) { p.setAuthor(author); } String changenote = props.getProperty(realVersion + ".changenote"); if (changenote != null) { p.setAttribute(WikiPage.CHANGENOTE, changenote); } String pageId = props.getProperty(realVersion + ".id"); p.setAttribute(WikiVersioningFileProvider.ID, pageId); } catch (IOException e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.getPageInfo()", "root.EX_GET_PAGE_INFO", e); } } return p; } public boolean pageExists(String pageName, int version) { File dir = findOldPageDir(pageName); if (!dir.exists() || !dir.isDirectory()) { return false; } File file = new File(dir, version + FILE_EXT); if (file.exists()) { return true; } return false; } /** * FIXME: Does not get user information. */ public List<?> getVersionHistory(String page) throws ProviderException { List<WikiPage> list = new ArrayList<WikiPage>(); int latest = findLatestVersion(page); for (int i = latest; i > 0; i--) { WikiPage info = getPageInfo(page, i); if (info != null) { list.add(info); } } return list; } /** * Removes the relevant page directory under "OLD" -directory as well, but does not remove any * extra subdirectories from it. It will only touch those files that it thinks to be WikiPages. * * @param page * @throws ProviderException */ // FIXME: Should log errors. @Override public void deletePage(String page) throws ProviderException { super.deletePage(page); int pageId = -1; String componentId = WikiMultiInstanceManager.getComponentId(); try { PageDetail currentPage = pageDAO.getPage(page, componentId); if (currentPage != null) { pageId = currentPage.getId(); pageDAO.deletePage(page, WikiMultiInstanceManager.getComponentId()); } } catch (WikiException e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.deletePage()", "wiki.EX_DELETE_PAGE_FAILED", e); throw new ProviderException("Could not delete page : " + e.getMessage()); } /* * Delete versioning information relative to the deleted page */ try { List<SimpleDocument> docs = AttachmentServiceFactory.getAttachmentService() .listDocumentsByForeignKey(new ForeignPK(String.valueOf(pageId), componentId), null); for (SimpleDocument doc : docs) { AttachmentServiceFactory.getAttachmentService().deleteAttachment(doc); } } catch (Exception e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.deletePage()", "wiki.EX_DELETE_PAGE_FAILED", e); throw new ProviderException("Could not delete page : " + e.getMessage()); } File dir = findOldPageDir(page); if (dir.exists() && dir.isDirectory()) { File[] files = dir.listFiles(new WikiFileFilter()); for (File file : files) { file.delete(); } File propfile = new File(dir, PROPERTYFILE); if (propfile.exists()) { propfile.delete(); } dir.delete(); } } @Override public void deleteVersion(String page, int version) throws ProviderException { File dir = findOldPageDir(page); int latest = findLatestVersion(page); if (version == WikiPageProvider.LATEST_VERSION || version == latest || (version == 1 && latest == -1)) { // // Delete the properties // try { Properties props = getPageProperties(page); props.remove(((latest > 0) ? latest : 1) + ".author"); putPageProperties(page, props); } catch (IOException e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.deleteVersion()", "wiki.EX_MODIFY_PROPERTIES", e); throw new ProviderException("Could not modify page properties"); } // We can let the FileSystemProvider take care // of the actual deletion super.deleteVersion(page, WikiPageProvider.LATEST_VERSION); // // Copy the old file to the new location // latest = findLatestVersion(page); File pageDir = findOldPageDir(page); File previousFile = new File(pageDir, Integer.toString(latest) + FILE_EXT); InputStream in = null; OutputStream out = null; try { if (previousFile.exists()) { in = new BufferedInputStream(new FileInputStream(previousFile)); File pageFile = findPage(page); out = new BufferedOutputStream(new FileOutputStream(pageFile)); FileUtil.copyContents(in, out); // // We need also to set the date, since we rely on this. // pageFile.setLastModified(previousFile.lastModified()); } } catch (IOException e) { SilverTrace.fatal("wiki", "WikiVersioningFileProvider.deleteVersion()", "root.EXCEPTION", "Something wrong with the page directory - you may have just lost data!", e); } finally { IOUtils.closeQuietly(in); IOUtils.closeQuietly(out); } return; } File pageFile = new File(dir, "" + version + FILE_EXT); if (pageFile.exists()) { if (!pageFile.delete()) { SilverTrace.error("wiki", "WikiVersioningFileProvider.deleteVersion()", "wiki.EX_DELETE_PAGE_FAILED"); } } else { throw new NoSuchVersionException("Page " + page + ", version=" + version); } } // FIXME: This is kinda slow, we should need to do this only once. @Override public Collection<?> getAllPages() throws ProviderException { Collection<?> pages = internalGetAllPages(); Collection<WikiPage> returnedPages = new ArrayList<WikiPage>(); for (Iterator i = pages.iterator(); i.hasNext();) { WikiPage page = (WikiPage) i.next(); WikiPage info = getPageInfo(page.getName(), WikiProvider.LATEST_VERSION); returnedPages.add(info); } return returnedPages; } @Override public String getProviderInfo() { return ""; } @Override public void movePage(String from, String to) throws ProviderException { // Rename in database try { pageDAO.renamePage(from, to, WikiMultiInstanceManager.getComponentId()); } catch (WikiException e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.movePage()", "wiki.EX_MOVE_PAGE", e); throw new ProviderException("Could not move page"); } // Move the file itself File fromFile = findPage(from); File toFile = findPage(to); fromFile.renameTo(toFile); // Move any old versions File fromOldDir = findOldPageDir(from); File toOldDir = findOldPageDir(to); fromOldDir.renameTo(toOldDir); } private static class CachedProperties { String m_page; Properties m_props; long m_lastModified; } @Override String getPageDirectory() { String componentId = WikiMultiInstanceManager.getComponentId(); if (componentId == null) { return super.getPageDirectory(); } else { return FileRepositoryManager.getAbsolutePath(componentId); } } /** * Finds a Wiki page from the page repository. * * @param page * @return */ @Override protected File findPage(String page) { return new File(getPageDirectory(), mangleName(page) + FILE_EXT); } public Collection<?> internalGetAllPages() throws ProviderException { List<WikiPage> set = new ArrayList<WikiPage>(); File wikipagedir = new File(getPageDirectory()); File[] wikipages = wikipagedir.listFiles(new WikiFileFilter()); if (wikipages == null) { SilverTrace.error("wiki", "WikiVersioningFileProvider.internalGetAllPages()", "wiki.GET_ALL_PAGES_FAILED", "Wikipages directory '" + getPageDirectory() + "' does not exist! Please check " + PROP_PAGEDIR + " in jspwiki.properties."); throw new InternalWikiException("Page directory does not exist"); } for (int i = 0; i < wikipages.length; i++) { String wikiname = wikipages[i].getName(); int cutpoint = wikiname.lastIndexOf(FILE_EXT); WikiPage page = getPageInfo(unmangleName(wikiname.substring(0, cutpoint)), WikiPageProvider.LATEST_VERSION); if (page == null) { // This should not really happen. // FIXME: Should we throw an exception here? SilverTrace.error("wiki", "WikiVersioningFileProvider.internalGetAllPages()", "wiki.GET_ALL_PAGES_FAILED", "Page " + wikiname + " was found in directory listing, but could not be located individually."); continue; } set.add(page); } return set; } @Override public int getPageCount() { File wikipagedir = new File(getPageDirectory()); File[] wikipages = wikipagedir.listFiles(new WikiFileFilter()); return wikipages.length; } /** * Iterates through all WikiPages, matches them against the given query, and returns a Collection * of SearchResult objects. * @param query * @return */ @Override public Collection findPages(QueryItem[] query) { File wikipagedir = new File(getPageDirectory()); TreeSet res = new TreeSet(new SearchResultComparator()); SearchMatcher matcher = new SearchMatcher(m_engine, query); File[] wikipages = wikipagedir.listFiles(new WikiFileFilter()); for (int i = 0; i < wikipages.length; i++) { FileInputStream input = null; String filename = wikipages[i].getName(); int cutpoint = filename.lastIndexOf(FILE_EXT); String wikiname = filename.substring(0, cutpoint); wikiname = unmangleName(wikiname); try { input = new FileInputStream(wikipages[i]); String pagetext = FileUtil.readContents(input, m_encoding); SearchResult comparison = matcher.matchPageContent(wikiname, pagetext); if (comparison != null) { res.add(comparison); } } catch (IOException e) { SilverTrace.error("wiki", "WikiVersioningFileProvider.findPages()", "wiki.EX_FIND_PAGES_FAILED", "Failed to read " + filename, e); } finally { try { if (input != null) { input.close(); } } catch (IOException e) { } // It's fine to fail silently. } } return res; } }