FileHistoryCache.java revision 754c32df22f09e74d30ff9ddaa8e0ad0e7441a1c
2046N/A/*
2046N/A * CDDL HEADER START
2046N/A *
2046N/A * The contents of this file are subject to the terms of the
2046N/A * Common Development and Distribution License (the "License").
2046N/A * You may not use this file except in compliance with the License.
2046N/A *
2046N/A * See LICENSE.txt included in this distribution for the specific
2046N/A * language governing permissions and limitations under the License.
2046N/A *
2046N/A * When distributing Covered Code, include this CDDL HEADER in each
2046N/A * file and include the License file at LICENSE.txt.
2046N/A * If applicable, add the following below this CDDL HEADER, with the
2046N/A * fields enclosed by brackets "[]" replaced with your own identifying
2046N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2046N/A *
2046N/A * CDDL HEADER END
2046N/A */
2046N/A
2046N/A/*
2046N/A * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
2046N/A * Use is subject to license terms.
3307N/A */
2046N/A
3339N/Apackage org.opensolaris.opengrok.history;
2046N/A
2046N/Aimport java.beans.Encoder;
2046N/Aimport java.beans.Expression;
2046N/Aimport java.beans.PersistenceDelegate;
2046N/Aimport java.beans.XMLDecoder;
2046N/Aimport java.beans.XMLEncoder;
2046N/Aimport java.io.BufferedOutputStream;
2046N/Aimport java.io.File;
2046N/Aimport java.io.FileOutputStream;
2838N/Aimport java.io.IOException;
2046N/Aimport java.io.BufferedInputStream;
2046N/Aimport java.io.FileInputStream;
2046N/Aimport java.util.zip.GZIPInputStream;
2046N/Aimport java.util.zip.GZIPOutputStream;
2046N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
2147N/A
2434N/Aclass FileHistoryCache implements HistoryCache {
2147N/A private final Object lock = new Object();
2046N/A
3158N/A static class FilePersistenceDelegate extends PersistenceDelegate {
2046N/A protected Expression instantiate(Object oldInstance, Encoder out) {
2046N/A File f = (File)oldInstance;
2046N/A return new Expression(oldInstance, f.getClass(), "new", new Object[] { f.toString() });
2046N/A }
2046N/A }
2046N/A
2046N/A /**
2046N/A * Get a <code>File</code> object describing the cache file.
2046N/A *
2046N/A * @param file the file to find the cache for
3158N/A * @return file that might contain cached history for <code>file</code>
2046N/A */
2046N/A private static File getCachedFile(File file) {
2046N/A RuntimeEnvironment env = RuntimeEnvironment.getInstance();
2147N/A
2147N/A StringBuilder sb = new StringBuilder();
2477N/A sb.append(env.getDataRootPath());
2477N/A sb.append(File.separatorChar);
2477N/A sb.append("historycache");
2147N/A
2046N/A String sourceRoot = env.getSourceRootPath();
2046N/A if (sourceRoot == null) {
2046N/A return null;
2147N/A }
2147N/A
2147N/A try {
2147N/A String add = file.getCanonicalPath().substring(sourceRoot.length());
2046N/A if (add.length() == 0) {
2046N/A add = File.separator;
2046N/A }
2046N/A sb.append(add);
2046N/A } catch (IOException ex) {
2046N/A ex.printStackTrace();
2046N/A }
2046N/A sb.append(".gz");
2046N/A
2147N/A return new File(sb.toString());
2147N/A }
2147N/A
2147N/A /**
2477N/A * Read history from a file.
2477N/A */
2046N/A private static History readCache(File file) throws IOException {
2046N/A XMLDecoder d = new XMLDecoder(
2046N/A new BufferedInputStream(new GZIPInputStream(new FileInputStream(file))));
2046N/A Object obj = d.readObject();
2046N/A d.close();
2046N/A return (History) obj;
2046N/A }
2046N/A
2597N/A public void store(History history, File file) throws Exception {
2046N/A
2046N/A File cache = getCachedFile(file);
2046N/A
2046N/A File dir = cache.getParentFile();
2597N/A if (!dir.isDirectory()) {
2597N/A if (!dir.mkdirs()) {
2597N/A throw new IOException(
2597N/A "Unable to create cache directory '" + dir + "'.");
2597N/A }
2597N/A }
2597N/A
2597N/A // We have a problem that multiple threads may access the cache layer
2597N/A // at the same time. Since I would like to avoid read-locking, I just
2597N/A // serialize the write access to the cache file. The generation of the
2597N/A // cache file would most likely be executed during index generation, and
2597N/A // that happens sequencial anyway....
2597N/A // Generate the file with a temporary name and move it into place when
2597N/A // I'm done so I don't have to protect the readers for partially updated
2597N/A // files...
2597N/A File output = File.createTempFile("oghist", null, dir);
2597N/A XMLEncoder e = new XMLEncoder(
2597N/A new BufferedOutputStream(new GZIPOutputStream(new FileOutputStream(output))));
2597N/A e.setPersistenceDelegate(File.class, new FilePersistenceDelegate());
2597N/A e.writeObject(history);
2597N/A e.close();
2597N/A synchronized (lock) {
2597N/A if (!cache.delete() && cache.exists()) {
2597N/A output.delete();
2597N/A throw new IOException(
2597N/A "Cachefile exists, and I could not delete it.");
2597N/A }
2597N/A if (!output.renameTo(cache)) {
2597N/A output.delete();
2597N/A throw new IOException("Failed to rename cache tmpfile.");
2597N/A }
2597N/A }
2597N/A }
2597N/A
2046N/A public History get(File file, Repository repository) throws Exception {
2597N/A Class<? extends HistoryParser> parserClass;
2597N/A parserClass = repository.getHistoryParser();
2597N/A File cache = getCachedFile(file);
2597N/A boolean hasCache = (cache != null) && cache.exists();
2046N/A if (hasCache && file.lastModified() < cache.lastModified()) {
2046N/A try {
2046N/A return readCache(cache);
2046N/A } catch (Exception e) {
2046N/A System.err.println("Error when reading cache file '" +
2046N/A cache + "':");
2046N/A e.printStackTrace();
2046N/A }
2046N/A }
2046N/A
2046N/A HistoryParser parser = parserClass.newInstance();
2091N/A History history = null;
2046N/A long time;
2046N/A try {
2046N/A time = System.currentTimeMillis();
2046N/A history = parser.parse(file, repository);
2046N/A time = System.currentTimeMillis() - time;
2046N/A } catch (UnsupportedOperationException e) {
2046N/A // In this case, we've found a file for which the SCM has no history
2046N/A // An example is a non-SCCS file somewhere in an SCCS-controlled
2046N/A // workspace.
2046N/A return null;
2213N/A }
2046N/A catch (Exception e) {
2046N/A System.err.println("Failed to parse " + file.getAbsolutePath());
2046N/A e.printStackTrace();
2731N/A throw e;
2046N/A }
2046N/A
2046N/A if (repository != null && repository.isCacheable() && !file.isDirectory()) {
2046N/A // Don't cache history-information for directories, since the
2046N/A // history information on the directory may change if a file in
2046N/A // a sub-directory change. This will cause us to present a stale
2046N/A // history log until a the current directory is updated and
2046N/A // invalidates the cache entry.
2081N/A RuntimeEnvironment env = RuntimeEnvironment.getInstance();
2081N/A if (env.useHistoryCache()) {
2081N/A if ((cache != null) &&
2046N/A (cache.exists() ||
2046N/A (time > env.getHistoryReaderTimeLimit()))) {
2046N/A // retrieving the history takes too long, cache it!
2046N/A try {
2046N/A store(history, cache);
2046N/A } catch (Exception e) {
2046N/A System.err.println("Error when writing cache file '" +
2213N/A cache + "':");
2046N/A e.printStackTrace();
2046N/A }
2046N/A }
2046N/A }
2046N/A }
2046N/A return history;
2046N/A }
2046N/A}
2046N/A