FileHistoryCache.java revision f4216ce32e4859fd256a197d10d19a0fdb9f468c
3177N/A/*
290N/A * CDDL HEADER START
290N/A *
290N/A * The contents of this file are subject to the terms of the
290N/A * Common Development and Distribution License (the "License").
290N/A * You may not use this file except in compliance with the License.
290N/A *
290N/A * See LICENSE.txt included in this distribution for the specific
290N/A * language governing permissions and limitations under the License.
290N/A *
290N/A * When distributing Covered Code, include this CDDL HEADER in each
290N/A * file and include the License file at LICENSE.txt.
290N/A * If applicable, add the following below this CDDL HEADER, with the
290N/A * fields enclosed by brackets "[]" replaced with your own identifying
290N/A * information: Portions Copyright [yyyy] [name of copyright owner]
290N/A *
290N/A * CDDL HEADER END
290N/A */
290N/A
290N/A/*
290N/A * Copyright 2008 Sun Microsystems, Inc. All rights reserved.
3158N/A * Use is subject to license terms.
395N/A */
290N/A
3143N/Apackage org.opensolaris.opengrok.history;
883N/A
454N/Aimport java.beans.Encoder;
290N/Aimport java.beans.Expression;
448N/Aimport java.beans.PersistenceDelegate;
290N/Aimport java.beans.XMLDecoder;
290N/Aimport java.beans.XMLEncoder;
290N/Aimport java.io.BufferedInputStream;
383N/Aimport java.io.BufferedOutputStream;
290N/Aimport java.io.File;
395N/Aimport java.io.FileInputStream;
290N/Aimport java.io.FileOutputStream;
395N/Aimport java.io.IOException;
849N/Aimport java.util.ArrayList;
1516N/Aimport java.util.HashMap;
2508N/Aimport java.util.List;
2826N/Aimport java.util.Map;
290N/Aimport java.util.logging.Level;
2535N/Aimport java.util.zip.GZIPInputStream;
2698N/Aimport java.util.zip.GZIPOutputStream;
290N/Aimport org.opensolaris.opengrok.OpenGrokLogger;
290N/Aimport org.opensolaris.opengrok.configuration.RuntimeEnvironment;
2535N/A
2561N/Aclass FileHistoryCache implements HistoryCache {
290N/A private final Object lock = new Object();
2508N/A
383N/A static class FilePersistenceDelegate extends PersistenceDelegate {
290N/A @Override
290N/A protected Expression instantiate(Object oldInstance, Encoder out) {
2339N/A File f = (File)oldInstance;
2535N/A return new Expression(oldInstance, f.getClass(), "new", new Object[] {f.toString()});
290N/A }
290N/A }
2535N/A
2535N/A @Override
290N/A public void initialize() {
290N/A // nothing to do
2508N/A }
2508N/A
290N/A @Override
1660N/A public void optimize() {
1660N/A // nothing to do
1660N/A }
1660N/A
1660N/A /**
1660N/A * Get a <code>File</code> object describing the cache file.
1660N/A *
1660N/A * @param file the file to find the cache for
1660N/A * @return file that might contain cached history for <code>file</code>
1660N/A */
1660N/A private static File getCachedFile(File file) throws HistoryException {
1660N/A RuntimeEnvironment env = RuntimeEnvironment.getInstance();
1660N/A
1660N/A StringBuilder sb = new StringBuilder();
1660N/A sb.append(env.getDataRootPath());
1660N/A sb.append(File.separatorChar);
1660N/A sb.append("historycache");
1660N/A
448N/A String sourceRoot = env.getSourceRootPath();
448N/A if (sourceRoot == null) {
2828N/A return null;
2828N/A }
2828N/A
534N/A String add;
534N/A try {
534N/A try {
534N/A add = file.getCanonicalPath().substring(sourceRoot.length());
534N/A } catch (StringIndexOutOfBoundsException exp) {
534N/A throw new HistoryException("Failed to get path for: <" + file + "> [" + file.getCanonicalPath() + "] [" + sourceRoot + "]", exp);
534N/A }
290N/A } catch (IOException ioe) {
290N/A throw new HistoryException("Failed to get path for: " + file, ioe);
954N/A }
954N/A if (add.length() == 0) {
954N/A add = File.separator;
954N/A }
534N/A sb.append(add);
1099N/A sb.append(".gz");
290N/A
3117N/A return new File(sb.toString());
3117N/A }
3203N/A
3117N/A /**
290N/A * Read history from a file.
290N/A */
290N/A private static History readCache(File file) throws IOException {
661N/A final FileInputStream in = new FileInputStream(file);
2867N/A try {
290N/A XMLDecoder d = new XMLDecoder(
2494N/A new BufferedInputStream(new GZIPInputStream(in)));
2494N/A Object obj = d.readObject();
2494N/A d.close();
2516N/A return (History) obj;
2516N/A } finally {
2516N/A in.close();
2516N/A }
2516N/A }
2516N/A
2516N/A private void storeFile(History history, File file) throws HistoryException {
290N/A
3185N/A File cache = getCachedFile(file);
2523N/A
3138N/A File dir = cache.getParentFile();
2390N/A if (!dir.isDirectory() && !dir.mkdirs()) {
1498N/A throw new HistoryException(
1498N/A "Unable to create cache directory '" + dir + "'.");
2867N/A }
2310N/A
2310N/A // We have a problem that multiple threads may access the cache layer
2310N/A // at the same time. Since I would like to avoid read-locking, I just
2852N/A // serialize the write access to the cache file. The generation of the
2852N/A // cache file would most likely be executed during index generation, and
2852N/A // that happens sequencial anyway....
2852N/A // Generate the file with a temporary name and move it into place when
2535N/A // I'm done so I don't have to protect the readers for partially updated
2867N/A // files...
2867N/A final File output;
2310N/A try {
290N/A output = File.createTempFile("oghist", null, dir);
1674N/A final FileOutputStream out = new FileOutputStream(output);
1674N/A try {
2262N/A XMLEncoder e = new XMLEncoder(
1674N/A new BufferedOutputStream(new GZIPOutputStream(out)));
395N/A e.setPersistenceDelegate(File.class, new FilePersistenceDelegate());
430N/A e.writeObject(history);
395N/A e.close();
1544N/A } finally {
1968N/A out.close();
1557N/A }
1903N/A } catch (IOException ioe) {
2046N/A throw new HistoryException("Failed to write history", ioe);
2240N/A }
1506N/A synchronized (lock) {
2928N/A if (!cache.delete() && cache.exists()) {
395N/A if (!output.delete()) {
395N/A OpenGrokLogger.getLogger().log(Level.WARNING, "Failed to remove temporary history cache file");
2026N/A }
395N/A throw new HistoryException(
395N/A "Cachefile exists, and I could not delete it.");
395N/A }
2310N/A if (!output.renameTo(cache)) {
2852N/A if (!output.delete()) {
395N/A OpenGrokLogger.getLogger().log(Level.WARNING, "Failed to remove temporary history cache file");
661N/A }
2867N/A throw new HistoryException("Failed to rename cache tmpfile.");
2867N/A }
2867N/A }
2867N/A }
2867N/A
2852N/A @Override
2310N/A public void store(History history, Repository repository)
3216N/A throws HistoryException {
3216N/A
2867N/A if (history.getHistoryEntries() == null) {
2867N/A return;
2867N/A }
661N/A
3185N/A HashMap<String, List<HistoryEntry>> map =
3185N/A new HashMap<String, List<HistoryEntry>>();
3185N/A
395N/A for (HistoryEntry e : history.getHistoryEntries()) {
849N/A for (String s : e.getFiles()) {
290N/A List<HistoryEntry> list = map.get(s);
395N/A if (list == null) {
395N/A list = new ArrayList<HistoryEntry>();
1968N/A map.put(s, list);
395N/A }
395N/A list.add(e);
395N/A }
395N/A }
395N/A
395N/A File root = RuntimeEnvironment.getInstance().getSourceRootFile();
395N/A for (Map.Entry<String, List<HistoryEntry>> e : map.entrySet()) {
395N/A for (HistoryEntry ent : e.getValue()) {
395N/A ent.strip();
395N/A }
395N/A History hist = new History();
290N/A hist.setHistoryEntries(e.getValue());
290N/A File file = new File(root, e.getKey());
395N/A if (!file.isDirectory()) {
395N/A storeFile(hist, file);
1231N/A }
1557N/A }
1903N/A }
1557N/A
395N/A @Override
395N/A public History get(File file, Repository repository, boolean withFiles)
395N/A throws HistoryException {
395N/A File cache = getCachedFile(file);
395N/A if (isUpToDate(file, cache)) {
395N/A try {
395N/A return readCache(cache);
395N/A } catch (Exception e) {
395N/A OpenGrokLogger.getLogger().log(Level.WARNING,
395N/A "Error when reading cache file '" + cache, e);
3185N/A }
3185N/A }
3185N/A
395N/A final History history;
290N/A long time;
290N/A try {
430N/A time = System.currentTimeMillis();
395N/A history = repository.getHistory(file);
395N/A time = System.currentTimeMillis() - time;
395N/A } catch (UnsupportedOperationException e) {
395N/A // In this case, we've found a file for which the SCM has no history
1302N/A // An example is a non-SCCS file somewhere in an SCCS-controlled
395N/A // workspace.
395N/A return null;
290N/A }
3139N/A
3139N/A if (!file.isDirectory()) {
395N/A // Don't cache history-information for directories, since the
3139N/A // history information on the directory may change if a file in
3139N/A // a sub-directory change. This will cause us to present a stale
3139N/A // history log until a the current directory is updated and
3139N/A // invalidates the cache entry.
3139N/A RuntimeEnvironment env = RuntimeEnvironment.getInstance();
3139N/A if ((cache != null) &&
3139N/A (cache.exists() ||
3139N/A (time > env.getHistoryReaderTimeLimit()))) {
3139N/A // retrieving the history takes too long, cache it!
3139N/A storeFile(history, file);
3139N/A }
3139N/A }
3139N/A return history;
3139N/A }
3139N/A
3139N/A /**
395N/A * Check if the cache is up to date for the specified file.
3139N/A * @param file the file to check
3139N/A * @param cachedFile the file which contains the cached history for
3139N/A * the file
3139N/A * @return {@code true} if the cache is up to date, {@code false} otherwise
3139N/A */
3139N/A private boolean isUpToDate(File file, File cachedFile) {
3139N/A return cachedFile != null && cachedFile.exists() &&
395N/A file.lastModified() <= cachedFile.lastModified();
3139N/A }
3139N/A
3139N/A /**
3139N/A * Check if the directory is in the cache.
3139N/A * @param directory the directory to check
2516N/A * @return {@code true} if the directory is in the cache
2516N/A */
3139N/A @Override
3139N/A public boolean hasCacheForDirectory(File directory, Repository repository)
3139N/A throws HistoryException {
3139N/A assert directory.isDirectory();
3139N/A Repository repos = HistoryGuru.getInstance().getRepository(directory);
3139N/A if (repos == null) {
3139N/A return true;
3139N/A }
3139N/A File dir = RuntimeEnvironment.getInstance().getDataRootFile();
3139N/A dir = new File(dir, "historycache");
3139N/A dir = new File(dir, repos.getDirectoryName().substring(RuntimeEnvironment.getInstance().getSourceRootPath().length()));
3139N/A return dir.exists();
3139N/A }
3139N/A
3139N/A @Override
2516N/A public String getLatestCachedRevision(Repository repository) {
3139N/A return null;
3139N/A }
3139N/A
3139N/A @Override
3139N/A public String getInfo() {
3139N/A return getClass().getSimpleName();
2516N/A }
3139N/A}
3139N/A